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,1172 @@
1
+ ---
2
+ category: render
3
+ ---
4
+
5
+ <!-- doc-type: pitfall-reference -->
6
+
7
+ # Text-Mode Render Pitfalls
8
+
9
+ Bugs that show up when drawing a game on the text screen: a
10
+ moving-piece overlay on a character playfield (Tetris-likes,
11
+ Sokoban-likes, Boulder Dash, etc.), and the inputs that playfield
12
+ depends on. Some come from over-thinking the rendering layer and one
13
+ from under-budgeting it; the rest are mistakes in what is fed to the
14
+ VIC rather than in the drawing itself: PETSCII bytes where it expects
15
+ screen codes, a colour RAM index that runs past the last cell and into
16
+ the CIA, a colour register compared against the value written to it, a
17
+ mode bit left set from the previous screen, and a charset file embedded
18
+ with its header still on. (An earlier version of this paragraph
19
+ numbered the entries; the numbering went stale twice in a day.)
20
+ An earlier version of this page opened by saying
21
+ a C64 text-mode field redraw is "cheap enough that the simplest 'rewrite
22
+ everything every frame, overlay last' loop just works". It does not: the
23
+ 10×20 `render_field` listed below costs about 16,400 cycles (15,500 with
24
+ the display blanked; VICE x64sc PAL, Oscar64 -O2, CIA-timed), and the
25
+ race-free budget is much smaller — `vic_waitBottom` returns at raster
26
+ 256 and the display window resumes at 51, 107 lines, ~6,700 cycles — so
27
+ the full repaint runs to about line 205 of the next frame. The shape
28
+ that works is: paint the field on state changes only, and per frame
29
+ erase the piece's previous cells from `field[][]` and draw its current
30
+ ones. The first pitfall below is what goes wrong when the erase is
31
+ skipped; the third is what goes wrong when the whole field is repainted
32
+ instead.
33
+
34
+ ---
35
+
36
+ ## dirty_cell_skip_leaves_overlay_trail — Skipping unchanged cells leaves the moving piece's prior position un-cleared
37
+
38
+ **Severity:** high
39
+ **Region:** both
40
+ **Triggered by techniques:** text_mode_overlay_render, mixed_sprite_char_actors, bobs_effect, isometric_tile_engine
41
+
42
+ ### Symptom
43
+
44
+ A Tetris-like (or any falling-piece / moving-overlay game) draws the
45
+ playfield + an active piece on top. The active piece moves every
46
+ gravity tick, but on screen you see EITHER (a) a smeared trail of every
47
+ position the piece has occupied since spawn, or (b) the piece appears
48
+ to "skip" rows and only becomes visible near the bottom of the field,
49
+ where row activity is highest. Empty rows above appear truly empty
50
+ even though the piece passed through them.
51
+
52
+ The bug is the same in both cases — render_field's "skip cells where
53
+ field[r][c] hasn't changed" optimization is silently dropping the
54
+ writes needed to *clear* the piece's previous position.
55
+
56
+ ### Mechanism
57
+
58
+ The piece is rendered as an OVERLAY — its cells aren't in the
59
+ `field[][]` array until the piece locks. The render loop is:
60
+
61
+ ```c
62
+ render_field(); // walks field[][], writes screen + color RAM
63
+ render_piece(); // walks active piece's 4 cells, overwrites screen + color RAM
64
+ ```
65
+
66
+ When `render_field` repaints every cell unconditionally, render_piece's
67
+ prior-frame writes get overwritten by the (correct) empty-cell content
68
+ of the playfield. The piece moves cleanly. The "trail" from the
69
+ previous frame is implicitly erased.
70
+
71
+ The moment `render_field` adds a "skip if field[r][c] equals last
72
+ frame's field[r][c]" cache, this property collapses:
73
+
74
+ - The piece at frame N-1 is drawn over cells (r, c) where field[r][c] == 0.
75
+ - Frame N: piece moves to (r+1, c). render_field sees field[r][c] is
76
+ still 0, "unchanged" — skips. The piece's prior screen-RAM write
77
+ ($A0 + color) survives. render_piece draws the new position.
78
+ - On screen: piece at (r, c) AND (r+1, c). Trail.
79
+ - After 20 gravity ticks: piece smeared across 20 rows.
80
+
81
+ If gravity is fast (level >= 5, or hard-drop) the smear is so dense
82
+ that the eye reads it as "the piece teleported to the bottom and
83
+ appeared with no descent."
84
+
85
+ ### Fix
86
+
87
+ The naïve advice "just rewrite the whole field every frame" is WRONG
88
+ on a typical Oscar64 build — see `full_field_redraw_exceeds_vblank`.
89
+ The correct overlay shape is to track the piece's prior position and
90
+ erase + draw only those ~8 cells per frame; `render_field` runs only
91
+ on persistent-state changes (line clear, restart, initial paint).
92
+
93
+ The render order must be: erase prev piece (from field[][] content),
94
+ then draw current piece. Both passes use the piece-cells table — no
95
+ separate dirty-rectangle tracking on the field.
96
+
97
+ ```c
98
+ // render_field: unconditional field repaint. Call it on state changes
99
+ // only (line clear, restart, initial paint) — NOT every frame; measured
100
+ // at about 16,400 cycles in Oscar64 -O2, see full_field_redraw_exceeds_vblank.
101
+ // (An earlier version of this comment called it the correct per-frame shape.)
102
+ void render_field(void) {
103
+ for (unsigned char r = 0; r < FIELD_H; r++) {
104
+ unsigned char *srow = Screen + (FIELD_ROW0 + r) * COLS + FIELD_COL0;
105
+ unsigned char *crow = Color + (FIELD_ROW0 + r) * COLS + FIELD_COL0;
106
+ for (unsigned char c = 0; c < FIELD_W; c++) {
107
+ unsigned char cell = field[r][c];
108
+ unsigned char sc = (cell == 0) ? 0x20 : 0xA0;
109
+ unsigned char col = piece_color[cell];
110
+ srow[c*2 + 0] = sc; srow[c*2 + 1] = sc;
111
+ crow[c*2 + 0] = col; crow[c*2 + 1] = col;
112
+ }
113
+ }
114
+ }
115
+
116
+ void render_piece(void) {
117
+ // Erase pass first: repaint the prev-position cells from field[][],
118
+ // then draw the current cells (0xA0 + color) — see
119
+ // full_field_redraw_exceeds_vblank for the listing. Do not rely on a
120
+ // per-frame render_field() to erase the prior position.
121
+ ...
122
+ }
123
+ ```
124
+
125
+ Because even the 10×20 field redraw is too slow for the per-frame
126
+ budget on an Oscar64 build, you must EITHER:
127
+
128
+ - Track the piece's prior position separately and explicitly write
129
+ spaces there before drawing the new position (a "dirty rectangle"
130
+ for the overlay, not the field), OR
131
+ - Skip the optimization for the rows the piece currently occupies +
132
+ the rows it occupied last frame.
133
+
134
+ The first approach is cleaner, and it is the one the listing under
135
+ `full_field_redraw_exceeds_vblank` shows. An earlier version of this
136
+ section ended by saying neither was needed on a 10×20 playfield because
137
+ the unconditional rewrite was "well within budget"; it is not — the
138
+ listing above measures about 16,400 cycles a call against a ~6,700-cycle
139
+ race-free window (VICE x64sc, PAL, CIA-timed), which is the third
140
+ pitfall on this page.
141
+
142
+ ### Worked example
143
+
144
+ The broken pattern that causes the smear / "only-visible-near-bottom"
145
+ symptom:
146
+
147
+ ```c
148
+ // BUGGY: prev_field caches last frame's content.
149
+ static unsigned char prev_field[FIELD_H][FIELD_W];
150
+
151
+ void render_field(void) {
152
+ for (unsigned char r = 0; r < FIELD_H; r++) {
153
+ for (unsigned char c = 0; c < FIELD_W; c++) {
154
+ unsigned char cell = field[r][c];
155
+ if (cell == prev_field[r][c]) continue; // ❌ bug: piece's
156
+ // prior screen write is at a cell where field[r][c] is still
157
+ // 0 and prev_field[r][c] is still 0 — this skip leaves the
158
+ // $A0 from last frame on screen, building a trail.
159
+ prev_field[r][c] = cell;
160
+ // ... write screen + color RAM ...
161
+ }
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### Cross-references
167
+
168
+ - Technique: `text_mode_overlay_render` (`docs/techniques/text-mode-render.md`)
169
+ - Related: any game-archetype briefing that proposes a text-mode
170
+ playfield + moving overlay (Tetris, Sokoban, Boulder Dash, board
171
+ games). `c64_game_briefing` with archetype `puzzle` should surface
172
+ this pitfall automatically.
173
+
174
+ ---
175
+
176
+ ## render_during_state_transition_clobbers_banner — Drawing field/piece after a state change overwrites GAME OVER or line-flash
177
+
178
+ **Severity:** medium
179
+ **Region:** both
180
+ **Triggered by techniques:** text_mode_overlay_render
181
+
182
+ ### Symptom
183
+
184
+ A Tetris-like correctly draws a GAME OVER banner (or a line-clear flash
185
+ animation) — for ONE frame. The next frame the banner disappears and
186
+ the playfield reappears as if nothing happened. The state machine has
187
+ correctly flipped to STATE_OVER (or STATE_LINE_FLASH), but the screen
188
+ keeps showing the active game.
189
+
190
+ ### Mechanism
191
+
192
+ A typical Tetris main loop looks like:
193
+
194
+ ```c
195
+ switch (game_state) {
196
+ case STATE_PLAY: update_play(act); break;
197
+ case STATE_LINE_FLASH: update_line_flash(); break;
198
+ case STATE_OVER: update_game_over(act); break;
199
+ }
200
+ ```
201
+
202
+ And `update_play` does the gravity tick + renders:
203
+
204
+ ```c
205
+ gravity_counter--;
206
+ if (gravity_counter == 0) {
207
+ gravity_tick(); // may flip game_state to OVER / LINE_FLASH
208
+ gravity_counter = ...;
209
+ }
210
+ render_field(); // ❌ runs even if state just changed
211
+ render_piece();
212
+ ```
213
+
214
+ If `gravity_tick` flips `game_state` to STATE_OVER (because the new
215
+ piece collides at spawn) and calls `render_game_over()` to draw the
216
+ banner, the `render_field()` / `render_piece()` calls that follow in
217
+ the *same* iteration of update_play will repaint the playfield
218
+ including the cells the banner just drew. The banner is on screen for
219
+ zero visible frames.
220
+
221
+ ### Fix
222
+
223
+ Gate the field+piece render on the current game_state, AFTER the
224
+ gravity tick has had a chance to change it:
225
+
226
+ ```c
227
+ if (game_state == STATE_PLAY) {
228
+ render_field();
229
+ render_piece();
230
+ }
231
+ ```
232
+
233
+ If the state transitioned, the renderer that handles the new state
234
+ (`render_game_over`, `render_line_flash`) has already drawn what it
235
+ needs, and the next loop iteration will dispatch to that state's
236
+ update function.
237
+
238
+ ### Cross-references
239
+
240
+ - Technique: `text_mode_overlay_render`
241
+ - Related pitfall: `dirty_cell_skip_leaves_overlay_trail`
242
+
243
+ ---
244
+
245
+ ## full_field_redraw_exceeds_vblank — Per-frame `render_field()` overruns PAL vblank, tearing the upper rows
246
+
247
+ **Severity:** high
248
+ **Region:** both
249
+ **Triggered by techniques:** text_mode_overlay_render, tile_map_render, char_scroll_buffer_v, soft_scroll_v, bitmap_scroll, colour_fade, plasma, text_zoom, speedcode_generation, charset_animation, dycp_scroller, colour_cycling, char_bullets, software_sprite_preshifted, charset_parallax, flip_screen_rooms, creature_state_machine, mixed_sprite_char_actors, eight_way_scroll_double_buffer, tunnel, voxel_landscape, isometric_tile_engine, dot_flag_sine_plotter, fire_effect, twister, shadebobs
250
+ **Mitigated by techniques:** screen_double_buffer_d018
251
+
252
+ ### Symptom
253
+
254
+ A Tetris-like (or any per-frame text-mode redraw game) shows the
255
+ piece correctly in the bottom few rows (at most screen rows 20-24) but
256
+ the UPPER rows look empty or stale, even though a memory dump of
257
+ screen RAM shows the piece at the correct upper row. The piece appears to "teleport" into
258
+ the lower screen — you can see it land but never see it descend from
259
+ the top.
260
+
261
+ Confusingly, `readMemory` on screen RAM at any moment shows the piece
262
+ at the expected row. The bug is visual-only and exists because the
263
+ CPU and the VIC raster are racing.
264
+
265
+ ### Mechanism
266
+
267
+ The C64 has no frame buffer. The VIC reads screen RAM line by line
268
+ during the visible portion of the raster (PAL lines ~50-249), and the
269
+ CPU can write to screen RAM at any time. After `vic_waitBottom`
270
+ returns at raster 256 there are 107 raster lines — 56 of lower border
271
+ and blanking to the wrap at 311, then 51 of upper border — before the
272
+ display window resumes at raster 51 and the VIC fetches screen RAM
273
+ again: 107 × 63 ≈ **6,700 cycles** in which the CPU can write screen
274
+ RAM without the raster racing it. (The 56 lines to the wrap alone are
275
+ ~3,500 cycles, which is what an earlier version of this page called
276
+ "the vblank window"; the race-free budget is nearly twice that.)
277
+
278
+ A naïve full-field redraw — walk every cell of a 10×20 playfield
279
+ (rendered 2-chars-wide = 200 cells × 2 chars), write screen RAM + color
280
+ RAM — costs about 78-86 cycles per cell in Oscar64 -O2 output: the
281
+ listing in the first section measures 15,519 cycles with the display
282
+ blanked and about 16,400 wall-clock started at raster 256 with badline
283
+ stalls counted in (CIA-timed in VICE x64sc), and the recipe's
284
+ `paint_cell` version 17,100-17,450. Call it **15,500-17,500 cycles**
285
+ for 200 cells. (An earlier version of this page said ~92 a cell /
286
+ ~18,400; that was an estimate, not a measurement.) The race-free
287
+ window is ~6,700 cycles.
288
+
289
+ When `render_field` is called every frame after `vic_waitBottom`:
290
+
291
+ 1. Started at raster 256 (where `vic_waitBottom` returns), the redraw
292
+ runs about 16,400 cycles for the listing in the first section
293
+ (measured in VICE x64sc, PAL, CIA2 timer; 17,100-17,450 for the
294
+ recipe's version) and does not finish until about raster 205-220 of
295
+ the NEXT frame — well past the top of the display window, which
296
+ resumed at line 51. An earlier version of this page said "lines 0 →
297
+ 156, ~76 lines past vblank end"; neither number follows from its own
298
+ figures (18,400 / 63 is 292 lines, which from 256 wraps to about
299
+ 236) and neither was measured.
300
+ 2. The field rows themselves are not what you see go wrong. The loop
301
+ advances about 13 raster lines per field row while the VIC's badlines
302
+ advance 8, so each playfield row is rewritten before the VIC fetches
303
+ it, for every playfield origin from screen row 1 down: rows 0-3 land
304
+ in the previous frame's bottom border, and row r after that at about
305
+ line 13r − 43 against a badline at 51 + 8·(FIELD_ROW0 + r). Measured:
306
+ a cell written by `render_field` is displayed in every one of the 20
307
+ rows. Only the last row is marginal, and only in the slower recipe
308
+ build with FIELD_ROW0 = 2 (its last cells land a line or two after
309
+ the row's badline, so that row can show the field one frame late
310
+ after a lock). An earlier version of this page said the CPU was
311
+ still clearing playfield rows 0..7 after the VIC had drawn them and
312
+ "caught up around row 8 of the playfield (= screen row ~20)" — which
313
+ is also impossible on its face, since a 20-row field on a 25-row
314
+ screen puts playfield row 8 on screen row 13 at the lowest.
315
+ 3. What loses the race is the piece overlay. `render_piece` runs only
316
+ after `render_field` returns, at about raster 205-220, when every
317
+ screen row whose badline (51 + 8·row) is earlier than that — rows 0
318
+ to about 19 — has already been fetched for this frame; and the next
319
+ frame's `render_field` erases the piece again before the badline of
320
+ every row it could have reached. So only piece cells on screen rows
321
+ whose fetch falls after the overlay write — screen rows 20-24 at
322
+ most, whose badlines are 211-243, and fewer the longer the overlay
323
+ takes — are ever displayed. With FIELD_ROW0 = 4 that is the bottom
324
+ two to four playfield rows (rows 16-19); with a field ending higher
325
+ on the screen, fewer or none. Every row above that shows the piece
326
+ erased.
327
+
328
+ Net effect: the field content is current everywhere, but the piece is
329
+ invisible in every row above the bottom few; it "appears" only once
330
+ gravity has moved it into the screen rows whose badline falls after
331
+ the overlay write — which is the "teleports into the lower screen"
332
+ symptom.
333
+
334
+ This is a frame-budget bug, not a logic bug. The unconditional redraw
335
+ pattern that "feels safe" because it has no caching, no dirty-cell
336
+ state — is itself the trap.
337
+
338
+ ### Fix
339
+
340
+ Stop full-redrawing every frame. Use an overlay pattern with explicit
341
+ prev-piece state:
342
+
343
+ ```c
344
+ // In render.c (or wherever the per-frame paint lives):
345
+
346
+ static signed char prev_x, prev_y;
347
+ static unsigned char prev_type, prev_rot;
348
+ static unsigned char have_prev = 0;
349
+
350
+ void render_piece(void) {
351
+ // Pass 1: erase prev cells by reading field[][] (which has the
352
+ // locked piece at those cells if a lock just happened — so this
353
+ // pass correctly redraws a just-locked piece instead of clearing
354
+ // it).
355
+ if (have_prev) {
356
+ for (unsigned char i = 0; i < 4; i++) {
357
+ // ... compute (cx, cy) from prev_type/rot/x/y ...
358
+ unsigned char cell = field[cy][cx];
359
+ unsigned char sc = (cell == 0) ? 0x20 : 0xA0;
360
+ // write screen + color
361
+ }
362
+ }
363
+ // Pass 2: draw current piece.
364
+ for (unsigned char i = 0; i < 4; i++) {
365
+ // ... compute (cx, cy) from current_type/rot/x/y ...
366
+ // write 0xA0 + piece_color
367
+ }
368
+ prev_x = current_x; prev_y = current_y;
369
+ prev_type = current_type; prev_rot = current_rot;
370
+ have_prev = 1;
371
+ }
372
+ ```
373
+
374
+ Per-frame cost drops to 8 cell writes — about **~2,000 cycles** in
375
+ Oscar64 -O2 (`render_piece` measured at 1,998 cycles on CIA 2 timer A,
376
+ ~250 a call through `paint_cell`; see
377
+ `recipes/oscar64/text-overlay-playfield.md`), or roughly 150–200 cycles
378
+ in hand assembly with precomputed addresses. Either fits the ~3,500
379
+ cycles to the frame wrap with room to spare, and the 107 raster lines
380
+ (~6,700 cycles) between raster 256 and the display window resuming at
381
+ line 51 are the real race-free budget. An earlier version of this page
382
+ said ~120 cycles, which is 15 a cell against the ~92 it had just priced
383
+ an Oscar64 cell write at. Call `render_field()` only when persistent
384
+ state actually changes:
385
+
386
+ - After a line-clear shift (rare).
387
+ - After game-over restart (rare).
388
+ - Initial paint from `render_init()` (once).
389
+
390
+ Reset `have_prev = 0` whenever `render_field` runs so the next frame
391
+ doesn't try to "erase" cells that have moved.
392
+
393
+ For a brief frame during a line clear, the full redraw will tear —
394
+ that's acceptable because line clears are rare and the tear lasts
395
+ exactly one frame.
396
+
397
+ ### Why the naïve advice is wrong
398
+
399
+ This pitfall corrects an earlier version of `text_mode_overlay_render`
400
+ and `dirty_cell_skip_leaves_overlay_trail` that claimed full redraw
401
+ is cheap on C64 ("4800 cycles, 24% of a PAL frame"). That estimate
402
+ under-counted Oscar64's loop overhead and ignored the cost of color
403
+ RAM writes. puzzle-tetris-c64-kb put it at ~18,000 cycles — an
404
+ estimate in the fix commit, never timed; the same listing measures
405
+ 15,500-16,400 in VICE and the recipe's version 17,100-17,450 — any of
406
+ which runs through the 107-line race-free window and well into the
407
+ visible draw of the next frame.
408
+
409
+ The takeaway: 200 cell writes IS expensive on a 1 MHz 6502 when
410
+ each "write" is actually 2 STA-absolute + 2 color-RAM STA + indexed
411
+ addressing overhead + loop counter. Don't trust intuitive estimates
412
+ for per-frame text-mode work; measure or budget explicitly.
413
+
414
+ ### Cross-references
415
+
416
+ - Technique: `text_mode_overlay_render`
417
+ - Related: `dirty_cell_skip_leaves_overlay_trail` (the bug class you
418
+ hit if you try to fix this by adding a dirty-cell cache to
419
+ `render_field` instead of switching to an overlay pattern).
420
+
421
+ ---
422
+
423
+ ## petscii_written_to_screen_ram — PETSCII bytes stored in $0400 show graphics glyphs where the letters should be
424
+
425
+ **Severity:** high
426
+ **Region:** both
427
+ **Triggered by registers:** D018
428
+ **Triggered by techniques:** petscii_screen_code_conversion, text_input_line, decimal_print, password_encoding, high_score_table_insert, adventure_database_engine, text_window_and_menu, two_word_parser
429
+ **Mitigated by techniques:** petscii_screen_code_conversion
430
+
431
+ ### Symptom
432
+
433
+ Text written straight into screen RAM comes out wrong in a pattern:
434
+ digits, space and punctuation are right, every letter is a graphics
435
+ character (a `HELLO` written this way shows five box-drawing and
436
+ line glyphs), and text taken from the keyboard with the shift or
437
+ Commodore key held comes out reversed. Sending the same bytes through
438
+ CHROUT shows the right letters. Switching character sets with `$0E`
439
+ or `$8E`, or by writing `$D018`, changes the pictures, and can make the
440
+ unshifted letters look right by accident: in the lower-case set they
441
+ show as upper-case letters, so a program tested after a `$0E` passes
442
+ and breaks when the set changes back. The reversed cells stay reversed
443
+ in both sets. The recipe below has the three rows side by side.
444
+
445
+ ### Mechanism
446
+
447
+ PETSCII and screen codes are two different encodings of the same
448
+ glyphs and agree only in `$20-$3F`. The VIC-II fetches a glyph at
449
+ `charset base + 8 * byte` and knows nothing of PETSCII; CHROUT is
450
+ where the translation lives, in the KERNAL's screen editor at `$E716`,
451
+ and a store to `$0400` bypasses it. A letter is PETSCII `$41-$5A` but
452
+ screen code `$01-$1A`, so the byte `$48` (`H`) selects glyph `$48`.
453
+ In the upper-case/graphics set that glyph is a vertical bar; in the
454
+ lower-case set glyphs `$41-$5A` are the upper-case letters, byte for
455
+ byte the same bitmaps as `$01-$1A` in the first set (read from the
456
+ `chargen-901225-01.bin` image), so the same wrong byte shows the right
457
+ letter. Shifted PETSCII is `$C1-$DA` with bit 7 set, and bit 7 of a
458
+ screen code is reverse video, so those cells come out reversed in
459
+ either set, and Commodore-key graphics (`$A0-$BF`) likewise. `$D018`
460
+ bit 1 picks which 2 KB of the character ROM supplies the pictures,
461
+ which is why changing it changes the glyphs without touching the bytes
462
+ (measured in VICE x64sc 3.10: CHROUT `$0E` sets `$D018` to `$17`, `$8E`
463
+ back to `$15`, and the bytes in screen RAM are the same before and
464
+ after).
465
+
466
+ The same mistake in the other direction is quieter: screen codes
467
+ `$01-$1A` are PETSCII control codes, so a name read back from `$0400`
468
+ and handed to CHROUT loses its letters and keeps only its digits,
469
+ spaces and punctuation. Most of those codes print nothing and leave
470
+ the cursor column where it was; some change the colour or move the
471
+ cursor (measured in VICE x64sc 3.10: CHROUT of `$01-$04` after a PLOT
472
+ to column 0 leaves `$0400-$0403` at zero and the column at 0).
473
+
474
+ ### Fix
475
+
476
+ Convert at the boundary, every time a byte crosses from a PETSCII
477
+ source (keyboard, file, CHROUT-style string) to screen RAM or back.
478
+ The rule is six compares and a mask (`petscii_screen_code_conversion`
479
+ in `techniques/text.md`): `$20-$3F` unchanged, `$40-$5F` less `$40`,
480
+ `$60-$7F` less `$20`, `$A0-$FF` low seven bits with bit 6 set, `$FF`
481
+ to `$5E`. Or write text through CHROUT and let the KERNAL convert,
482
+ accepting the cursor, the wrap and the clobbered registers. In
483
+ KickAssembler, `.text` already emits screen codes; the trap there is
484
+ the other way round, a `.text` string sent through CHROUT
485
+ (`toolchains/kickassembler-reference.md`). Keep stored text in one encoding,
486
+ PETSCII by preference, and convert on the way to the screen.
487
+
488
+ ### Worked example
489
+
490
+ ```c
491
+ // Bad: PETSCII bytes poked into screen RAM; every letter is a graphics glyph.
492
+ const char msg[] = { 0x48, 0x45, 0x4c, 0x4c, 0x4f, 0 }; // HELLO in PETSCII
493
+ for (char i = 0; msg[i]; i++) SCREEN[40 * 4 + i] = msg[i];
494
+
495
+ // Good: convert on the way in; digits and space pass through unchanged.
496
+ for (char i = 0; msg[i]; i++) SCREEN[40 * 3 + i] = pet2scr(msg[i]);
497
+ ```
498
+
499
+ `pet2scr()` is the routine in `recipes/oscar64/petscii-screen-codes.md`,
500
+ whose screenshot shows both rows.
501
+
502
+ ### Cross-references
503
+
504
+ - Technique: `petscii_screen_code_conversion` (the rule, measured
505
+ against CHROUT over every code)
506
+ - Techniques that write screen RAM from a PETSCII source:
507
+ `text_input_line`, `decimal_print`
508
+ - Register: `$D018` bit 1 (`hardware/vic-ii-reference.md`) selects the
509
+ set; it never changes the code
510
+ - Recipe: `recipes/oscar64/petscii-screen-codes.md`
511
+
512
+ ---
513
+
514
+ ## colour_ram_index_past_last_cell_hits_cia1 — A colour RAM index of 1,024 or more writes CIA1's registers, not colour RAM
515
+
516
+ **Severity:** high
517
+ **Region:** both
518
+ **Triggered by registers:** DC00, DC02, DC04, DC0D, DC0E
519
+ **Triggered by techniques:** tile_map_render, colour_cycling, colour_fade, screen_wipe, text_window_and_menu, text_mode_overlay_render, flip_screen_rooms, plasma, koala_format, char_scroll_buffer_h, difficulty_ramp_tables, fire_effect, screen_dissolve_lfsr, shadebobs
520
+
521
+ ### Symptom
522
+
523
+ A colour fill or colour copy runs, the screen looks right, and
524
+ something unrelated stops. Which thing depends on how far the index
525
+ went and what value it carried. The jiffy clock freezes, the cursor
526
+ stops flashing and the keyboard goes dead; or the keyboard alone goes
527
+ dead while the joystick still reads; or a CIA-timed measurement returns
528
+ nonsense while the frame count stays plausible (that last is how the
529
+ `recipes/oscar64/difficulty-tables.md` build met it: coins drawn on text
530
+ rows up to 28 put the colour writes at `$D800 + 1120` and beyond). No
531
+ cell on the screen is wrong, because every cell was written before the
532
+ index left the chip.
533
+
534
+ ### Mechanism
535
+
536
+ The screen is 40 by 25, which is 1,000 cells, and colour RAM holds
537
+ them at `$D800` to `$DBE7`. The page it sits in, `$D800` to `$DBFF`, is
538
+ 1,024 bytes long (arithmetic), so a fill whose 16-bit index is compared
539
+ against a page boundary, or a copy that moves four pages of 256 because
540
+ 1,000 does not divide, writes 24 bytes past the last cell into the
541
+ spare colour RAM at `$DBE8` to `$DBFF`, which is harmless, and then
542
+ byte 1,024 lands at `$DC00`: CIA1. The next sixteen indices walk the
543
+ chip's sixteen registers in order, port A, port B, the two data
544
+ direction registers, timer A, timer B, the four time-of-day registers,
545
+ the serial register, the interrupt control register and the two control
546
+ registers. The registers repeat every 16 bytes to `$DCFF` (index 1,279),
547
+ so a longer overrun writes each of them again on every pass, and index
548
+ 1,280 reaches CIA2.
549
+
550
+ What a single pass does was measured in VICE x64sc 3.10 (PAL) with a
551
+ fill of `$0E`, light blue, whose 16-bit index ran to 1,040. CIA1 before,
552
+ `$DC00` to `$DC0F`:
553
+
554
+ ```text
555
+ 7f ff ff 00 93 24 ff ff 00 00 00 01 00 00 01 08
556
+ ```
557
+
558
+ After the fill:
559
+
560
+ ```text
561
+ ff 7f 0e 0e 4e 21 0e 0e 0e 0e 0e 01 0e 00 0e 0e
562
+ ```
563
+
564
+ Every register that reads back its written value now reads `$0E`.
565
+ Timer A read `$214E` on two samples about 600 cycles apart, where before
566
+ the fill the same two samples read `$2448` then `$2243`: the `$0E`
567
+ written to `$DC0E` has bit 0 clear, so timer A is stopped, and the `$0E`
568
+ written to `$DC0D` cleared the interrupt masks for timers B, the alarm
569
+ and the serial port. The KERNAL's jiffy clock at `$A2` advanced from
570
+ `$38` to `$4D` over a delay loop run before the fill and stayed at `$50`
571
+ over the same loop run after it. With no jiffy interrupt there is no
572
+ SCNKEY, so no key is read. `$DC02`, port A's data direction register,
573
+ reads `$0E`: only bits 1 to 3 drive, and a keyboard scan that then
574
+ selects every column by writing `$00` to `$DC00` reads `$F1` back from
575
+ the port, not `$00`. Bits 6 and 7 of `$DC01` now carry the timer outputs
576
+ (`$7F` after this run, `$3F` after an identical one), because the `$0E`
577
+ in both control registers set the port-B-on bit. The same fill with the
578
+ index stopped at 1,000 left all sixteen bytes as they were, timer A
579
+ counting (`$27C7` then `$256C`), and the column select reading `$00` from
580
+ `$DC00` and `$FF` from `$DC01`.
581
+
582
+ The most common fill value is `$00`, black, and it does its damage at
583
+ index 1,026: `$00` into `$DC02` turns every port A line into an input,
584
+ which is the state `cia1_ddr_cleared_kills_keyboard` in
585
+ `pitfalls/input.md` describes. Measured with a `$00` fill whose index
586
+ ran to 1,027: `$DC02` read `$00` after it, the timers were still running
587
+ (`$228A` then `$205A`), and the column select read `$FF` from `$DC00`
588
+ whatever was written to it, so the scanner's column drive is gone and the
589
+ keyboard is dead while the joystick, which grounds its lines, still
590
+ reads. A fill that runs to 1,136 rewrites the sixteen registers seven
591
+ times over; after it timer A read `$0E0E` on both samples, stopped and
592
+ reloaded from the latch the fill wrote (measured, same build).
593
+
594
+ The 24 spare bytes are real nibble RAM. After the `$0E` fill they read
595
+ `$0E` from `$DBE8` to `$DBFF`; after the `$00` fill they read `$00` in
596
+ 21 places and `$F0` in three, the low nibble the value written and the
597
+ high nibble whatever the VIC last fetched, which changes from read to
598
+ read. With the index stopped at 1,000 they held what the machine came up
599
+ with (`02 0f f8 07 00 0f 04 0f 04 0f 05 0f 03 0e 00 0b`; a second boot
600
+ gave the same sixteen low nibbles under different high ones), because
601
+ the KERNAL's screen clear stops at 1,000 as well. Mask reads of this region
602
+ with `AND #$0F`; `hardware/c64-registers-reference.md` says the same.
603
+
604
+ ### Fix
605
+
606
+ Bound the index at 1,000, not at a page boundary. A fill by rows, 25
607
+ rows of 40 with an 8-bit loop inside a row, cannot pass the last cell.
608
+ A fill that must be a single 16-bit loop compares the low byte against
609
+ `<1000` and the high byte against `>1000`, or counts four pages and
610
+ stops the last one at `$E8`. In a compiler, put the size in the type
611
+ (`char colour[25][40]`) or assert it (`static_assert(sizeof(map) ==
612
+ 1000)` in Oscar64), and clamp any row index before it multiplies by 40:
613
+ row 25 is already the spare bytes, row 25 cell 24 onward is the CIA. If
614
+ a fill is allowed to run over on purpose, to 1,024 for a fast unrolled
615
+ copy, it must stop at 1,024 exactly, and the 24 bytes it writes past the
616
+ screen must not be counted on to hold a full byte.
617
+
618
+ ### Worked example
619
+
620
+ Bad: a "fill four pages" loop, the shape a screen clear takes when the
621
+ programmer rounds 1,000 up. It writes `$DC00` to `$DC17` on its last
622
+ page.
623
+
624
+ ```asm
625
+ // BAD: 4 x 256 = 1,024, and cell 1,000 onward is not colour RAM.
626
+ fill_colour_bad:
627
+ lda #$0e
628
+ ldx #$00
629
+ !: sta $d800,x
630
+ sta $d900,x
631
+ sta $da00,x
632
+ sta $db00,x // stops at $dbff: the 24 spare nibbles, no harm
633
+ inx
634
+ bne !-
635
+ rts
636
+ ```
637
+
638
+ That form stops at `$DBFF` and is harmless. The one that reaches the
639
+ CIA is its 16-bit cousin, a pointer walked until the high byte turns
640
+ over, which is what the difficulty-tables build and the measured fill
641
+ above both did:
642
+
643
+ ```asm
644
+ // BAD: meant to stop when the high byte reaches $dc; the compare is
645
+ // one page late, so all of $dc00-$dcff is written, CIA1 sixteen times.
646
+ fill_colour_bad16:
647
+ lda #<$d800
648
+ sta $fd
649
+ lda #>$d800
650
+ sta $fe
651
+ ldy #$00
652
+ lda #$0e
653
+ !: sta ($fd),y
654
+ iny
655
+ bne !-
656
+ inc $fe
657
+ lda $fe
658
+ cmp #$dd // should be #$dc
659
+ bne !-
660
+ rts
661
+ ```
662
+
663
+ Good: 25 rows of 40, and no index that can leave the chip. Run under
664
+ the same harness, this routine left all sixteen CIA1 bytes as the boot
665
+ had them (timer A still counting), wrote `$0E` to cell 0 and to cell
666
+ 999 at `$DBE7`, and left `$DBE8` onward as it found it (measured, VICE
667
+ x64sc 3.10).
668
+
669
+ ```asm
670
+ // GOOD: rows of 40, an 8-bit index inside each row.
671
+ fill_colour_good:
672
+ lda #<$d800
673
+ sta $fd
674
+ lda #>$d800
675
+ sta $fe
676
+ ldx #25
677
+ lda #$0e
678
+ !row: ldy #39
679
+ !: sta ($fd),y
680
+ dey
681
+ bpl !-
682
+ pha
683
+ clc
684
+ lda $fd
685
+ adc #40
686
+ sta $fd
687
+ bcc !+
688
+ inc $fe
689
+ !: pla
690
+ dex
691
+ bne !row-
692
+ rts
693
+ ```
694
+
695
+ ### Cross-references
696
+
697
+ - **Sibling pitfall:** `cia1_ddr_cleared_kills_keyboard` in
698
+ `pitfalls/input.md` is the state a `$00` fill leaves at index 1,026,
699
+ reached there by a deliberate write; this entry is the same state
700
+ reached by an index.
701
+ - **Hardware:** `hardware/c64-registers-reference.md`, the Color RAM
702
+ section, for `$DBE8` to `$DBFF` (nibble RAM, high nibble undefined) and
703
+ the I/O map that puts CIA1 at `$DC00`; `hardware/cia-reference.md` for
704
+ what each of the sixteen registers does with the byte it is given.
705
+ - **Recipe:** `recipes/oscar64/difficulty-tables.md`, "A bug this page
706
+ had": the overrun met in the wild, at `$D800 + 1120`.
707
+ - **Not measured here:** NTSC (the mechanism is address arithmetic and
708
+ has no region term); a real key press after the overrun (the harness
709
+ cannot press one; the keyboard consequence is read from the port bytes
710
+ and from `cia1_ddr_cleared_kills_keyboard`); the CIA2 case at index
711
+ 1,280.
712
+
713
+ ---
714
+
715
+ ## vic_colour_register_upper_nibble_reads_set — A VIC colour register reads back as the colour plus 240, so a compare against the value written never matches
716
+
717
+ **Severity:** medium
718
+ **Region:** both
719
+ **Triggered by registers:** D020, D021, D022, D023, D024, D025, D026, D027, D028, D029, D02A, D02B, D02C, D02D, D02E, D016, D018, D019, D01A
720
+ **Triggered by techniques:** basic_extension_wedge
721
+
722
+ ### Symptom
723
+
724
+ An `IF PEEK(53280)=2` that is silently false after `POKE 53280,2`. A
725
+ `cmp #2` after `lda $d020` whose branch is never taken. A colour saved
726
+ with `lda $d021` and used as a table index that reads 240 bytes past the
727
+ end of a sixteen-entry table. A "restore the border" routine that works,
728
+ because a write only takes the low four bits, while the compare in the
729
+ same program does not. Nothing crashes and nothing is drawn wrong; the
730
+ program just takes the other branch, and the bug looks like logic.
731
+
732
+ This bit the BASIC wedge recipe on its first run: `&B 2` set the border
733
+ red, `IF PEEK(53280)=2 THEN` skipped its line without a word, and the
734
+ verdict byte was never written.
735
+
736
+ ### Mechanism
737
+
738
+ The VIC-II has four bits of storage behind each colour register
739
+ ($D020 to $D02E). A write keeps the low nibble and drops the rest. A
740
+ read drives the low four bits from that storage and leaves the upper
741
+ four data lines undriven, and on this chip an undriven line reads as 1.
742
+ So every colour register reads back as the colour plus $F0 (240). VICE
743
+ models that as ones as well; measured below on both models, and the
744
+ audited `hardware/vic-ii-reference.md` says the same ("Only the low 4
745
+ bits matter; the upper 4 bits read as 1" under $D020, "Bits 7-4 read 1"
746
+ under $D021).
747
+
748
+ The same thing happens to every other unused VIC bit. Measured in the
749
+ same run: $D016 written $00 reads $C0 (bits 7-6), $D018 written $14
750
+ reads $15 (bit 0), $D019 reads with bits 6-4 set, and $D01A written $00
751
+ reads $F0 (bits 7-4). The hardware page marks each of those bits "read 1"
752
+ and agrees with the measurement. $D011 has no unused bit and read back
753
+ exactly what was written ($1B). $D01E and $D01F are eight full bits of
754
+ collision latch: both read $00 with no sprites on, and no bit reads set
755
+ there, so this pitfall does not touch them (their own trap is that the
756
+ read clears them, `sprite_priority_collision_silent` in
757
+ `pitfalls/sprite.md`). $D02F to $D03F have no register at all and read
758
+ $FF. Whether the upper nibble reads as 1 on every real VIC revision is
759
+ not measured here; VICE, both models, is what the table below shows.
760
+
761
+ ### Fix
762
+
763
+ Mask before you compare: `and #$0f` after the read, or `(PEEK(53280)
764
+ AND 15)` in BASIC. If a colour is going to index a table, mask it first.
765
+ Better, keep your own copy of each colour in RAM and never read the
766
+ register back; the register is write-only in effect, and a shadow byte
767
+ also survives a raster routine that changes the border mid-frame. A read
768
+ of a VIC colour register that is stored, compared or indexed without a
769
+ mask is wrong; a read that is written straight back to a colour register
770
+ is harmless, because the write drops the nibble again.
771
+
772
+ ### Worked example
773
+
774
+ The fragment builds as written. The first compare is the bug; the
775
+ second is the fix.
776
+
777
+ ```asm
778
+ // Bad: after a write of 2, $D020 reads $F2. Z is never set here.
779
+ lda #2
780
+ sta $d020
781
+ lda $d020
782
+ cmp #2
783
+ beq bad_match // never taken
784
+ jmp keep_going
785
+ bad_match:
786
+ inc $0400 // would show a glyph; it never does
787
+ keep_going:
788
+
789
+ // Good: mask the read, then compare the low nibble.
790
+ lda $d020
791
+ and #$0f
792
+ cmp #2
793
+ beq good_match // taken
794
+ jmp done
795
+ good_match:
796
+ inc $0401 // the glyph appears
797
+ done:
798
+ rts
799
+
800
+ // Better: a shadow byte, written once with the colour, is always exact.
801
+ border_shadow:
802
+ .byte 2
803
+ ```
804
+
805
+ The read-back measured in VICE 3.10 x64sc, `-model ntsc` and default
806
+ PAL, with the program above's write-and-read loop over every value.
807
+ Each register was written 0 to 15 and read straight back; the two
808
+ models gave identical bytes:
809
+
810
+ ```text
811
+ register written 0..15 reads back as
812
+ $D020-$D02E 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
813
+ (all fifteen registers, PAL and NTSC; written value + 240)
814
+ $D020 <- 2, read, AND #$0F -> 2
815
+ $D016 <- $08 / $C8 / $00, read -> $C8 / $C8 / $C0 (bits 7-6 read 1)
816
+ $D018 <- $15 / $14, read -> $15 / $15 (bit 0 reads 1)
817
+ $D019 read -> $71 PAL, $70 NTSC (bits 6-4 read 1;
818
+ bit 0 is the raster latch, not an unused bit)
819
+ $D01A <- $00 / $0F / $01, read -> $F0 / $FF / $F1 (bits 7-4 read 1)
820
+ $D011 <- $1B, read -> $1B (no unused bit)
821
+ $D01E, $D01F read, sprites off -> $00, $00 (no unused bit)
822
+ $D02F, $D03F read -> $FF, $FF (no register)
823
+
824
+ BASIC, typed into the KERNAL buffer, PAL and NTSC:
825
+ POKE53280,2:PRINTPEEK(53280);PEEK(53280)AND15 -> 242 2
826
+ IF PEEK(53280)=2 THEN PRINT "EQ2" -> (nothing printed)
827
+ IF PEEK(53280)=242 THEN PRINT "EQ242" -> EQ242
828
+ POKE53281,3:PRINTPEEK(53281);PEEK(53281)AND15 -> 243 3
829
+ ```
830
+
831
+ ### Cross-references
832
+
833
+ - Technique: `basic_extension_wedge` (`techniques/text.md`), whose
834
+ recipe met this and masks with `AND 15`
835
+ - Recipe: `recipes/kickassembler/basic-wedge.md`, the run that found it
836
+ - Registers: `$D020` to `$D02E`, `$D016`, `$D018`, `$D019`, `$D01A`
837
+ (`hardware/vic-ii-reference.md`; each entry marks the bits that read 1)
838
+ - Pitfall: `d016_unmasked_rmw_clobbers_csel_mcm` (`pitfalls/scroll.md`),
839
+ the read-modify-write face of the same two bits on `$D016`
840
+ - Pitfall: `sprite_priority_collision_silent` (`pitfalls/sprite.md`),
841
+ the read trap on `$D01E`/`$D01F`, which is clearing, not garbage
842
+ - Pitfall: `sid_write_only_registers` (`pitfalls/sid.md`), the
843
+ neighbouring chip, where the whole byte is garbage rather than one
844
+ nibble
845
+
846
+ ## ecm_with_mcm_set_is_invalid_black_mode — Setting ECM while MCM is still on selects an invalid mode that draws the whole window black
847
+
848
+ **Severity:** medium
849
+ **Region:** both
850
+ **Triggered by registers:** D011, D016
851
+ **Triggered by techniques:** ecm_mode, mcm_text, raster_split_modes
852
+
853
+ ### Symptom
854
+
855
+ The program switches a text screen to Extended Colour Mode and the
856
+ display window goes black. Not blank: black. The border keeps its
857
+ colour, the raster interrupts keep firing, and sprites still show, so
858
+ the machine is plainly running. The four background bands the code set
859
+ up are not there, and neither are the glyphs. Worse, the game keeps
860
+ playing against the field nobody can see: the sprite-to-background
861
+ collision bit still sets when a sprite crosses a glyph, and a sprite
862
+ set to run behind the playfield is still cut by the glyph pixels, which
863
+ are now the same black as everything round them.
864
+
865
+ The usual route in is a mode change. The previous screen was a
866
+ multicolour character screen, so `$D016` still holds `$D8` with MCM
867
+ (bit 4) set. The ECM screen's setup writes `$D011` with bit 6 and never
868
+ touches `$D016`, because ECM is a `$D011` mode and the writer's mental
869
+ model of `$D016` is "scroll and 38-column". ECM and MCM are now both
870
+ set, and that pair is not a mode.
871
+
872
+ ### Mechanism
873
+
874
+ The VIC-II decodes its display mode from three bits: ECM (`$D011` bit
875
+ 6), BMM (`$D011` bit 5) and MCM (`$D016` bit 4). Five of the eight
876
+ combinations are modes. The other three, every combination in which ECM
877
+ is set alongside BMM or MCM, are the invalid modes: the audited
878
+ `hardware/vic-ii-reference.md` lists ECM+MCM text, ECM+BMM and
879
+ ECM+BMM+MCM as "output is black", and says that in those modes "the
880
+ display sequencer still runs, but the pixel data output is forced to
881
+ black. Collisions and sprites still function."
882
+
883
+ That last sentence is the trap. The sequencer still classifies each
884
+ pixel as foreground or background, and the sprite unit still compares
885
+ against that classification for `$D01F` and for the `$D01B` priority
886
+ mask; only the colour lookup is replaced by black. So the picture
887
+ disappears while every part of the logic that reads the picture through
888
+ the VIC carries on as if it were there.
889
+
890
+ Measured in VICE 3.10 x64sc, PAL and NTSC, with a screen of four
891
+ character bands (codes 1, 65, 129, 193), white colour RAM, `$D021` to
892
+ `$D024` set to blue, green, red and yellow, a light blue border and one
893
+ solid white sprite parked over the second band. Setting `$D011` bit 6
894
+ with `$D016` at `$C8` gave the four coloured bands with white glyphs on
895
+ both models. Setting the same bit with `$D016` at `$D8` gave a window of
896
+ 63,496 black pixels and 504 white ones, and the 504 are the 24 by 21
897
+ sprite. The border stayed light blue. `$D01F` read `$01` two frames
898
+ after the mode write and `$01` again a frame later, on both models, in
899
+ the black mode exactly as in the good one. With `$D01B` bit 0 set in
900
+ the black mode, 210 of the sprite's 504 pixels were black: the glyphs
901
+ under it still masked it. ECM with BMM (`$D011` written with bits 6 and
902
+ 5) gave the same 63,496 black pixels and the same `$D01F` of `$01`.
903
+
904
+ Whether a real VIC of any revision matches VICE pixel for pixel in the
905
+ invalid modes is not measured here; the black output and the live
906
+ collisions are what the hardware page states and what the emulator
907
+ showed.
908
+
909
+ A mid-frame split that goes from a multicolour character zone to an ECM
910
+ zone meets the same pair for the cells between its two stores if it
911
+ writes `$D011` before `$D016`; `raster_split_modes` warns of an
912
+ "undefined intermediate mode" between the writes, and for this pair the
913
+ intermediate mode is black. How many cells that covers is not measured
914
+ here.
915
+
916
+ ### Fix
917
+
918
+ Clear MCM before you set ECM, or in the same handful of cycles, and
919
+ never assume `$D016` from the last screen. Write the whole `$D016` byte
920
+ for the new screen (`$C8` for a 40-column, unscrolled ECM screen) rather
921
+ than leaving whatever the previous mode put there. If you must
922
+ read-modify-write, `and #$EF` clears the bit without disturbing CSEL and
923
+ XSCROLL. In a raster split from multicolour text to ECM, store `$D016`
924
+ first and `$D011` second, so the only intermediate state is plain text
925
+ rather than black.
926
+
927
+ Write a sanity check into the setup routine during development: after
928
+ the mode writes, `lda $d011`, `and #$40`, and if it is set, `lda $d016`,
929
+ `and #$10`, which must be zero. Bits 7 and 6 of `$D016` read as ones
930
+ (`vic_colour_register_upper_nibble_reads_set` above), so mask before
931
+ you compare.
932
+
933
+ ### Worked example
934
+
935
+ The fragment builds as written. The first block is the bug as it
936
+ arrives from a multicolour screen; the second is the fix.
937
+
938
+ ```asm
939
+ // Bad: $D016 still holds $D8 from the multicolour text screen.
940
+ // Setting ECM on top of it selects the ECM+MCM invalid mode.
941
+ lda #$d8 // MCM on, as the previous screen left it
942
+ sta $d016
943
+ lda $d011
944
+ ora #$40 // ECM on: the window goes black
945
+ sta $d011
946
+
947
+ // Good: put $D016 into its ECM-screen state first, then set ECM.
948
+ lda #$c8 // MCM off, CSEL on, XSCROLL 0
949
+ sta $d016
950
+ lda $d011
951
+ ora #$40 // ECM on: four background bands appear
952
+ sta $d011
953
+
954
+ // Also good, when $D016 carries scroll state you want to keep.
955
+ lda $d016
956
+ and #$ef // clear MCM only
957
+ sta $d016
958
+ lda $d011
959
+ ora #$40
960
+ sta $d011
961
+ rts
962
+ ```
963
+
964
+ The runs behind the table used the register writes above on a screen of
965
+ four bands with one white sprite at (100, 110). Each row is the bytes a
966
+ `-moncommands` trace dumped after the program stored them, followed by
967
+ the exit screenshot's pixel count over the 320 by 200 window. The two
968
+ models gave identical bytes and identical counts:
969
+
970
+ ```text
971
+ variant $D011 $D016 $D01F $D01F window pixels (PAL and NTSC)
972
+ +2 fr +3 fr
973
+ plain text (control) $1B $C8 $01 $01 64,000 non-black: blue field, white glyphs
974
+ A: ECM, $D016 = $C8 $DB $C8 $01 $01 64,000 non-black: blue/green/red/yellow bands
975
+ B: ECM, $D016 = $D8 $DB $D8 $01 $01 63,496 black + 504 white (the sprite)
976
+ B with $D01B bit 0 set $DB $D8 $01 $01 63,706 black + 294 white (sprite cut by glyphs)
977
+ C: ECM + BMM, $D016 = $C8 $FB $C8 $01 $01 63,496 black + 504 white (the sprite)
978
+ fix: $D016 -> $C8, then ECM $DB $C8 $01 $01 64,000 non-black, identical to A
979
+
980
+ Border pixel (2, 100): light blue in every run, both models.
981
+ $D01E: $00 in every run (one sprite).
982
+ ```
983
+
984
+ `$D016` reads back with bits 7 and 6 set, so `$C8` is the byte written
985
+ as `$C8` and also the byte written as `$08`; the low nibble is what the
986
+ table is about.
987
+
988
+ ### Cross-references
989
+
990
+ - Technique: `ecm_mode` (`techniques/bitmap-modes.md`), which names
991
+ ECM+BMM as invalid and is silent on ECM+MCM
992
+ - Technique: `mcm_text` (`techniques/bitmap-modes.md`), the screen that
993
+ leaves MCM set on the way in
994
+ - Technique: `raster_split_modes` (`techniques/raster.md`), whose
995
+ "undefined intermediate mode" between the `$D011` and `$D016` stores
996
+ is this pair when the split runs from multicolour text to ECM
997
+ - Registers: `$D011`, `$D016` (`hardware/vic-ii-reference.md`, the mode
998
+ table and "Illegal display modes")
999
+ - Pitfall: `d016_unmasked_rmw_clobbers_csel_mcm` (`pitfalls/scroll.md`),
1000
+ the other way to end up with the wrong MCM bit
1001
+ - Pitfall: `sprite_priority_collision_silent` (`pitfalls/sprite.md`),
1002
+ the `$D01F` read that clears; the reads in the table were the only
1003
+ reads after the mode write
1004
+ - Pitfall: `vic_colour_register_upper_nibble_reads_set` (this page), why
1005
+ the `$D016` check masks before comparing
1006
+
1007
+ ---
1008
+
1009
+ ## ctm_embedded_whole_shifts_charset — A CharPad `.ctm` embedded whole puts its header where glyph 0 should be and shifts every glyph
1010
+
1011
+ **Severity:** medium
1012
+ **Region:** both
1013
+ **Triggered by registers:** D018
1014
+ **Triggered by techniques:** tile_map_render
1015
+
1016
+ ### Symptom
1017
+
1018
+ The program points `$D018` at a charset the build embedded from the
1019
+ artist's CharPad project file, and the screen is wrong everywhere at
1020
+ once. If the program cleared the screen to code 0 itself, every cleared
1021
+ cell shows the same small pattern, so the whole window is covered in
1022
+ it. If it cleared through the KERNAL (`CHR$(147)`, or `$E544`), the
1023
+ screen is full of `$20` instead, and that cell draws a mixture of two
1024
+ other shifted glyphs, so the window reads as garbage rather than as one
1025
+ repeated pattern (arithmetic from the shift below, not measured here).
1026
+ The glyphs the program did print are there in outline but not as
1027
+ drawn, each one a mixture of the tail of one source glyph and the head
1028
+ of the next. Text
1029
+ written with the same screen codes against a raw charset export comes
1030
+ out right, so the codes and the `$D018` value are not the problem. The
1031
+ assembler and the compiler both said nothing.
1032
+
1033
+ ### Mechanism
1034
+
1035
+ A `.ctm` is CharPad's save format, not a charset. The file opens with
1036
+ the signature `CTM`, a version byte and a short fixed header, and then
1037
+ the data sections; the character section is first, but it is not at
1038
+ offset 0. On a version 8 file the header is 14 bytes and the character
1039
+ section opens with a 2-byte marker and a 2-byte count, so the first
1040
+ glyph row is at offset 18 (`$12`). The other versions put it elsewhere:
1041
+ 20 (`$14`) on version 5, which has no markers, and 23 (`$17`) on
1042
+ version 9, whose header carries five extra grid bytes (both arithmetic
1043
+ from the field tables in `formats/c64-file-formats.md`, not measured
1044
+ here). After the glyphs come the materials, the optional tiles and the
1045
+ map, so the file is also longer than the charset it holds.
1046
+
1047
+ Oscar64's `#embed "file"` and KickAssembler's `.import binary "file"`
1048
+ both copy the file as written. The header lands on glyph 0, the marker
1049
+ and count follow it, and every glyph after that sits 18 bytes late: two
1050
+ whole glyphs and two rows. What the VIC draws for code `n` is the last
1051
+ two rows of source glyph `n - 3` followed by the first six rows of
1052
+ source glyph `n - 2`. Code 0, the code the rig below cleared the screen
1053
+ to, draws the eight bytes `43 54 4D 08 00 00 00 0E`, which is `CTM`,
1054
+ the version and the first half of the header, and that is the pattern
1055
+ that covers the window. A KERNAL clear fills the screen with code 32,
1056
+ which under the same shift is the last two rows of source glyph 29 and
1057
+ the first six of glyph 30 (arithmetic, not measured here).
1058
+
1059
+ Measured on the windowless x64sc build of VICE 3.10, PAL, with a
1060
+ synthetic version 8 file built in Python from the layout on the formats
1061
+ page (no CharPad file exists on this machine): 64 glyphs, no tiles, a
1062
+ 40 by 25 map, 2,602 bytes, the same shape and size as the page's
1063
+ `introfont.ctm` sample. Glyph 0 was blank, glyph 1 solid, glyph 2 a
1064
+ checkerboard, glyph 3 a horizontal bar. The charset went to `$3000`
1065
+ and `$D018` to `$1C`. A `-moncommands` trace on the store to `$D018`
1066
+ dumped `$3000`:
1067
+
1068
+ - Oscar64 `#embed 2048 0 "font.ctm"`, and KickAssembler
1069
+ `.import binary "font.ctm"`, both gave `43 54 4D 08 00 00 00 0E 00
1070
+ 0F 0C 09 08 07 DA B0 3F 00 00 00 00 00 00 00 00 00 FF FF FF FF FF
1071
+ FF`: header, marker, count, then glyph 0's zeros running into glyph
1072
+ 1's `FF` rows two bytes late. Identical bytes from the two toolchains.
1073
+ - Oscar64 `#embed 512 18 "font.ctm"` and KickAssembler
1074
+ `.import binary "font.ctm", 18, 512` both gave `00 00 00 00 00 00 00
1075
+ 00 FF FF FF FF FF FF FF FF AA 55 AA 55 AA 55 AA 55 00 00 00 FF FF 00
1076
+ 00 00`, the four glyphs as drawn.
1077
+
1078
+ The exit screenshots agree. The whole-file runs lit 14,086 pixels of
1079
+ the 320 by 200 window with the screen cleared to code 0, and the code 0
1080
+ cell itself lit 14 pixels; the eight cells printed with codes 0 to 7
1081
+ lit 14, 20, 6, 48, 40, 24, 12 and 22. The skipped runs lit 332 pixels,
1082
+ the code 0 cell lit none, and the same eight cells lit 0, 64, 32, 16,
1083
+ 16, 28, 8 and 8, which is blank, solid, checkerboard, bar, bar, box and
1084
+ two diagonals. Oscar64 and KickAssembler gave the same counts as each
1085
+ other in both cases.
1086
+
1087
+ The bare `#embed "font.ctm"` did not compile into a sized array: with
1088
+ `char Charset[2048]` Oscar64's first message was
1089
+ `font.ctm(2049, 1) : error 3006: '}' expected`, followed by a run of
1090
+ 3037 and 3006 errors on the lines after it (the file's bytes past the
1091
+ 2,048 the array holds), and with `char Charset[]` inside a 2 KB region
1092
+ it reported "Could not place object 'Charset'", size 2,602 against
1093
+ 2,048. The two
1094
+ forms that build are the sized slice `#embed 2048 0`, which drops the
1095
+ tail, and an unsized array in a region wider than 2 KB, which put the
1096
+ same header at `$3000` and ran the data to `$3A2A`, 554 bytes into the
1097
+ next charset bank. KickAssembler's whole import did the same: the
1098
+ `.prg` for it was 12,843 bytes and the skipped one 10,753.
1099
+
1100
+ Within version 8 the character section is always first, so the 18-byte
1101
+ skip is a constant for that version on the file built here; it is not
1102
+ a constant across versions, and whether any CharPad option puts a
1103
+ variable-length section before the characters is not measured here.
1104
+
1105
+ ### Fix
1106
+
1107
+ Skip the container. In Oscar64, `#embed 2048 18 "font.ctm"` takes the
1108
+ 2 KB of glyphs from a 256-character version 8 file, or use the format-aware
1109
+ `#embed ctm_chars "font.ctm"`, which reads the header and the markers
1110
+ itself (versions 8 and 9 only; it takes a version 5 file without
1111
+ complaint and returns the wrong bytes, see the formats page). In
1112
+ KickAssembler, `.import binary "font.ctm", 18, $800`. Better than
1113
+ either: have the artist export the raw charset (CharPad's "Characters
1114
+ binary" export, per `art/asset-pipelines.md`, not measured here) and
1115
+ embed that, so the build does
1116
+ not carry the version-dependent offset at all; or strip the container
1117
+ in a build step with the walker on the formats page. Check the version
1118
+ byte before you settle on an offset.
1119
+
1120
+ A cheap guard for the build you have: read byte 0 of the charset bank
1121
+ before pointing `$D018` at it, and stop if it is `$43`, the `C` of
1122
+ `CTM`. A real glyph 0 can hold `$43` (it is a row of `01000011`), so
1123
+ the check is a tripwire during development, not a proof.
1124
+
1125
+ ### Worked example
1126
+
1127
+ The four embed lines with what each put at `$3000` (measured above):
1128
+
1129
+ ```text
1130
+ Oscar64: #embed 2048 0 "font.ctm" -> 43 54 4D 08 00 00 00 0E ... header on glyph 0
1131
+ Oscar64: #embed 512 18 "font.ctm" -> 00 00 00 00 00 00 00 00 FF FF ... glyphs
1132
+
1133
+ KickAss: .import binary "font.ctm" -> 43 54 4D 08 ... same 32 bytes as above
1134
+ KickAss: .import binary "font.ctm", 18, 512 -> 00 00 00 00 ... glyphs, same as above
1135
+
1136
+ The file built here holds 64 glyphs; a 256-glyph file takes 2048 in
1137
+ place of 512 (arithmetic, not measured here).
1138
+ ```
1139
+
1140
+ The tripwire, which builds as written:
1141
+
1142
+ ```asm
1143
+ // Tripwire: refuse to switch charsets while the CTM header is on glyph 0.
1144
+ .label charset = $3000 // the bank the build embedded into
1145
+
1146
+ lda charset // byte 0 of glyph 0
1147
+ cmp #$43 // 'C' of "CTM": the container came along
1148
+ beq container
1149
+ lda #$1c // screen $0400, charset $3000
1150
+ sta $d018
1151
+ rts
1152
+ container:
1153
+ inc $d020 // flash the border and stop
1154
+ jmp container
1155
+ ```
1156
+
1157
+ ### Cross-references
1158
+
1159
+ - Format: `.CTM` in `formats/c64-file-formats.md`, the per-version
1160
+ header tables, the section order and the walker that prints each
1161
+ section's offset
1162
+ - Toolchain: `#embed` in `toolchains/oscar64-reference.md`, the
1163
+ `LIMIT OFFSET` slice and the `ctm_chars` specifier;
1164
+ `.import binary` in `toolchains/kickassembler-reference.md`, the
1165
+ offset and length parameters
1166
+ - Technique: `tile_map_render` (`techniques/scroll.md`), whose CharPad
1167
+ paragraph names the `ctm_*` specifiers; `table_generation`
1168
+ (`techniques/cpu-cycle-tricks.md`), the slice form on a host-built
1169
+ table
1170
+ - Pipeline: `art/asset-pipelines.md`, "Charsets (.ctm from CharPad)",
1171
+ the raw export paths
1172
+ - Register: `$D018` (`hardware/vic-ii-reference.md`)