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,1615 @@
1
+ ---
2
+ category: kernal
3
+ ---
4
+
5
+ <!-- doc-type: pitfall-reference -->
6
+
7
+ # KERNAL and I/O Pitfalls
8
+
9
+ Most pitfalls in this document share a common thread: they arise from
10
+ the KERNAL's implicit contract with the surrounding hardware state (three
11
+ entries sit outside that thread — the Oscar64 `krnio_save()` splat, the c1541
12
+ uppercase-filename PETSCII shift and the Oscar64 `getchx()` RETURN remap are
13
+ library and host-tooling traps in the same disk-and-keyboard I/O workflow — and
14
+ the last entry is a hardware-wiring trap: RESTORE drives /NMI directly, so no
15
+ CIA mask reaches it). The KERNAL
16
+ was written assuming a specific execution environment — interrupts enabled,
17
+ registers free to clobber, the CPU memory map at its stock $37 configuration,
18
+ and decimal mode cleared. Each entry below describes one way that assumption
19
+ collides with the real-world context of a demo or game that has customised
20
+ IRQs, banked memory, or BCD arithmetic.
21
+
22
+ ---
23
+
24
+ ## kernal_assumes_sei_cleared — KERNAL re-enables interrupts internally; calling under SEI breaks IRQ discipline
25
+
26
+ **Severity:** high
27
+ **Region:** both
28
+ **Triggered by kernal:** OPEN, LOAD, SAVE, CHKIN, CHKOUT, CLOSE, CLRCHN
29
+ **Triggered by techniques:** stable_raster_irq, kernal_file_write_seq, kernal_file_read_seq, error_channel_check, kernal_load_to_address, kernal_relative_file_io, directory_read_and_select
30
+
31
+ ### Symptom
32
+
33
+ A raster IRQ scheme runs cleanly until the code issues a disk or tape I/O call.
34
+ Immediately after the JSR to OPEN, LOAD or CHKIN, the raster split collapses:
35
+ color bars bleed, sprites misplace, or the entire display tears. In more subtle
36
+ cases the raster handler appears to execute twice on the same frame, or the
37
+ stable-raster double-IRQ polling loop hangs indefinitely. With tape routines the
38
+ symptom is often a complete machine hang — the KERNAL is waiting for a
39
+ keyboard character inside CLI context, the raster IRQ fires at an unexpected
40
+ stack depth, and the machine can no longer process RESTORE.
41
+
42
+ A variation: the code wraps a KERNAL call in SEI to protect a critical section.
43
+ The routine returns normally but the critical section's register state has been
44
+ scrambled — the KERNAL's own internal CLI re-enabled IRQs and the raster handler
45
+ fired mid-section.
46
+
47
+ ### Mechanism
48
+
49
+ The CLIs are not in OPEN, LOAD, SAVE, CHKIN, CHKOUT, CLOSE or CLRCHN
50
+ themselves — none of those bodies contains a CLI (ROM census of
51
+ `kernal-901227-03.bin`: no $58 byte in OPEN $F34A-$F3D4, CHKIN $F20E-$F24F,
52
+ CHKOUT $F250-$F290, LOAD $F49E-$F5DC or SAVE $F5DD-$F68E; an earlier version
53
+ of this entry said the CLIs were "in their bodies") — but in the serial-bus
54
+ primitives they call (LISTEN, TALK, SECOND, TKSA, ACPTR, CIOUT, UNTALK,
55
+ UNLSN, $ED09-$EEB2). Each primitive brackets its bit-level handshake in
56
+ SEI ... CLI: the byte transfer itself runs with interrupts DISABLED, and the
57
+ routine exits with an unconditional CLI ($EDAB and $EDB5 after LISTEN/TALK/
58
+ SECOND, $EDDB after the bus turnaround, $EE82 after ACPTR) whatever the I flag
59
+ the caller had. Every routine that reaches them on a serial device — OPEN,
60
+ CHKIN, CHKOUT, CLOSE, CLRCHN, LOAD and SAVE — therefore returns with
61
+ interrupts enabled (measured in VICE x64sc with a true-drive 1541 and traps
62
+ off: all seven return with I=0 when entered under SEI). Tape does the same by
63
+ a different route: tape LOAD and SAVE set up their own IRQ under SEI and then
64
+ execute an unconditional CLI at $F8BD, run the whole transfer with interrupts
65
+ enabled, and the restore at $FC93 (PHP/SEI ... PLP) puts back that post-CLI
66
+ state, so they too return with I=0.
67
+
68
+ This is by design: OPEN, LOAD, and SAVE can take millions of cycles (a
69
+ standard KERNAL IEC LOAD from a 1541 runs at roughly 300-600 bytes per
70
+ second, i.e. two to three seconds per kilobyte — measured in VICE x64sc with
71
+ true drive emulation: 8,192 bytes in 871 jiffies, about 14.5 s;
72
+ `hardware/cia-reference.md`, `formats/iec-disk-reference.md` and
73
+ `techniques/loaders-packers.md` measure the same order. An earlier version of
74
+ this entry said "tens of thousands of cycles" and "roughly 1 second per
75
+ kilobyte", which understated the exposure window by half), and the jiffy
76
+ clock IRQ at $EA31 must continue running during that time to keep the
77
+ 60/50 Hz time base accurate and to service the keyboard queue. The KERNAL
78
+ authors assumed the caller had IRQs enabled at the time of the JSR — the machine
79
+ boots with CLI, BASIC runs with CLI, and the KERNAL's own IRQ handler at $EA31
80
+ is designed to be re-entrant only in specific ways.
81
+
82
+ When a demo or game installs a custom raster IRQ scheme, the raster handler's
83
+ stack discipline assumes IRQs arrive only when the CPU is in the main loop. If
84
+ the main loop calls OPEN under SEI and the KERNAL executes CLI, the raster IRQ
85
+ fires with the stack at an unexpected depth. The handler RTIs into the middle of
86
+ the KERNAL routine, and the KERNAL either hangs or corrupts its own zero-page
87
+ workspace because the handler saved new state on top of the KERNAL's
88
+ partially-built stack frame.
89
+
90
+ SETLFS and SETNAM do not contain CLI (they merely store values in zero page —
91
+ SETLFS at $FE00 is STA $B8/STX $BA/STY $B9/RTS, and P measured after
92
+ SEI;SETLFS is $35, I still set), but OPEN, LOAD, SAVE, CHKIN, CHKOUT, CLRCHN
93
+ and CLOSE all return with interrupts enabled when the channel is a serial-bus
94
+ (IEC) device — the CLI sits in the byte-send tail at $EDAB and the receive
95
+ tail at $EE82, so LISTEN/TALK/SECOND/TKSA/ACPTR/UNLSN/UNTLK inherit it.
96
+ Addressed to the screen or keyboard (devices 0-3) the same calls leave the I
97
+ flag untouched (measured: CHKIN on a screen file under SEI left P = $36).
98
+ An earlier version of this entry listed SETLFS on the trigger line and left
99
+ CLOSE, CLRCHN and SAVE off it.
100
+
101
+ ### Fix
102
+
103
+ Never call KERNAL file I/O routines from inside a raster IRQ handler. The IRQ
104
+ handler is the wrong context for long, unpredictable-duration operations.
105
+
106
+ Leave IRQs enabled (CLI) when issuing KERNAL file I/O. The raster IRQ will
107
+ fire during the operation, but as long as the handler is reentrant-safe this
108
+ is harmless. If a critical section must use SEI, complete it before the KERNAL
109
+ call:
110
+
111
+ ```kick
112
+ sei // Begin critical section
113
+ // ... modify shared state ...
114
+ cli // End critical section BEFORE KERNAL call
115
+ jsr $ffba // SETLFS — safe; IRQs are now enabled
116
+ jsr $ffbd // SETNAM
117
+ jsr $ffc0 // OPEN — may internally CLI/SEI; we are already at CLI
118
+ bcs open_error
119
+ ```
120
+
121
+ If I/O must be triggered from a protected context, set a flag and service the
122
+ KERNAL call from the main loop's next idle frame, where IRQs are enabled and
123
+ the stack is at its natural depth.
124
+
125
+ ### Worked example
126
+
127
+ ```kick
128
+ // BAD: KERNAL OPEN called from inside the raster IRQ handler.
129
+ // OPEN's serial primitives execute CLI; the raster scheme re-enters at wrong stack depth.
130
+ raster_irq_bad:
131
+ lda #WHITE
132
+ sta $d020
133
+ jsr $ffc0 // OPEN from IRQ — WRONG; CLI inside OPEN corrupts IRQ scheme
134
+ asl $d019
135
+ rti
136
+
137
+ // GOOD: queue a flag; main loop services I/O with IRQs naturally enabled.
138
+ io_requested: .byte 0 // flag byte (lives with the code, not in zero page)
139
+
140
+ raster_irq_good:
141
+ lda #WHITE
142
+ sta $d020
143
+ asl $d019
144
+ rti
145
+
146
+ main_loop:
147
+ lda io_requested
148
+ beq main_loop
149
+ lda #0
150
+ sta io_requested
151
+ // IRQs are enabled here — KERNAL calls are safe
152
+ lda #1
153
+ ldx #8
154
+ ldy #2
155
+ jsr $ffba // SETLFS
156
+ lda #fname_end - fname
157
+ ldx #<fname
158
+ ldy #>fname
159
+ jsr $ffbd // SETNAM
160
+ jsr $ffc0 // OPEN — KERNAL may CLI/SEI internally; fine here
161
+ bcs io_error
162
+ jmp main_loop
163
+
164
+ fname: .text "MYFILE,S,R"
165
+ fname_end:
166
+ ```
167
+
168
+ ### Cross-references
169
+
170
+ - Technique: `stable_raster_irq` — the raster scheme most likely to be
171
+ disrupted by an unexpected CLI from the KERNAL; the technique doc covers
172
+ IRQ vector placement ($0314 through the $FF48 dispatcher versus
173
+ $FFFE/$FFFF with the KERNAL out). Stack depth across a chained pair of
174
+ handlers is covered by `double_irq` (the dispatcher's own TSX clobbers X,
175
+ so a saved stack pointer goes through memory) and in detail by
176
+ `recipes/kickassembler/stable-raster-irq.md`, section "The stack".
177
+ - KERNAL routines: `SETLFS` ($FFBA), `OPEN` ($FFC0), `LOAD` ($FFD5),
178
+ `SAVE` ($FFD8), `CHKIN` ($FFC6), `CHKOUT` ($FFC9), `CLOSE` ($FFC3),
179
+ `CLRCHN` ($FFCC).
180
+ - Pitfall: `kernal_io_mapping_dependency` — the two pitfalls often appear
181
+ together; banking out the KERNAL while also calling OPEN is doubly fatal.
182
+
183
+ ---
184
+
185
+ ## kernal_clobbers_a_x_y — KERNAL routines do not preserve A, X, or Y unless the Affects line says otherwise
186
+
187
+ **Severity:** medium
188
+ **Region:** both
189
+ **Triggered by kernal:** CHROUT, CHRIN, GETIN, CHKIN, CHKOUT
190
+ **Triggered by techniques:** text_input_line, kernal_relative_file_io, petscii_screen_code_conversion
191
+
192
+ ### Symptom
193
+
194
+ A loop that calls GETIN to poll the keyboard loses its Y index. A sprite
195
+ multiplex routine that calls GETIN to check for keypresses returns with the
196
+ sprite index in Y replaced by the key code and X by the old queue length — but
197
+ only when a key was waiting; with an empty queue both come back intact, so the
198
+ bug appears only while the player types (measured in VICE x64sc: empty queue,
199
+ X=$77/Y=$88 unchanged; one key queued, X=$01, Y=$41 — an earlier version of
200
+ this entry said Y came back as zero, which it never does for a real key). A
201
+ character output sequence that builds a string index in X finds X reset to a
202
+ garbage value after CHKOUT. The bugs are particularly elusive because they
203
+ surface only on certain code paths — if the developer tests the loop without
204
+ any KERNAL calls inserted, everything is fine; adding a single JSR $FFE4 breaks
205
+ the loop silently if the developer assumed Y was preserved. (An earlier version
206
+ of this entry used CHROUT as the example; CHROUT preserves A on success, so
207
+ that loop worked as written — see the contract list below.)
208
+
209
+ A subtler variant: the code saves only A around CHKIN, assumes X and Y are
210
+ untouched, then finds X changed on return — on a disk or other serial-bus
211
+ channel it is the device number (8), on a keyboard or screen file it is the
212
+ open-file-table index (0 for the first file), on a tape file it is the stored
213
+ secondary address ($60) — and mistakes it for an error sentinel. An earlier
214
+ version of this entry said X was always the table index; ROM $F237 is TAX on
215
+ the device number before TALK, and CHKIN on logical file 1, device 8 was
216
+ measured in VICE x64sc returning X=$08.
217
+
218
+ ### Mechanism
219
+
220
+ The KERNAL authors saved space and cycles by preserving only the registers that
221
+ callers genuinely need back. The documented contract is the Affects line in each
222
+ routine's reference entry. Any register listed in Affects may be changed; any
223
+ register absent from Affects is not guaranteed preserved either — treat absence
224
+ as "not documented as changed on the success path only." The safe assumption is:
225
+ any register not explicitly listed as output is potentially clobbered.
226
+
227
+ Specific contracts for the most-called routines:
228
+
229
+ - **CHROUT ($FFD2):** Affects C; A is preserved on success (C=0) and comes
230
+ back as 0 on the error return (C=1) — KERNAL reference; measured in VICE
231
+ x64sc (LDA #$41 / JSR $FFD2 to the screen returned A=$41) and in ROM ($E716
232
+ PHA … $E6B0 PLA/TAX/PLA/CLC/CLI/RTS; error tail $F201 LDA $9E / BCC +2 /
233
+ LDA #0 / RTS). X and Y are preserved. An earlier version of this entry listed A
234
+ as clobbered.
235
+ - **CHRIN ($FFCF):** Affects A, X, Y, C — all three registers may change.
236
+ - **GETIN ($FFE4):** Affects A, X, Y, C — same as CHRIN.
237
+ - **CHKIN ($FFC6):** Affects A, X, C. A returns the device number on success
238
+ (A=8 disk, A=3 screen, A=0 keyboard, measured; ROM $F233 STA $99 with
239
+ A = FA).
240
+ - **CHKOUT ($FFC9):** Affects A, X, C.
241
+
242
+ OPEN, CLOSE, LOAD, and SAVE clobber A, X, Y, and C. Only SETLFS and SETNAM
243
+ preserve all three (their Affects lines say None).
244
+
245
+ ### Fix
246
+
247
+ Audit every KERNAL call site. For each JSR $FFxx, check the Affects line. Save
248
+ and restore every register listed in Affects that the caller also needs after
249
+ the call returns. Use PHA/PLA for A, TXA/PHA/PLA/TAX for X, and TYA/PHA/PLA/TAY
250
+ for Y.
251
+
252
+ Unless you have verified the Affects line and confirmed which registers the
253
+ caller doesn't need after the call, preserve all three around every KERNAL JSR.
254
+ The cost is 29 cycles (13 to save, 16 to restore, from the 6510 reference's
255
+ per-instruction figures: PHA 3, PLA 4, the transfers 2 each) plus the
256
+ JSR/RTS — negligible outside of tight raster loops. An earlier version of this
257
+ entry said 15, which no subset that saves all three registers can reach.
258
+
259
+ ```kick
260
+ // Canonical full-preservation wrapper macro.
261
+ // Saves A, X, Y; calls the given KERNAL address; restores Y, X, A.
262
+ .macro KernalCall(addr) {
263
+ pha
264
+ txa
265
+ pha
266
+ tya
267
+ pha
268
+ jsr addr
269
+ pla
270
+ tay
271
+ pla
272
+ tax
273
+ pla
274
+ }
275
+ ```
276
+
277
+ In tight loops, check Affects and save only what is needed. If the counter is
278
+ in X and the routine touches only A and C (e.g. CHROUT), a bare PHA/JSR/PLA
279
+ suffices — and for CHROUT even that is only needed if you take the error path.
280
+
281
+ ### Worked example
282
+
283
+ ```kick
284
+ // BAD: assumes CHKIN leaves X untouched.
285
+ // CHKIN Affects A and X; on a disk channel X comes back as the device number
286
+ // (8 here), not 1; on a screen or keyboard file it is the open-file-table
287
+ // index (measured in VICE x64sc).
288
+ ldx #1 // logical file number
289
+ jsr $ffc6 // CHKIN — X is clobbered
290
+ cpx #1 // WRONG: comparing clobbered X
291
+
292
+ // GOOD: save and restore around CHKIN.
293
+ ldx #1
294
+ txa
295
+ pha // save X
296
+ jsr $ffc6 // CHKIN — Affects: A, X, C
297
+ bcs chkin_error
298
+ pla
299
+ tax // restore X = 1
300
+
301
+ // BAD: keyboard poll loop keeps its count in Y across GETIN (Y is in Affects).
302
+ // With the queue empty Y survives, so the loop works until the player types;
303
+ // then GETIN hands back the key code in Y and the count is gone.
304
+ poll_loop:
305
+ jsr $ffe4 // GETIN — A, X, Y may change
306
+ dey // WRONG: Y may now be the key code
307
+ bne poll_loop
308
+
309
+ // GOOD: save Y around GETIN.
310
+ poll_loop_good:
311
+ tya
312
+ pha
313
+ jsr $ffe4 // GETIN — Affects: A, X, Y, C
314
+ pla
315
+ tay
316
+ dey
317
+ bne poll_loop_good
318
+
319
+ // CHROUT is the exception this entry used to get wrong: A comes back intact
320
+ // when C=0 and as 0 when C=1, so test C after CHROUT, not A.
321
+ print_loop:
322
+ lda msg,y
323
+ jsr $ffd2 // CHROUT — A preserved on success, C=1 on error
324
+ bcs print_error
325
+ iny
326
+ dex
327
+ bne print_loop
328
+ print_error:
329
+
330
+ msg: .text "HELLO"
331
+ msg_end:
332
+ ```
333
+
334
+ ### Cross-references
335
+
336
+ - KERNAL routines: `CHROUT` ($FFD2), `CHRIN` ($FFCF), `GETIN` ($FFE4),
337
+ `CHKIN` ($FFC6), `CHKOUT` ($FFC9).
338
+ - Pitfall: `kernal_assumes_sei_cleared` — both pitfalls stem from the same
339
+ root cause: the KERNAL assumes a specific runtime contract that the caller
340
+ must uphold.
341
+
342
+ ---
343
+
344
+ ## decimal_mode_in_irq_handler — IRQ may fire mid-SED arithmetic; handler must CLD on entry or BCD corruption ensues
345
+
346
+ **Severity:** critical
347
+ **Region:** both
348
+ **Triggered by techniques:** stable_raster_irq, decimal_mode_pitfalls, irq_chain_table, logic_rate_decoupling, sprite_multiplex_game, tod_alarm_interrupt, irq_keyboard_own_scan
349
+
350
+ ### Symptom
351
+
352
+ BCD arithmetic running in the main loop produces correct results in isolation
353
+ but yields random garbage when the raster IRQ fires during the calculation.
354
+ The corruption is intermittent — it depends on whether the IRQ fires between
355
+ the SED and CLD instructions in the main-loop BCD block. Debugging is painful
356
+ because inserting a SEI/CLI wrapper around the BCD block "fixes" the problem
357
+ (it does — by preventing the IRQ from firing during the critical window) without
358
+ revealing the real cause. The same corruption appears in SID player timing
359
+ code that uses BCD to accumulate tick counts, and in any BASIC-style score
360
+ counter kept as packed BCD in zero page.
361
+
362
+ A related variant: two handlers coexist (music player + raster effect) and one
363
+ uses SED/CLD for a BCD calculation. If the second handler fires while the first
364
+ is between SED and CLD — via NMI or a chained IRQ that re-enables interrupts —
365
+ the second handler's ADC/SBC produces wrong results.
366
+
367
+ ### Mechanism
368
+
369
+ The 6502/6510 decimal mode flag (D flag) is one bit of the processor status
370
+ register (P). When D = 1, ADC and SBC perform BCD (binary-coded-decimal)
371
+ arithmetic: result nibbles are adjusted so that each hex digit represents a
372
+ decimal digit 0-9. When D = 0, addition and subtraction are pure binary.
373
+
374
+ Crucially, the D flag is **not automatically cleared or saved on IRQ entry**.
375
+ The 6502 interrupt sequence pushes the program counter (PCH, PCL) and the
376
+ processor status register (P) onto the stack, then fetches the IRQ vector and
377
+ begins executing the handler. The pushed P contains the D flag as it was at
378
+ the time of the interrupt — but the processor itself does not clear D. The
379
+ handler runs with D still set if the interrupted code had executed SED and not
380
+ yet executed CLD.
381
+
382
+ This means: if main-loop code is between `SED` and `CLD`, and an IRQ fires,
383
+ the IRQ handler's ADC and SBC instructions run in BCD mode. Any addition or
384
+ subtraction in the handler — including pointer arithmetic, counter decrements,
385
+ and index calculations — will produce BCD-adjusted results instead of binary
386
+ results. A handler that adds 8 to a pointer stored in zero page will compute
387
+ 8 + 0 = 8 correctly in BCD, but 9 + 1 will produce $10 (decimal 10 as BCD)
388
+ instead of $0A (decimal 10 as binary). The pointer lands in the wrong page.
389
+ The raster effect writes to the wrong address. The corruption is data-dependent
390
+ and nearly impossible to trace without knowing that D is set.
391
+
392
+ On NMOS silicon, N, V, and Z are undefined after a decimal-mode ADC/SBC. C
393
+ reflects decimal carry. Code in the handler that branches on carry after an
394
+ addition will take the wrong branch when D is unexpectedly set.
395
+
396
+ RTI restores P from the stack, including D. After RTI, the main-loop BCD block
397
+ resumes with D = 1 as expected — but the damage inside the handler's execution
398
+ window may already have corrupted effect or music state for the current frame.
399
+
400
+ ### Fix
401
+
402
+ Every IRQ handler must execute `CLD` as part of its entry stanza, before any
403
+ arithmetic. The canonical IRQ handler prelude is:
404
+
405
+ ```kick
406
+ irq_handler:
407
+ pha // save A
408
+ txa
409
+ pha // save X
410
+ tya
411
+ pha // save Y
412
+ cld // !! MANDATORY: clear decimal mode !!
413
+ // ... handler body — all arithmetic is now binary ...
414
+ pla
415
+ tay
416
+ pla
417
+ tax
418
+ pla
419
+ rti // RTI restores P, which restores the D flag
420
+ // to whatever the interrupted code had set
421
+ ```
422
+
423
+ The `CLD` costs 2 cycles and 1 byte. It is not optional. Place it after the
424
+ register saves (the PHA/TXA/PHA/TYA/PHA sequence does not perform arithmetic,
425
+ so those instructions are safe even with D set) and before any instruction that
426
+ uses ADC, SBC, or reads the C/N/V/Z flags after such an instruction.
427
+
428
+ The RTI at the end of the handler automatically restores D (along with all
429
+ other flags) from the pushed P — so the main-loop BCD block resumes correctly
430
+ with D = 1 after the handler exits. No explicit SED is needed in the handler's
431
+ exit path.
432
+
433
+ NMI handlers require the same discipline. The NMI vector fires regardless of
434
+ the I flag and also does not clear D — if you use the NMI for digi playback or
435
+ any other purpose, it needs CLD too.
436
+
437
+ ### Worked example
438
+
439
+ ```kick
440
+ // BAD: handler has no CLD. ADC in the handler runs in BCD mode if main-loop
441
+ // code was between SED and CLD when the IRQ fired.
442
+ // E.g. $15 + $06 = $1B binary, but BCD adjusts it to $21 — wrong sprite Y.
443
+ irq_bad:
444
+ pha
445
+ txa
446
+ pha
447
+ tya
448
+ pha
449
+ // No CLD!
450
+ lda sprite_y,x
451
+ clc
452
+ adc #21 // BCD mode if D=1: result may be wrong
453
+ sta $d001
454
+ pla
455
+ tay
456
+ pla
457
+ tax
458
+ pla
459
+ rti
460
+
461
+ // GOOD: CLD as the first post-save instruction.
462
+ irq_good:
463
+ pha
464
+ txa
465
+ pha
466
+ tya
467
+ pha
468
+ cld // Mandatory: clears D before any arithmetic
469
+ lda sprite_y,x
470
+ clc
471
+ adc #21 // Always binary — correct regardless of D on entry
472
+ sta $d001
473
+ pla
474
+ tay
475
+ pla
476
+ tax
477
+ pla
478
+ rti // RTI restores P from stack, including original D flag
479
+
480
+ // Main-loop BCD block — safe with the corrected handler above.
481
+ update_score:
482
+ sed
483
+ lda score_lo
484
+ clc
485
+ adc #$10 // +10 in BCD
486
+ sta score_lo
487
+ lda score_hi
488
+ adc #$00
489
+ sta score_hi
490
+ cld // Exit BCD mode — keep this window as short as possible
491
+ rts
492
+
493
+ score_lo: .byte 0
494
+ score_hi: .byte 0
495
+ sprite_y: .fill 8, i * 21 + 50
496
+ ```
497
+
498
+ ### Cross-references
499
+
500
+ - Technique: `stable_raster_irq` — the raster handler this pitfall most
501
+ commonly hits. The technique doc does not show a handler prelude (it only
502
+ notes that the KERNAL dispatcher pushes A, X and Y); the
503
+ PHA/TXA/PHA/TYA/PHA + CLD stanza above is the reference form. Neither
504
+ stable-raster recipe currently executes CLD, and on the Oscar64 side
505
+ `rasterirq.h`'s own ISRs do not either — the KERNAL's $FF48 dispatcher and
506
+ $EA31 service routine contain no CLD, so a handler reached through $0314
507
+ inherits whatever D was. (An earlier version of this entry said the
508
+ technique doc discussed the prelude; it does not.)
509
+ - Opcodes: `SED` ($F8), `CLD` ($D8) — the two instructions that set and clear
510
+ the decimal flag.
511
+ - Pitfall: `kernal_clobbers_a_x_y` — the PHA/TXA/PHA/TYA/PHA prelude described
512
+ in both pitfalls is the same stanza; the CLD belongs between the last PHA
513
+ and the first handler instruction.
514
+
515
+ ---
516
+
517
+ ## kernal_io_mapping_dependency — KERNAL file/disk routines crash when $01 has banked out the KERNAL ROM
518
+
519
+ **Severity:** high
520
+ **Region:** both
521
+ **Triggered by kernal:** SETLFS, LOAD, SAVE, OPEN, CLOSE
522
+ **Triggered by techniques:** cpu_io_port_bank, kernal_file_write_seq, kernal_file_read_seq, error_channel_check, kernal_load_to_address, kernal_relative_file_io, directory_read_and_select
523
+
524
+ ### Symptom
525
+
526
+ Disk or tape I/O that works in a plain BASIC or kernal environment crashes
527
+ silently when called from a demo or game that has changed $01 to enable an
528
+ all-RAM or partial-RAM banking mode. The crash is typically a wild branch: the
529
+ CPU fetches an opcode from RAM at $E000-$FFFF where the KERNAL ROM used to be,
530
+ executes whatever byte it finds there as an opcode, and veers off into garbage.
531
+ In some configurations the machine appears to freeze; in others it resets.
532
+
533
+ A variant: the code correctly sets $01 = $37 for the KERNAL call, but later
534
+ restores $01 to $34 (all RAM) or $35 (I/O + KERNAL but no BASIC) without
535
+ saving and restoring the exact value it found. Subsequent KERNAL calls use the
536
+ restored value, which may not match what the KERNAL's internal routines expect.
537
+
538
+ A related issue: code banks in $35 for bitmap RAM access, then calls CHROUT.
539
+ CHROUT at $FFD2 is a three-byte JMP in KERNAL ROM. With HIRAM = 0 ($35 has
540
+ bit 1 clear), the address $FFD2 contains RAM, not ROM. The JSR executes
541
+ whatever bytes happen to be there and branches to a garbage address.
542
+
543
+ ### Mechanism
544
+
545
+ The 6510 processor port at $01 controls three memory mapping lines through the
546
+ PLA (906114):
547
+
548
+ - **Bit 0 (LORAM):** When 0, $A000-$BFFF is RAM. When 1 (and HIRAM = 1),
549
+ $A000-$BFFF is BASIC ROM.
550
+ - **Bit 1 (HIRAM):** When 0, $E000-$FFFF is RAM. When 1, $E000-$FFFF is
551
+ KERNAL ROM.
552
+ - **Bit 2 (CHAREN):** When 0, $D000-$DFFF is character ROM (if HIRAM or LORAM).
553
+ When 1, $D000-$DFFF is the I/O block (VIC-II, SID, CIA1, CIA2).
554
+
555
+ The stock reset state is $01 = $37 (binary %00110111), giving LORAM=1, HIRAM=1,
556
+ CHAREN=1 — KERNAL ROM at $E000-$FFFF, BASIC ROM at $A000-$BFFF, I/O at
557
+ $D000-$DFFF.
558
+
559
+ Common demo/game banking modes and their effects on KERNAL accessibility:
560
+
561
+ | $01 value | LORAM | HIRAM | CHAREN | KERNAL ROM visible? | I/O visible? |
562
+ |-----------|-------|-------|--------|---------------------|--------------|
563
+ | $37 | 1 | 1 | 1 | **Yes** | Yes |
564
+ | $36 | 0 | 1 | 1 | **Yes** | Yes (BASIC hidden) |
565
+ | $35 | 1 | 0 | 1 | **No — RAM** | Yes |
566
+ | $34 | 0 | 0 | 1 | **No — RAM** | Yes |
567
+ | $33 | 1 | 1 | 0 | **Yes** | No (char ROM)|
568
+ | $30 | 0 | 0 | 0 | **No — RAM** | No |
569
+
570
+ Modes $35, $34, and $30 are popular in demos because they allow the CPU to
571
+ see RAM at $E000-$FFFF (useful for placing time-critical code, decompression
572
+ buffers, or sprite data up high). But KERNAL ROM must be visible ($01 bit 1
573
+ HIRAM = 1) for any KERNAL call — including the jump-table entries themselves —
574
+ to work. The jump table entries at $FF81-$FFF3 are three-byte JMP instructions
575
+ in KERNAL ROM. If HIRAM = 0, those addresses contain whatever the program wrote
576
+ into RAM there, not JMP instructions.
577
+
578
+ KERNAL routines also require I/O to be visible (CHAREN = 1) because they drive
579
+ CIA1/CIA2 and the IEC bus directly. The safe values for KERNAL file I/O are
580
+ $37 (stock) or $36 (no BASIC, which is fine since the KERNAL does not call
581
+ BASIC ROM during I/O). Both have HIRAM = 1 and CHAREN = 1.
582
+
583
+ ### Fix
584
+
585
+ Wrap every KERNAL file I/O call with a save-and-restore of $01:
586
+
587
+ ```kick
588
+ .macro KernalBankIn() {
589
+ lda $01
590
+ pha // save current banking state
591
+ lda #$37
592
+ sta $01 // KERNAL + I/O + BASIC all visible
593
+ }
594
+
595
+ .macro KernalBankOut() {
596
+ pla
597
+ sta $01 // restore caller's banking state
598
+ }
599
+ ```
600
+
601
+ This ensures:
602
+ 1. The KERNAL jump table entries are ROM (HIRAM = 1).
603
+ 2. The CIA1/CIA2 I/O is visible (CHAREN = 1).
604
+ 3. The exact caller's $01 value is restored after the call, not a hardcoded
605
+ value that may not match what the caller had.
606
+
607
+ The save is to the stack (PHA/PLA). Using a zero-page byte for the save is
608
+ also common, but the stack is simpler and does not require allocating a
609
+ zero-page scratch byte.
610
+
611
+ ### Worked example
612
+
613
+ ```kick
614
+ // BAD: LOAD called with $01 = $35 (HIRAM = 0 — KERNAL ROM is RAM).
615
+ // JSR $FFD5 executes whatever byte is in RAM at $FFD5, not the KERNAL JMP.
616
+ lda #$35
617
+ sta $01 // Bank out KERNAL for bitmap buffer access
618
+ lda #0
619
+ ldx #<$0801
620
+ ldy #>$0801
621
+ jsr $ffd5 // CRASHES — $FFD5 is RAM, not KERNAL jump table
622
+
623
+ // GOOD: save $01, bank in KERNAL, call, restore.
624
+ lda $01
625
+ pha // Save current banking state ($35)
626
+ lda #$37
627
+ sta $01 // KERNAL ROM + I/O visible
628
+ lda #0
629
+ ldx #<$0801
630
+ ldy #>$0801
631
+ jsr $ffd5 // LOAD — safe: $FFD5 is KERNAL ROM JMP
632
+ pla
633
+ sta $01 // Restore $35 — bitmap RAM back at $E000
634
+
635
+ // BAD: restoring to a hard-coded literal rather than the saved value.
636
+ lda $01
637
+ pha
638
+ lda #$37
639
+ sta $01
640
+ jsr $ffc0 // OPEN
641
+ lda #$35
642
+ sta $01 // !! Wrong if caller had $36 or $34
643
+
644
+ // CORRECT: always PLA back, never restore to a literal.
645
+ lda $01
646
+ pha
647
+ lda #$37
648
+ sta $01
649
+ jsr $ffc0 // OPEN
650
+ pla
651
+ sta $01 // Exact pre-call value, whatever it was
652
+ ```
653
+
654
+ ### Cross-references
655
+
656
+ - Memory region [$0000-$0001 — Processor I/O port](../hardware/c64-memory-map.md#0000-0001--processor-io-port)
657
+ — the 6510 processor port data register; bits 0-2 control
658
+ LORAM/HIRAM/CHAREN. Resolvable via `c64_memory_map 0001`, not
659
+ `c64_register_lookup` (the KB has no Register node for the CPU port).
660
+ - KERNAL routines: `SETLFS` ($FFBA), `OPEN` ($FFC0), `CLOSE` ($FFC3),
661
+ `LOAD` ($FFD5), `SAVE` ($FFD8).
662
+ - Pitfall: `kernal_assumes_sei_cleared` — the two pitfalls are frequently
663
+ encountered together in demo loaders; OPEN can simultaneously trip both
664
+ if the caller has disabled IRQs and banked out the KERNAL.
665
+ - `docs/hardware/c64-memory-map.md` — full PLA truth table with all 32 banking
666
+ mode combinations.
667
+
668
+ ---
669
+
670
+ ## krnio_save_leaves_splat_file — a *PRG splat after krnio_save() means the emulator was stopped before the drive finished, not that SAVE is broken
671
+
672
+ **Severity:** high
673
+ **Region:** both
674
+ **Triggered by kernal:** SAVE, OPEN, CLOSE
675
+
676
+ ### Symptom
677
+
678
+ An Oscar64 game calls `krnio_save(8, &state, &state + sizeof(state))` to
679
+ persist a struct to a `.d64` attached as drive 8. The function returns
680
+ `true` (apparent success) and a directory entry appears with the chosen
681
+ filename — but the entry is marked `*PRG` with 0 blocks, and the file
682
+ cannot be read back via `c1541 -read`, via the game's own `krnio_load`,
683
+ or via BASIC `LOAD"NAME",8`. It appears in two forms: no blocks allocated
684
+ at all; or blocks allocated (the free-block count drops) with no directory
685
+ record of them. Both forms were reproduced on demand by terminating x64sc
686
+ with `-limitcycles` while the 1541 was still writing — which is what the
687
+ symptom means (see Mechanism).
688
+
689
+ The `*` flag preceding `PRG` in a `c1541 -list` output is the
690
+ "improperly closed" marker: the directory entry was created, but the
691
+ DOS-side close handshake that finalises block count and clears the
692
+ splat bit never completed.
693
+
694
+ ### Mechanism
695
+
696
+ Oscar64's `krnio_save()` wraps KERNAL `SAVE` ($FFD8) — SETLFS plus
697
+ `JSR $FFD8`, per `kernalio.c` — which is the same routine that BASIC's
698
+ `SAVE"NAME",8` invokes. The KERNAL SAVE flow snapshots a contiguous memory
699
+ block as a PRG file (two-byte load address header + raw bytes): it forces
700
+ the secondary address to $61 at $F5FA (ROM bytes A9 61 85 B9) and closes the
701
+ file through the KERNAL's own IEC close path. It is not broken. Under VICE
702
+ 3.10 with true drive emulation — the default, and the only configuration
703
+ in which drive 8 exists under `-default`; with `+drive8truedrive` there is
704
+ no device 8 at all and the program never returns from its first IEC call —
705
+ `krnio_save()` of a 64-byte struct to a fresh c1541-formatted `.d64`
706
+ produces a clean 1-block PRG (`c1541 -list`: `1 "tideline" prg`, no `*`,
707
+ 662 blocks free) that reads back intact (66 bytes = load address + struct),
708
+ and `krnio_save` returns true. Built with Oscar64 2026-05-19 and run in
709
+ x64sc 3.10; an earlier version of this entry said the splat was
710
+ "observed under VICE 3.x with and without -drive8truedrive", and that
711
+ does not reproduce.
712
+
713
+ The splat arises when the emulator exits (`-limitcycles`,
714
+ `-exitscreenshot`, window close) or the program resets while the 1541 is
715
+ still writing: the directory entry has been created but the DOS-side
716
+ close that finalises the block count has not happened yet. A 64-byte SAVE
717
+ needs on the order of 1-2 M emulated cycles after autostart with true drive
718
+ on. The open/write/close pattern splats identically when cut at the same
719
+ point (measured: `*prg`, 643 blocks free), so it is not a remedy for this
720
+ symptom.
721
+
722
+ The Oscar64 sample at `samples/kernalio/filewrite.c` does NOT use
723
+ `krnio_save`. It uses the structured file API instead:
724
+
725
+ 1. `krnio_setnam("@0:NAME,P,W")` — name with the `@0:` replace prefix
726
+ and a `,P,W` (PRG write) suffix
727
+ 2. `krnio_open(fnum, dev, 2)` — open a logical file on a secondary
728
+ address that the DOS recognises as a user data channel (2)
729
+ 3. `krnio_write(fnum, data, size)` — stream bytes through the channel
730
+ 4. `krnio_close(fnum)` — close, which gives the DOS the explicit
731
+ "end of file" signal that finalises the directory entry
732
+
733
+ This sequence does not rely on the KERNAL SAVE routine at all; it produces
734
+ a file with no 2-byte load-address header, reads back symmetrically with
735
+ `krnio_read`, and takes the `@0:` replace prefix.
736
+
737
+ ### Fix
738
+
739
+ Give the drive time to finish. In a headless run, do not cut x64sc before
740
+ the program's own done marker appears on screen; interactively, wait for
741
+ the busy LED to go out or for `krnio_save` to return — and check the disk
742
+ only afterwards. An earlier version of this entry told you to avoid
743
+ `krnio_save()`; the splat it described was the emulator being stopped
744
+ mid-write, and the same cut splats the open/write/close pattern too.
745
+
746
+ `krnio_open` / `krnio_write` / `krnio_close` is still the better fit for
747
+ arbitrary structured data — game state, score tables, scenario maps — on
748
+ its real merits: no 2-byte load-address header in the file, a symmetric
749
+ read with `krnio_read`, and `@0:` replace semantics. Reserve `krnio_save()`
750
+ for genuinely BASIC-compatible memory snapshots (e.g. a sprite table at a
751
+ fixed address that BASIC will `LOAD` into the right place).
752
+
753
+ ### Worked example
754
+
755
+ ```c
756
+ // krnio_save produces a clean PRG (load address + bytes) when the drive is
757
+ // allowed to finish; measured under VICE 3.10 with true drive emulation.
758
+ krnio_setnam("TIDELINE,P,W");
759
+ bool ok = krnio_save(8,
760
+ (const char*)&state,
761
+ (const char*)&state + sizeof(state));
762
+ // ok == true; c1541 -list shows 1 "tideline" prg, 1 block, reads back as
763
+ // 66 bytes ($0B00 load address + the 64-byte struct).
764
+
765
+ // PREFERRED for structured data: open/write/close as in
766
+ // samples/kernalio/filewrite.c (no load-address header, symmetric read).
767
+ krnio_setnam("@0:TIDELINE,P,W");
768
+ if (krnio_open(2, 8, 2)) {
769
+ krnio_write(2, (const char*)&state, sizeof(state));
770
+ krnio_close(2);
771
+ }
772
+ // Read back symmetrically: setnam("TIDELINE,P,R"); open(2, 8, 2);
773
+ // read(2, &state, sizeof(state)); close(2).
774
+ ```
775
+
776
+ The `@0:` replace prefix is important if the file might already exist.
777
+ Without it the drive refuses the open with DOS error 63 FILE EXISTS — but
778
+ `krnio_open()` still returns true, because it only reports KERNAL OPEN's
779
+ carry, which a serial device sets for device-not-present or too-many-files,
780
+ never for a DOS error. The following `krnio_write()` also reports the full
781
+ byte count, yet nothing reaches the disk and the old file is left as it was
782
+ (measured under VICE 3.10: open returned true, write returned 64, command
783
+ channel read 63, file contents unchanged; an earlier version of this entry
784
+ said the call returned false). If you need to know, open the command channel
785
+ (secondary address 15) and `krnio_read` the status line after the open.
786
+
787
+ ### Cross-references
788
+
789
+ - Oscar64 sample: `samples/kernalio/filewrite.c` and `fileread.c` —
790
+ the authoritative pattern.
791
+ - Oscar64 header: `c64/kernalio.h` exposes both `krnio_save` (memory
792
+ snapshots with a load address) and `krnio_open`/`write`/`close`
793
+ (structured data).
794
+ - KERNAL routines: `SETNAM` ($FFBD), `SETLFS` ($FFBA), `OPEN`
795
+ ($FFC0), `CHKOUT` ($FFC9), `CHROUT` ($FFD2), `CLRCHN` ($FFCC),
796
+ `CLOSE` ($FFC3). `krnio_open` chains setlfs + open; `krnio_write`
797
+ chains chkout + per-byte chrout + clrchn.
798
+ - Pitfall: `kernal_io_mapping_dependency` — both save APIs need the
799
+ KERNAL ROM banked in at $E000 when called.
800
+
801
+ ---
802
+
803
+ ## c1541_uppercase_filename_petscii_shift — c1541 -write of an UPPERCASE c64 name stores shifted PETSCII the game can't open
804
+
805
+ **Severity:** medium
806
+ **Region:** both
807
+ **Triggered by kernal:** SETNAM, OPEN
808
+ **Triggered by techniques:** kernal_file_write_seq, kernal_file_read_seq, kernal_load_to_address, directory_read_and_select
809
+
810
+ ### Symptom
811
+
812
+ A host tool authors a data file onto a `.d64` with
813
+ `c1541 -attach disk.d64 -write host.bin TDLVL00`. The file appears in the
814
+ directory, and `c1541 -read TDLVL00` round-trips it byte-for-byte. But the C64
815
+ program that opens it with `krnio_setnam("TDLVL00")` + `krnio_open(2, 8, 2)`
816
+ gets an open that appears to succeed — `krnio_open` returns true, because
817
+ KERNAL OPEN's carry reports only device-not-present and table errors, never a
818
+ DOS error — but `krnio_read` returns 0 bytes and the command channel (open
819
+ 15,8,15 and read) answers `62, FILE NOT FOUND` (measured in VICE x64sc; an
820
+ earlier version of this entry said `krnio_open` returned false). Listing the
821
+ disk directory from inside the emulator (`LOAD"$",8` then `LIST`) shows the
822
+ filename rendered as graphics characters (e.g. `#####00`) instead of `TDLVL00`.
823
+ A file the *game itself* wrote with the same logical name (via `krnio_write`)
824
+ opens fine — only the host-authored file fails.
825
+
826
+ ### Mechanism
827
+
828
+ `c1541` converts the host-supplied c64 filename from ASCII to PETSCII:
829
+
830
+ - **lowercase** ASCII `a-z` (0x61-0x7A) → unshifted PETSCII 0x41-0x5A, which
831
+ render as `A-Z` in the default uppercase/graphics charset.
832
+ - **UPPERCASE** ASCII `A-Z` (0x41-0x5A) → **shifted** PETSCII 0xC1-0xDA, which
833
+ render as graphics symbols in uppercase mode.
834
+
835
+ So `-write host.bin TDLVL00` stores the filename bytes
836
+ `0xD4,0xC4,0xCC,0xD6,0xCC,0x30,0x30`.
837
+
838
+ A C64 program (e.g. Oscar64) sends the bytes of its C-string literal to SETNAM.
839
+ The char literals `'T','D','L','V','L'` are ASCII / PETSCII-uppercase
840
+ `0x54,0x44,0x4C,0x56,0x4C`. The 1541 DOS matches directory filenames
841
+ **byte-for-byte**, so `0x54...` never matches the stored `0xD4...`, and the
842
+ drive answers 62, FILE NOT FOUND on the command channel; KERNAL OPEN itself
843
+ still returns C=0, so the C64 side sees a successful open with an empty file.
844
+
845
+ Game-written files are immune because the game both writes and reads with the
846
+ same ASCII byte sequence — they match each other regardless of absolute
847
+ encoding. The mismatch appears only when one side is `c1541` (host) and the
848
+ other is the game (C64). `c1541 -read` is symmetric with `-write`, so a
849
+ host-only round-trip never reveals the problem — only a real C64-side open does.
850
+
851
+ ### Fix
852
+
853
+ Pass the c64 filename to `c1541` in **lowercase**. `c1541` then maps lowercase
854
+ ASCII `a-z` to unshifted PETSCII 0x41-0x5A — i.e. `'t'` (0x74) → 0x54 — exactly
855
+ the bytes the game's uppercase-ASCII C string requests, and they render
856
+ correctly as `TDLVL00` in the C64 directory.
857
+
858
+ ```bash
859
+ # BAD: uppercase c64 name -> shifted PETSCII; game krnio_open("TDLVL00") returns true but reads 0 bytes (DOS error 62).
860
+ c1541 -attach disk.d64 -write tdlvl00.bin TDLVL00
861
+
862
+ # GOOD: lowercase c64 name -> 0x54... matches the game's request + renders right.
863
+ c1541 -attach disk.d64 -write tdlvl00.bin tdlvl00
864
+ ```
865
+
866
+ Diagnose a suspected mismatch by listing the directory from inside the running
867
+ emulator (`LOAD"$",8` then `LIST`): a name rendered as graphics characters is
868
+ the tell. Test for the condition by reading the error channel after OPEN, or
869
+ by checking `krnio_read`'s byte count — not by testing `krnio_open`'s return
870
+ value, which is true either way. (The autostart program name's encoding does
871
+ not matter — autostart loads by directory position, not by name match.)
872
+
873
+ ### Worked example
874
+
875
+ ```python
876
+ # Host authoring tool (Python + c1541). The on-disk file content is identical
877
+ # either way; only the FILENAME's PETSCII encoding differs.
878
+ slot = 0
879
+ c64name = f"tdlvl{slot:02d}" # lowercase -> matches game's krnio_setnam("TDLVL00")
880
+ # c64name = f"TDLVL{slot:02d}" # WRONG: uppercase -> shifted PETSCII -> no match
881
+ subprocess.run(["c1541", "-attach", "disk.d64", "-write", "tdlvl00.bin", c64name])
882
+ ```
883
+
884
+ ### Cross-references
885
+
886
+ - KERNAL routines: `SETNAM` ($FFBD), `OPEN` ($FFC0). The 1541 DOS matches
887
+ directory filenames byte-for-byte; SETNAM passes the bytes verbatim.
888
+ - Pitfall: `krnio_save_leaves_splat_file` — the other half of the host↔C64
889
+ disk-data round-trip; both live in the structured-file save/load workflow,
890
+ and both are cases where `krnio_open`/`krnio_save` return true and the
891
+ evidence is on the disk or the command channel, not in the return value.
892
+ - Tooling: VICE `c1541 -write` / `-read` perform ASCII→PETSCII on the c64
893
+ filename; their conversion is self-consistent, which is why a host-only
894
+ round-trip hides the bug.
895
+ - Discovered authoring Tideline level files (`TDLVLnn`) for Phase H4; see
896
+ `loop/games/CLAUDE.md` disk-testing notes.
897
+
898
+ ---
899
+
900
+ ## getchx_petscii_remaps_return — Oscar64 getchx() delivers RETURN as $0A (not $0D) under every charmap except IOCHM_TRANSPARENT — including the default IOCHM_ASCII
901
+
902
+ **Severity:** medium
903
+ **Region:** both
904
+ **Triggered by kernal:** GETIN
905
+ **Triggered by techniques:** text_input_line
906
+
907
+ ### Symptom
908
+
909
+ A key handler that tests `key == 0x0d` for RETURN never fires — RETURN feels
910
+ "dead" — while letter and space keys work normally. Typically shows up on a
911
+ menu ("press RETURN to start") or a confirm action: the cursor moves, letters
912
+ register, but RETURN does nothing.
913
+
914
+ ### Mechanism
915
+
916
+ Oscar64's `conio` input functions (`getch`, `getchx`, `getche`) run the raw key
917
+ through `convch()`. When `iocharmap()` is set to an input map at or above ASCII
918
+ (`IOCHM_ASCII`, `IOCHM_PETSCII_1`, `IOCHM_PETSCII_2`), `convch()` rewrites
919
+ carriage return to line feed:
920
+
921
+ ```c
922
+ // Oscar64 include/conio.c (convch)
923
+ if (giocharmap >= IOCHM_ASCII) {
924
+ if (ch == 13) // PETSCII RETURN
925
+ ch = 10; // delivered to your code as $0A
926
+ ...
927
+ }
928
+ ```
929
+
930
+ `giocharmap` starts at IOCHM_ASCII (`conio.c` line 3: `static IOCharMap
931
+ giocharmap = IOCHM_ASCII;`), so the remap is on unless you call
932
+ `iocharmap(IOCHM_TRANSPARENT)` — a program that never calls `iocharmap()` at
933
+ all is affected. Measured in VICE 3.10 with Oscar64 2026-05-19 (`-keybuf` with
934
+ a newline, which puts PETSCII $0D in the KERNAL buffer): raw GETIN $0D;
935
+ `getchx()` on the default map $0A; after `iocharmap(IOCHM_TRANSPARENT)` $0D;
936
+ after `iocharmap(IOCHM_PETSCII_2)` $0A. An earlier version of this entry named
937
+ only IOCHM_PETSCII_2 as the cause.
938
+
939
+ So a RETURN keypress (PETSCII `$0D`) reaches your code as `$0A`. Any comparison
940
+ against `$0D` silently misses. (The reverse map preserves `$0D` on *output*, so
941
+ printing is unaffected — only keyboard input is remapped.)
942
+
943
+ The bug hides easily when input is also drivable by a test harness that writes
944
+ an action code directly (e.g. a state byte) instead of going through `getchx` —
945
+ the harness path never exercises the key comparison, so RETURN looks fine in
946
+ automated tests but is broken for a real keypress.
947
+
948
+ ### Fix
949
+
950
+ Accept `$0A` (or both `$0A` and `$0D`) everywhere you test for RETURN:
951
+
952
+ ```c
953
+ // bad — never matches RETURN unless iocharmap(IOCHM_TRANSPARENT) was called
954
+ if (key == 0x0d) start_level();
955
+
956
+ // good
957
+ if (key == 0x0d || key == 0x0a) start_level();
958
+ ```
959
+
960
+ In a `switch`, add `case 0x0a:` alongside `case 0x0d:`.
961
+
962
+ The alternative is `iocharmap(IOCHM_TRANSPARENT)`, which disables the
963
+ rewrite — but it also disables the PETSCII case-swap, so use it only if you
964
+ want raw PETSCII throughout.
965
+
966
+ ### Cross-references
967
+
968
+ - Oscar64 `include/conio.c` (`convch`); the active map defaults to
969
+ IOCHM_ASCII and is changed by `iocharmap()`; only IOCHM_TRANSPARENT
970
+ disables the $0D→$0A rewrite.
971
+ - Discovered wiring RETURN-to-start on the Tideline level-select screen; the
972
+ select→play path had only ever been exercised via the test-harness action
973
+ byte, which masked the remap.
974
+
975
+ ---
976
+
977
+ ## restore_nmi_not_maskable — RESTORE drives /NMI directly; masking $DD0D does nothing, only the $0318 vector or an NMI lock neutralises it
978
+
979
+ **Severity:** high
980
+ **Region:** both
981
+ **Triggered by registers:** DD0D
982
+ **Triggered by kernal:** RESTOR, VECTOR
983
+ **Triggered by techniques:** nmi_handler_and_restore_key
984
+
985
+ ### Symptom
986
+
987
+ A demo or game drops to a cleared blue screen and `READY.` when the user
988
+ presses RUN/STOP+RESTORE, even though its init wrote `$10` (or `$7F`) to
989
+ `$DD0D` "to switch the RESTORE NMI off". Everything the program held through
990
+ the KERNAL goes with it: `$0314` is back at `$EA31`, the jiffy IRQ is running
991
+ again, `$01` is back to the stock map, the VIC is in text mode at `$0400`. The
992
+ code is still in memory, which is the whole point of the key.
993
+
994
+ RESTORE alone, without RUN/STOP, is quieter: a stable-raster split tears or a
995
+ sprite multiplexer misplaces for one frame per press — the KERNAL's handler
996
+ ran 182 cycles at an unpredictable point in the frame, from the NMI sequence
997
+ to its `RTI`, with no cartridge, `$02A1 = 0` and no key held (rung 1: measured
998
+ in VICE x64sc 3.10 as 189 cycles across a CIA1 Timer A count, 7 of them the
999
+ trampoline described under Mechanism; the instruction path summed by hand
1000
+ from the bytes gives the same 189) — while `$DD0D` reads `$00` and no CIA2
1001
+ source is enabled. A program with its own NMI-timed player is not in this
1002
+ case: it owns `$0318`, so the KERNAL path never runs for it, and a press hands
1003
+ its handler one extra, early entry instead — spurious unless the handler
1004
+ tests bit 7 of `$DD0D` before acting.
1005
+
1006
+ A third form: the protection was there and vanished. The program pointed
1007
+ `$0318` at its own handler, then later ran the customary "put the KERNAL
1008
+ vectors back" line. `JSR $FF8A` (RESTOR) copies the ROM table at `$FD30` and
1009
+ writes `$FE47` back unconditionally. VECTOR with C = 0 (`$FF8D`) installs
1010
+ whatever 32-byte table the caller points at — VECTOR (`$FF8D` → `$FD1A`)
1011
+ copies through a 32-byte loop at `$FD20`, whose C = 0 path is `LDA ($C3),Y`
1012
+ (`$FD25`) `: STA ($C3),Y : STA $0314,Y` (`$FD29`); an earlier draft put the
1013
+ pair at `$FD1A`, which is the entry's `STX $C3` — so it puts `$FE47` back
1014
+ when that table was captured with C = 1
1015
+ before `$0318` was changed, which is the usual snapshot-then-restore idiom.
1016
+ Either way `$0318` is `$FE47` again.
1017
+
1018
+ ### Mechanism
1019
+
1020
+ There are two halves, and they stand on different rungs.
1021
+
1022
+ **The wiring — rung 4 for the circuit, rung 2–3 for the part list.** RESTORE
1023
+ is one of the two keys outside the 8×8 matrix that CIA1 scans (SHIFT LOCK is
1024
+ the other, and it is only LSHIFT's wire); the C64-Wiki's keyboard page has it
1025
+ as "tied to the NMI line and not part of the matrix". The switch is coupled
1026
+ through a capacitor, C38, to a monostable whose output pulls the 6510's /NMI
1027
+ pin low for the length of its pulse. CIA2's /IRQ output is on the same pin.
1028
+ The two are in parallel: either can assert /NMI, neither passes through the
1029
+ other, and nothing about the key touches CIA2's FLAG pin, its interrupt
1030
+ control register, or any bit you can write in `$DD0D`. The monostable is one
1031
+ half of the 556 dual timer at U20 on the boards whose parts lists the
1032
+ C64-Wiki's motherboard page carries — ASSY 326298 (1982, schematic 326106),
1033
+ 250407 (1983), 250425 (1984) and 250466 (1986, schematic 252278); on the
1034
+ 250469 (1987 on) U20 is the 8701 clock generator and the RESTORE one-shot was
1035
+ not traced for this entry. On early boards C38 is 51 pF, small enough that a
1036
+ slow press does not fire the one-shot (the German C64-Wiki's cure is 4.7 nF).
1037
+ No figure for the pulse length is claimed here; it was not measured. The key
1038
+ was not pressed for this entry — `-keybuf` stuffs the KERNAL's keyboard
1039
+ buffer, and RESTORE is not a matrix key — so the circuit itself stands on the
1040
+ C64-Wiki's description; what the KERNAL does with the resulting NMI, below,
1041
+ is from the bytes. An earlier draft cited "drawing 252278, reproduced in the
1042
+ Programmer's Reference Guide": 252278 is the 250466's schematic and the 1982
1043
+ Guide cannot contain it.
1044
+
1045
+ **The KERNAL's side — rung 1, from the bytes of `kernal-901227-03.bin`.**
1046
+ The CPU vector at `$FFFA` holds `$FE43`. What runs from there:
1047
+
1048
+ | Where | Bytes | Does |
1049
+ |---|---|---|
1050
+ | `$FE43` | `78` `6C 18 03` | `SEI`, then `JMP ($0318)`. The `SEI` (2 cycles) is the only thing that runs before the RAM vector — harmless to a handler, since `RTI` restores P. The vector's default is `$FE47` (vector table at `$FD30`). |
1051
+ | `$FE47` | `48 8A 48 98 48` | Push A, X, Y. No BRK test — that is the IRQ dispatcher's job at `$FF48`, not this one's. |
1052
+ | `$FE4C` | `A9 7F 8D 0D DD` | `LDA #$7F : STA $DD0D` — mask every CIA2 source. |
1053
+ | `$FE51` | `AC 0D DD` | `LDY $DD0D` — read the flags, which also clears them. |
1054
+ | `$FE54` | `30 1C` | `BMI $FE72` — bit 7 set means some enabled CIA2 source fired: take the RS-232 path. |
1055
+ | `$FE56` | `20 02 FD` `D0 03` `6C 02 80` | No flag. `JSR $FD02` compares `$8004-$8008` with `CBM80`; on a match, `JMP ($8002)` — a cartridge's warm-start vector. |
1056
+ | `$FE5E` | `20 BC F6` | `JSR $F6BC` — the tail of UDTIM: reads `$DC01` until two reads agree. Bit 7 set (nothing in column 7): store the row in `$91`. Bit 7 clear: re-read with `$DC00 = $BD` (columns 1 and 6, where the SHIFT keys are), write the first row value back to `$DC00` (`$F6D4`), and if anything in those columns is down skip the store (`INX : BNE $F6DC`) — SHIFT held with STOP leaves `$91` unwritten. A fresh hardware sample, not the IRQ scan's leftover. |
1057
+ | `$FE61` | `20 E1 FF` | `JSR $FFE1` — STOP, through `($0328)` = `$F6ED`: `LDA $91 : CMP #$7F`. Z is set only when `$91` is exactly `$7F`: RUN/STOP down and no other column-7 key with it (1, ←, CTRL, 2, SPACE, C=, Q) — a second key in that column clears another bit and blinds the check the same way a wrong `$DC00` does. |
1058
+ | `$FE64` | `D0 0C` | `BNE $FE72` — not held: join the RS-232 path, which finds nothing to do, writes `$02A1` back to `$DD0D` (`$FEB6`-`$FEBB`), pulls Y, X, A and `RTI`s (`$FEBC`-`$FEC1`). |
1059
+ | `$FE66` | `20 15 FD` `20 A3 FD` `20 18 E5` `6C 02 A0` | Held: RESTOR, IOINIT, the screen editor's VIC and screen reset, then `JMP ($A002)` — the BASIC warm start. |
1060
+
1061
+ Read the branch at `$FE54` again. The handler decides "this was RESTORE" by
1062
+ finding **no** CIA2 flag. It never sees the key; it cannot. Masking FLAG — or
1063
+ every source — in `$DD0D` only guarantees that the flag is absent, which *is*
1064
+ the RESTORE case. `$DD0D = $10` is a no-op twice over: bit 7 clear makes it a
1065
+ CLEAR-mask write, so it clears a FLAG mask that IOINIT had already cleared,
1066
+ and the key was never going to raise that flag in the first place.
1067
+
1068
+ The same bytes give one more thing for free. The `LDA #$7F : STA $DD0D` at
1069
+ `$FE4C` wipes whatever CIA2 mask the program had set, and the exit at `$FEB6`
1070
+ re-enables only what `$02A1` — the KERNAL's RS-232 shadow — holds. A program
1071
+ that arms a CIA2 timer NMI while leaving `$0318` at `$FE47` loses that mask
1072
+ on the first RESTORE press or RS-232 event (rung 1, from the bytes; not run).
1073
+
1074
+ Measured (rung 1; VICE x64sc 3.10, PAL C64C: 8565, 8580, 8521 — the build on
1075
+ this machine, the rest of this repository was checked against 3.9; an
1076
+ earlier version said PAL 6569, but `x64sc -default` is the C64C): a CIA2 Timer A one-shot NMI
1077
+ was sent through `$0318` to a trampoline of `BIT $DD0D` and `JMP $FE47`,
1078
+ preceded by a counter increment, with IRQs off and `$91` pre-set to `$00`.
1079
+ After the NMI, `$91`
1080
+ read `$FF`: the KERNAL's handler took the no-flag branch and `$F6BC` wrote the
1081
+ keyboard row into it. The control — the same trampoline without the `BIT` —
1082
+ left `$91` at `$00`: the standing flag sent it down the `BMI`. The screen
1083
+ cells that displayed `$91` were decoded against the character ROM, not read
1084
+ by eye. The warm-start branch itself (`$FE66` onward) was not exercised: it
1085
+ needs RUN/STOP held, and a headless run has no keys.
1086
+
1087
+ **RUN/STOP without the IRQ.** Because `$F6BC` samples the hardware,
1088
+ RUN/STOP+RESTORE warm-starts with IRQs disabled and the keyboard scan stopped.
1089
+ What it does need is `$DC00` still driving column 7 low: IOINIT leaves
1090
+ `$DC00 = $7F` (the store at `$FDAB`) and SCNKEY writes `$7F` back on exit
1091
+ (`$EB42`), so the row read at `$F6BC` sees STOP on bit 7. A program that has
1092
+ left another value in `$DC00` with bit 7 high blinds the check, and
1093
+ RUN/STOP+RESTORE then behaves like RESTORE alone. Read from the bytes; not
1094
+ measured.
1095
+
1096
+ ### Fix
1097
+
1098
+ **A — take the vector (`$0318`).** `$FE43` runs `SEI` and then jumps through
1099
+ `$0318`, so a handler there replaces the whole dispatch from its second
1100
+ instruction on. The smallest one is a single `RTI`; from BASIC, `POKE
1101
+ 792,193` aims `$0318` at `$FEC1`, which is the `RTI` at the end of the
1102
+ KERNAL's own handler (the byte at `$FEC1` is `$40`). Conditions and costs:
1103
+
1104
+ - Every press still costs 20 cycles — the NMI sequence (7), the `SEI` (2),
1105
+ the `JMP ($0318)` (5) and the `RTI` (6) — at an arbitrary point in the
1106
+ frame; cycle-exact code shows it once per press. Measured (rung 1, VICE
1107
+ x64sc 3.10): CIA1 Timer A counting across a 200-cycle block of `NOP`s with
1108
+ DEN off read `$FF2C` with the CIA2 NMI masked and `$FF18` with it taken,
1109
+ through a RAM `rti` and through the `$FEC1` stub alike — 20 either way. An
1110
+ earlier draft of this entry said 13, having left out the `SEI` and the
1111
+ indirect jump.
1112
+ - The handler gets control with nothing saved. The KERNAL's `PHA : TXA : PHA
1113
+ : TYA : PHA` is at `$FE47`, after the vector, not before it — a handler
1114
+ that does more than `RTI` saves what it touches.
1115
+ - Do not read `$DD0D` in this handler "to be safe" if the program also uses
1116
+ CIA2 NMIs: the read discards a CIA2 flag that may have arrived, and with it
1117
+ the timer or RS-232 event it announced. If both are in play, test bit 7 of
1118
+ `$DD0D` and read it only when you mean to.
1119
+ - It holds only while `$0318` does. RESTOR (`$FF8A`) copies the ROM table
1120
+ and writes `$FE47` back unconditionally, and so does the warm start, which
1121
+ calls it (`$FE66`). VECTOR with C = 0 (`$FF8D`) installs whatever table the
1122
+ caller passes — the usual snapshot-then-restore idiom passes one captured
1123
+ before `$0318` was changed, and that one holds `$FE47`. That is why those
1124
+ two routines are on this entry's Triggered-by line.
1125
+ - It needs the KERNAL ROM mapped in (`$01` bit 1 set). With HIRAM = 0 the CPU
1126
+ fetches `$FFFA` from RAM and the vector is yours to supply there;
1127
+ `ram_under_rom_traps` in `pitfalls/banking.md` shows the pattern.
1128
+
1129
+ **B — the NMI lock.** The 6510's NMI input is edge-sensitive: an NMI is taken
1130
+ on the high-to-low transition of /NMI, and a line that then stays low is not
1131
+ taken again. Arm a CIA2 Timer A one-shot with its NMI mask set and give it a
1132
+ handler that never reads `$DD0D`. The timer underflows once, CIA2 sets its IR
1133
+ bit and pulls /NMI low, the handler runs once — and /NMI then stays low for as
1134
+ long as the flag stands. No later source can make an edge: not a second
1135
+ underflow, not the 556. The lock holds until something reads `$DD0D`.
1136
+
1137
+ Measured (rung 1; VICE x64sc 3.10): with the handler in the listing below, two
1138
+ one-shots produced one NMI (the counter cell showed `1`); the same program
1139
+ with `LDA $DD0D` in the handler produced two (`2`); and the locked program
1140
+ with a single `LDA $DD0D` from the main loop before a third one-shot produced
1141
+ two — the read, and nothing else, re-arms the edge. Costs: every CIA2 NMI is
1142
+ forfeited (RS-232, NMI-timed digi and music players), and any code that reads
1143
+ `$DD0D` — yours, or a KERNAL RS-232 routine — silently unlocks it.
1144
+
1145
+ **What does not work.** Any value written to `$DD0D`; `SEI` (it masks /IRQ
1146
+ only); stopping the KERNAL IRQ scan (the NMI path samples STOP itself).
1147
+
1148
+ ### Worked example
1149
+
1150
+ The pattern the registers page used to recommend. It clears a mask that is
1151
+ already clear, and the key never went through the CIA anyway:
1152
+
1153
+ ```kick
1154
+ // DOES NOTHING TO RESTORE. Bit 7 clear = CLEAR-mask write; FLAG's mask is
1155
+ // already clear after IOINIT, and RESTORE never raises a CIA2 flag.
1156
+ lda #$10
1157
+ sta $dd0d
1158
+ ```
1159
+
1160
+ Fix A. Only the `SEI` at `$FE43` runs between the CPU's vector fetch and
1161
+ `($0318)`, so the stub is the whole story of a RESTORE press:
1162
+
1163
+ ```kick
1164
+ // Fix A: take the NMI vector. $FE43 is SEI / JMP ($0318); only the SEI runs
1165
+ // before the vector, and RTI puts P back.
1166
+ install_nmi_stub:
1167
+ sei
1168
+ lda #<nmi_stub
1169
+ sta $0318
1170
+ lda #>nmi_stub
1171
+ sta $0319
1172
+ cli
1173
+ rts
1174
+
1175
+ nmi_stub:
1176
+ rti // a press now costs 20 cycles (7 NMI + 2 SEI
1177
+ // + 5 JMP ind + 6 RTI, measured) and does
1178
+ // nothing (do not add "lda $dd0d" here if
1179
+ // CIA2 NMIs are also in use — it discards
1180
+ // their flag)
1181
+ ```
1182
+
1183
+ Fix B, exactly as run and measured. Cell 0 of the screen counts NMIs, cell 1
1184
+ gets a `*` when the program reaches its end; `pause` busy-waits about a
1185
+ second (3 × 256 × 1286 cycles, rung 3). Two one-shots, one NMI:
1186
+
1187
+ ```kick
1188
+ // lock.asm — fix B, the NMI lock. Handler never reads $DD0D, so CIA2 keeps
1189
+ // /NMI low; a second Timer A underflow produces no new edge.
1190
+ // Expected: cell 0 = '1', cell 1 = '*'.
1191
+ BasicUpstart2(start)
1192
+ * = $0810
1193
+ start:
1194
+ sei
1195
+ ldx #39
1196
+ lda #$20
1197
+ clr: sta $0400,x
1198
+ dex
1199
+ bpl clr
1200
+ lda #<nmi
1201
+ sta $0318
1202
+ lda #>nmi
1203
+ sta $0319
1204
+ lda #$30 // '0'
1205
+ sta $0400
1206
+ lda #$7f
1207
+ sta $dd0d // mask every CIA2 source
1208
+ lda $dd0d // drop anything pending
1209
+ lda #$ff
1210
+ sta $dd04
1211
+ lda #$00
1212
+ sta $dd05 // Timer A latch = 255: 256 cycles to underflow
1213
+ lda #$81
1214
+ sta $dd0d // Timer A underflow -> /NMI
1215
+ lda #$19
1216
+ sta $dd0e // force load, one-shot, start
1217
+ jsr pause
1218
+ lda #$19
1219
+ sta $dd0e // second one-shot: a second underflow
1220
+ jsr pause
1221
+ lda #$2a // '*' = reached the end
1222
+ sta $0401
1223
+ hang: jmp hang
1224
+
1225
+ nmi: inc $0400
1226
+ rti // no read of $DD0D: /NMI stays low
1227
+
1228
+ pause: lda #3
1229
+ sta $02
1230
+ p1: ldx #0
1231
+ p2: ldy #0
1232
+ p3: dey
1233
+ bne p3
1234
+ dex
1235
+ bne p2
1236
+ dec $02
1237
+ bne p1
1238
+ rts
1239
+ ```
1240
+
1241
+ Change the handler to `inc $0400 : pha : lda $dd0d : pla : rti` and the same
1242
+ program shows `2`. That variant is also the shape of fix A when the program
1243
+ has its own CIA2 NMI source: the handler runs, acknowledges, returns, and the
1244
+ main program continues.
1245
+
1246
+ The experiment behind the "absence of a flag" claim. Put this at `$0318`,
1247
+ fire a CIA2 timer NMI, and the KERNAL's own handler cannot tell it from a
1248
+ RESTORE press — with RUN/STOP held it would warm-start:
1249
+
1250
+ ```kick
1251
+ tramp: bit $dd0d // clear the CIA2 flag before the KERNAL looks
1252
+ jmp $fe47 // KERNAL NMI handler, as if from RESTORE
1253
+ ```
1254
+
1255
+ ### Cross-references
1256
+
1257
+ - `hardware/cia-reference.md` → "NMI vector (CIA2 + RESTORE)" — the dispatch
1258
+ summary; its "Disabling RESTORE" paragraph points back here.
1259
+ - `hardware/c64-registers-reference.md` → CIA2 key wiring points and quick
1260
+ lookup — corrected together with this entry; they used to name FLAG bit 4
1261
+ and recommend `$DD0D = $10`.
1262
+ - `hardware/kernal-routines-reference.md` → Vectors table (NMINV `$0318`,
1263
+ ISTOP `$0328`), RESTOR (`$FF8A`), VECTOR (`$FF8D`).
1264
+ - Pitfall `decimal_mode_in_irq_handler` — an NMI handler that does arithmetic
1265
+ needs `CLD` as well.
1266
+ - Pitfall `ram_under_rom_traps` (`pitfalls/banking.md`) — the all-RAM case,
1267
+ where `$FFFA` is yours and the KERNAL dispatch is out of the picture.
1268
+ - Technique `stable_raster_irq` — the routine whose once-per-press jitter is
1269
+ the RESTORE-alone symptom.
1270
+
1271
+ ### Sources
1272
+
1273
+ - Commodore 64 KERNAL ROM 901227-03 (`kernal-901227-03.bin` as shipped with
1274
+ VICE), bytes read and disassembled by hand for this entry — rung 1.
1275
+ - VICE x64sc 3.10, headless PAL runs of the listings above and their controls,
1276
+ screen cells decoded against `chargen-901225-01.bin` — rung 1.
1277
+ - C64-Wiki, "RESTORE (Key)", https://www.c64-wiki.com/wiki/RESTORE_(Key) —
1278
+ the direct connection to the CPU, `POKE 792,193`, the edge-triggered lock.
1279
+ - C64-Wiki, "Keyboard", https://www.c64-wiki.com/wiki/Keyboard — RESTORE and
1280
+ SHIFT LOCK outside the matrix.
1281
+ - C64-Wiki (German), "RESTORE (Taste)",
1282
+ https://www.c64-wiki.de/wiki/RESTORE_(Taste) — the capacitor coupling
1283
+ (C38, 51 pF on early boards, 4.7 nF as the cure) and early boards'
1284
+ insensitivity to a slow press.
1285
+ - C64-Wiki, "Motherboard", https://www.c64-wiki.com/wiki/Motherboard
1286
+ (fetched 2026-09-22) — the per-board parts lists: U20 = LM556/NE556 dual
1287
+ timer on ASSY 326298, 250407, 250425 and 250466, U20 = 8701 clock generator
1288
+ on 250469; schematic drawing numbers 326106 (326298) and 252278 (250466).
1289
+ Rung 2–3. An earlier draft of this list cited "drawing 252278, reproduced in
1290
+ the Programmer's Reference Guide", from memory; the Guide is from 1982 and
1291
+ 252278 is the 1986 board's drawing.
1292
+ - Joe Forster/STA, "Commodore 64 memory map", https://sta.c64.org/cbm64mem.html
1293
+ — `$0318` default `$FE47`, `$02A1`.
1294
+
1295
+ ---
1296
+
1297
+ ## raster_irq_during_serial_io — A raster IRQ armed across KERNAL disk I/O misses most frames, and rirq_stop() does not stop it
1298
+
1299
+ **Severity:** medium
1300
+ **Region:** both
1301
+ **Triggered by kernal:** OPEN, CLOSE, CHKIN, CHKOUT, CLRCHN, CHRIN, CHROUT, LOAD, SAVE
1302
+ **Triggered by techniques:** stable_raster_irq, frame_sync_loop, kernal_file_write_seq, kernal_file_read_seq, kernal_relative_file_io, kernal_load_to_address, directory_read_and_select
1303
+
1304
+ ### Symptom
1305
+
1306
+ A border split at rows 100 and 200 is clean while the bus is idle. While
1307
+ a 2 KB sequential file is written to drive 8 the split is gone from most
1308
+ frames; when it appears it is a few lines low, or a three-line white
1309
+ sliver near line 240 with the rest of the border in the idle colour. A
1310
+ frame counter kept in the raster handler falls behind by two frames in
1311
+ three. Wrapping the file calls in `rirq_stop()` and `rirq_start()`
1312
+ changes nothing. The file itself is correct: status `$00` after the
1313
+ write, `$40` after the read, the read-back checksum matches.
1314
+
1315
+ Measured in VICE x64sc 3.10 with a true-drive 1541 on a fresh D64, an
1316
+ Oscar64 `rasterirq.h` split (row 100 white, row 200 light blue) and a
1317
+ `rirq_call` handler on the row-100 slot that counts entries and records
1318
+ the lowest and highest `$D012` at entry; the test source is quoted on
1319
+ `recipes/oscar64/high-score-persist.md`, section "A raster IRQ during
1320
+ file I/O". Cycles are CIA2 timers A and B cascaded; frames are those
1321
+ cycles over 19,656 (PAL) or 17,095 (NTSC), arithmetic.
1322
+
1323
+ | Build | Model | Write 2,048 B | Handler entries | `$D012` at entry | Read 2,048 B | Handler entries | `$D012` at entry |
1324
+ |---|---|---|---|---|---|---|---|
1325
+ | IRQ armed | PAL | 9,359,321 cycles, 476 frames | 145 | 101 to 240 | 5,656,966 cycles, 288 frames | 226 | 101 to 234 |
1326
+ | IRQ armed | NTSC | 9,702,621 cycles, 568 frames | 169 | 101 to 241 | 5,991,068 cycles, 350 frames | 268 | 101 to 242 |
1327
+ | `rirq_stop()` around each call | PAL | 9,359,424 cycles, 476 frames | 144 | 101 to 238 | 5,659,833 cycles, 288 frames | 226 | 101 to 233 |
1328
+ | `rirq_stop()` around each call | NTSC | 9,702,713 cycles, 568 frames | 168 | 101 to 240 | 5,983,652 cycles, 350 frames | 270 | 101 to 247 |
1329
+ | `$D01A` cleared around each call | PAL | 9,158,718 cycles, 466 frames | 0 | none | 5,553,710 cycles, 283 frames | 0 | none |
1330
+ | `$D01A` cleared around each call | NTSC | 9,498,969 cycles, 556 frames | 0 | none | 5,765,998 cycles, 337 frames | 0 | none |
1331
+
1332
+ Idle, the same handler entered on line 101 in every one of 50 frames on
1333
+ both models. Every row above ended with status `$00` after the write,
1334
+ `$40` (EOF) after the read, 2,048 bytes back, checksum `F000` matching,
1335
+ and `00, OK` on the error channel.
1336
+
1337
+ ### Mechanism
1338
+
1339
+ The KERNAL's serial primitives run each byte with interrupts off. The
1340
+ send routine at `$ED40` opens with `SEI` and exits through `CLI` at
1341
+ `$EDAB`; the receive routine at `$EE13` does the same and exits at
1342
+ `$EE82`; the talk turnaround at `$EDCC` to `$EDDB` is bracketed the same
1343
+ way (ROM `kernal-901227-03.bin`, rung 1; `kernal_assumes_sei_cleared`
1344
+ above has the census). Inside those brackets are wait loops with no
1345
+ timeout: `$ED50` to `$ED5D` wait on DATA for the listener to be ready
1346
+ before each byte, `$EE1B` waits on CLK for the talker, `$EDD6` waits on
1347
+ CLK for the drive to take the bus. Between bytes the drive is doing its
1348
+ own work, and while it is the C64 sits in one of those loops with the I
1349
+ flag set. Under the monitor, the byte that follows a directory lookup
1350
+ spent 1,512,351 cycles between `$ED40` and `$EDAB`, about 77 PAL frames
1351
+ in one `SEI` bracket.
1352
+
1353
+ The VIC raster latch holds one interrupt. A match that lands inside a
1354
+ bracket is delivered at the `CLI`, wherever the beam is by then, which is
1355
+ the handler entering at line 240 for a slot armed at row 100. A second
1356
+ match inside the same bracket is not remembered, which is the 331 PAL
1357
+ frames out of 476 with no entry at all. The `rasterirq.h` slot code spins
1358
+ on `CMP $D012` until the counter passes its row, so a handler that is
1359
+ delivered after the counter has wrapped waits for the next pass of its
1360
+ row and reads 101 again; the recorded maximum is therefore a floor on
1361
+ the lateness, not its extent.
1362
+
1363
+ `rirq_stop()` is one instruction, `SEI` (`rasterirq.c`), and the first
1364
+ serial primitive's `CLI` cancels it. That is why the second pair of rows
1365
+ matches the first. Clearing `$D01A` removes the source instead of masking
1366
+ the CPU, and the transfer then runs about two per cent faster, the
1367
+ handler time it no longer pays.
1368
+
1369
+ The split in the exit screenshot at 8,000,000 cycles, mid-write: IRQ
1370
+ armed on PAL, white on lines 240 to 242 only, the row-100 slot delivered
1371
+ late and the row-200 slot run straight after it; IRQ armed on NTSC, white
1372
+ on lines 104 to 201 against 101 to 200 idle; `$D01A` cleared, one colour
1373
+ for the whole frame, white on PAL and light blue on NTSC, whichever the
1374
+ border held when the register was cleared. The right border changes one
1375
+ row before the left in every split, the store landing mid-line.
1376
+
1377
+ ### Fix
1378
+
1379
+ Do not run file I/O under a raster effect you want to keep. For a game,
1380
+ save and load on a static screen with the raster IRQ off, and put the
1381
+ screen and border into the state you want held before the first call:
1382
+
1383
+ ```c
1384
+ vic.color_border = VCOL_BLACK; // whatever the static screen wants
1385
+ vic.intr_enable = 0; // $D01A: no raster IRQ source
1386
+ krnio_setnam("HISCORE,S,W");
1387
+ if (krnio_open(2, 8, 2)) {
1388
+ krnio_write(2, buf, sizeof(buf));
1389
+ krnio_close(2);
1390
+ }
1391
+ vic.intr_ctrl = 1; // $D019: drop a match latched meanwhile
1392
+ vic.intr_enable = 1; // re-arm; rasterirq.h resumes on its next row
1393
+ ```
1394
+
1395
+ `rirq_stop()` is not a substitute; it cannot outlive the first byte on
1396
+ the bus. If the raster IRQ must stay armed, expect it to enter late or
1397
+ not at all for the length of the transfer, and do not count frames or
1398
+ drive music from it across the calls: the cascaded CIA2 timer the test
1399
+ used kept time, the handler did not. The transfer speed (about 216
1400
+ bytes per second writing and 355 reading, either model, measured above)
1401
+ is the KERNAL's; the IRQ costs it two per cent.
1402
+
1403
+ ### Worked example
1404
+
1405
+ ```text
1406
+ // BAD: the split is expected to survive the write
1407
+ rirq_stop(); // sei -- undone at $EDAB by the first byte
1408
+ krnio_open(2, 8, 2); krnio_write(2, buf, 2048); krnio_close(2);
1409
+ rirq_start();
1410
+ // measured: 144 handler entries in 476 PAL frames, entry as late as line 238
1411
+
1412
+ // GOOD: take the source away, hold the picture still, put it back after
1413
+ vic.intr_enable = 0;
1414
+ krnio_open(2, 8, 2); krnio_write(2, buf, 2048); krnio_close(2);
1415
+ vic.intr_ctrl = 1; vic.intr_enable = 1;
1416
+ // measured: 0 entries during the write, the split back on line 101 after
1417
+ ```
1418
+
1419
+ ### Cross-references
1420
+
1421
+ - Pitfall `kernal_assumes_sei_cleared` above: where the `SEI`/`CLI`
1422
+ pairs are; this entry is what they do to a raster IRQ that is armed
1423
+ when the bus is busy.
1424
+ - Technique `stable_raster_irq`, `frame_sync_loop`
1425
+ (`techniques/raster.md`): the schemes whose handler and frame counter
1426
+ this measurement stalled.
1427
+ - Techniques `kernal_file_write_seq`, `kernal_file_read_seq`
1428
+ (`techniques/file-io.md`): the calls that hold the bus.
1429
+ - Recipe `recipes/oscar64/high-score-persist.md`, section "A raster IRQ
1430
+ during file I/O": the test program and the same figures beside the
1431
+ save-file recipe.
1432
+ - `recipes/oscar64/stable-raster-irq.md`, "What `rirq_init` actually
1433
+ does": the dispatcher shares the IRQ line with the CIA jiffy timer,
1434
+ which the brackets above stall in the same way (not measured here).
1435
+
1436
+ ### Sources
1437
+
1438
+ - Commodore 64 KERNAL ROM 901227-03 (`kernal-901227-03.bin` as shipped
1439
+ with VICE), `$ED40`-`$EDB9`, `$EDC7`-`$EDDC`, `$EE13`-`$EE84`,
1440
+ disassembled for this entry, rung 1.
1441
+ - VICE x64sc 3.10 `-default` with `-8` and a fresh `TEST,01` D64, PAL and
1442
+ `-model ntsc`, six builds run to completion at 40,000,000 cycles and
1443
+ again to an exit screenshot at 8,000,000; screen cells decoded against
1444
+ `chargen-901225-01.bin`, border colour read down x = 2 and x = 380,
1445
+ rung 1.
1446
+ - Oscar64 `include/c64/rasterirq.c`, `rirq_start` and `rirq_stop`,
1447
+ rung 1.
1448
+
1449
+ ---
1450
+
1451
+ ## first_open_after_reset_hangs_on_pal — The first file read after reset can hang for ever in the TALK turnaround on PAL under VICE autostart, and a frame wait only moves the phase
1452
+
1453
+ **Severity:** high
1454
+ **Region:** PAL
1455
+ **Triggered by kernal:** OPEN, CHKIN
1456
+ **Triggered by techniques:** kernal_file_read_seq, kernal_file_write_seq, error_channel_check, kernal_load_to_address, directory_read_and_select
1457
+
1458
+ ### Symptom
1459
+
1460
+ A program whose first act after its own set-up is to open a file on
1461
+ drive 8 and read it never returns from the read. The screen keeps
1462
+ whatever was drawn before the call; the drive line the program prints
1463
+ on return never appears; a frame counter kept by the main loop stays at
1464
+ zero. The KERNAL status byte `$90` is `$00`, so nothing is reported, and
1465
+ the CPU sits with the I flag set. Left for 30,000,000 cycles it is still
1466
+ there. The same binary on `-model ntsc` runs. A build that waits a
1467
+ different number of frames before the call runs, and so does the same
1468
+ build when the drive loaded it.
1469
+
1470
+ The hang is deterministic for a given binary, model and autostart delay:
1471
+ the build that hung did so in three runs out of three.
1472
+
1473
+ ### Mechanism
1474
+
1475
+ What is measured (VICE x64sc 3.10 remote monitor, `break edd6`, first
1476
+ stop, the platformer scaffold built with `-dDISK_WAIT_FRAMES=10`, PAL):
1477
+
1478
+ - The C64 is at `$EDD6` at cycle 4,885,925: the loop after TKSA that
1479
+ waits for the drive to pull CLK low and become the talker. It has no
1480
+ timeout and runs inside the `SEI` bracket that
1481
+ `kernal_assumes_sei_cleared` above describes. `$90` is `$00`, `$BA` is
1482
+ 8, `$DD00` reads `$67`: ATN released, CLK released, DATA held low by
1483
+ the C64 as listener.
1484
+ - The drive CPU is at `$E8E5`, and 27 C64 cycles later at `$E8F4`, drive
1485
+ stopwatch 4,959,055 (the 1541 clock is 1.000 MHz to the PAL C64's
1486
+ 0.985 MHz, which accounts for the whole difference). `$7A` is 1
1487
+ (talker active), `$79` is 0, `$7C` is 0 (no ATN pending), `$84` and
1488
+ `$85` are `$62` (TALK, secondary address 2), `$1800` reads `$03` then
1489
+ `$01`, the job queue `$00` to `$05` reads `00 00 01 0F 01 00` with
1490
+ every job on track 18, `$22` is 18.
1491
+ - In the drive ROM (`dos1541-325302-01+901229-05.bin`, rung 1) `$E902`
1492
+ to `$E907` is the wait for ATN release; `$E8D7` to `$E8E0` clears the
1493
+ ATN acknowledge bit; `$E8E3` tests the listener flag and `$E8ED` the
1494
+ talker flag; a talker goes through `$E99C` and `$E9AE` to `$E909`,
1495
+ which is TALK, the routine that pulls CLK. So at the stop the drive
1496
+ had already seen ATN go, knew it was the talker, and was a few dozen
1497
+ cycles from pulling CLK.
1498
+
1499
+ Unperturbed, the C64 never sees CLK go low in 30,000,000 cycles. Reading
1500
+ the drive's registers from the monitor at that stop lets the run through
1501
+ (two runs each way on `recipes/oscar64/high-score-persist.md`; this
1502
+ session's monitor run was cut after two stops, so its own outcome is not
1503
+ known). What is inferred from that: the drive state the monitor shows is
1504
+ not the state the drive holds in the unperturbed run. The monitor's read
1505
+ advances the drive in a different step from the emulator's own
1506
+ scheduling, and in that step the drive sees the release. The outcome
1507
+ turns on where the two CPUs stand relative to each other when the TALK
1508
+ byte goes out under ATN, which is why zero and five frames of wait ran,
1509
+ ten hung, twenty and fifty ran, a six-second autostart delay ran, and
1510
+ loading the program through the drive ran. A frame wait is a phase
1511
+ shift, not a settle time: the drive had been in its idle loop for about
1512
+ two seconds by the time of the OPEN in the earlier session's trace, and
1513
+ `-iecreset 1`, which resets the drive with the computer, changed
1514
+ nothing. Whether a real 1541 can be caught the same way, and which side
1515
+ of the emulator's bus model is at fault, are not measured here. The job
1516
+ return code `0F` in slot 3 of the queue was not chased.
1517
+
1518
+ ### Fix
1519
+
1520
+ No wait is safe by construction: the smallest wait that ran here was
1521
+ zero frames and the one that hung was ten. What held across every run:
1522
+
1523
+ - Run the binary you ship on PAL under autostart with a fresh disk, once.
1524
+ The hang is deterministic for that binary, so one run answers.
1525
+ - If it hangs, move the phase: a different frame count before the first
1526
+ file call, or the call later in start-up. Every wait other than ten
1527
+ ran here, and so did the ten-frame build with `-autostart-delay 6` and
1528
+ when the drive loaded it. Test again after any change to the code that
1529
+ runs before the call; a change of code size has moved it before.
1530
+ - Check `$90` (READST) and the error channel after the read rather than
1531
+ assuming the read returns. Nothing the program does once it is inside
1532
+ `$EDD6` gets it out: the loop has no timeout and the I flag is set, so
1533
+ a raster or CIA IRQ watchdog cannot fire. An NMI could, but no such
1534
+ watchdog was built or measured here.
1535
+
1536
+ The recipe's `DISK_WAIT_FRAMES 50` default ran in every run tried on two
1537
+ pages, and remains a phase that happens to work for that binary, not a
1538
+ settle time.
1539
+
1540
+ ### Worked example
1541
+
1542
+ The platformer scaffold's listing
1543
+ (`recipes/oscar64/platformer-scaffold.md`), built with
1544
+ `oscar64 -tm=c64 -O2 -dDISK_WAIT_FRAMES=N`, run in x64sc 3.10 `-default
1545
+ -warp +sound +autostart-delay-random -autostartprgmode 1` with a fresh
1546
+ `TEST,01` D64 on a true-drive 1541, wobble off, 10,000,000 cycles unless
1547
+ stated, one run per cell unless stated. "Ran" means the top row shows a
1548
+ frame count `F` and the bottom row `DRIVE: 62, FILE NOT FOUND,00,00`;
1549
+ "hung" means `F 00000` and no drive line. Cells decoded against
1550
+ `chargen-901225-01.bin`.
1551
+
1552
+ | Wait before the first OPEN | PAL, PRG injected | NTSC, PRG injected |
1553
+ |---|---|---|
1554
+ | 0 frames (6,457-byte PRG) | ran, F 264 | ran, F 293 |
1555
+ | 5 frames | ran, F 259 | ran, F 287 |
1556
+ | 10 frames | hung, three runs, one of them at 30,000,000 cycles | ran, F 283 |
1557
+ | 20 frames | ran, F 244 | ran, F 273 |
1558
+ | 50 frames | ran, F 215 | ran, F 243 |
1559
+
1560
+ The 10-frame PAL build, one thing changed at a time, one run each:
1561
+
1562
+ | Change | Outcome |
1563
+ |---|---|
1564
+ | `-iecreset 1` | hung |
1565
+ | `-autostart-delay 6` (12,000,000 cycles) | ran, F 206 |
1566
+ | PRG written to the disk and the D64 autostarted, so the drive loaded it (40,000,000 cycles) | ran, F 800, drive line `01, FILES SCRATCHED` |
1567
+
1568
+ Region PAL rests on the five NTSC runs above and the one on the recipe
1569
+ page; no NTSC run has hung, and nothing here says none can.
1570
+
1571
+ ```text
1572
+ // BAD: the read is the first thing on the bus and its return is assumed
1573
+ krnio_setnam("HISCORE,S,R");
1574
+ krnio_open(2, 8, 2);
1575
+ n = krnio_read(2, buf, 16); // CHKIN -> TALK, TKSA, then $EDD6 for ever
1576
+ // measured: F 00000 and no drive line at 30,000,000 cycles, PAL, ten-frame build
1577
+
1578
+ // BETTER: a phase that ran for this binary, tested on PAL once, and the
1579
+ // status checked after the read
1580
+ for (char i = 0; i < DISK_WAIT_FRAMES; i++) vic_waitFrame();
1581
+ krnio_setnam("HISCORE,S,R");
1582
+ if (krnio_open(2, 8, 2)) {
1583
+ n = krnio_read(2, buf, 16);
1584
+ st = krnio_status(); // $90: $40 EOF is the good case, $02 timeout
1585
+ }
1586
+ krnio_close(2);
1587
+ // measured: ran at 0, 5, 20 and 50 frames; the wait does not make the read safe
1588
+ ```
1589
+
1590
+ ### Cross-references
1591
+
1592
+ - `recipes/oscar64/high-score-persist.md`, "A start-up hang seen in
1593
+ VICE, located but not explained": the first trace of the same spin,
1594
+ with the byte timings of the OPEN and the directory search.
1595
+ - `recipes/oscar64/platformer-scaffold.md`, `DISK_WAIT_FRAMES`: the
1596
+ workaround as shipped, and the listing the runs above were built from.
1597
+ - `formats/iec-disk-reference.md`, "Drive-Not-Ready and Timeout Errors":
1598
+ the KERNAL's one timeout and the waits that have none.
1599
+ - Pitfall `kernal_assumes_sei_cleared` above: the `SEI` bracket the spin
1600
+ sits in.
1601
+ - Pitfall `raster_irq_during_serial_io` above: the same loops, seen from
1602
+ the raster IRQ's side.
1603
+ - Technique `error_channel_check` (`techniques/file-io.md`): the status
1604
+ read the fix asks for after every file call.
1605
+
1606
+ ### Sources
1607
+
1608
+ - Commodore 1541 DOS ROM 325302-01 + 901229-05 (`dos1541-325302-01+901229-05.bin`
1609
+ as shipped with VICE), `$E8D7`-`$E90B`, disassembled for this entry,
1610
+ rung 1.
1611
+ - Commodore 64 KERNAL ROM 901227-03, `$EDC7`-`$EDDC`, as disassembled for
1612
+ `raster_irq_during_serial_io` above, rung 1.
1613
+ - VICE x64sc 3.10, twenty runs of the platformer scaffold at five wait
1614
+ counts on PAL and NTSC and five variants of the ten-frame PAL build,
1615
+ and one remote-monitor session with `break edd6`, 2026-09-22, rung 1.