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,1067 @@
1
+ ---
2
+ category: banking
3
+ ---
4
+
5
+ <!-- doc-type: pitfall-reference -->
6
+
7
+ # Memory Banking Pitfalls
8
+
9
+ The C64 has three independent banking layers: $01 selects which ROMs the CPU
10
+ reads, $DD00 selects which 16 KB window VIC uses, and $D018 positions assets
11
+ within that window. Each layer has its own addressing logic, reset defaults, and
12
+ failure mode. The pitfalls here cover the most costly traps: the char ROM
13
+ invisible to the CPU under I/O mapping, the VIC 16 KB constraint that silently
14
+ invalidates all asset pointers when you move data between banks, the
15
+ read/write asymmetry that makes RAM under BASIC or KERNAL appear to vanish, and
16
+ the high-level-language trap where a growing code section silently runs into a
17
+ hardcoded charset/bitmap blit address.
18
+
19
+ ---
20
+
21
+ ## charset_under_io_invisible_to_cpu — Char ROM readable from CPU only when I/O is swapped out
22
+
23
+ **Severity:** medium
24
+ **Region:** both
25
+ **Triggered by techniques:** char_rom_under_vic, cpu_io_port_bank, big_font_2x2, charset_copy_rom_to_ram
26
+
27
+ ### Symptom
28
+
29
+ Code that tries to copy the built-in C64 character font from $D000-$D7FF reads
30
+ back all zeros or garbage bytes. The first 2 KB of the copy contains corrupted
31
+ data despite a loop that looks correct. The same loop copies fine from any other
32
+ address in the $0000-$CFFF range. On some rigs the bytes are not zero but shift
33
+ by one byte per iteration, suggesting a read from the SID or VIC shadow registers
34
+ instead of font data.
35
+
36
+ ### Mechanism
37
+
38
+ The $D000-$DFFF range is what hardware engineers call a "multiplexed window." At
39
+ any given moment it can contain one of three things:
40
+
41
+ 1. The I/O devices: VIC-II registers ($D000-$D3FF), SID ($D400-$D7FF), Color
42
+ RAM ($D800-$DBFF), CIA #1 ($DC00-$DCFF), CIA #2 ($DD00-$DDFF), and cartridge
43
+ expansion I/O ($DE00-$DFFF).
44
+ 2. The 4 KB character ROM, containing the two built-in C64 fonts.
45
+ 3. The underlying 4 KB of DRAM, when both ROM and I/O are banked out.
46
+
47
+ Which of those three wins is determined by bits 0-2 of the CPU I/O port at $0001.
48
+ Bit 2 = CHAREN: when set (default $37), I/O wins. When clear and at least HIRAM
49
+ or LORAM is set, character ROM wins. When CHAREN=0 and both LORAM=0 and HIRAM=0,
50
+ RAM wins. $01 = $33 (CHAREN=0, HIRAM=1, LORAM=1) is the canonical value for
51
+ reading the char ROM from the CPU.
52
+
53
+ The C64 resets with $01 = $37. With CHAREN = 1, reading $D000 returns a VIC-II
54
+ register byte, not font data.
55
+
56
+ The character ROM never appears to the CPU unless CHAREN (bit 2 of $01) is
57
+ cleared AND at least one of HIRAM (bit 1) or LORAM (bit 0) is set. The
58
+ combination $01 = $33 (binary 00110011) satisfies this: CHAREN=0, HIRAM=1,
59
+ LORAM=1. In that state the character ROM is readable at $D000-$DFFF by the CPU,
60
+ and the default BASIC + KERNAL ROMs remain banked in at $A000-$BFFF and
61
+ $E000-$FFFF respectively.
62
+
63
+ The VIC-II is not subject to this constraint. VIC has its own separate hardware
64
+ pathway to the character ROM, routed through the PLA independently of the CPU
65
+ banking bits. In VIC banks 0 and 2, the PLA automatically redirects VIC's
66
+ character generator fetches to the char ROM at the appropriate offset ($1000-$1FFF
67
+ in bank 0, $9000-$9FFF in bank 2) regardless of $01. This is why the default
68
+ screen works correctly out of reset — VIC reads the char ROM via its own path
69
+ while the CPU cannot see it at all.
70
+
71
+ The second trap is interrupt safety. While $01 = $33, I/O is invisible: any IRQ
72
+ that fires — the KERNAL's own 60 Hz CIA-1 timer interrupt as much as a raster
73
+ IRQ you set up — cannot acknowledge its source. The KERNAL handler's LDA $DC0D
74
+ at $EA7E reads character ROM instead of the CIA, and a raster handler's write to
75
+ $D019 lands in the RAM under the ROM instead of the VIC. The flag stays set,
76
+ /IRQ stays low, the handler re-enters after every RTI and the main program never
77
+ runs another instruction (measured in VICE x64sc: with $01 = $33, no raster IRQ
78
+ configured and a counting $0314 handler, 28,695 handler passes and 0 main-loop
79
+ iterations in 20M cycles, against 1,028 passes and 31,183 iterations with
80
+ $01 = $37). An earlier version of this paragraph blamed only a raster IRQ's
81
+ $D019 acknowledge, which read as if SEI were optional without one; it is not.
82
+ Always SEI before swapping I/O out.
83
+
84
+ ### Fix
85
+
86
+ SEI before swapping I/O out. Always read-modify-write $01 to preserve bits 3-5
87
+ (datasette motor/write/sense). Pattern: SEI → read $01 → mask bits 0-2 → OR $03
88
+ ($33 result) → write → copy $D000-$D7FF → read $01 → mask bits 0-2 → OR $07
89
+ ($37 result) → write → CLI. The DDR at $0000 is $2F after KERNAL init; bits 0-2
90
+ are already outputs, so no DDR change is needed.
91
+
92
+ ### Worked example
93
+
94
+ ```kick
95
+ // Destination: RAM at $2000 (example: custom charset location)
96
+ // Source: built-in font from char ROM at $D000-$D7FF (2 KB)
97
+ //
98
+ // ─── BAD: reads VIC registers, not char ROM ──────────────────────────────────
99
+ bad_copy:
100
+ // $01 is $37 at this point — I/O is mapped. Reading $D000 gives
101
+ // VIC register 0 (sprite 0 X position), not font data.
102
+ ldx #0
103
+ !: lda $d000, x // Returns VIC/SID register byte, not font pixel!
104
+ sta $2000, x
105
+ inx
106
+ bne !- // All 256 bytes are I/O register garbage
107
+
108
+ // ─── GOOD: temporarily bank char ROM into $D000-$DFFF for the CPU ────────────
109
+ copy_charset_from_rom:
110
+ sei // Disable IRQs — I/O is about to disappear
111
+ lda $01
112
+ and #%11111000 // Preserve bits 3-5 (cassette lines), clear bits 0-2
113
+ ora #%00000011 // $33: CHAREN=0, HIRAM=1, LORAM=1 → char ROM visible
114
+ sta $01
115
+
116
+ // Primary font: $D000-$D7FF = eight 256-byte pages (256 chars × 8 bytes)
117
+ ldx #0
118
+ copy_page:
119
+ lda $d000, x // Now reads char ROM glyph data
120
+ sta $2000, x // Store into custom RAM charset location
121
+ lda $d100, x
122
+ sta $2100, x
123
+ lda $d200, x
124
+ sta $2200, x
125
+ lda $d300, x
126
+ sta $2300, x
127
+ lda $d400, x
128
+ sta $2400, x
129
+ lda $d500, x
130
+ sta $2500, x
131
+ lda $d600, x
132
+ sta $2600, x
133
+ lda $d700, x
134
+ sta $2700, x
135
+ inx
136
+ bne copy_page
137
+
138
+ // Restore I/O layout
139
+ lda $01
140
+ and #%11111000 // Preserve cassette bits
141
+ ora #%00000111 // $37: CHAREN=1, HIRAM=1, LORAM=1 → I/O visible again
142
+ sta $01
143
+ cli // IRQs safe to re-enable now
144
+ rts
145
+ ```
146
+
147
+ A full copy of the primary font is 2 KB (256 characters × 8 bytes = eight
148
+ 256-byte pages, $D000-$D7FF), which is why the loop above has eight load/store
149
+ pairs; a single-page or two-page loop copies only the first 32 or 64 characters.
150
+ An earlier version of this listing had two pages and claimed to copy the whole
151
+ font — it moved 512 bytes, and the remaining 1,536 were never copied (measured in
152
+ VICE x64sc: 174 mismatches against char ROM in $2200-$27FF; the eight-pair loop
153
+ leaves $2000-$27FF byte-identical). In Oscar64 or cc65, a 16-bit pointer loop
154
+ over 2048 bytes does the same job.
155
+
156
+ ### Cross-references
157
+
158
+ - Memory region [$0000-$0001 — Processor I/O port](../hardware/c64-memory-map.md#0000-0001--processor-io-port) — bits 0-2 = LORAM/HIRAM/CHAREN; resolvable via `c64_memory_map 0001`, not `c64_register_lookup` (the KB has no Register node for the CPU port).
159
+ - Register `D018` — VIC video matrix and charset base; independent of CPU char ROM visibility.
160
+ - Register `DD00` — CIA2 port A; selects the VIC 16 KB bank.
161
+ - Technique `char_rom_under_vic` — char ROM shadow in VIC banks 0 and 2.
162
+ - Technique `cpu_io_port_bank` — all seven CPU memory configurations.
163
+
164
+ ---
165
+
166
+ ## vic_bank_visibility_collision — VIC's 16 KB window invalidates all asset pointers simultaneously
167
+
168
+ **Severity:** high
169
+ **Region:** both
170
+ **Triggered by registers:** DD00, D018
171
+ **Triggered by techniques:** vic_bank_select, char_rom_under_vic, screen_ram_relocation, screen_double_buffer_d018, bitmap_relocation, standard_bitmap, multicolor_bitmap, koala_format, fli_image, afli_image, ifli_image, mci_interlace_bitmap, charset_animation, big_font_2x2, dycp_scroller, sprite_cache_flip, sprite_animation_table, wireframe_pipeline, eight_way_scroll_double_buffer, hires_plot, bresenham_line, solid_vector_3d
172
+
173
+ ### Symptom
174
+
175
+ Sprites that display correctly in one VIC bank become invisible or show corrupt
176
+ graphics when the VIC bank is changed. The screen fills with garbage characters
177
+ or goes black. Moving a sprite's shape data to a new RAM location produces no
178
+ visible change, while the old — now incorrect — graphics continue to appear.
179
+ Setting up a bitmap in bank 1 while leaving the screen matrix in bank 0 produces
180
+ a garbled display that looks nothing like the intended bitmap. Setting sprite
181
+ pointer values ($07F8-$07FF or their equivalent in the current screen RAM) to
182
+ seemingly correct offsets results in the wrong sprite image appearing.
183
+
184
+ All of these symptoms share the same root cause: a mismatch between the 16 KB
185
+ window VIC is looking through and the actual RAM location of the assets it is
186
+ supposed to fetch.
187
+
188
+ ### Mechanism
189
+
190
+ The VIC-II chip has a 14-bit address bus. It cannot independently access all
191
+ 64 KB of the C64's RAM — it can only see 16,384 bytes at a time. CIA2 port A
192
+ bits 0-1 (register $DD00) act as the upper two bits of VIC's address, selecting
193
+ which 16 KB "bank" the VIC sees:
194
+
195
+ | $DD00 bits 1-0 | VIC bank | CPU address range | Notes |
196
+ |----------------|----------|-------------------|-------------------------------|
197
+ | 11 (default) | 0 | $0000-$3FFF | Default boot bank |
198
+ | 10 | 1 | $4000-$7FFF | No char ROM shadow here |
199
+ | 01 | 2 | $8000-$BFFF | Char ROM shadow at $9000 |
200
+ | 00 | 3 | $C000-$FFFF | No char ROM shadow here |
201
+
202
+ The bit patterns are inverted: `11` selects bank 0, `00` selects bank 3. This
203
+ inversion comes from inverting buffers between CIA2 PA0-PA1 and VIC's VA14-VA15
204
+ lines on the motherboard. Writing the raw bank number directly to $DD00 selects
205
+ the wrong bank — always invert and mask.
206
+
207
+ Within its current 16 KB window, VIC interprets every pointer as a bank-relative
208
+ offset, not an absolute address. Every VIC data structure is affected at once:
209
+
210
+ - **Screen matrix (video matrix):** The high nibble of $D018 selects which 1 KB
211
+ block within the VIC bank holds the screen character codes. A value of `%0001`
212
+ means offset $0400, which is $0000+$0400 in bank 0 (absolute $0400), or
213
+ $4000+$0400 in bank 1 (absolute $4400), and so on.
214
+
215
+ - **Character generator / bitmap base:** The low nibble of $D018 selects which
216
+ 2 KB block within the VIC bank holds the character font or bitmap data (in
217
+ text mode) or the high bit of the low nibble selects the 8 KB bitmap base
218
+ (in bitmap mode). These offsets are bank-relative.
219
+
220
+ - **Sprite data pointers:** Each of the 8 sprite pointer bytes at the end of
221
+ the screen matrix (at screen_base + $03F8 through screen_base + $03FF, i.e.
222
+ $07F8-$07FF in the default bank-0 layout) contains an index into VIC's bank
223
+ in 64-byte blocks. Sprite pointer 0 = $07F8; the value N means the sprite
224
+ shape data starts at (vic_bank_base + N × 64).
225
+
226
+ The most common collision scenario: a developer moves asset data to bank 1 for
227
+ more RAM, writes $DD00 to select bank 1, but leaves the screen matrix at $0400
228
+ and sprite pointers at $07F8-$07FF. VIC now reads its screen data from $4400
229
+ (bank 1 offset $0400), not $0400. Everything that was working in bank 0 is now
230
+ reading from a completely different area of RAM that may be uninitialized.
231
+
232
+ A subtler variant: screen matrix moved to bank 1 correctly, but sprite shape data
233
+ left in bank 0. Sprite pointer N × 64 now addresses bank 1 RAM at $4000 + N×64,
234
+ not the bank 0 shapes.
235
+
236
+ Bank 2 adds a char ROM shadow at VIC-relative $1000-$1FFF ($9000-$9FFF in CPU
237
+ view). Placing a custom charset there means VIC reads ROM instead. Banks 1 and 3
238
+ have no char ROM shadow and are preferred for fully custom graphics.
239
+
240
+ ### Fix
241
+
242
+ Keep all VIC-readable assets (screen matrix, charset/bitmap, sprite shapes) in
243
+ the same 16 KB bank. For custom layouts prefer bank 1 ($4000-$7FFF) — no char
244
+ ROM shadow. When switching:
245
+
246
+ 1. Pick the VIC bank; use bank 1 or 3 for custom graphics (no char ROM shadow).
247
+ 2. Write $DD00: `lda $DD00 / and #$FC / ora #<inverted_bank_bits> / sta $DD00`.
248
+ Inverted codes: bank 0 = %11, bank 1 = %10, bank 2 = %01, bank 3 = %00.
249
+ 3. Set $D018: hi-nibble = screen_offset / $0400; low nibble =
250
+ (charset_offset / $0800) << 1 — the 3-bit charset field occupies bits 1-3 and
251
+ bit 0 is unused (reads as 1). In bitmap mode only bit 3 matters:
252
+ (bitmap_offset / $2000) << 3. An earlier version of this step omitted the
253
+ shift, which for the example below yields $D018 = $11 and a charset fetched
254
+ from bank offset $0000, not $0800.
255
+ 4. Compute sprite pointer bytes: `ptr = (shape_addr - bank_base) / 64`.
256
+ Store at `screen_base + $03F8` through `+ $03FF`.
257
+ 5. Make bank changes during vertical blank to avoid mid-frame tears.
258
+
259
+ ### Worked example
260
+
261
+ ```kick
262
+ // Target layout: VIC bank 1 ($4000-$7FFF)
263
+ // Screen RAM at $4400 → D018 hi-nibble = %0001
264
+ // Charset at $4800 → D018 lo-nibble = %0010 (offset $0800 / $0800 = 1, << 1)
265
+ // Sprite 0 at $4FC0 → sprite ptr = ($4FC0-$4000)/64 = 63 = $3F
266
+ // Sprite pointer table at screen_base + $03F8 = $47F8
267
+
268
+ // ─── BAD: changed DD00 but forgot to move screen and sprite data ──────────────
269
+ lda $dd00
270
+ and #%11111100
271
+ ora #%00000010 // Bank 1 selected (inverted: %10 = bank 1)
272
+ sta $dd00
273
+ // $D018 hi-nibble still %0001 → VIC reads screen from $4400 (uninitialized)
274
+ // Sprite pointers still written at $07F8 (bank 0) → VIC reads ptrs from $47F8 (garbage)
275
+
276
+ // ─── GOOD: full bank-1 layout ────────────────────────────────────────────────
277
+ setup_bank1:
278
+ lda $dd00
279
+ and #%11111100
280
+ ora #%00000010 // Bank 1: inverted bits = %10
281
+ sta $dd00
282
+
283
+ lda #%00010010 // hi=%0001 → screen at $4400; lo=%0010 → charset at $4800
284
+ // ($12 — an earlier version wrote $14, which selects
285
+ // bank offset $1000 = $5000, verified in VICE)
286
+ sta $d018
287
+
288
+ // Clear screen RAM at $4400
289
+ lda #$20
290
+ ldx #0
291
+ !: sta $4400, x
292
+ sta $4500, x
293
+ sta $4600, x
294
+ sta $4700, x
295
+ inx
296
+ bne !-
297
+
298
+ // Sprite 0 pointer: $4FC0 is 63 blocks of 64 bytes into bank 1
299
+ lda #$3f
300
+ sta $47f8 // Sprite ptr table lives at screen_base($4400)+$03F8
301
+
302
+ lda #%00000001
303
+ sta $d015 // Enable sprite 0
304
+ rts
305
+ ```
306
+
307
+ ### Cross-references
308
+
309
+ - Register `DD00` — CIA2 port A; bits 0-1 select VIC bank (inverted encoding).
310
+ - Register `D018` — video matrix and char base; nibble values are bank-relative.
311
+ - Technique `vic_bank_select` — bank selection mechanism and inverting buffer.
312
+ - Technique `char_rom_under_vic` — char ROM shadow in VIC banks 0 and 2.
313
+ - Technique `screen_ram_relocation` — $D018 hi-nibble address calculations.
314
+
315
+ ---
316
+
317
+ ## ram_under_rom_traps — Writes go to RAM under ROM; reads return ROM bytes
318
+
319
+ **Severity:** medium
320
+ **Region:** both
321
+ **Triggered by techniques:** cpu_io_port_bank, ram_under_kernal, bitmap_relocation, speedcode_generation, irq_owns_processor_port, cartridge_save, cartridge_bank_easyflash, basic_rom_float_calls
322
+
323
+ ### Symptom
324
+
325
+ Code or data written to $A000-$BFFF or $E000-$FFFF appears to vanish. A store
326
+ loop runs without error, but reading back the same addresses returns the original
327
+ BASIC or KERNAL ROM contents rather than the stored data. Self-modifying code
328
+ placed at $E000 seems to have no effect. A sprite shape table written to $A000
329
+ reads back as floating-point math routines. Checksumming RAM that was just written
330
+ with known values returns the KERNAL ROM checksum instead.
331
+
332
+ The converse symptom also occurs: a developer banks ROM out with $01 = $35 to
333
+ reclaim RAM at $E000-$FFFF and then is surprised that the memory was already
334
+ populated — because a previous write through the ROM window succeeded silently
335
+ and the bytes were waiting in RAM all along.
336
+
337
+ ### Mechanism
338
+
339
+ The PLA enforces an asymmetric rule: reads honour the banking state (ROM wins
340
+ when banked in), but a write to a ROM-mapped range reaches the underlying DRAM —
341
+ $A000-$BFFF and $E000-$FFFF whatever $01 says, and $D000-$DFFF while character
342
+ ROM is mapped there (CHAREN = 0 with LORAM or HIRAM set, e.g. $33). It is NOT
343
+ true of $D000-$DFFF while I/O is mapped ($35/$36/$37): there the write lands in
344
+ the VIC/SID/CIA/colour-RAM register and the RAM beneath is untouched (measured in
345
+ VICE x64sc: RAM under $D000 seeded $A5 at $34, $2D written at $37 read back $2D —
346
+ the sprite-0 X register — and the RAM still read $A5 once I/O was banked out; the
347
+ same write at $A000 and $E000 landed in RAM). An earlier version of this
348
+ paragraph said writes reach RAM "regardless" of the banking state, which is
349
+ false for the I/O window. To put data under I/O, bank it out first ($34, or $33
350
+ if char ROM is acceptable) with interrupts disabled. See
351
+ techniques/memory-banking.md and hardware/c64-memory-map.md, which record the
352
+ same measurement. This means:
353
+
354
+ ```kick
355
+ // $01 = $37 — BASIC ROM at $A000
356
+ lda #$42
357
+ sta $a000 // Byte stored in DRAM at $A000 — the write succeeds
358
+ lda $a000 // Returns BASIC ROM content, not $42 — the ROM wins the read
359
+ ```
360
+
361
+ | Range | ROM content | Bank-out bit | Safe $01 value |
362
+ |---------------|---------------|----------------------|----------------|
363
+ | $A000-$BFFF | BASIC (8 KB) | LORAM (bit 0) = 0 | $36 or $34 |
364
+ | $D000-$DFFF | I/O (default), or char ROM when CHAREN=0 | LORAM=0 AND HIRAM=0 (CHAREN then ignored) | $30 or $34 |
365
+ | $E000-$FFFF | KERNAL (8 KB) | HIRAM (bit 1) = 0 | $35 or $34 |
366
+
367
+ $33 is the value for READING the char ROM at $D000 (see above), not for reaching
368
+ the RAM under it — an earlier version of this table listed $33 on the $D000 row,
369
+ which banks the char ROM *in* (measured in VICE x64sc: $D000 under $31/$32/$33
370
+ reads $3C, glyph '@' row 0; RAM seeded $A5 under $D000 is seen only at $30 and
371
+ $34). $30/$34 also bank out KERNAL, BASIC and all I/O, so SEI first and touch no
372
+ VIC/SID/CIA register until $01 is restored. The write asymmetry in this section
373
+ applies to the ROM windows only: with I/O banked in ($35-$37) a write to
374
+ $D000-$DFFF goes to the I/O chip, not to the RAM beneath; with the char ROM
375
+ banked in ($31-$33) the write does reach RAM.
376
+
377
+ Banking KERNAL out moves the CPU's interrupt vectors ($FFFA-$FFFF) from ROM to
378
+ the RAM underneath. Pre-write custom IRQ/NMI addresses to those RAM locations
379
+ while KERNAL is still visible (writes go to RAM), then SEI and switch.
380
+
381
+ Cartridge ROM at $8000-$BFFF (ROML and ROMH) is a fourth ROM-mapped range with
382
+ the same asymmetry: while a cartridge maps it, a write lands in the RAM beneath
383
+ and a read returns the cartridge byte (recorded under `cartridge_bank_easyflash`
384
+ in techniques/memory-banking.md; not re-measured here).
385
+
386
+ ### Fix
387
+
388
+ BASIC zone: SEI → `lda $01 / and #%11111000 / ora #%00000110 / sta $01` ($36:
389
+ BASIC out, I/O + KERNAL remain) → access $A000-$BFFF → restore $01 → CLI.
390
+
391
+ KERNAL zone: Write IRQ/NMI vectors to $FFFE/$FFFA while KERNAL is banked in
392
+ (the writes reach RAM). Then SEI → `$01 = ($01 & %11111000) | %00000101` ($35)
393
+ → access $E000-$FFFF → restore or stay in $35 for a permanent KERNAL replacement.
394
+
395
+ ### Worked example
396
+
397
+ ```kick
398
+ // Demonstrate the read/write asymmetry at $A000, then fix it.
399
+
400
+ // ─── BAD: write to $A000 while BASIC ROM is banked in ────────────────────────
401
+ bad_ram_write:
402
+ // $01 = $37 (default). BASIC ROM visible at $A000-$BFFF.
403
+ lda #$42
404
+ sta $a000 // Write: lands in RAM under BASIC. Byte stored.
405
+ lda $a000 // Read: returns BASIC ROM byte ($94 = STY, the first
406
+ // byte of the BASIC cold-start vector table) — NOT $42!
407
+ // Accumulator contains BASIC ROM content, not $42. Surprise.
408
+
409
+ // ─── GOOD: bank BASIC out before reading ─────────────────────────────────────
410
+ safe_ram_under_basic:
411
+ sei
412
+ lda #$42
413
+ sta $a000 // Write lands in RAM regardless
414
+
415
+ lda $01
416
+ and #%11111000
417
+ ora #%00000110 // $36: LORAM=0 — BASIC banked out, I/O + KERNAL remain
418
+ sta $01
419
+
420
+ lda $a000 // Now reads RAM: returns $42
421
+ // ... use $A000-$BFFF freely ...
422
+
423
+ lda $01
424
+ and #%11111000
425
+ ora #%00000111 // $37: restore BASIC
426
+ sta $01
427
+ cli
428
+ rts
429
+
430
+ // ─── Permanent KERNAL replacement ────────────────────────────────────────────
431
+ install_custom_kernal:
432
+ sei
433
+ // Write IRQ/NMI vectors to RAM while KERNAL ROM is still visible.
434
+ // The ROM is write-transparent — bytes land in the RAM underneath.
435
+ lda #<custom_irq
436
+ sta $fffe
437
+ lda #>custom_irq
438
+ sta $ffff
439
+ lda #<custom_nmi
440
+ sta $fffa
441
+ lda #>custom_nmi
442
+ sta $fffb
443
+
444
+ lda $01
445
+ and #%11111000
446
+ ora #%00000101 // $35: HIRAM=0 — KERNAL banked out, RAM vectors now live
447
+ sta $01
448
+ cli
449
+ rts
450
+
451
+ custom_irq:
452
+ pha
453
+ lda #$ff
454
+ sta $d019 // Ack all VIC interrupt sources
455
+ pla
456
+ rti
457
+
458
+ custom_nmi:
459
+ rti // Suppress RESTORE key NMI
460
+ ```
461
+
462
+ ### Cross-references
463
+
464
+ - Memory region [$0000-$0001 — Processor I/O port](../hardware/c64-memory-map.md#0000-0001--processor-io-port) — bits 0-2 are LORAM, HIRAM, CHAREN; the
465
+ read-modify-write pattern for bits 3-5 (datasette lines) must be preserved.
466
+ Resolvable via `c64_memory_map 0001`, not `c64_register_lookup` (the KB has no
467
+ Register node for the CPU port).
468
+ - Technique `cpu_io_port_bank` — the full table of all seven CPU memory
469
+ configurations, including the exact $01 values for each combination of banked
470
+ ROMs. The technique doc has the full discussion of the PLA's write-transparency
471
+ behavior and the interrupt-vector pre-write requirement.
472
+ - Technique `ram_under_kernal` — specifically covers using $E000-$FFFF as RAM,
473
+ the custom interrupt vector setup, and cycle budget implications of losing the
474
+ KERNAL IRQ chain.
475
+
476
+ ---
477
+
478
+ ## charset_blit_overruns_grown_code — A hardcoded charset/bitmap address collides with a code section that grew into it
479
+
480
+ **Severity:** high
481
+ **Region:** both
482
+ **Triggered by registers:** D018
483
+ **Triggered by techniques:** screen_double_buffer_d018, bitmap_relocation, speedcode_generation, charset_animation, big_font_2x2, dycp_scroller, char_bullets, charset_parallax, destructible_char_terrain, hires_plot
484
+ **Mitigated by techniques:** memory_layout_plan
485
+
486
+ ### Symptom
487
+
488
+ A C64 program built with a high-level toolchain (Oscar64, cc65) boots, runs its
489
+ init, then crashes — often back to the BASIC `READY.` prompt — after a code
490
+ change that only touched logic far from the boot path. The init runs far enough
491
+ to set early globals (a sentinel byte you poll reads as initialised), but the
492
+ main loop never starts. Adding *more* unrelated code makes the crash appear or
493
+ worsen; reverting a few hundred bytes of code makes it vanish. The crash is
494
+ sensitive to total program size, not to the content of the change.
495
+
496
+ ### Mechanism
497
+
498
+ C compilers place code and read-only data contiguously from the load address
499
+ upward; the linker grows the code/data image toward higher addresses as you add
500
+ functions and string literals. Custom-graphics C64 programs frequently blit a
501
+ charset to a *hardcoded* VIC address (commonly `$3000` or `$3800` in bank 0) and
502
+ sprite/bitmap data to other fixed addresses, deliberately time-sharing those
503
+ addresses with const data (e.g. a title bitmap that was already copied elsewhere
504
+ at startup). This works only while an **implicit invariant** holds: the code
505
+ section ends *below* the lowest hardcoded blit address.
506
+
507
+ Nothing enforces that invariant. The compiler does not know `$3000` is special —
508
+ it is just a literal pointer in a `memcpy`. When the code section grows past the
509
+ blit address, the runtime blit (e.g. `init_charset()` writing 2 KB of glyph data
510
+ to `$3000`) overwrites *live executable code* with charset bytes. Execution
511
+ survives until the CPU calls a function that now lives in the clobbered range and
512
+ runs glyph data as instructions — typically a `JAM`/`BRK` storm or a stray `RTS`
513
+ that unwinds into the KERNAL warm-start, i.e. `READY.`.
514
+
515
+ `$D018` is the register that makes the blit address load-bearing: its low nibble
516
+ selects the charset/bitmap offset within the VIC bank, so the asset *must* live
517
+ at that fixed 2 KB-aligned (charset) or 8 KB-aligned (bitmap) address — it cannot
518
+ simply be relocated to wherever the linker has free space.
519
+
520
+ ### Fix
521
+
522
+ 1. **Diagnose with the linker map.** Oscar64 emits a `.MAP` on every build; its
523
+ `sections` list shows `<start> - <end> : DATA, code` (address range first,
524
+ then the name — an earlier version of this step had the order reversed, so a
525
+ grep for `DATA, code :` finds nothing). Compare the code-section end
526
+ against every hardcoded blit address. If `code_end > charset_addr`, that is the
527
+ bug. (cc65: read the map's segment list the same way.)
528
+ 2. **Move the blit targets to the top of the bank**, above the projected code
529
+ ceiling: put the charset at the highest 2 KB-aligned slot in the bank (bank 0
530
+ → `$3800`) and any sprite/extra data just below it at the right alignment.
531
+ Update the asset pointer register write — with Oscar64's `vic_setmode(mode,
532
+ screen, charset)` this is automatic; in assembly recompute `$D018` and the
533
+ sprite-pointer bytes by hand.
534
+ 3. **Document the surviving invariant** ("code must end below `$3780`") next to
535
+ the address constants, because step 2 only raises the ceiling — it does not
536
+ remove it. The durable cure is to move the graphics to a VIC bank that does
537
+ not overlap the program image at all, or to shrink the time-shared const.
538
+
539
+ ### Worked example
540
+
541
+ ```c
542
+ // BAD: charset hardcoded at $3000. Works until the code section grows past it,
543
+ // after which init_charset() overwrites live code at $3000+ → crash to READY.
544
+ #define CHARSET ((unsigned char *)0x3000u)
545
+ // .MAP after a feature lands: 0880 - 3050 : DATA, code ← code end $3050 > $3000
546
+
547
+ // GOOD: charset at the top 2KB of bank 0; sprite just below; vic_setmode
548
+ // recomputes $D018. Invariant becomes "code must end below $3780".
549
+ #define CHARSET ((unsigned char *)0x3800u) // $3800-$3FFF
550
+ #define SELSPRITE_DATA_ADDR 0x3780u // 64-byte aligned
551
+ #define SELSPRITE_PTR_VALUE 0xdeu // $3780 / 64
552
+ // vic_setmode(VICM_TEXT, SCREEN, CHARSET) → writes $D018 = $1E (screen $0400, charset $3800;
553
+ // reads back as $1F because bit 0 always reads 1)
554
+ ```
555
+
556
+ ### Cross-references
557
+
558
+ - Register `D018` — VMCSB; high nibble = screen offset, low nibble bits 1-3 =
559
+ charset offset (× $0800), bit 3 = bitmap offset (× $2000). Forces graphics
560
+ assets to fixed bank-relative addresses.
561
+ - Pitfall `vic_bank_visibility_collision` — the other half of the layout story:
562
+ once assets are correctly placed, they must all share one 16 KB VIC bank.
563
+ - Toolchain `oscar64-reference` — the `.MAP` "objects by size" / region list is
564
+ the primary diagnostic for an over-large image; Oscar64 places BSS/heap/stack
565
+ above the code+data image, so a growing image walks toward fixed asset
566
+ addresses with no build-time warning.
567
+
568
+ ---
569
+
570
+ ## irq_during_charen_window — An interrupt taken while the char ROM covers I/O can never acknowledge itself
571
+
572
+ **Severity:** high
573
+ **Region:** both
574
+ **Triggered by registers:** DC0D, D019
575
+ **Triggered by techniques:** charset_copy_rom_to_ram, char_rom_under_vic, cpu_io_port_bank, irq_owns_processor_port
576
+
577
+ ### Symptom
578
+
579
+ The program stops dead somewhere inside a character ROM copy, or just
580
+ after one. No crash to `READY.`, no garbage, no border flash: the
581
+ screen stays as it was. If the copy was meant to be followed by a
582
+ `$D018` write, the font never changes. A machine in this state does not
583
+ respond to keys, and on a real C64 RUN/STOP-RESTORE may or may not get
584
+ it back, depending on what the NMI handler touches.
585
+
586
+ ### Mechanism
587
+
588
+ With `$01` = `$33` the `$D000-$DFFF` window is the character ROM. An
589
+ interrupt that arrives in that state runs a handler written for the
590
+ normal map. The KERNAL IRQ handler ends with `LDA $DC0D` to clear CIA1's
591
+ interrupt flag; a raster handler writes `$D019` to clear the VIC's. Both
592
+ now address font bytes. The read does not reach the CIA, the write goes
593
+ to the RAM under the ROM, and the source's flag stays set. `/IRQ`
594
+ stays low, `RTI` restores a clear I flag, the CPU takes the interrupt
595
+ again on the next instruction boundary, and the copy loop between
596
+ those instruction boundaries gets nothing. Everything the handler does
597
+ before the failed acknowledge also happens against the wrong chip: the
598
+ KERNAL keyboard scan writes its column selects into RAM and reads its
599
+ row byte from the ROM, and decodes whatever glyph row that is as keys.
600
+
601
+ Measured in VICE x64sc 3.10 (`charset-copy-rom-to-ram` recipe, PAL): a
602
+ 4 KB copy started with interrupts enabled ran 30 of 256 loop iterations
603
+ before the KERNAL IRQ fell due, then the handler ran 26 times in the
604
+ 61,000 cycles a watchdog allowed and the loop counter never moved; one
605
+ byte was in the keyboard buffer afterwards. NTSC: 7 iterations, 27
606
+ handler passes, one key. Both runs reproduce byte for byte. The
607
+ iteration count depends only on where in the timer period the copy
608
+ began (CIA1 timer A is latched with `$4025` on PAL, `$4295` on NTSC,
609
+ from the KERNAL image; about 16,400 and 17,000 cycles), so a copy that
610
+ happens to start just after a tick can get through several thousand
611
+ bytes and still hang.
612
+
613
+ `charset_under_io_invisible_to_cpu` on this page has the other half of
614
+ this trap and a longer run of the same hang; that pitfall is about
615
+ reading the wrong bytes when `$01` is left at `$37`, this one is about
616
+ what happens when `$01` is set right and interrupts are left on.
617
+
618
+ ### Fix
619
+
620
+ `SEI` before the `$01` write that maps the ROM; `CLI` after the write
621
+ that restores I/O. Nothing in between may enable interrupts, which
622
+ rules out KERNAL calls (`kernal_assumes_sei_cleared`). If a raster IRQ
623
+ is live and the copy is long, stop it rather than defer it: clear
624
+ `$D01A` bit 0 and acknowledge `$D019` before the switch, restart after.
625
+ A program that has to take an interrupt while the ROM is mapped (rare;
626
+ a sample player, say) needs a handler that restores `$37` as its first
627
+ instruction and puts `$33` back before `RTI`, and that handler must be
628
+ in RAM or KERNAL, never under `$D000`.
629
+
630
+ The recipe's watchdog is a debugging aid, not a fix: a CIA2 timer NMI
631
+ that restores `$37`, acknowledges both CIAs and unwinds the stack turns
632
+ the silent hang into a reported failure while the copy is being
633
+ developed.
634
+
635
+ ### Worked example
636
+
637
+ ```kick
638
+ // BAD: the ROM is mapped and the KERNAL IRQ is still armed.
639
+ lda $01
640
+ and #$f8
641
+ ora #$03
642
+ sta $01 // char ROM at $D000
643
+ ldx #0
644
+ !: lda $d000, x // ~30 iterations later on PAL the IRQ fires,
645
+ sta $3000, x // LDA $DC0D reads a font byte, and the handler
646
+ inx // re-enters for ever
647
+ bne !-
648
+
649
+ // GOOD: no interrupt can run while I/O is out of the map.
650
+ sei
651
+ lda $01
652
+ and #$f8
653
+ ora #$03
654
+ sta $01
655
+ ldx #0
656
+ !: lda $d000, x
657
+ sta $3000, x
658
+ inx
659
+ bne !-
660
+ lda $01
661
+ and #$f8
662
+ ora #$07
663
+ sta $01 // I/O back
664
+ cli // the deferred IRQ runs now, and acknowledges
665
+ ```
666
+
667
+ ### Cross-references
668
+
669
+ - Technique `charset_copy_rom_to_ram`: the copy done safely, timed, and done once unsafely under a watchdog.
670
+ - Technique `cpu_io_port_bank`: the `$01` values and the bits to preserve.
671
+ - Technique `char_rom_under_vic`: where the VIC sees the ROM without any copy.
672
+ - Pitfall `charset_under_io_invisible_to_cpu`: the copy made with I/O still mapped.
673
+ - Pitfall `kernal_assumes_sei_cleared` (kernal-and-io.md): why a KERNAL call inside the window would defeat the `SEI`.
674
+ - Register `DC0D`: CIA1 interrupt control; the acknowledge the KERNAL handler cannot make.
675
+ - Register `D019`: VIC interrupt flags; the acknowledge a raster handler cannot make.
676
+ - Recipe `recipes/kickassembler/charset-copy-rom-to-ram.md`.
677
+
678
+ ---
679
+
680
+ ## decruncher_overwrites_kernal_zero_page — A self-extractor that runs from the zero page leaves the KERNAL's variables full of decruncher code
681
+
682
+ **Severity:** high
683
+ **Region:** both
684
+ **Triggered by kernal:** CHROUT
685
+ **Triggered by techniques:** zx0_lzsa_decrunchers, pucrunch_decruncher, doynax_decruncher, zero_page_burst, byteboozer_packer
686
+ **Mitigated by techniques:** cpu_io_port_bank
687
+
688
+ ### Symptom
689
+
690
+ A program that runs from its own PRG stops working the moment it is
691
+ wrapped in a self-extracting cruncher. Nothing is reported. The two shapes
692
+ measured here, on a payload that prints one line through `CHROUT` and
693
+ then sets a byte:
694
+
695
+ - Under Dali 0.3.5 `--sfx --small`, the payload is entered and never
696
+ comes back from its first `JSR $FFD2`. No text, no later store, the
697
+ border colour it sets afterwards never appears.
698
+ - Under bitfire's `zx0 --sfx`, every `CHROUT` call returns, the later
699
+ store happens and the border changes, but the line is not on the
700
+ screen. The eleven screen codes were found at `$4CC7`, an address in
701
+ otherwise unused RAM well above the payload (which ends at `$1433`):
702
+ it is nothing more than the two decruncher bytes left in the editor's
703
+ line pointer `$D1/$D2` (`$4CBA`) plus its column `$D3` (`$0D`).
704
+
705
+ The same payload crunched with Dali's standard `--sfx` and with pucrunch
706
+ `-c64` printed its line where the uncrunched build did.
707
+
708
+ ### Mechanism
709
+
710
+ The zero page is the cheapest place to put a decruncher: zero-page
711
+ addressing saves a byte and a cycle on every operand, `(zp),Y` is the
712
+ only indirect mode the 6502 has, and the page is free of anything the
713
+ decruncher itself needs. Dali's and bitfire's self-extractors both copy
714
+ their decruncher into it from the top of the copy down to `$01`, and the
715
+ byte that lands at `$01` banks every ROM out so the whole 64 KB can be a
716
+ decrunch target: `$34` under Dali `--small`, `$38` under bitfire, both
717
+ with bits 0 to 2 clear (measured with a store trace on `$0001`; neither
718
+ stub writes the port again during the decrunch). The stream is moved to the top of
719
+ memory under the KERNAL and decrunched forwards from the payload's load
720
+ address. The page they overwrite is where the KERNAL and BASIC keep
721
+ their state.
722
+
723
+ Measured in the windowless x64sc build of VICE 3.10, PAL, 2026-09-23. A
724
+ tracepoint at the payload's entry (`SYS 2061`, `$080D`) dumped `$0000`
725
+ to `$00FF`, and each dump was compared with the same dump from the
726
+ uncrunched payload. The stopwatch at entry was 2,970,597 cycles for the
727
+ plain build and 3,032,679 to 3,180,431 for the four crunched builds, so
728
+ each decrunch cost under a quarter of a second of emulated time.
729
+
730
+ | Build | Zero-page bytes changed at entry | `$01` | `$9A` | Printed |
731
+ |---|---|---|---|---|
732
+ | uncrunched | 0 | `$37` | `$03` | yes |
733
+ | Dali `--sfx` | 20 | `$37` | `$03` | yes |
734
+ | pucrunch `-c64` | 16 | `$37` | `$03` | yes |
735
+ | Dali `--sfx --small` | 178 (`$01` to `$B7`) | `$34` | `$02` | no |
736
+ | bitfire `zx0 --sfx` | 201 (`$02` to `$D4`) | `$37` | `$A6` | no |
737
+
738
+ The two failures have different fatal bytes, and neither is the one a
739
+ first guess would name:
740
+
741
+ - **Dali `--small`** leaves `$01` at `$34`. `JSR $FFD2` then executes
742
+ the last bytes of the crunched stream, which the stub parked under
743
+ the KERNAL. Putting `$37` back is not enough on its own: measured,
744
+ the payload still hung. The byte that hangs it is `$9A`, the default
745
+ output device (DFLTO), which the decruncher code left at `$02`.
746
+ `CHROUT` reads `$9A` before it does anything else, and `2` is the
747
+ RS-232 device. Traced: `CHROUT` hands the byte to the RS-232 output
748
+ routine, which stores it through the unopened output buffer pointer
749
+ at `$F9/$FA` (zero on a clean boot, so the byte lands in `$0000`),
750
+ starts CIA2's timer and enables its NMI; the NMI handler at `$FE47`
751
+ then re-enters about every 75 cycles, the stack pointer falls six
752
+ bytes per entry, and the payload never runs again. It is not a wait
753
+ loop: the routine's buffer check at `$F017` ran once. Restoring `$01` and `$9A` (with `$99` for
754
+ good measure) and nothing else made the payload print on the
755
+ original screen, at the row `RUN` left the cursor on.
756
+ - **bitfire** hands over with the KERNAL mapped: the last thing its
757
+ zero-page decruncher does before the jump is `DEC $01`, thirteen
758
+ cycles before entry, which turns its `$38` into `$37` (measured at
759
+ the store, not read from its source; Dali `--small` leaves `$34`
760
+ there). It leaves `$9A` at `$A6`, so
761
+ `CHROUT` treats the output as a serial-bus device and returns after
762
+ the bus times out. That is why its calls come back. With `$9A` put
763
+ back to `3`, the calls reach the screen editor, and the text still
764
+ does not appear: the editor's line pointer `$D1/$D2` (PNT) reads
765
+ `$4CBA` and its column `$D3` reads `$0D`, so the characters go to
766
+ `$4CC7`. `$C7`, the reverse-video flag, reads `$85`, so once the
767
+ pointer is right the line comes out in reverse video. Three of the
768
+ KERNAL's variables are wrong in three different ways, and the list
769
+ stops there only because the payload calls nothing else.
770
+
771
+ The two stubs that pass are not clean either, and the difference is
772
+ what they touch. Dali's standard stub pushes the zero page onto the
773
+ stack before the copy and pops it back before the jump; it entered the
774
+ payload with the stack pointer at `$FF`, ten bytes of its own exit code
775
+ still at `$E3` to `$EC` (in the screen line link table), and BASIC's
776
+ pointers at `$2D` to `$32`, `$39/$3A` and `$AE/$AF` rewritten, none of
777
+ which the print path reads. Pucrunch's decruncher sits at `$F7` to `$FF`
778
+ and leaves those nine bytes changed, plus the same BASIC pointers; the
779
+ KERNAL's own variables are below `$F7` and the RS-232 pointers at `$F7`
780
+ to `$FA` are idle. Pucrunch is on the Triggered-by line for those nine
781
+ unsaved bytes all the same: `$F7` to `$FA` are the RS-232 buffer
782
+ pointers, `$FB` to `$FE` the free zero page a payload may already be
783
+ using, `$FF` BASIC's float-to-ASCII workspace, and `$2D/$2E` BASIC's
784
+ end-of-program pointer, so a payload that expects any of them to hold
785
+ what they held before the stub ran meets the same mechanism on a
786
+ narrower front; this payload did not. The Doynax self-extractor copies its depacker to
787
+ `$00C2` and up (its technique entry says so); whether it saves what it
788
+ covers was not measured here.
789
+
790
+ ### Fix
791
+
792
+ One of three, in order of cost:
793
+
794
+ 1. **Choose a stub that saves.** Dali's standard `--sfx` and pucrunch's
795
+ default decruncher both let a KERNAL-calling payload run unchanged
796
+ here. The `--small` flag bought 59 bytes of file on this payload and cost
797
+ the machine state; take it only for a payload that owns the machine.
798
+ 2. **Re-initialise in the payload's prologue.** Before the first KERNAL
799
+ call: write `$37` to `$01` (`cpu_io_port_bank`), then `JSR $FF84`
800
+ (IOINIT) and `JSR $FF81` (CINT). CINT resets the screen editor's
801
+ pointers, sets `$9A` back to the screen and `$99` to the keyboard,
802
+ and clears the screen; it does not clear `$C7`, so store zero there
803
+ as well. Measured: the payload printed its line at row 0 under both
804
+ failing stubs. A payload that must keep the screen contents restores
805
+ the specific bytes instead: for Dali `--small` that was `$01` and
806
+ `$9A`; for bitfire it was `$9A`, `$C7`, `$D3` and `$D1/$D2`.
807
+ Measured: a build that stored `3` to `$9A`, `0` to `$C7` and `$D3`
808
+ and `$0518` to `$D1/$D2`, and nothing else, printed its line on row
809
+ 7 in normal video under bitfire. `$99` was left as the decruncher
810
+ left it and `$D4`, the quote-mode flag, stayed at `$08`; plain
811
+ letters were unaffected, though a payload that prints colour or
812
+ cursor codes would see them as reversed glyphs under that flag (the
813
+ editor's quote-mode rule, not measured here). The line pointer is
814
+ `$0400` plus 40 times the row, and CINT rebuilds it if the screen
815
+ contents do not matter.
816
+ 3. **Keep the payload's own zero-page use above the stub's span.** The
817
+ stub's copy runs down to `$01`, so nothing of the payload's survives
818
+ below `$B8` (Dali `--small`) or `$D5` (bitfire); values the payload
819
+ needs at start must be in its body, not in the zero page it was
820
+ crunched with.
821
+
822
+ A program that sets up its own interrupts, screen and I/O and never
823
+ returns to BASIC needs none of this, which is why the `--small` stub
824
+ exists.
825
+
826
+ ### Worked example
827
+
828
+ The good prologue was measured with and without the `$C7` store: with
829
+ it the line reads normally under both stubs; without it, under bitfire,
830
+ row 0 is eleven reverse-video glyphs.
831
+
832
+ ```kick
833
+ // BAD: the payload's first act is a KERNAL call. Under a stub that
834
+ // decrunches from the zero page, $01 may be $34 and $9A is whatever
835
+ // opcode the decruncher left there. Measured: hangs (Dali --small)
836
+ // or prints to $4CC7 (bitfire).
837
+ entry_bad:
838
+ ldx #$00
839
+ bad_loop:
840
+ lda msg, x
841
+ beq bad_done
842
+ jsr $ffd2
843
+ inx
844
+ bne bad_loop
845
+ bad_done:
846
+ rts
847
+
848
+ // GOOD: bank the KERNAL in, re-initialise I/O and the screen editor,
849
+ // clear the one flag CINT leaves alone. Measured: prints under both.
850
+ entry_good:
851
+ lda #$37
852
+ sta $01 // KERNAL, BASIC and I/O back in the map
853
+ jsr $ff84 // IOINIT: CIAs, timers, $01 direction bits
854
+ jsr $ff81 // CINT: editor pointers, $99/$9A, clears screen
855
+ lda #$00
856
+ sta $c7 // reverse-video flag is not CINT's to clear
857
+ ldx #$00
858
+ good_loop:
859
+ lda msg, x
860
+ beq good_done
861
+ jsr $ffd2
862
+ inx
863
+ bne good_loop
864
+ good_done:
865
+ rts
866
+
867
+ msg:
868
+ .text "DECRUNCH OK"
869
+ .byte $0d, $00
870
+ ```
871
+
872
+ The diff that names the bytes, from the bitfire run (address, value in
873
+ the uncrunched build, value at entry after decrunch; the lines the print
874
+ path reads, out of 201 that changed):
875
+
876
+ ```text
877
+ 01: 37 > 37 port: $38 during the decrunch, DEC $01 before the jump; $34 under Dali --small
878
+ 99: 00 > 00 DFLTN untouched
879
+ 9a: 03 > a6 DFLTO output device is now "serial device $A6"
880
+ c7: 00 > 85 RVS reverse video on
881
+ d1: 18 > ba PNT lo screen line pointer was $0518 (row 7), now $4CBA
882
+ d2: 05 > 4c PNT hi
883
+ d3: 00 > 0d column 13
884
+ d4: 00 > 08 quote-mode flag set
885
+ ```
886
+
887
+ ### Cross-references
888
+
889
+ - Technique `zx0_lzsa_decrunchers`: the stubs' layout, the copy loop's span (`$EC` and `$D4` down to `$01`) and the measured footprints.
890
+ - Technique `pucrunch_decruncher`: a decruncher that sits at `$F7` and up and leaves the KERNAL's variables alone.
891
+ - Technique `doynax_decruncher` (loaders-packers.md): a depacker at `$00C2` and up; its saving behaviour is not measured here.
892
+ - Technique `cpu_io_port_bank`: the `$01` values; `$34` is the all-RAM map the stubs decrunch under.
893
+ - Technique `memory_layout_plan`: where the payload's own zero-page claims should be written down, so the stub's span is checked against them.
894
+ - Pitfall `ram_under_rom_traps`: the other consequence of a `$01` left at `$34`, reads that return RAM where ROM was expected.
895
+ - Hardware `c64-memory-map.md`: `$0099`/`$009A` (DFLTN/DFLTO), `$00C7`, `$00D1`-`$00D4`.
896
+
897
+ ---
898
+
899
+ ## cartridge_bank_switch_under_executing_pc — A bank or mode write executed from the window it switches
900
+
901
+ **Severity:** high
902
+ **Region:** both
903
+ **Triggered by techniques:** cartridge_bank_easyflash
904
+
905
+ `$DE00` and `$DE02` are not Register nodes in this knowledge base (the
906
+ hardware pages carry `$DE00-$DEFF` as a memory region, not as registers),
907
+ so this entry anchors on the technique alone.
908
+
909
+ ### Symptom
910
+
911
+ The first bank switch of an EasyFlash program is also its last sane
912
+ instruction. Code that runs to the `STA $DE00` and then crashes, or runs a
913
+ routine nobody called, or leaves the screen and border colours the previous
914
+ bank's code would never have chosen. The same routine works when the test
915
+ build copies it to RAM and runs it there, and fails again the moment it is
916
+ run from the cartridge. Debugging is confusing because the store itself is
917
+ fine: a monitor confirms the bank register took the value.
918
+
919
+ The mode-register form is worse. A `STA $DE02` written in bank 0 HIROM at
920
+ `$E000` (where the cartridge boots, in Ultimax mode) swaps the KERNAL ROM in
921
+ under the program counter, and the CPU runs the KERNAL's floating-point
922
+ code from wherever the PC happened to be.
923
+
924
+ ### Mechanism
925
+
926
+ The 6510 does not know a bank switch happened. `STA $DE00` is four cycles:
927
+ opcode, operand low, operand high, write. The cartridge latches the new bank
928
+ number on the write, which is the instruction's last cycle, so the store
929
+ always completes and the accumulator keeps its value. The very next cycle is
930
+ the next opcode fetch, at the next address, and if that address is inside
931
+ `$8000-$9FFF` (or `$A000-$BFFF` in 16 KB mode) the fetch is served by the
932
+ NEW bank. Whatever bytes the new bank holds at that offset are executed as
933
+ code. Nothing is skipped and nothing is delayed by an instruction: the
934
+ switch lands on the first fetch after the write.
935
+
936
+ Measured in VICE x64sc 3.10 (windowless build, PAL and NTSC, same bytes in
937
+ both) with a two-bank type `$0020` EasyFlash `.crt`. Bank 0 LOROM at `$8000`
938
+ held `LDX #0 / LDA #1 / STA $DE00 / LDA #$41 / STA $0400 / STA $0401 / STX $0402 / INC $D020`.
939
+ Bank 1 LOROM held `$FF` bytes up to `$8006` and, at `$8007`, `LDX #$42 /
940
+ STX $0400 / STA $0401 / STX $0402 / LDA #6 / STA $D020`. The monitor trace
941
+ (`trace exec 8000 8020`, which prints the bytes actually fetched):
942
+
943
+ ```text
944
+ .C:8004 8D 00 DE STA $DE00 - A:01 X:00 cycle 193
945
+ .C:8007 A2 42 LDX #$42 - A:01 X:00 cycle 197 <- bank 1's bytes
946
+ .C:8009 8E 00 04 STX $0400 - A:01 X:42 cycle 199
947
+ ```
948
+
949
+ The store began at cycle 193, wrote on cycle 196 (arithmetic, four-cycle
950
+ STA), and the opcode fetched at cycle 197 was `$A2`, bank 1's byte, not the
951
+ `$A9` that bank 0 holds at `$8007`. Final memory, from `m 0400 0403` and
952
+ `m d020 d020` on the last store:
953
+
954
+ ```text
955
+ run $0400 $0401 $0402 $D020 border in the screenshot
956
+ BAD: switch from $8004 42 01 42 F6 (blue) (44, 61, 236)
957
+ CONTROL: STA $DE00 with #0 41 41 00 FF (grey) (205, 205, 205)
958
+ FIX: sequence run from $0200 41 41 00 FF (grey) (205, 205, 205)
959
+ FIX: bank 1 identical here 41 41 00 FF (grey) (205, 205, 205)
960
+ ```
961
+
962
+ `$0400` never reads `$41` in the bad build: the `LDA #$41` that follows the
963
+ store in bank 0 was never fetched. `$0401` reads `$01`, the accumulator as
964
+ the STA left it, so the write itself was whole. The control build writes
965
+ bank 0 back into `$DE00` and reads `$41`, `$41`, `$00`, which is the same
966
+ instruction stream with the same store and no switch, so the fault is the
967
+ switch and not the store. (`$D020` reads with its upper nibble set, `$F6`
968
+ for blue and `$FF` for `$0F`, light grey.)
969
+
970
+ The mode write was measured the same way. Bank 0 HIROM at `$E022` ran
971
+ `LDA #$07 / STA $DE02 / LDA #$41 / STA $0400 / INC $D020` from Ultimax mode:
972
+
973
+ ```text
974
+ .C:e024 8D 02 DE STA $DE02 - A:07 X:00 cycle 52
975
+ .C:e027 10 F5 BPL $E01E - A:07 X:00 cycle 56 <- KERNAL's bytes
976
+ .C:e026 CA DEX
977
+ .C:e029 A5 56 LDA $56
978
+ .C:e02d 20 53 B8 JSR $B853
979
+ ```
980
+
981
+ The bytes fetched from `$E027` onwards (`10 F5 ... A5 56 85 70 20 53 B8`)
982
+ are the KERNAL ROM's (901227-03, offsets `$0027-$002F`), not the
983
+ cartridge's. `$0400` stayed `$00` and the border stayed light blue: nothing
984
+ after the store in HIROM ever ran.
985
+
986
+ The whole of the pitfall is therefore in the bytes at the ADDRESS AFTER the
987
+ store. Two things make it safe: the code after the store lives in RAM, which
988
+ no bank register touches; or every bank that can be selected carries the
989
+ same bytes at that address, so it does not matter which one serves the
990
+ fetch. The fix build that made bank 1 a byte-for-byte copy of bank 0 at the
991
+ switch site read `$41` like the RAM build, and its trace shows `A9 41` at
992
+ `$8007` served by bank 1.
993
+
994
+ ### Fix
995
+
996
+ Run the switch from RAM. Copy a stub of `LDA #bank / STA $DE00 / JMP entry`
997
+ to a page the cartridge does not map (`$0200`, or the `$DF00` cartridge RAM
998
+ if the program already uses it), jump to the stub, and let it jump into the
999
+ new bank at an address the new bank guarantees is code. Do the same for
1000
+ `$DE02`: the EasyFlash start-up stub in the recipe copies its main code to
1001
+ RAM before it writes `$07`, for this reason.
1002
+
1003
+ If the switch must stay in ROM, put the switch routine at the same offset in
1004
+ every bank, byte for byte, in the linker or the assembler's segment layout,
1005
+ and check that claim with a byte compare of the built `.crt` rather than by
1006
+ reading the source. A common shape is a small shared trampoline in the top
1007
+ page of LOROM present in all banks.
1008
+
1009
+ Never put the store at the end of a routine and rely on the `RTS` after it:
1010
+ the `RTS` is fetched from the new bank too. The same holds for a subroutine
1011
+ in RAM that switches banks and returns to a caller in ROM: the return
1012
+ address is in the old bank's code, and the new bank's bytes are there now
1013
+ (not measured here; it follows from the fetch order above).
1014
+
1015
+ ### Worked example
1016
+
1017
+ ```kick
1018
+ // BAD: the switch runs from the LOROM it switches. Measured: after the
1019
+ // STA the next opcode fetch, at switch_from_rom+5, already comes from
1020
+ // bank 1, and the LDA #$41 below is never executed.
1021
+ switch_from_rom:
1022
+ lda #$01
1023
+ sta $de00 // latches bank 1 on this instruction's last cycle
1024
+ lda #$41 // bank 1's byte at this address runs instead
1025
+ sta $0400
1026
+ rts
1027
+
1028
+ // GOOD: copy the switch and the entry jump to RAM and run them there.
1029
+ // Measured: $0400 reads $41 with the same two banks in the cartridge.
1030
+ switch_from_ram:
1031
+ ldx #$00
1032
+ copy_stub:
1033
+ lda stub, x
1034
+ sta $0200, x
1035
+ inx
1036
+ cpx #stub_end - stub
1037
+ bne copy_stub
1038
+ jmp $0200
1039
+ stub:
1040
+ .pseudopc $0200 {
1041
+ lda #$01
1042
+ sta $de00 // fetched from RAM: what $8000 shows no longer matters
1043
+ jmp $8000 // an address that is code in bank 1
1044
+ }
1045
+ stub_end:
1046
+ ```
1047
+
1048
+ The alternative fix is not code but layout: the bytes at the switch site
1049
+ must be the same in every bank. That is a property of the built `.crt`, so
1050
+ check it there.
1051
+
1052
+ ### Cross-references
1053
+
1054
+ - Technique `cartridge_bank_easyflash` (techniques/memory-banking.md): the
1055
+ bank and mode registers, the Ultimax boot, and its Cycle budget section,
1056
+ which states this hazard; the measurement here is the one that section
1057
+ did not have.
1058
+ - Recipe `recipes/kickassembler/easyflash-save.md`: a boot stub that copies
1059
+ its main code to `$0800` before writing `$DE02`, and a two-bank `.crt`
1060
+ emitted from one listing (the rig above was built the same way).
1061
+ - Technique `cartridge_save` (techniques/file-io.md): keeps its flash
1062
+ writing code in RAM below `$1000` because Ultimax mode maps nothing else,
1063
+ which also keeps it clear of this pitfall; it is not on the Triggered-by
1064
+ line because its text never switches banks from cartridge code.
1065
+ - Pitfall `ram_under_rom_traps`: the other direction of the same PLA rule,
1066
+ a write that reaches the RAM under `$8000-$BFFF` while a read returns the
1067
+ cartridge byte.