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,1427 @@
1
+ ---
2
+ category: io
3
+ ---
4
+
5
+ <!-- doc-type: technique-reference -->
6
+
7
+ # File I/O Techniques
8
+
9
+ Reading and writing files on a serial-bus disk drive through the KERNAL's
10
+ logical-file layer: SETLFS, SETNAM, OPEN, the CHKIN/CHKOUT redirection
11
+ pair, CHRIN/CHROUT, READST, CLRCHN, CLOSE and LOAD. The register
12
+ contracts for each routine are in `hardware/kernal-routines-reference.md`;
13
+ the drive-side command set and the secondary-address table are in
14
+ `formats/iec-disk-reference.md`; the ways the KERNAL bites are in
15
+ `pitfalls/kernal-and-io.md`. Every number below that came from an
16
+ instrument says so. The instrument was VICE 3.10 x64sc with true drive
17
+ emulation of a 1541 (the `-default` configuration) and a disk freshly
18
+ formatted by c1541; a real 1541 was not on the bench.
19
+
20
+ Two facts run through every technique below. First, a disk OPEN does not
21
+ report drive-side errors: OPEN returns C=0 for a name that is not on the
22
+ disk (measured: OPEN of `SCORES,S,R` on an empty disk returned C=0,
23
+ CHKIN C=0, and the first CHRIN left ST = `$42`) and for a write to a
24
+ name that already is (measured, see error_channel_check); only the
25
+ drive's error channel knows. Second, the
26
+ KERNAL talks to the drive at the bus's own pace, so these calls take
27
+ real time. The recipe below spends 4,846,078 cycles on the drive for a
28
+ 32-byte write, a 32-byte read and two status reads, about 4.9 s at PAL
29
+ speed (measured with a CIA2 timer inside the program; rung 1).
30
+
31
+ ---
32
+
33
+ ## kernal_file_write_seq — Write a sequential file with OPEN/CHKOUT/CHROUT
34
+
35
+ **Complexity:** low
36
+ **Region:** both
37
+ **Uses registers:** (none)
38
+ **Uses kernal:** SETLFS, SETNAM, OPEN, CHKOUT, CHROUT, CLRCHN, CLOSE
39
+
40
+ ### Why
41
+
42
+ A game that keeps a high-score table, a save slot or a level editor's
43
+ output needs to put a run of bytes on a disk under a name, and get the
44
+ same bytes back later. The KERNAL's sequential-file protocol does that
45
+ with seven calls and no drive code of its own. It is the same path
46
+ BASIC's `OPEN 2,8,2,"NAME,S,W"` / `PRINT#2` / `CLOSE 2` takes.
47
+
48
+ ### How
49
+
50
+ 1. SETLFS with A = a logical file number of your choosing (1 to 127),
51
+ X = 8 for the first drive, Y = a secondary address from 2 to 14. The
52
+ secondary address is the drive's channel number; 0 and 1 are LOAD and
53
+ SAVE, 15 is the command channel.
54
+ 2. SETNAM with the length and address of the name string. The suffix
55
+ `,S,W` names the type (sequential) and the mode (write). The string is
56
+ not copied: it must still be there when OPEN runs.
57
+ 3. OPEN. Test the carry: C=1 with A = 1, 2, 5 or 6 is a KERNAL-side
58
+ refusal (table full, number already open, no device, number 0). C=0
59
+ says nothing about the drive.
60
+ 4. CHKOUT with X = the logical file number. From here every CHROUT byte
61
+ goes to the file instead of the screen.
62
+ 5. CHROUT once per byte. A sequential file is byte-transparent: `$00`
63
+ and `$0D` go in and come back unchanged (measured: the recipe's
64
+ payload contains both).
65
+ 6. CLRCHN, then CLOSE. CLRCHN first: it sends UNLISTEN and puts CHROUT
66
+ back on the screen; CLOSE then tells the drive to finish the file.
67
+ The other order leaves output pointed at a closed file.
68
+
69
+ ```asm
70
+ // kernal_file_write_seq: create SCORES as a SEQ file and write 32 bytes
71
+ .const SETLFS = $ffba
72
+ .const SETNAM = $ffbd
73
+ .const OPEN = $ffc0
74
+ .const CLOSE = $ffc3
75
+ .const CHKOUT = $ffc9
76
+ .const CLRCHN = $ffcc
77
+ .const CHROUT = $ffd2
78
+
79
+ * = $c000
80
+ write_scores:
81
+ // scratch any old copy first: the command goes out as OPEN's filename
82
+ lda #15
83
+ ldx #8
84
+ ldy #15 // secondary 15 = command channel
85
+ jsr SETLFS
86
+ lda #scr_end-scr
87
+ ldx #<scr
88
+ ldy #>scr
89
+ jsr SETNAM
90
+ jsr OPEN // sends S0:SCORES to the drive
91
+ lda #15
92
+ jsr CLOSE
93
+
94
+ lda #2 // logical file 2
95
+ ldx #8 // device 8
96
+ ldy #2 // secondary 2..14 = a data channel
97
+ jsr SETLFS
98
+ lda #wname_end-wname
99
+ ldx #<wname
100
+ ldy #>wname
101
+ jsr SETNAM
102
+ jsr OPEN
103
+ bcs open_failed // C=1: KERNAL-side error, A = code
104
+ ldx #2
105
+ jsr CHKOUT // from here CHROUT writes to the file
106
+ bcs open_failed
107
+ ldy #0
108
+ put:
109
+ lda payload,y
110
+ jsr CHROUT
111
+ iny
112
+ cpy #32
113
+ bne put
114
+ jsr CLRCHN // output back to the screen FIRST
115
+ lda #2
116
+ jsr CLOSE // now the drive finalises the entry
117
+ clc
118
+ rts
119
+ open_failed:
120
+ jsr CLRCHN
121
+ lda #2
122
+ jsr CLOSE // a failed OPEN can leave a table entry
123
+ sec
124
+ rts
125
+
126
+ .encoding "petscii_upper" // a drive reads PETSCII, not screen codes
127
+ scr: .text "S0:SCORES"
128
+ scr_end:
129
+ wname: .text "SCORES,S,W" // name, type S, mode W
130
+ wname_end:
131
+ payload: .fill 32, i
132
+ ```
133
+
134
+ ### Why it works
135
+
136
+ OPEN puts the drive into LISTEN with secondary `$F0 | 2` and sends the
137
+ name; the drive parses `,S,W`, allocates a first data block and keeps the
138
+ channel open. CHKOUT sends LISTEN with `$60 | 2`, after which the KERNAL's
139
+ CHROUT vector writes each byte to the bus with IECOUT. CLOSE sends
140
+ `$E0 | 2`, and it is on receiving that close that the drive writes out
141
+ the last partial block, fills in the block count and clears the "open"
142
+ flag in the directory entry. Until then the entry is a splat file: a
143
+ `*SEQ` with 0 blocks that no OPEN can read. That is also what an
144
+ emulator run stopped before CLOSE finished leaves behind; the
145
+ `krnio_save_leaves_splat_file` pitfall shows the same signature for SAVE.
146
+
147
+ ### Overwriting
148
+
149
+ Opening `NAME,S,W` when NAME exists is refused by the drive. The OPEN
150
+ still returns C=0 and the error channel reads `63, FILE EXISTS,00,00`
151
+ (measured with a PRG of that name: OPEN of `BLOB,P,W` returned C=0 and
152
+ the channel read 63). What CHKOUT and CHROUT do on that channel
153
+ afterwards was not measured here. Two ways round it:
154
+
155
+ - `@0:NAME,S,W` asks the DOS to replace the file in place. 1541 DOS 2.6's
156
+ save-with-replace has a long-reported defect that can corrupt the disk
157
+ when the drive's buffers are in a particular state; that defect is not
158
+ measured here (rung 4), and this page does not recommend relying on
159
+ the command.
160
+ - Scratch, then write: send `S0:NAME` over the command channel as the
161
+ fragment does, and then OPEN for write. It costs one extra OPEN/CLOSE
162
+ pair, the result is visible on the error channel (`01, FILES
163
+ SCRATCHED,01,00` reports the count; not measured here), and a
164
+ scratch of a name that does not exist is harmless. This is the safer
165
+ habit and the one the fragment uses.
166
+
167
+ ### Variations
168
+
169
+ - **Logical file numbers 128 to 255** are safe here. CHROUT adds no
170
+ linefeed whatever the number; the linefeed-after-CR rule belongs to
171
+ BASIC's PRINT#, as `hardware/kernal-routines-reference.md` measured.
172
+ - **PRG rather than SEQ.** `,P,W` writes a PRG; the two-byte load address
173
+ is then yours to CHROUT first. Use SAVE instead when the bytes are
174
+ already contiguous in memory.
175
+ - **Text files for other software.** Write `$0D` after each line; the
176
+ drive stores it as an ordinary byte.
177
+
178
+ ### Recipes
179
+
180
+ - `recipes/kickassembler/file-io-roundtrip.md`
181
+ - `recipes/oscar64/save-load-seq-file.md` (the same sequence through Oscar64's kernalio.h, with a provoked 62)
182
+ - `recipes/oscar64/high-score-persist.md` (the policy around the calls: first run, scratch-then-write, version byte, no drive; 74 and the scratch reply measured)
183
+
184
+ ---
185
+
186
+ ## kernal_file_read_seq — Read a sequential file with CHKIN/CHRIN/READST
187
+
188
+ **Complexity:** low
189
+ **Region:** both
190
+ **Uses registers:** (none)
191
+ **Uses kernal:** SETLFS, SETNAM, OPEN, CHKIN, CHRIN, READST, CLRCHN, CLOSE
192
+
193
+ ### Why
194
+
195
+ The read half of kernal_file_write_seq. The trap is end-of-file: CHRIN
196
+ delivers the last byte with the carry clear, exactly as it delivered
197
+ every earlier one, and the only signal that the byte was the last is bit
198
+ 6 of the status byte. A loop that waits for the carry reads one byte too
199
+ many.
200
+
201
+ ### How
202
+
203
+ 1. SETLFS, SETNAM with `,S,R`, OPEN, as for writing. OPEN returns C=0
204
+ whether or not the file exists.
205
+ 2. CHKIN with X = the logical file number. From here CHRIN reads from the
206
+ file.
207
+ 3. CHRIN, store the byte, then READST. Zero means more bytes follow.
208
+ `$40` means the byte just stored was the last one. Any other set bit
209
+ is an error: `$42` (read timeout plus EOI) is what a missing file
210
+ looks like, because the drive has nothing to say on that channel
211
+ (measured: `SCORES,S,R` with no such file gave OPEN C=0, CHKIN C=0
212
+ and ST = `$42` on the first CHRIN; VICE 3.10, empty disk).
213
+ 4. Bound the loop by your buffer size as well as by ST.
214
+ 5. CLRCHN, then CLOSE.
215
+
216
+ ```asm
217
+ // kernal_file_read_seq: read SCORES back until the drive signals EOI
218
+ .const SETLFS = $ffba
219
+ .const SETNAM = $ffbd
220
+ .const OPEN = $ffc0
221
+ .const CLOSE = $ffc3
222
+ .const CHKIN = $ffc6
223
+ .const CLRCHN = $ffcc
224
+ .const CHRIN = $ffcf
225
+ .const READST = $ffb7
226
+
227
+ * = $c000
228
+ read_scores:
229
+ lda #3
230
+ ldx #8
231
+ ldy #3
232
+ jsr SETLFS
233
+ lda #rname_end-rname
234
+ ldx #<rname
235
+ ldy #>rname
236
+ jsr SETNAM
237
+ jsr OPEN // C=0 even when the file is missing
238
+ bcs read_failed
239
+ ldx #3
240
+ jsr CHKIN // from here CHRIN reads from the file
241
+ bcs read_failed
242
+ ldy #0
243
+ get:
244
+ jsr CHRIN // one byte, blocking
245
+ sta buffer,y
246
+ iny
247
+ jsr READST // ST: 0 = more, $40 = that was the last
248
+ bne last_or_error
249
+ cpy #64 // never overrun the buffer
250
+ bne get
251
+ last_or_error:
252
+ sty count
253
+ and #$bf // anything but the EOI bit is an error
254
+ bne read_failed // ($42 = read timeout: no such file)
255
+ jsr CLRCHN
256
+ lda #3
257
+ jsr CLOSE
258
+ clc
259
+ rts
260
+ read_failed:
261
+ jsr CLRCHN
262
+ lda #3
263
+ jsr CLOSE
264
+ sec
265
+ rts
266
+
267
+ .encoding "petscii_upper"
268
+ rname: .text "SCORES,S,R"
269
+ rname_end:
270
+ count: .byte 0
271
+ buffer: .fill 64, 0
272
+ ```
273
+
274
+ ### Why it works
275
+
276
+ CHKIN sends TALK with `$60 | 3` and turns the bus round so the drive
277
+ drives DATA. Each CHRIN then runs IECIN, which receives one byte and
278
+ notices EOI, the talker's "this is the last byte" signal described under
279
+ EOI in `formats/iec-disk-reference.md`; IECIN records it as bit 6 of
280
+ `$90`. READST returns `$90` unchanged (ROM `$FE1A`: it reads and rewrites
281
+ the byte; the read-and-clear applies only to the RS-232 status at
282
+ `$0297`). The byte is zeroed elsewhere in the ROM: `LDA #0 / STA $90`
283
+ sits at `$F30F`, `$F3DD` and `$F4A7` (rung 1, byte search of
284
+ kernal-901227-03), which is why a fresh OPEN or LOAD starts from ST = 0;
285
+ which routine owns each of those three was not traced here. The recipe's
286
+ readback of a 32-byte file
287
+ returned 32 bytes with ST = `$40` on the thirty-second (measured). The
288
+ status-byte table and the carry-versus-READST explanation are in
289
+ `hardware/kernal-routines-reference.md` under READST and "Status-byte
290
+ interaction with file I/O".
291
+
292
+ ### Variations
293
+
294
+ - **GETIN instead of CHRIN.** On a serial channel GETIN goes through the
295
+ same path; use CHRIN, which is the documented blocking read.
296
+ - **Counted reads.** If the writer stored a length first, read it and
297
+ loop on the count; still test READST each time so a short file cannot
298
+ hang the loop.
299
+ - **Two channels at once.** A second file on secondary 4 with its own
300
+ logical number can stay open while this one reads; switch with
301
+ CHKIN/CHKOUT and CLRCHN between them.
302
+
303
+ ### Recipes
304
+
305
+ - `recipes/kickassembler/file-io-roundtrip.md`
306
+ - `recipes/oscar64/save-load-seq-file.md` (the same sequence through Oscar64's kernalio.h, with a provoked 62)
307
+ - `recipes/oscar64/high-score-persist.md` (the policy around the calls: first run, scratch-then-write, version byte, no drive; 74 and the scratch reply measured)
308
+
309
+ ---
310
+
311
+ ## error_channel_check — Read the drive's status line from channel 15
312
+
313
+ **Complexity:** low
314
+ **Region:** both
315
+ **Uses registers:** (none)
316
+ **Uses kernal:** SETLFS, SETNAM, OPEN, CHKIN, CHRIN, READST, CLRCHN, CLOSE
317
+
318
+ ### Why
319
+
320
+ The drive keeps one status line and the KERNAL never reads it for you.
321
+ A disk OPEN returns C=0 for a name that is not there and for a write to
322
+ a name that is; LOAD reports a missing file as A=4 but says nothing
323
+ about a full or protected disk. The status line is the only place these
324
+ answers live, so a program reads it after any operation whose outcome it
325
+ cares about.
326
+
327
+ ### How
328
+
329
+ 1. SETLFS with logical file 15, device 8, secondary 15. SETNAM with
330
+ length 0: opening the channel bare sends no command.
331
+ 2. OPEN. Here C=1 with A=5 does mean something: no device answered.
332
+ 3. CHKIN 15, then CHRIN/READST as in kernal_file_read_seq. The line ends
333
+ with a CR and the drive raises EOI on that CR. The longest line seen
334
+ in these runs was 27 bytes including the CR; 40 is a comfortable
335
+ buffer (every message in the ROM's table, read from the drive ROM on this
336
+ machine, is in `../formats/iec-disk-reference.md`, "The 1541 DOS Error
337
+ Codes").
338
+ 4. CLRCHN, CLOSE 15. Reading the line is what resets it to `00, OK,00,00`
339
+ (the DOS rule; not measured separately here).
340
+
341
+ ```asm
342
+ // error_channel_check: read the drive's status line into stbuf
343
+ .const SETLFS = $ffba
344
+ .const SETNAM = $ffbd
345
+ .const OPEN = $ffc0
346
+ .const CLOSE = $ffc3
347
+ .const CHKIN = $ffc6
348
+ .const CLRCHN = $ffcc
349
+ .const CHRIN = $ffcf
350
+ .const READST = $ffb7
351
+
352
+ * = $c000
353
+ read_status:
354
+ lda #15
355
+ ldx #8
356
+ ldy #15 // secondary 15 = command channel
357
+ jsr SETLFS
358
+ lda #0 // no filename: open the channel bare
359
+ jsr SETNAM
360
+ jsr OPEN
361
+ bcs no_drive // C=1, A=5: nothing answered on the bus
362
+ ldx #15
363
+ jsr CHKIN
364
+ bcs no_drive
365
+ ldy #0
366
+ get:
367
+ jsr CHRIN
368
+ sta stbuf,y
369
+ iny
370
+ jsr READST
371
+ bne done // EOI comes with the closing CR
372
+ cpy #39
373
+ bne get
374
+ done:
375
+ sty stlen
376
+ jsr CLRCHN
377
+ lda #15
378
+ jsr CLOSE
379
+ lda stbuf // first digit of the code, PETSCII
380
+ cmp #$31 // C=0 for 00 OK and 01 FILES SCRATCHED,
381
+ rts // C=1 for anything from 20 upwards
382
+ no_drive:
383
+ jsr CLRCHN
384
+ lda #15
385
+ jsr CLOSE
386
+ sec
387
+ rts
388
+
389
+ stlen: .byte 0
390
+ stbuf: .fill 40, 0 // "62, FILE NOT FOUND,00,00" + CR fits
391
+ ```
392
+
393
+ The line is `code,message,track,sector` in PETSCII with the two-digit
394
+ code first, so the first byte alone tells you whether to look further.
395
+
396
+ ### When to read it
397
+
398
+ - **Once at start, to clear the power-on message.** The first read after
399
+ the drive resets returns `73,CBM DOS V2.6 1541,00,00` (measured; VICE's
400
+ 1541-II ROM). A program that treats any non-zero code as failure will
401
+ refuse a healthy drive unless it reads this line first.
402
+ - **After CLOSE of a written file**, which is when a full disk or a
403
+ refused name shows up.
404
+ - **After an OPEN for read** whose CHRIN loop ended with ST other than
405
+ `$40`.
406
+ - **After a LOAD that returned C=1**, to tell 62 from a disk error.
407
+
408
+ ### The codes an agent meets first
409
+
410
+ Measured in this page's runs: `00, OK,00,00` after a clean write and
411
+ read; `73,CBM DOS V2.6 1541,00,00` on the first read; `62, FILE NOT
412
+ FOUND,00,00` after LOAD of a name that was not on the disk (LOAD itself
413
+ returned C=1, A=4); `63, FILE EXISTS,00,00` after OPEN of `,P,W` on an
414
+ existing name (OPEN itself returned C=0).
415
+
416
+ Measured since, in `../recipes/oscar64/high-score-persist.md`: `74,DRIVE
417
+ NOT READY,00,00` with no disk attached and `01, FILES SCRATCHED,01,00`
418
+ after a scratch, where the track field is the count. Not measured here,
419
+ from the 1541 DOS message set (rung 4): `26, WRITE PROTECT ON` when the
420
+ notch is covered; `72, DISK FULL` when the last block goes. Codes 20 to
421
+ 29 are read or write errors with the track and sector filled in. The
422
+ full table of codes, message text as the ROM spells it, causes and an
423
+ agent's class for each (retry, media, user error, program bug), read
424
+ from the 1541 ROM with four codes provoked in VICE, is
425
+ `../formats/iec-disk-reference.md`, "The 1541 DOS Error Codes", with
426
+ the recipe `../recipes/kickassembler/dos-error-codes.md`.
427
+
428
+ ### Variations
429
+
430
+ - **Send a command and read the answer in one open.** SETNAM the command
431
+ (`S0:NAME`, `I0`, `V0`), OPEN, then CHKIN and read: the reply to that
432
+ command comes back on the same channel.
433
+ - **Keep channel 15 open for the program's life** and only CHKIN it when
434
+ needed. This holds one of the KERNAL's ten table slots and one of the
435
+ drive's channels, which is fine for a game.
436
+ - **Hang on a missing drive.** CHKIN on a device that does not answer
437
+ hangs with no timeout, per `hardware/kernal-routines-reference.md`;
438
+ the OPEN's C=1, A=5 return is the last chance to notice, so test it.
439
+
440
+ ### Recipes
441
+
442
+ - `recipes/kickassembler/file-io-roundtrip.md`
443
+ - `recipes/oscar64/save-load-seq-file.md` (the same sequence through Oscar64's kernalio.h, with a provoked 62)
444
+ - `recipes/oscar64/high-score-persist.md` (the policy around the calls: first run, scratch-then-write, version byte, no drive; 74 and the scratch reply measured)
445
+ - `recipes/oscar64/relative-file-records.md` (the P command's 50 reply read next to the KERNAL status byte; 51 in a side run)
446
+
447
+ ---
448
+
449
+ ## directory_read_and_select — Read the disk directory into a table and pick an entry
450
+
451
+ **Complexity:** low
452
+ **Region:** both
453
+ **Uses registers:** (none)
454
+ **Uses kernal:** SETLFS, SETNAM, OPEN, CHKIN, CHRIN, CLRCHN, CLOSE, READST
455
+ **Requires:** kernal_file_read_seq
456
+ **Cost:** bytes_data=1539
457
+ **Cost basis:** derived-listing
458
+
459
+ ### Why
460
+
461
+ A loader menu, a level chooser or a save slot picker needs to know
462
+ what is on the disk. The DOS will tell you: the name `$` opens a
463
+ listing of the directory as if it were a file, and the same CHKIN and
464
+ CHRIN loop that reads a SEQ file reads it. What comes back is not
465
+ text. It is a BASIC program image, with link bytes and line numbers,
466
+ and the block counts live in the line numbers. A parser that knows
467
+ that turns the stream into a table in one pass, and a highlight moved
468
+ by the joystick over that table is the menu.
469
+
470
+ The trap that makes this worth an entry: `LOAD "$",8` from BASIC puts
471
+ that program image where the BASIC program was, so a BASIC program
472
+ that lists the directory that way has replaced itself. Machine code
473
+ and C programs never meet the trap, because OPEN and CHRIN load
474
+ nothing; the bytes go only where the program puts them.
475
+
476
+ ### How
477
+
478
+ 1. SETLFS with your logical file number, device 8 and secondary
479
+ address 0. SETNAM with the one byte `$`. OPEN. Test the carry:
480
+ C=1 with A=5 is device not present.
481
+ 2. CHKIN with X = the logical file number. From here CHRIN reads the
482
+ listing.
483
+ 3. The first two bytes are a load address (`$0401`). Skip them.
484
+ 4. Each line is two link bytes, a two-byte line number low byte first,
485
+ PETSCII text and a zero. Two zero link bytes end the listing. The
486
+ link bytes carry nothing; skip them. The line number is the drive
487
+ number on the first line, the block count on every entry, and the
488
+ free block count on the last line.
489
+ 5. The first line is the header: `$12` (reverse on), the disk name in
490
+ quotes padded to sixteen characters with `$A0`, then the id. An
491
+ entry's text is spaces, the name in quotes, spaces, an optional `*`
492
+ (a file never closed), the three type letters `PRG`, `SEQ`, `REL`,
493
+ `USR` or `DEL`, and an optional `<` (locked). The last line has no
494
+ quotes and reads `BLOCKS FREE.`
495
+ 6. Call READST after every CHRIN. Zero means more; `$40` arrives with
496
+ the last byte; anything else is an error and the loop stops.
497
+ 7. CLRCHN, then CLOSE. Do this before the next file operation, since
498
+ the drive keeps the directory channel until the listing is consumed
499
+ or closed.
500
+ 8. For the selector: draw one row per entry, keep an index, move it on
501
+ joystick up and down edges, act on fire. Reprint the old row plain
502
+ and the new row in reverse video rather than redrawing the table.
503
+
504
+ ```asm
505
+ // directory_read_and_select: stream "$" through CHRIN into a table of
506
+ // name, block count and type, one entry per line, no buffer for the listing
507
+ .const SETLFS = $ffba
508
+ .const SETNAM = $ffbd
509
+ .const OPEN = $ffc0
510
+ .const CLOSE = $ffc3
511
+ .const CHKIN = $ffc6
512
+ .const CLRCHN = $ffcc
513
+ .const CHRIN = $ffcf
514
+ .const READST = $ffb7
515
+ .const MAXENT = 16 // entries the table holds
516
+ .const ENTLEN = 20 // 16 name, 1 zero, 2 blocks, 1 type
517
+ .const ptr = $fb // zero page pair for (ptr),y
518
+
519
+ * = $c000
520
+ read_dir:
521
+ lda #0
522
+ sta count
523
+ sta line
524
+ sta status
525
+ lda #2
526
+ ldx #8
527
+ ldy #0 // secondary 0: the load channel
528
+ jsr SETLFS
529
+ lda #1
530
+ ldx #<dollar
531
+ ldy #>dollar
532
+ jsr SETNAM
533
+ jsr OPEN
534
+ bcc opened
535
+ jmp dir_failed
536
+ opened:
537
+ ldx #2
538
+ jsr CHKIN
539
+ bcc reading
540
+ jmp dir_failed
541
+ reading:
542
+ jsr get // load address, two bytes, unused
543
+ jsr get
544
+ next_line:
545
+ jsr get // link low
546
+ sta tmp
547
+ jsr get // link high
548
+ ora tmp
549
+ beq dir_done // a zero link ends the listing
550
+ jsr get // line number = blocks (or free count)
551
+ sta blocks
552
+ jsr get
553
+ sta blocks+1
554
+ lda line
555
+ beq skip_text // line 0 is the disk header
556
+ ldy #0
557
+ find_quote:
558
+ jsr get
559
+ beq end_of_line // no quote at all: BLOCKS FREE line
560
+ cmp #$22
561
+ bne find_quote
562
+ ldx count
563
+ cpx #MAXENT
564
+ bcs skip_text // table full: drain the rest
565
+ jsr entry_ptr // ptr = table + count*ENTLEN
566
+ name_char:
567
+ jsr get
568
+ beq end_of_line
569
+ cmp #$22
570
+ beq name_done
571
+ sta (ptr),y
572
+ iny
573
+ cpy #16
574
+ bne name_char
575
+ jsr get // a full 16-byte name: swallow its closing quote
576
+ name_done:
577
+ lda #0
578
+ sta (ptr),y // terminate the name
579
+ ldy #17
580
+ lda blocks
581
+ sta (ptr),y
582
+ lda blocks+1
583
+ iny
584
+ sta (ptr),y
585
+ type_char:
586
+ jsr get // spaces, maybe '*', then P/S/R/U/D
587
+ beq end_of_line
588
+ cmp #' '
589
+ beq type_char
590
+ cmp #'*'
591
+ beq type_char // a splat: the type letter follows
592
+ ldy #19
593
+ sta (ptr),y // first letter of the type
594
+ inc count
595
+ skip_text:
596
+ jsr get // drain to the line's zero
597
+ bne skip_text
598
+ end_of_line:
599
+ inc line
600
+ lda status
601
+ beq next_line // ST = 0: more bytes follow
602
+ dir_done:
603
+ lda blocks
604
+ sta free
605
+ lda blocks+1
606
+ sta free+1 // the last line number is BLOCKS FREE
607
+ jsr CLRCHN
608
+ lda #2
609
+ jsr CLOSE
610
+ clc
611
+ rts
612
+ dir_failed:
613
+ jsr CLRCHN
614
+ lda #2
615
+ jsr CLOSE
616
+ sec
617
+ rts
618
+
619
+ // one byte from the channel; Z reflects the byte, status holds ST
620
+ get:
621
+ jsr CHRIN
622
+ sta byte
623
+ jsr READST
624
+ sta status
625
+ lda byte
626
+ rts
627
+
628
+ entry_ptr:
629
+ lda #<table
630
+ sta ptr
631
+ lda #>table
632
+ sta ptr+1
633
+ txa
634
+ beq ptr_done
635
+ mul:
636
+ lda ptr
637
+ clc
638
+ adc #ENTLEN
639
+ sta ptr
640
+ bcc no_carry
641
+ inc ptr+1
642
+ no_carry:
643
+ dex
644
+ bne mul
645
+ ptr_done:
646
+ rts
647
+
648
+ .encoding "petscii_upper"
649
+ dollar: .text "$"
650
+ count: .byte 0
651
+ line: .byte 0
652
+ status: .byte 0
653
+ byte: .byte 0
654
+ tmp: .byte 0
655
+ blocks: .word 0
656
+ free: .word 0
657
+ table: .fill MAXENT*ENTLEN, 0
658
+ ```
659
+
660
+ Assembled and run here from BASIC (`LOAD "DIRTECH",8,1` then
661
+ `SYS 49152`) against a disk holding three files and itself: `count`
662
+ came back 4, `status` `$40` and `free` 655, the figures `c1541 -list`
663
+ printed for the same disk. A second run against a disk holding a
664
+ 300-byte SEQ file with a sixteen-character name and the routine itself
665
+ read the table back with PEEK: `count` 2, `status` `$40`, the first
666
+ entry's name bytes `A` to `P` with a zero after them, its block count 2
667
+ and its type letter `S`, the second entry's type `P`. An earlier draft
668
+ of the name loop left the closing quote unread after a full
669
+ sixteen-byte name and stored it as the type; the `jsr get` after the
670
+ loop is what that run checks.
671
+
672
+ ### Why it works
673
+
674
+ The 1541 DOS recognises `$` at OPEN and builds the listing block by
675
+ block from the directory sectors on track 18, sending it on the data
676
+ channel with EOI on the last byte, which is why READST reads `$40`
677
+ there and nowhere earlier. The image is a BASIC program so that
678
+ `LOAD "$",8` followed by `LIST` shows it without any code on the C64
679
+ side. That convenience is the whole reason for the load address, the
680
+ link bytes and the line numbers, and a parser that reads it as a file
681
+ just has to know the shape. The block count sits in the line number
682
+ because BASIC prints line numbers in decimal for free; the DOS never
683
+ writes it into the text. Each entry costs 32 bytes on the wire (link,
684
+ number, up to 27 bytes of text, zero), so a directory of `n` files is
685
+ about `32 * (n + 2)` bytes, 160 for three files (measured, the recipe
686
+ below).
687
+
688
+ ### Variations
689
+
690
+ - **More entries than the screen.** A 1541 directory holds up to 144
691
+ entries and a 25-row screen shows about 22. Keep the whole table
692
+ (144 entries of 20 bytes is under 3 KB) and draw a window of it,
693
+ moving the window when the index leaves it. The parse is one pass
694
+ regardless.
695
+ - **Only PRG files.** Test the first type letter and skip an entry
696
+ whose type is not `P`; the table stays small and the menu shows only
697
+ what the loader can use. Skip splat files too, or mark them.
698
+ - **Filtered by the drive.** `$:NAME*` or `$:*=P` asks the DOS to
699
+ filter by pattern or type before sending, so the C64 receives only
700
+ the matching lines. Not measured here.
701
+ - **The LOAD "$" trap from BASIC.** A BASIC program that wants a
702
+ directory opens `$` with `OPEN 2,8,0,"$"` and reads it with `GET#2`,
703
+ skipping the link and number bytes as above; it never uses LOAD for
704
+ it. The DOS wedge's `@$` does the same read.
705
+ - **Streaming or buffered.** The listing above parses straight from
706
+ CHRIN and needs no buffer. The recipe buffers the stream first so the
707
+ parse can be timed apart from the transfer and the channel is closed
708
+ before any screen output; either shape gives the same table.
709
+
710
+ ### Cycle budget
711
+
712
+ Measured in VICE 3.10 x64sc with a true-drive 1541 and wobble off, on
713
+ the recipe below, CIA2 timers chained: the transfer of a 160-byte
714
+ listing (OPEN, CHKIN, 160 CHRIN and READST pairs, CLRCHN, CLOSE) took
715
+ 515,635 cycles on PAL and 535,744 on NTSC, about 3,200 cycles a byte.
716
+ The parse of the buffered 160 bytes took 13,821 cycles on PAL and
717
+ 14,251 on NTSC, about 86 cycles a byte. Both are one-off costs paid
718
+ when the menu is built, not per frame, so the Cost line carries no
719
+ `cycles_per_frame`; both timings read the chained counter unlatched,
720
+ so each is good to within a few hundred cycles rather than to the
721
+ cycle. The Cost line's `bytes_data` is the recipe's own data: the
722
+ 1,024-byte listing buffer, the 411-byte table, the 40-byte reply
723
+ buffer and the 64-byte write block; the linker map's data and BSS
724
+ segments together come to 1,620 bytes with the runtime's own. By
725
+ arithmetic from those rates, a full 144-entry directory of about
726
+ 4,700 bytes would take some 15 million cycles to transfer and 400,000
727
+ to parse. The transfer runs with interrupts off inside the KERNAL, so
728
+ a raster IRQ misses most frames during it
729
+ (`raster_irq_during_serial_io`).
730
+
731
+ ### Recipes
732
+
733
+ - `recipes/oscar64/directory-reader.md` (three files written, the table parsed and checked, a joystick selector with an autopilot pick; transfer and parse timed)
734
+
735
+ ---
736
+
737
+ ## kernal_load_to_address — LOAD a raw asset to an address of your choosing
738
+
739
+ **Complexity:** low
740
+ **Region:** both
741
+ **Uses registers:** (none)
742
+ **Uses kernal:** SETLFS, SETNAM, LOAD, SETMSG
743
+
744
+ ### Why
745
+
746
+ Level data, a character set or a music file arrives as a PRG on the
747
+ disk: two bytes of load address, then the bytes. Sometimes the program
748
+ wants it where the file says; sometimes it wants it somewhere else, for
749
+ example the same tile file into whichever of two buffers is free. LOAD
750
+ does both, and which one is chosen by the secondary address given to
751
+ SETLFS, not by a LOAD argument.
752
+
753
+ ### How
754
+
755
+ 1. SETLFS with any logical file number, X = 8, and Y = 0 or 1. With
756
+ Y = 0 the address you pass in X/Y to LOAD is used and the file's own
757
+ header is discarded. With Y = 1 the header decides and LOAD's X/Y are
758
+ ignored.
759
+ 2. SETNAM with the bare name. No `,P,R` is needed: the drive assumes PRG
760
+ for a load.
761
+ 3. A = 0 for load (1 verifies against memory instead). X/Y = the
762
+ destination when the secondary address is 0.
763
+ 4. JSR LOAD. On return C=0 and X/Y hold the address one past the last
764
+ byte written, which is how to learn the file's length. C=1 with A=4
765
+ is file not found, 5 no device, and A=0 means RUN/STOP was pressed.
766
+
767
+ ```asm
768
+ // kernal_load_to_address: pull a raw asset in at an address of our choosing
769
+ .const SETLFS = $ffba
770
+ .const SETNAM = $ffbd
771
+ .const LOAD = $ffd5
772
+ .const SETMSG = $ff90
773
+
774
+ * = $c000
775
+ load_tiles:
776
+ lda #0
777
+ jsr SETMSG // no SEARCHING FOR / LOADING on screen
778
+ lda #1 // logical file number: LOAD ignores it
779
+ ldx #8
780
+ ldy #0 // secondary 0: OUR address wins
781
+ jsr SETLFS
782
+ lda #tname_end-tname
783
+ ldx #<tname
784
+ ldy #>tname
785
+ jsr SETNAM
786
+ lda #0 // 0 = load, 1 = verify
787
+ ldx #<$6000 // destination, used only with secondary 0
788
+ ldy #>$6000
789
+ jsr LOAD
790
+ bcs load_failed // C=1: A=4 file not found, 5 no device,
791
+ stx end_lo // 0 RUN/STOP; X/Y = end address + 1
792
+ sty end_hi
793
+ clc
794
+ rts
795
+ load_failed:
796
+ sec
797
+ rts
798
+
799
+ load_where_saved:
800
+ lda #1
801
+ ldx #8
802
+ ldy #1 // secondary 1: the file's own header wins
803
+ jsr SETLFS
804
+ lda #tname_end-tname
805
+ ldx #<tname
806
+ ldy #>tname
807
+ jsr SETNAM
808
+ lda #0
809
+ jsr LOAD // X/Y are ignored here
810
+ rts
811
+
812
+ .encoding "petscii_upper"
813
+ tname: .text "TILES" // a PRG: two-byte load address, then data
814
+ tname_end:
815
+ end_lo: .byte 0
816
+ end_hi: .byte 0
817
+ ```
818
+
819
+ Measured (VICE 3.10, 1541 true drive): a 16-byte PRG saved from `$5000`
820
+ loaded with secondary 0 and X/Y = `$6000` returned C=0 with X/Y =
821
+ `$6010` and the 16 bytes at `$6000`; the same file loaded with secondary
822
+ 1 and X/Y set to `$1234` returned X/Y = `$5010` with the bytes back at
823
+ `$5000`; a name not on the disk returned C=1, A=`$04` and the error
824
+ channel then read `62, FILE NOT FOUND,00,00`.
825
+
826
+ ### What the KERNAL prints
827
+
828
+ LOAD writes `SEARCHING FOR name` and `LOADING` to the screen only when
829
+ bit 7 of the message flag at `$9D` is set. BASIC sets that bit in direct
830
+ mode and clears it while a program runs, so a LOAD called from a program
831
+ started with RUN or a `SYS` line prints nothing: `$9D` read `$00` at
832
+ entry to the test program and the first LOAD left the screen alone
833
+ (measured). After `SETMSG` with A = `$80` the same LOAD printed a blank
834
+ line, `SEARCHING FOR BLOB` on the next, and `LOADING` with no carriage
835
+ return after it, so the program's next CHROUT landed on the same line
836
+ as `LOADING` (measured). Call SETMSG with A = 0 before loading from a game screen, or
837
+ save and restore `$9D` around the call if a BASIC front end expects it.
838
+
839
+ ### Why it works
840
+
841
+ The serial LOAD reads the two header bytes first. With secondary 0 the
842
+ KERNAL substitutes the caller's X/Y; with secondary 1 it keeps them. Either way it then stores every following byte through
843
+ the zero-page pointer `$AE/$AF`, which is why a load lands in RAM under
844
+ BASIC or KERNAL ROM but into the chip registers at `$D000-$DFFF`, both
845
+ measured in `hardware/kernal-routines-reference.md` under LOAD. The
846
+ end address returned in X/Y is that pointer after the last store.
847
+
848
+ ### Variations
849
+
850
+ - **Verify.** A = 1 compares the file against memory and sets bit 4 of
851
+ ST on a mismatch; the memory is not written.
852
+ - **Load into colour RAM.** Secondary 0 with X/Y = `$D800` writes the
853
+ colour nybbles straight in; the reference's LOAD entry shows why this
854
+ works and why `$D000-$DFFF` in general is a trap.
855
+ - **Chained loads under an IRQ.** LOAD, like OPEN and the channel
856
+ calls, does not respect a caller's SEI; the `kernal_assumes_sei_cleared`
857
+ pitfall covers what that does to a raster IRQ. Measured on LOAD
858
+ itself in `recipes/oscar64/load-asset-runtime.md`: entered with the
859
+ I flag set, LOAD returned with it clear, and a `rasterirq.h` split
860
+ armed across a 2 KB load entered in 222 of 283 PAL frames, as late
861
+ as line 170; the screen was not blanked (`$D011` bit 4 still set,
862
+ text drawn in a mid-transfer picture). `raster_irq_during_serial_io`
863
+ has the fix, which is to clear `$D01A` around the call.
864
+ - **From Oscar64.** `krnio_load(fnum, device, channel)` in
865
+ `kernalio.c` passes X = Y = 0 to LOAD, so with secondary 0 it loads
866
+ to `$0000`; it is only useful with secondary 1. To choose the address
867
+ from C, call SETLFS, SETNAM and LOAD from inline assembly as the
868
+ recipe below does. `krnio_save(device, start, end)` writes the
869
+ header from `start`, so a file saved from a buffer LOADs back to that
870
+ buffer with secondary 1 and anywhere with secondary 0.
871
+
872
+ ### Recipes
873
+
874
+ - `recipes/kickassembler/file-io-roundtrip.md` (write and read side;
875
+ the LOAD measurements above came from a scratch program that is not a
876
+ recipe)
877
+ - `recipes/oscar64/load-asset-runtime.md` (a 2 KB charset built in
878
+ RAM, saved as a PRG on the first run, loaded with secondary 0 to
879
+ `$3800` and shown; the I flag, the raster IRQ and `$D011` measured
880
+ across the LOAD)
881
+
882
+ ---
883
+
884
+ ## kernal_relative_file_io — Read and write one record of a relative file by number
885
+
886
+ **Complexity:** medium
887
+ **Region:** both
888
+ **Uses registers:** (none)
889
+ **Uses kernal:** SETLFS, SETNAM, OPEN, CHKOUT, CHROUT, CHKIN, CHRIN, READST, CLRCHN, CLOSE
890
+
891
+ ### Why
892
+
893
+ A sequential file gives back its bytes from the start, every time. A
894
+ game with eight save slots, a level bank of sixty rooms or a table of
895
+ player names wants slot 5 or room 42 without streaming everything before
896
+ it, and wants to overwrite one entry without rewriting the file. The
897
+ 1541's relative file (directory type `REL`) does that: fixed-length
898
+ records, addressed by number, positioned by a command on channel 15,
899
+ and the same KERNAL calls as a sequential file for the bytes themselves.
900
+ The drive does the seeking; the C64 side sends five bytes and reads a
901
+ reply.
902
+
903
+ ### How
904
+
905
+ 1. SETLFS with a logical file number, device 8 and a secondary address
906
+ from 2 to 14; SETNAM with the name followed by `,L,` and **one more
907
+ byte, the record length** (1 to 254). The length byte is binary, not
908
+ a digit, so the name is set by length and address rather than as a
909
+ text string. OPEN. On a fresh disk this creates the file; the drive
910
+ answers `00, OK` on channel 15 and the directory shows a `REL` entry.
911
+ 2. Open the command channel (secondary 15, empty name) before the data
912
+ file or after it, and **keep it open until the data file is closed**:
913
+ CLOSE of secondary 15 makes the drive close every channel it has, the
914
+ REL file included.
915
+ 3. To reach a record, send the P command on channel 15: CHKOUT 15, then
916
+ CHROUT of the five bytes `P`, the data channel's secondary address
917
+ plus 96, the record number low byte, the record number high byte,
918
+ and the byte offset inside the record, then CLRCHN. Records and
919
+ offsets count from 1. Then read the reply from channel 15 as
920
+ error_channel_check does: `00` means the record exists, `50, RECORD
921
+ NOT PRESENT` means it does not yet.
922
+ 4. Read: CHKIN the data file, CHRIN and READST until EOF, CLRCHN. The
923
+ drive sends the record's bytes up to the last non-zero one and raises
924
+ EOI there, so a record padded with zeros comes back short.
925
+ 5. Write: CHKOUT the data file, CHROUT up to the record length, CLRCHN.
926
+ The bytes go when the KERNAL sends EOI on the last one, and the
927
+ drive pads the rest of the record with zeros. More bytes than the
928
+ record length answer `51,OVERFLOW IN RECORD` on channel 15 and the
929
+ KERNAL status stays `$00`.
930
+ 6. Send a P before every read and every write. The pointer moves as
931
+ bytes move, so a second read without a P starts on the next record.
932
+ 7. CLOSE the data file, then CLOSE 15.
933
+
934
+ Measured in VICE x64sc 3.10 with the recipe below, a 32-byte record
935
+ length on a fresh disk (rung 1): after the OPEN that created the file
936
+ the disk was two blocks shorter, one data block and one side sector;
937
+ positioning on records 1 to 7 answered `00` before any of them was
938
+ written, and a read of an unwritten one returned a single `$FF` with
939
+ EOF; positioning on record 8, 9 or 20 answered `50, RECORD NOT
940
+ PRESENT,00,00`, and a read after that reply returned a single `$0D`
941
+ with EOF and left the status at `50`; a 32-byte write to record 9 after
942
+ the `50` answered `00, OK`, grew the file by one block, and the next P
943
+ to record 9 answered `00` with all 32 bytes reading back. Seven 32-byte
944
+ records are 224 bytes and an eighth would cross the 254 data bytes of a
945
+ block, so the boundary is the end of the first data block; that the
946
+ open allocates that block and marks each record in it with `$FF` is
947
+ inference from those two measurements (rung 3). The DOS also reports
948
+ `50` when a write extends the file, per the ROM's call sites in
949
+ `../formats/iec-disk-reference.md`; in these runs the write itself
950
+ answered `00` and only the P before it said `50`. `50` and `51` never
951
+ reached the KERNAL's status byte, which read `$00` after every write
952
+ and `$40` after every read; only channel 15 knows. A side run with a
953
+ scratch program, not the recipe, checked steps 3, 4 and 6 (rung 1): a
954
+ record written as ten letters and 22 zeros read back as ten bytes; a P
955
+ with offset 5 read six bytes from the fifth letter, and offset 0
956
+ behaved as 1; a read with no P after a full record returned a single
957
+ byte, the next record's.
958
+
959
+ ### Why it works
960
+
961
+ The drive holds a record pointer per open relative file and a set of
962
+ side sectors, blocks that list the track and sector of every data block
963
+ in the file in order. A P command is arithmetic on the drive: record
964
+ number times record length gives a byte offset, the side sector turns
965
+ that into a data block, and the drive seeks straight to it. Extending
966
+ the file is where the cost lives: a write past the end allocates the
967
+ data blocks up to and including the new record, plus a side sector for
968
+ every 120 data blocks, and every new record starts with `$FF`. The
969
+ side sectors are why a REL costs one block more than the data on
970
+ creation and why creating a large file takes long. The figure of one
971
+ side sector per 120 data blocks was the format's stated rule when this
972
+ was first written; it has since been read off a disk image, a
973
+ one-byte write to record 125 of a 254-byte-record file allocating 125
974
+ data blocks and two side sectors of 120 and 5 entries
975
+ (`../formats/c64-file-formats.md`, "REL file", rung 1).
976
+
977
+ `c1541 -write` can create a REL entry but cannot lay out records. The
978
+ record length goes on the end of the name as one byte, so `c1541
979
+ -write blob.bin "blob,l, "` (the trailing space is byte 32) printed
980
+ `Open new REL file 'BLOB' with record length 32 on channel 1.`, and
981
+ the directory then showed `blob` as `rel` with 662 blocks free. But
982
+ c1541 streams the whole file as one record: a 64-byte blob drew `ERR =
983
+ 51, OVERFLOW IN RECORD, 00, 00` once for each byte past the
984
+ thirty-second, so at most record 1 is filled. Without the length byte,
985
+ `c1541 -write blob.bin "blob,l"` answered `Open non-existing REL file
986
+ 'BLOB' with unspecified record length on channel 1.` and `floppy write
987
+ failed`, and the directory gained no entry (both rung 1, c1541 3.10).
988
+
989
+ The KERNAL knows none of this. SETLFS, SETNAM, OPEN and the CHKIN,
990
+ CHKOUT, CHRIN, CHROUT, CLRCHN, CLOSE pairs behave exactly as in
991
+ kernal_file_write_seq and kernal_file_read_seq, and everything on this
992
+ page about interrupts, bus pace and the status byte applies: the calls
993
+ re-enable interrupts (`kernal_assumes_sei_cleared`), a raster IRQ armed
994
+ across them misses most frames (`raster_irq_during_serial_io`), and
995
+ they need the KERNAL ROM in (`kernal_io_mapping_dependency`). In
996
+ assembly, the P command is five CHROUT calls, and CHROUT and CHKOUT
997
+ keep none of A, X or Y, so a record number held in X or Y across them
998
+ is gone by the second byte (`kernal_clobbers_a_x_y`); keep it in
999
+ memory and load each byte fresh.
1000
+
1001
+ ### When a REL beats a SEQ
1002
+
1003
+ - **Random access with rewrites in place.** Save slot 3 of 8, room 42
1004
+ of 60, the name at table entry 17: one P and one read or write, no
1005
+ rewrite of the neighbours. A SEQ needs the whole file read into RAM
1006
+ and written back.
1007
+ - **Not for streaming.** A level that is always read whole is a SEQ or
1008
+ a LOAD; the REL's side sector and its per-record P are a cost that
1009
+ gives nothing back.
1010
+ - **Not for assets shipped on the disk image.** `c1541 -write` can
1011
+ create the REL entry but fills at most record 1 (measured above), so
1012
+ the program should create and fill the REL on first run; a shipped
1013
+ table is a SEQ or PRG the program copies into a REL if it wants one.
1014
+
1015
+ ### Variations
1016
+
1017
+ - **Record length 254 or less.** Choose a length that divides 254 with
1018
+ no remainder (2, 127, 254) or accept that records straddle blocks,
1019
+ which the drive handles and the program never sees.
1020
+ - **Position on a byte inside the record.** The fifth byte of the P
1021
+ command is a 1-based offset (measured above on a read); a P with
1022
+ offset 17 followed by a write of four bytes changes only bytes 17 to
1023
+ 20 (the DOS rule; the write at an offset was not measured here).
1024
+ - **Pre-size the file.** Position on the highest record you will use
1025
+ and write one byte to it once, at first run, so later writes never
1026
+ extend the file mid-game and pay the block allocation then. The
1027
+ extension cost itself was not measured here.
1028
+ - **Ask before reading.** The `50` reply to a P is the cheap way to know
1029
+ a slot is empty: no read, no `$0D`, and no need to reserve a byte in
1030
+ the record as an "in use" flag.
1031
+
1032
+ ### Recipes
1033
+
1034
+ - `recipes/oscar64/relative-file-records.md` (create, write 1, 3 and 5, read back with a checksum, the `50` on record 9 and the write that clears it; `51` in a side run)
1035
+ - `recipes/oscar64/rel-side-sectors.md` (100-byte records across four blocks, then the directory entry and the side sector read back through `U1`; the bytes are decoded in `../formats/c64-file-formats.md`, "REL file")
1036
+
1037
+ ---
1038
+
1039
+ ## cartridge_save — Save game data to the cartridge: EasyFlash flash sectors, GMod2 serial EEPROM
1040
+
1041
+ **Complexity:** medium
1042
+ **Region:** both
1043
+ **Uses registers:** DE00, DE02
1044
+ **Requires:** cartridge_bank_easyflash
1045
+ **Demands:** kernal_rom_out
1046
+
1047
+ ### Why
1048
+
1049
+ A game shipped on a cartridge may run on a C64 with no disk drive. Even
1050
+ when there is one, the disk in it is not the game's. High scores and
1051
+ save games therefore go into the cartridge. Two cartridge types built for
1052
+ this are in common use. EasyFlash writes to its own flash chips.
1053
+ GMod2 writes to a small serial EEPROM beside its flash. The C64 cannot
1054
+ write to ROM, so both need their own write protocol, and both are slow
1055
+ compared with a RAM store. This technique describes both and when to
1056
+ pick which.
1057
+
1058
+ ### How: EasyFlash
1059
+
1060
+ EasyFlash holds two 512 KB Am29F040-type flash chips, one behind ROML
1061
+ (`$8000`) and one behind ROMH. Each 8 KB bank register value
1062
+ (`$DE00`) selects the upper chip address lines. Three rules decide
1063
+ everything.
1064
+
1065
+ 1. **Erase is per 64 KB sector, programming is per byte.** Erase sets a
1066
+ whole sector to `$FF`. That is 8 banks of one chip: the ROML halves of
1067
+ banks 8 to 15, for example. Programming can only turn 1 bits into 0
1068
+ bits. So reserve whole sectors for saves, never a sector that also
1069
+ holds code or data. The Am29F040B data sheet gives 1,000,000
1070
+ program/erase cycles per sector at minimum (not measured here).
1071
+ 2. **Writes need Ultimax mode.** Reads work in 16 KB mode (`$DE02` =
1072
+ `$07`), but writes to ROML in that mode reach only the C64 RAM
1073
+ underneath. Measured in VICE x64sc 3.10: an erase issued in 16 KB mode
1074
+ returned in 130 cycles and changed nothing, and the RAM at `$8555`
1075
+ read back `$A0`, the program command's third byte. EAPI switches to
1076
+ `$85` (Ultimax plus LED) for every write. Write to ROML at
1077
+ `$8000-$9FFF` and to ROMH at `$E000-$FFFF`, the Ultimax addresses.
1078
+ In Ultimax mode only RAM `$0000-$0FFF` is mapped and the KERNAL is
1079
+ gone, so the writing code and its data live below `$1000` (or in the
1080
+ cartridge's 256 bytes of RAM at `$DF00`), with interrupts off.
1081
+ 3. **Use EAPI in anything you release.** EAPI is the EasyFlash flash
1082
+ driver. A CRT carries it at bank 0 ROMH offset `$1800` (768 bytes
1083
+ reserved). The program copies it to C64 RAM (c64gameframework uses
1084
+ `$C000`) and calls EAPIInit, which builds a jump table in the
1085
+ cartridge RAM at `$DF80`. The calls are
1086
+ EAPIWriteFlash `$DF80`, EAPIEraseSector `$DF83`, EAPISetBank `$DF86`,
1087
+ EAPIGetBank `$DF89`, EAPISetPtr `$DF8C`, EAPISetLen `$DF8F`,
1088
+ EAPIReadFlashInc `$DF92` and EAPIWriteFlashInc `$DF95`. When EasyProg
1089
+ flashes a CRT that has the `eapi` signature there, it swaps in the
1090
+ version for the flash chip actually fitted. Code that sends Am29F040
1091
+ commands itself, as the recipe does, works only on that chip. VICE
1092
+ emulates it and warns `EF: EAPI not found!` when a CRT has no EAPI.
1093
+
1094
+ Saving then works like this. Keep the save sector's banks as `$FF` in
1095
+ the CRT, because EasyProg erases only the sectors a CRT contains. At
1096
+ boot, check a signature and erase the sector if it is foreign. Append
1097
+ each save as a fixed-size record in the next all-`$FF` slot, program a
1098
+ commit byte last, and load the newest committed record. Erase only when
1099
+ no slot is left. A save of a few dozen bytes then costs one sector erase
1100
+ every few hundred saves.
1101
+
1102
+ Times. VICE's sector erase took 1,000,147 to 1,000,191 cycles over three
1103
+ erases (two PAL, one NTSC). That is about 1 s: freeze the game and blank the screen, or show
1104
+ a "saving" message before starting. Programming 32 bytes took 2,866 to
1105
+ 3,123 cycles in VICE (PAL and NTSC), including the code around each byte. The
1106
+ Am29F040B data sheet gives 1 s typical and 8 s maximum per sector erase,
1107
+ and 7 µs typical and 300 µs maximum per byte (not measured here). VICE
1108
+ does not model the byte time or its spread, so budget for the maximum
1109
+ on hardware.
1110
+
1111
+ The recipe's wait loops poll the toggle bit (DQ6) and do not check DQ5,
1112
+ which the data sheet sets when an operation exceeds its time limit; a
1113
+ released game should add that check or use EAPI (hardware failure not
1114
+ tested here). A power cut is still a risk in two places, and the recipe's
1115
+ torn-slot path was not exercised. (a) When the bank is full, the erase
1116
+ comes before the new record is written, so a cut there loses every save.
1117
+ (b) A cut during an erase can leave the signature and a `$00` commit
1118
+ byte over half-erased data, which goes undetected without a checksum.
1119
+ Remedies: alternate between two sectors, and add a checksum byte.
1120
+
1121
+ ### How: GMod2
1122
+
1123
+ GMod2 keeps saves in a serial EEPROM behind `$DE00`. The same register
1124
+ also selects the ROM bank, so the code keeps a shadow copy and changes
1125
+ one bit at a time. The bits, from c64gameframework's `gmod2boot.s`:
1126
+
1127
+ | `$DE00` bit | Signal | Direction |
1128
+ |---|---|---|
1129
+ | 7 | EEPROM data out | read |
1130
+ | 6 | chip select | write |
1131
+ | 5 | clock | write |
1132
+ | 4 | EEPROM data in | write |
1133
+
1134
+ Each command is a start bit, a 2-bit opcode and a 10-bit word address,
1135
+ clocked out MSB first. Opcode `10` reads and `01` writes. `00` followed
1136
+ by `11` enables writes and `00` followed by `00` disables them. Data
1137
+ follows as 16-bit words: the framework halves its byte address to get
1138
+ the word address, and a write sends one word as two bytes. 1,024 16-bit
1139
+ words is 2 KB (arithmetic from the 10-bit address). After each word, the
1140
+ code drops and raises chip select, then waits until data out reads 1.
1141
+ The framework issues no erase command, so there is no sector to manage.
1142
+ It holds only 2 KB, though, and every bit costs several register writes. The part number and its
1143
+ write time come from neither the framework nor VICE and are not stated
1144
+ here. VICE emulates it with `-gmod2eepromimage <file>` and
1145
+ `-gmod2eepromrw`. Nothing on GMod2 was measured here.
1146
+
1147
+ ### Which one
1148
+
1149
+ - **EasyFlash** if the game is on EasyFlash already, the save is large (a
1150
+ whole level state), or saves are rare enough that a 1 s erase is
1151
+ acceptable. Reserve a sector per save area.
1152
+ - **GMod2** for a 16-bit-word save of up to 2 KB that must be written
1153
+ often, with no erase planning. Its write time is not established here.
1154
+ - **Neither** for data that changes every frame: keep it in RAM and
1155
+ write it on checkpoint or game over.
1156
+
1157
+ ### Sources
1158
+
1159
+ - EasyFlash Programmer's Guide (Thomas Giesel): http://skoe.de/easyflash/files/devdocs/EasyFlash-ProgRef.pdf
1160
+ - Am29F040B data sheet, AMD publication 21445: https://instrumentation.obs.carnegiescience.edu/ccd/parts/AM29F040B.pdf
1161
+ - c64gameframework (Lasse Öörni, MIT), `efboot.s`, `gmod2boot.s`, `eapi-am29f040-14.bin`: https://github.com/cadaver/c64gameframework
1162
+
1163
+ ### Recipes
1164
+
1165
+ - `recipes/kickassembler/easyflash-save.md` (a self-built EasyFlash CRT that appends a high-score record to bank 8 each boot; persistence shown across two VICE runs with `-easyflashcrtwrite`; erase and program timed)
1166
+
1167
+ ---
1168
+
1169
+ ## drive_code_upload_and_job_queue — Upload code to the 1541 with M-W, start it with M-E, read sectors through its job queue
1170
+
1171
+ **Complexity:** medium
1172
+ **Region:** both
1173
+ **Uses registers:** (none)
1174
+ **Uses kernal:** SETLFS, SETNAM, OPEN, CHKOUT, CHROUT, CHKIN, CHRIN, CLRCHN, CLOSE
1175
+ **Requires:** error_channel_check
1176
+ **Cost:** bytes_code=865, bytes_data=341
1177
+ **Cost basis:** derived-listing
1178
+ **Cost measured on:** kickassembler-drive-job-queue (whole PRG less the BASIC stub: 837 bytes of host code and the 28-byte drive routine; the 40-byte ramp, the strings and the 128-byte read buffer are data)
1179
+
1180
+ ### Why
1181
+
1182
+ The DOS reads what its file system describes. A loader that wants a
1183
+ sector by track and sector number, a protection check that wants to
1184
+ look at a sector the directory does not point to, or a fast loader that
1185
+ wants the drive's CPU running its own transfer loop, all need code on
1186
+ the drive side. The 1541 has a 6502 of its own, 2 KiB of RAM, and a
1187
+ command channel that will write that RAM, read it back and jump into
1188
+ it. The drive's disk controller then does the reading: uploaded code
1189
+ never has to touch the head or decode GCR to fetch a sector, it asks
1190
+ through the job queue.
1191
+
1192
+ ### How
1193
+
1194
+ 1. Open channel 15 bare (SETLFS 15, 8, 15; SETNAM length 0; OPEN), as
1195
+ error_channel_check does. It stays open for the whole exchange.
1196
+ 2. Upload. Each `M-W` is a command on channel 15: CHKOUT 15, then the
1197
+ bytes `M`, `-`, `W`, address low, address high, count, and `count`
1198
+ data bytes, then CLRCHN. The UNLISTEN runs it. Send at most 32 data
1199
+ bytes per command (the DOS parses the command from a fixed buffer
1200
+ and 32 is the figure loaders use; the recipe sends 32, 32 and 4; a
1201
+ 34-byte `M-W` also uploaded and ran in VICE, measured under
1202
+ `pitfalls/loader.md#atn_assert_drives_data_low_via_atna`, and 35 or
1203
+ more was not tried). Advance the address by the count each time.
1204
+ 3. Verify with `M-R`: the six bytes `M`, `-`, `R`, low, high, count,
1205
+ then CHKIN 15 and `count` CHRIN calls, then CLRCHN. Compare with the
1206
+ source. The recipe reads 68 bytes in one command; larger counts were
1207
+ not measured here. The DOS takes the count from the sixth byte of
1208
+ the command.
1209
+ 4. Start with `M-E`: the five bytes `M`, `-`, `E`, low, high. The drive
1210
+ executes a JSR to that address from its command parser, with
1211
+ interrupts enabled, and returns to its idle loop on the routine's
1212
+ RTS. The host's next command is not accepted until then, so a host
1213
+ that sends `M-E` and then `M-R` simply waits.
1214
+ 5. Inside the routine, to read a sector: put the track in `$08` and the
1215
+ sector in `$09`, write `$80` to `$01`, and loop while `$01` has bit 7
1216
+ set. The controller, which runs from the drive's timer interrupt,
1217
+ replaces the job code with a result: `$01` is success and the data
1218
+ is in buffer 1 at `$0400`. Slot `$00` uses buffer 0 at `$0300` with
1219
+ its header at `$06`/`$07`; slot 2 buffer 2 at `$0500` with `$0A`/`$0B`;
1220
+ slot 3 buffer 3 at `$0600` with `$0C`/`$0D`; slot 4 buffer 4 at
1221
+ `$0700` with `$0E`/`$0F`; slot 5 has no RAM behind it. Job codes:
1222
+ `$80` read, `$90` write, `$A0` verify, `$B0` seek, `$C0` bump,
1223
+ `$D0` jump, `$E0` execute. The codes as run here were `$80` and
1224
+ `$B0`; the rest are from the ROM disassembly's list and were not run
1225
+ (see `../formats/iec-disk-reference.md`, "1541 job queue and
1226
+ buffers").
1227
+ 6. Before the first read, teach the controller the disk ID. It checks
1228
+ every header's two ID bytes against its master copy at `$12`/`$13`,
1229
+ and after power-on nothing has set those, so the first read job
1230
+ fails with `$0B` (measured). A seek job, `$B0` with the track in
1231
+ `$08`, reads any header and copies its ID there (measured: `$01`,
1232
+ and `$12`/`$13` then read `30 31` for a `TEST,01` disk). The DOS's
1233
+ own `I0` does the same seek.
1234
+ 7. Fetch the result and the data with `M-R` from the host: `M-R $0001`
1235
+ for the job byte, `M-R $0400` in chunks for the sector.
1236
+
1237
+ ### Why it works
1238
+
1239
+ The drive's 6502 spends its time in two roles. From its idle loop it
1240
+ parses commands and runs the file system; from its timer interrupt,
1241
+ every ten milliseconds by the ROM disassembly's account (rung 4), it
1242
+ becomes the disk controller, scans `$00` to `$05` for a byte with bit 7
1243
+ set, and does that job with the head. `M-W` and `M-R` are ordinary
1244
+ commands that read and write the drive's address space; `M-E` is a
1245
+ command whose action is a subroutine call. Code started that way runs
1246
+ in the file-system role, so it can post jobs for the controller role
1247
+ exactly as the DOS does, and the result comes back in the same byte.
1248
+ The status line is not involved: after the track-40 job failed with
1249
+ `$03` the error channel still read `00, OK,00,00` (measured), because
1250
+ only the DOS writes that line and the DOS did not run the job.
1251
+
1252
+ Buffer choice: the DOS lends buffers 0 to 3 to data channels and keeps
1253
+ the BAM in buffer 4 at `$0700` (the ROM disassembly's note, rung 4; the
1254
+ "70, NO CHANNEL" on a fifth `#` open is the four in use). With no files
1255
+ open, `$0300` to `$06FF` is free. The recipe puts its code in buffer 3
1256
+ and reads into buffer 1; the code survived the four jobs and a seek.
1257
+
1258
+ ### Variations
1259
+
1260
+ - **A resident drive program.** Upload once, `M-E` once, and never
1261
+ return: the routine takes over the bus with its own protocol over
1262
+ CLK and DATA, the host side drives it with a matching routine, and
1263
+ the KERNAL is out of the loop until the drive is reset. That is a
1264
+ fast loader, and the pitfalls are its own: `../pitfalls/loader.md`,
1265
+ `gcr_timing_assumes_stock_drive` (an SD2IEC has no CPU to run the
1266
+ upload, and a 1571 or 1581 has a different ROM and different
1267
+ addresses), `fastloader_resident_in_kernal_workspace` and
1268
+ `fastloader_dd00_write_corrupts_resident` for the host half. Not
1269
+ measured here.
1270
+ - **The 1571 and 1581.** Both accept `M-W`, `M-R` and `M-E` (rung 4), but
1271
+ their job queues, buffer addresses and controller codes are their own
1272
+ ROMs' and were not measured here; detect the drive first
1273
+ (`../formats/iec-disk-reference.md`, "Identifying the drive over the
1274
+ command channel").
1275
+ - **Writing.** Fill the buffer, post `$90`. Not run here, and it would
1276
+ change the disk, so it does not belong in the recipe's pinned run.
1277
+ - **Another buffer.** Point the read at buffer 2 (`$0500`) by posting the
1278
+ job in slot `$02` with the track and sector at `$0A`/`$0B`: the same
1279
+ code, one slot along. Not run here.
1280
+
1281
+ ### Cycle budget
1282
+
1283
+ Bus time, PAL, VICE 3.10 (rung 1): 132,742 host cycles for the three
1284
+ `M-W` commands carrying 68 bytes, and 163,545 for the single `M-R` that
1285
+ read them back, about 135 ms and 166 ms. On the drive side, in drive
1286
+ cycles at 1 MHz: the seek 125,859, the successful read 224,071, the
1287
+ failed read before the seek 902,630 and the track-40 failure 766,533;
1288
+ the failures are the controller's retries and bumps. A loader that
1289
+ uploads a few hundred bytes therefore spends a noticeable fraction of a
1290
+ second on the upload alone, which is why resident loaders upload once.
1291
+
1292
+ ### Recipes
1293
+
1294
+ - `recipes/kickassembler/drive-job-queue.md` (upload, readback, execute, a failed read, a seek, the BAM read and compared, a provoked `$03`, and what the error channel says afterwards; drive-side monitor trace of each job)
1295
+
1296
+ ---
1297
+
1298
+ ## tape_turbo_loader — Read a one-pulse-per-bit tape block by timing FLAG edges against a threshold
1299
+
1300
+ **Complexity:** medium
1301
+ **Region:** both
1302
+ **Uses registers:** DC06, DC07, DC0D, DC0F
1303
+ **Uses kernal:** (none)
1304
+ **Cost:** bytes_code=1060, bytes_data=110, zp_bytes=24
1305
+ **Cost basis:** derived-listing
1306
+ **Cost measured on:** kickassembler-tape-turbo-loader (whole PRG less the BASIC stub; the code figure includes the report and the 32-bit division, the data is the strings and counters)
1307
+
1308
+ ### Why
1309
+
1310
+ The KERNAL's tape format spends twenty pulses on a byte, draws them from
1311
+ three lengths, and writes every block twice; measured from a SAVE in
1312
+ VICE (`../formats/c64-file-formats.md`, "KERNAL bit encoding") a byte
1313
+ costs 9,448 cycles, about 104 bytes a second for one copy and half that
1314
+ for the pair the KERNAL actually writes. A turbo loader replaces the
1315
+ stream, not the reading of it: one pulse per bit, two lengths, eight
1316
+ pulses to a byte, one copy. The recipe below moves 321 bytes a second on
1317
+ PAL and 334 on NTSC (measured, VICE 3.10), three times the KERNAL's one
1318
+ copy and six times its pair, with generous pulses; the trade is that the
1319
+ loader now depends on the tape running at the speed the file was
1320
+ mastered for, and on a threshold that sits between the two lengths.
1321
+
1322
+ ### How
1323
+
1324
+ 1. Master the tape with one pulse per bit. The recipe's script writes a
1325
+ TAP file: a lead-in of 1 bits, a sync byte `$5A`, a two-byte length,
1326
+ the data and an XOR checksum, most significant bit first, a 0 as a
1327
+ 256-cycle pulse and a 1 as a 512-cycle pulse. TAP entries are cycles
1328
+ divided by eight, so the two lengths are `$20` and `$40`.
1329
+ 2. Take the machine over: `SEI`, `$7F` to `$DC0D` to mask CIA 1's
1330
+ interrupt sources, and one read of `$DC0D` to clear what was pending.
1331
+ Reading the register clears every bit in it, so any interrupt handler
1332
+ that reads it (the KERNAL's does) would steal the FLAG edges the
1333
+ loader needs. This is also why the KERNAL's tape routines are not
1334
+ called: they own the same register and the same timers and expect
1335
+ their own stream.
1336
+ 3. Start CIA 1 Timer B free-running: `$FF` to `$DC06` and `$DC07`, `$11`
1337
+ to `$DC0F` (force load, start, count the system clock, continuous).
1338
+ It is never restarted; each edge's reading is subtracted from the
1339
+ previous one.
1340
+ 4. Wait for PLAY (bit 4 of `$01` low), then drive bit 5 of `$01` low for
1341
+ the motor. The KERNAL's interrupt normally does this from its own
1342
+ sense logic; with interrupts off the loader must. Blank the screen
1343
+ (bit 4 of `$D011`) so no badline stretches a poll.
1344
+ 5. Per pulse: spin on bit 4 of `$DC0D`; read Timer B high, low, high
1345
+ again, and take both again if the high byte moved (the low byte can
1346
+ wrap in the seven cycles between the reads, and a version that only
1347
+ re-read on a low byte of `$FF` mismeasured one pulse in about sixty
1348
+ by 256 cycles); length is previous less current; the bit is length
1349
+ at or above the threshold, 384 here.
1350
+ 6. Sync: count consecutive 1 bits; after sixty-four, the first 0 is bit
1351
+ 7 of the sync byte. Rotate seven more bits in and compare with `$5A`;
1352
+ on a mismatch start the count again. Because the tape has run since
1353
+ the KERNAL first saw PLAY, the loader joins the lead-in wherever it
1354
+ happens to be, and this is what makes that harmless.
1355
+ 7. Read the length, the block and the checksum, eight bits to a byte,
1356
+ most significant first. Verify, motor off, screen on, `$81` to
1357
+ `$DC0D` to give the KERNAL its Timer A interrupt back, `CLI`.
1358
+
1359
+ ### Why it works
1360
+
1361
+ The cassette read line is wired to CIA 1's FLAG input, and the CIA
1362
+ records each falling edge as bit 4 of its interrupt control register
1363
+ whether or not that source is enabled; VICE raises one such edge per
1364
+ TAP entry. The information is entirely in the time between edges, and a
1365
+ free-running 16-bit timer at the system clock measures it to the cycle
1366
+ with no restart cost and no drift, provided the two-byte read is made
1367
+ consistent. The threshold turns a continuous measurement into a bit,
1368
+ and its margin is what absorbs everything that moves the edges: tape
1369
+ speed, the loader's own polling granularity (nine cycles a turn here,
1370
+ and the measured pulses were within 8 cycles of nominal) and the time
1371
+ the loader spends between one poll and the next. The lead-in of one
1372
+ value followed by a sync byte whose first bit is the other value gives
1373
+ byte alignment from a cold start with no marker pulse of a third
1374
+ length, which is the KERNAL's answer to the same problem.
1375
+
1376
+ ### Variations
1377
+
1378
+ - **An adaptive threshold.** Measure the lead-in's pulses, which are all
1379
+ the long value, and set the threshold at three quarters of their
1380
+ average (or, with a lead-in that alternates the two values, halfway
1381
+ between the two averages). The loader then follows a tape recorded on
1382
+ a fast or slow deck, or played on one, instead of failing at a fixed
1383
+ figure. Not measured here: the recipe's threshold is a constant. What
1384
+ was measured is the margin it needs: with VICE's default tape speed
1385
+ error and wobble on, the pulse spread grew from 16 cycles to 52 on
1386
+ each value and the block still verified, at least 101 cycles of margin
1387
+ on either side.
1388
+ - **Shorter pulses.** The recipe's per-bit path is about 175 cycles,
1389
+ most of it the minimum and maximum bookkeeping for the report, and a
1390
+ 208-cycle 0 pulse failed on it (one bit read long after a 170-cycle
1391
+ setup between two bytes). A loader with nothing in the path but the
1392
+ poll, the timer read, the compare and the rotate can run pulses well
1393
+ under 200 cycles; the floor for a given loop is its longest path
1394
+ between two polls, not its average. Not measured here.
1395
+ - **The FLAG interrupt instead of a poll.** Enable bit 4 in `$DC0D`
1396
+ (`$90`) and take the IRQ or, on CIA 2, the NMI; the handler reads the
1397
+ timer and stores a bit while the main code decrunches or draws. The
1398
+ interrupt latency and its variation then eat into the margin in the
1399
+ poll's place. Not built here.
1400
+ - **A counted loop instead of a timer.** Increment a register while
1401
+ waiting for the edge and compare the count with a constant; that is
1402
+ the classic form and costs no CIA. Its unit is the loop's length, so
1403
+ a badline or an interrupt adds whole units, and the constant is
1404
+ specific to the loop. Not built here.
1405
+ - **The mastering side.** A turbo needs its tape written the same way.
1406
+ No host tool for that ships in this knowledge base; the recipe's
1407
+ Python script is the whole of it, and a real cassette would need the
1408
+ TAP played out through a deck or written by a program on the C64 that
1409
+ drives the write line through bit 3 of `$01` with a timer, as the
1410
+ KERNAL's SAVE does. Not built here.
1411
+
1412
+ ### Cycle budget
1413
+
1414
+ Measured, VICE 3.10, `-warp`, screen blanked, tape speed error and
1415
+ wobble off (rung 1): 1,540,498 cycles on PAL and 1,540,502 on NTSC from
1416
+ the end of the sync byte to the end of the checksum, 503 bytes, which is 3,062
1417
+ cycles a byte and 383 cycles a bit for this block's mix of ones and
1418
+ zeros (256 for a 0, 512 for a 1). The same cycles are 321 bytes a
1419
+ second at the PAL clock and 334 at NTSC: the TAP stores cycles, so a
1420
+ tape mastered for one region reads at the other's speed and the same
1421
+ thresholds hold. The KERNAL figure it is set against is arithmetic from
1422
+ the measured pulse modes on the formats page: 9,448 cycles a byte for
1423
+ one copy.
1424
+
1425
+ ### Recipes
1426
+
1427
+ - `recipes/kickassembler/tape-turbo-loader.md` (the TAP-writing script, the loader, the checksum verdict, bytes per second and pulse ranges on both models, and the run with VICE's tape wobble left on)