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,1524 @@
1
+ ---
2
+ category: maths
3
+ chip: 6510
4
+ ---
5
+
6
+ <!-- doc-type: technique-reference -->
7
+
8
+ # Maths: fixed point, table multiply, jump arcs
9
+
10
+ The 6510 has no multiply, no divide and no fraction. Game movement that
11
+ needs sub-pixel speeds or acceleration is done with fixed-point integers
12
+ and lookup tables. Every cycle figure on this page was measured in VICE
13
+ x64sc 3.10 with CIA1 timer B unless it says otherwise. The measuring
14
+ program is at the end of the page.
15
+
16
+ ## fixed_point_8_8 — 8.8 fixed-point positions and velocities
17
+
18
+ **Complexity:** low
19
+ **Region:** both
20
+ **Uses registers:** (none)
21
+
22
+ ### Why
23
+
24
+ A sprite register takes a whole pixel. A jump that accelerates by an
25
+ eighth of a pixel per frame cannot be expressed in whole pixels, and
26
+ rounding each frame drifts. Keeping eight extra bits of fraction gives
27
+ speeds down to 1/256 pixel per frame with no drift, at the cost of one
28
+ extra byte per coordinate and one extra add per update.
29
+
30
+ ### How
31
+
32
+ A value is a 16-bit two's complement integer read as `whole * 256 +
33
+ fraction`. `$0100` is 1.0, `$0080` is 0.5, `$FFC0` is -0.25. The high
34
+ byte is the whole part and the low byte the fraction. Nothing on the CPU
35
+ knows this; the programmer does.
36
+
37
+ Adding two 8.8 values is a plain 16-bit add, low byte first, and the
38
+ carry moves the fraction overflow into the whole part:
39
+
40
+ ```asm
41
+ clc
42
+ lda pos_lo
43
+ adc vel_lo
44
+ sta pos_lo
45
+ lda pos_hi
46
+ adc vel_hi
47
+ sta pos_hi
48
+ ```
49
+
50
+ Measured: 26 cycles with all six operands in absolute memory (100 calls
51
+ timed, 7,504 cycles less the 4,904 of the empty loop, display off so no
52
+ badline stole a cycle). With zero-page operands the same sequence is 20
53
+ cycles by the instruction table (rung 3, not measured here).
54
+
55
+ The pixel byte for a sprite register is the high byte, `pos_hi`. In
56
+ Oscar64 that is `(char)(y_fp >> 8)`; the compiler emits a byte move, not
57
+ eight shifts.
58
+
59
+ Signed values need no special add. A negative velocity is just a large
60
+ unsigned 16-bit number and the carry does the right thing. The sign
61
+ lives in bit 15, so `bmi` after `lda pos_hi` tests it, and a signed
62
+ comparison against a limit uses `sec / sbc / bvc / eor #$80` on the high
63
+ byte or, in C, an `int` compare. Where the whole part must stay
64
+ unsigned (a screen Y of 0 to 255) keep the position in an `unsigned`
65
+ and the velocity in an `int`; two's complement makes the add of the two
66
+ correct without a cast on the 6510.
67
+
68
+ ### 16.8 when the range needs it
69
+
70
+ 8.8 in a signed 16-bit word covers -128.00 to +127.99. A world
71
+ coordinate in a scrolling level, or a screen Y above 127 that must also
72
+ go negative, does not fit. 16.8 uses three bytes: two whole, one
73
+ fraction. The add gains one more `lda / adc / sta` (four cycles each
74
+ absolute, three zero page, rung 3). The pixel byte is the middle byte;
75
+ the top byte is the scroll or the sign. `c64-game-archetypes.md` names
76
+ 8.8 and 16.8 as the standard for the scrolling platformer.
77
+
78
+ ### Exhaustive check
79
+
80
+ The measuring program adds every pair `a = i * $0101`, `b = j * $0101`
81
+ for `i, j` in 0..255: 65,536 pairs, every low-byte pair once, with the
82
+ high-byte pair `(i, j)` added under the carry that low-byte pair
83
+ produces. Each 16-bit result is folded into a checksum (rotate left one,
84
+ exclusive-or the result, add 13) and compared with the compiler's own
85
+ `add_a + add_b`. Result on screen, PAL, 70,000,000 cycles:
86
+
87
+ ```
88
+ add cs 8aa1 exp 8aa1 miss 00000
89
+ pass
90
+ ```
91
+
92
+ Python over the same 65,536 pairs gives `0x8AA1`. Zero mismatches. A
93
+ first version of the harness ran two more sweeps meant to cover the
94
+ high-byte pairs under a forced carry of 0 and of 1; they left the
95
+ checksum unchanged, which means they did not run as written, so they
96
+ were removed and are not claimed here.
97
+
98
+ ### Recipes
99
+
100
+ - `recipes/oscar64/fixed-point-jump.md`
101
+
102
+ ## table_multiply_8x8 — Quarter-square table multiply
103
+
104
+ **Complexity:** medium
105
+ **Region:** both
106
+ **Uses registers:** (none)
107
+ **Cost:** cycles_per_frame=52, bytes_data=2048
108
+ **Cost basis:** arithmetic
109
+
110
+ ### Why
111
+
112
+ A shift-and-add 8x8 multiply is a loop of eight iterations, around 100
113
+ to 150 cycles depending on the routine (rung 4, not measured here). A
114
+ table gives the product in a fixed 52 cycles. `effects-vector-3d.md`
115
+ budgets six table multiplies per rotated point on that basis.
116
+
117
+ ### How
118
+
119
+ The identity is `a * b = q(a + b) - q(a - b)` with `q(n) = floor(n^2 / 4)`.
120
+ When `a + b` is odd, `a - b` is odd too and the two floors lose the same
121
+ quarter, so the result is exact.
122
+
123
+ Two tables of `q` are kept, each 512 entries split into a low-byte and a
124
+ high-byte table: `sqr[i] = q(i)` for `i` in 0..511, and `nsq[i] =
125
+ q(|i - 255|)`. That is 2,048 bytes, built at start-up in a loop of 512
126
+ steps using `q(i + 1) = q(i) + floor((i + 1) / 2)`, so no multiply is
127
+ needed to build the multiply table. Each table starts on a page
128
+ boundary.
129
+
130
+ The routine patches `a` into the low address byte of two reads into
131
+ `sqr` and `255 - a` (which is `a eor $ff`) into two reads into `nsq`,
132
+ then indexes all four with `Y = b`. `sqr + a` indexed by `b` is
133
+ `q(a + b)`; `nsq + 255 - a` indexed by `b` is `q(|b - a|)`.
134
+
135
+ ```asm
136
+ lda a
137
+ sta m1 + 1
138
+ sta m2 + 1
139
+ eor #$ff
140
+ sta m3 + 1
141
+ sta m4 + 1
142
+ ldy b
143
+ sec
144
+ m1: lda sqr_lo,y
145
+ m3: sbc nsq_lo,y
146
+ sta result
147
+ m2: lda sqr_hi,y
148
+ m4: sbc nsq_hi,y
149
+ sta result + 1
150
+ ```
151
+
152
+ ### Cycle budget
153
+
154
+ Measured: 52 cycles for `a = b = 0` with `a`, `b` and `result` in
155
+ absolute memory (10,104 cycles for 100 calls less the 4,904 empty-loop
156
+ baseline; the call and return are in both figures and cancel). That is
157
+ exactly the instruction-table sum: six absolute stores and loads at 4,
158
+ one `eor #` and one `sec` at 2, four indexed reads at 4 and two stores
159
+ at 4. Each indexed read adds one cycle when `base + a + b` crosses a
160
+ page, so the worst case is 56 (rung 3). With `a`, `b` and the result in
161
+ zero page the fixed part drops by 4 to 48 (rung 3, not measured here);
162
+ only the two operand loads and the two result stores move, because the
163
+ four patch stores write into the routine's own code and stay absolute.
164
+ The page-aligned tables are what keep the patch to one byte: without
165
+ alignment the high address byte would need a carry as well.
166
+
167
+ ### The optimiser will move the patched instructions
168
+
169
+ In Oscar64 the block above must be written `__asm volatile`. Without
170
+ `volatile` the assembler optimiser at `-O2` rewrote `ldy / lda ,y` as
171
+ `ldx / lda ,x`, duplicated the block, and left the four `sta m1 + 1`
172
+ stores pointing into the other copy. The running copy keeps the
173
+ operands it was assembled with, so every product came out as
174
+ `q(b) - q(|b - 255|)`, which is `$C080` for `0 * 0`, the figure the
175
+ first version of the harness on this page read (an earlier version of
176
+ this sentence gave the formula as `q(a + b) - q(b)`, which is 0 for
177
+ `0 * 0` and does not fit that reading; the executed sequence is quoted
178
+ in `pitfalls/cpu.md#asm_optimiser_moves_self_modified_instruction`).
179
+ `toolchains/oscar64-reference.md` (the assembler
180
+ optimizer paragraph) documents the switch; the symptom is recorded here because the compiled
181
+ listing looked right in the copy that was inspected first.
182
+
183
+ ### Signed variant
184
+
185
+ For signed 8-bit operands, run the unsigned routine on the raw bytes and
186
+ correct: if `a` is negative subtract `b` from the high byte, if `b` is
187
+ negative subtract `a` from the high byte. That is two tests and at most
188
+ two `sbc` on the high byte (rung 3, not measured here). The other common
189
+ form biases both operands by 128 and uses a table built for the biased
190
+ range; it needs no correction but a different table.
191
+
192
+ ### Exhaustive check
193
+
194
+ All 65,536 `(a, b)` pairs, `a` outer and `b` inner, each product folded
195
+ into the same checksum as the add and compared with Oscar64's own
196
+ `(unsigned)a * b`. Result on screen, PAL:
197
+
198
+ ```
199
+ mul cs 0f09 exp 0f09 miss 00000
200
+ pass
201
+ ```
202
+
203
+ Python over the same pairs in the same order gives `0x0F09`. Zero
204
+ mismatches.
205
+
206
+ ### Recipes
207
+
208
+ - No recipe yet. The tables and the checking harness are the measuring
209
+ program at the end of this page; `recipes/oscar64/fixed-point-jump.md`
210
+ does not use a multiply.
211
+
212
+ ## division_8_16bit — Shift-and-subtract division, reciprocals and divide by ten
213
+
214
+ **Complexity:** low
215
+ **Region:** both
216
+ **Uses registers:** (none)
217
+ **Cost:** cycles_per_frame=791
218
+ **Cost basis:** measured-vice
219
+ **Cost measured on:** oscar64-divide-check (one call, worst case 65535/1)
220
+
221
+ ### Why
222
+
223
+ The 6510 has no divide. A game needs one for a tile coordinate from a
224
+ pixel position when the tile is not a power of two, for an average, a
225
+ percentage, a speed from a distance and a time, and for splitting a
226
+ number into decimal digits. A binary shift-and-subtract loop does any
227
+ of these in a few hundred cycles; a divisor that is known at assembly
228
+ time can be turned into a multiply or a table and costs a tenth of
229
+ that. The figures here were measured with CIA1 timer A in
230
+ `recipes/oscar64/divide-check.md` (rung 1, VICE x64sc 3.10), with the
231
+ operands in absolute memory; the zero-page and constant-divisor
232
+ figures are instruction-table arithmetic from those loops (rung 3).
233
+
234
+ ### How
235
+
236
+ Long division in binary. Clear the remainder. Repeat once per dividend
237
+ bit: shift the dividend left, shifting its top bit into the bottom of
238
+ the remainder; if the remainder is now at least the divisor, subtract
239
+ the divisor from it and set the bit of the dividend that the shift
240
+ vacated. When the passes are done the dividend holds the quotient and
241
+ the remainder is the remainder. The loop for a 16-bit dividend and an
242
+ 8-bit divisor, quotient in `dvd`, remainder in `rem`:
243
+
244
+ ```
245
+ lda #0
246
+ sta rem
247
+ ldx #16
248
+ loop: asl dvd
249
+ rol dvd+1
250
+ rol rem ; ninth bit of the remainder lands in C
251
+ lda rem
252
+ bcs sub ; a nine-bit remainder always exceeds the divisor
253
+ cmp dvs
254
+ bcc next ; remainder < divisor: quotient bit stays 0
255
+ sub: sbc dvs ; C is set on both paths, so sbc is exact
256
+ sta rem
257
+ inc dvd ; the bit asl just vacated becomes 1
258
+ next: dex
259
+ bne loop
260
+ ```
261
+
262
+ The 8/8 form drops `rol dvd+1` and runs eight passes. The 16/16 form
263
+ keeps a two-byte remainder, catches its 17th bit with the same `bcs`
264
+ after the second `rol`, compares high bytes first and falls through to
265
+ the low bytes only when they are equal, and subtracts two bytes.
266
+
267
+ The remainder is one bit wider than the divisor before the subtract:
268
+ it was less than the divisor, and it has just been doubled plus one.
269
+ That bit is the carry out of `rol rem`. Taking `bcs` straight to `sbc`
270
+ handles it, because a remainder with that bit set is at least any
271
+ divisor of the narrower width, and because `rol` left the carry set,
272
+ which is the state `sbc` needs. The bit can only appear once the loop
273
+ has consumed more dividend bits than the divisor is wide, so it is a
274
+ 16/8 hazard: without the `bcs` that loop is wrong for divisors of `$81`
275
+ and above (24,400 misses over the recipe's 65,536 dividends; `256 / 255`
276
+ comes out as 0 remainder 0), while the 8/8 and 16/16 loops pass their
277
+ sweeps with the guard deleted, because an 8-bit dividend runs out of
278
+ passes before its remainder can reach `$80` and a 16-bit divisor would
279
+ need a seventeenth pass. An earlier version of this paragraph said the
280
+ guardless loop was wrong "for every divisor of `$80` (8-bit) or `$8000`
281
+ (16-bit) and above"; the measurement is in
282
+ `pitfalls/maths.md#division_loop_missing_ninth_bit_guard`.
283
+
284
+ A divisor of zero is not trapped. Every compare succeeds, so the
285
+ quotient comes out as all ones and the remainder as the dividend (its
286
+ low byte in the 16/8 form; rung 3, from the loop; not run). Test for
287
+ it before the call if the divisor can be zero.
288
+
289
+ Signed operands: divide the absolute values, negate the quotient if
290
+ the signs differed, and give the remainder the sign of the dividend.
291
+ The tests and negations are about 30 cycles on top of the unsigned
292
+ loop (rung 3, not measured here).
293
+
294
+ ### Division by a constant
295
+
296
+ A divisor fixed at assembly time is a multiply by its reciprocal. Pick
297
+ a shift `k` and the multiplier `m = ceil(2^k / d)`; then
298
+ `q = (n * m) >> k` equals `n / d` for every `n` with `n * e < 2^k`,
299
+ where `e = m * d - 2^k` is the rounding error of the reciprocal, which
300
+ lies between 0 and `d - 1`. For ten, `m = 205` with `k = 11` has
301
+ `e = 2`, so the quotient is exact for `n` up to 1023; Python over all
302
+ of them agrees. `m = $CCCD` with `k = 19` has `e = 2` as well and is
303
+ exact for every 16-bit `n`, but the product is 32 bits wide. `6554`
304
+ with `k = 16` has `e = 4` and is exact only below 16,384: the first
305
+ wrong value is 16,389. Get the remainder as `n - q * d`.
306
+
307
+ For an 8-bit `n` the product `n * 205` is an 8x8 multiply, and
308
+ `table_multiply_8x8` above delivers it in 52 cycles; three `lsr` of
309
+ the high byte give the quotient, about 65 cycles with the loads
310
+ (rung 3, not measured here). The multiply then needs its 2 KB of
311
+ tables. A form with no multiply and no table does the same job in
312
+ shifts and adds and was measured:
313
+
314
+ ```
315
+ lda n
316
+ lsr
317
+ sta q ; n/2
318
+ lsr
319
+ clc
320
+ adc q ; n/2 + n/4 = 0.75 n
321
+ sta q
322
+ lsr
323
+ lsr
324
+ lsr
325
+ lsr
326
+ clc
327
+ adc q ; 0.75 n + 0.75 n / 16 = 0.797 n
328
+ lsr
329
+ lsr
330
+ lsr ; 0.0996 n, equal to n/10 or one below it
331
+ sta q
332
+ ```
333
+
334
+ Then `r = n - 10 * q` is 0 to 11 (Python over 0..255; the recipe
335
+ checks every value), and one `cmp #10 / sbc #10 / inc q` fixes it.
336
+ 79 cycles for quotient and remainder, any `n`, measured. The 16-bit
337
+ version of the same series adds a `q + q >> 8` term, leaves a
338
+ remainder of 0 to 13, and needs one fix as well (rung 3, Python; not
339
+ run on the machine).
340
+
341
+ ### Division by ten and the decimal print
342
+
343
+ Splitting a 16-bit number into five digits by repeated 16/8 division
344
+ by ten costs four calls of the loop above, 2,793 cycles in the recipe
345
+ for 65,535 against 936 for the subtract-powers route that
346
+ `decimal_print` (`techniques/text.md`) uses on the same value, three
347
+ times as much. Moving the operands to zero page and writing the
348
+ divisor as an immediate brings a subtracting pass down from 49 to 39
349
+ cycles and a pass without a subtract from 36 to 30 (rung 3), which by
350
+ the same proportion puts the four calls near 2,200, still more than
351
+ double. The divide route gives the units digit first, which is what a
352
+ right-to-left field wants; the subtract route gives the most
353
+ significant digit first and is cheaper. A removed divide by ten should
354
+ be replaced with subtract-powers or a table, not left out.
355
+
356
+ ### When a table wins
357
+
358
+ An 8-bit dividend and a constant divisor is a 256-byte table, or two
359
+ for quotient and remainder; the recipe's `div10_tab` reads both in 20
360
+ cycles, measured, and the tables build in a counting loop at start
361
+ with no divide. A tile column from a pixel X with 8-pixel tiles is a
362
+ shift, not a divide, and with 12 or 20-pixel tiles it is this table.
363
+ A variable 8-bit divisor cannot be a direct table (64 KB); the usual
364
+ compromise is a 256-byte table of `256 / d` and a multiply by it,
365
+ which is one less than the true quotient for some inputs and needs a
366
+ correction step (rung 4, not measured here).
367
+
368
+ ### Cycle budget
369
+
370
+ Measured, body only, operands in absolute memory:
371
+
372
+ | Route | Cycles | Case |
373
+ |---|---|---|
374
+ | 8/8 loop | 351 | 255/1, a subtract on every pass |
375
+ | 8/8 loop | 260 | 255/255, one subtract |
376
+ | 16/8 loop | 791 | 65535/1, worst case |
377
+ | 16/8 loop | 674 | 65535/10 |
378
+ | 16/16 loop | 1,339 | 65535/1, worst case |
379
+ | 16/16 loop | 715 | 65535/$8000 |
380
+ | by 10, shift-add | 79 | any 8-bit value |
381
+ | by 10, two tables | 20 | any 8-bit value |
382
+ | five digits by four 16/8 divides | 2,793 | 65535 |
383
+ | five digits by subtract-powers | 936 | 65535 |
384
+
385
+ The loop figures are the instruction-table sums: a 16/8 pass with a
386
+ subtract is 49 cycles with the operands absolute (`asl`, `rol`, `rol`
387
+ and `inc` at 6, the loads, compare and stores at 4, the branches and
388
+ `dex` at 2 or 3), 36 without one, and 8 cycles of setup with the
389
+ remainder in absolute memory (7 in zero page); 65535/1 is
390
+ `8 + 16 * 49 - 1`, and the 8/8 figures close the same way: 255/1 is
391
+ `8 + 8 * 43 - 1` and 255/255 is `8 + 7 * 30 + 43 - 1`. In zero page the
392
+ same pass is 41 and 31, so the range is 502 to 662 for 16/8 and 214 to
393
+ 294 for 8/8 (rung 3). An immediate divisor takes 1 off the compare and
394
+ 1 off the subtract in zero page, so a pass there is 30 without a
395
+ subtract and 39 with one, or 36 when the `bcs` path was taken. The
396
+ Cost line
397
+ carries the measured 16/8 worst case; a routine called once a frame is
398
+ about 4 per cent of a PAL frame in its slowest form (791 of 19,656).
399
+
400
+ ### Recipes
401
+
402
+ - `recipes/oscar64/divide-check.md` — the three loops, the shift-add
403
+ and table divide by ten, checked over every 8-bit pair and a 16-bit
404
+ sweep against Oscar64's `/` and `%` and against Python, with the
405
+ cycle harness on screen.
406
+
407
+ ## jump_arc_table — Gravity in 8.8 and a precomputed arc
408
+
409
+ **Complexity:** low
410
+ **Region:** both
411
+ **Uses registers:** (none)
412
+ **Requires:** fixed_point_8_8
413
+
414
+ ### Why
415
+
416
+ A jump is a launch velocity and a constant downward acceleration. Done
417
+ in 8.8, each frame is two adds. Done as a table, each frame is one
418
+ indexed load, and the height at every frame is known before the game
419
+ runs, which is what a level designer needs to place a platform.
420
+
421
+ ### How
422
+
423
+ Per frame, in this order: velocity gains gravity, then position gains
424
+ velocity, then the pixel byte goes to the sprite register. With a
425
+ launch of `-$0400` (4.0 pixels per frame upward) and gravity `+$0020`
426
+ (0.125 pixels per frame per frame) the sprite is back on the ground
427
+ after 63 updates; the apex is 62 pixels up and lasts frames 28 to 35.
428
+ These numbers come from running the update in Python and were confirmed
429
+ by the recipe, which runs the same update on the 6510 every frame and
430
+ turns the border red if the live value ever differs from the table (it
431
+ stayed green through 252 PAL and 283 NTSC frames in the pinned runs).
432
+
433
+ Update order matters. Gravity-then-position (semi-implicit Euler)
434
+ lands exactly at update 63 with these constants. Position-then-gravity
435
+ moves the full launch speed on the first step, lands after 65 updates
436
+ and differs from jump frame 4 onward (rung 3, from the same Python
437
+ integration). Pick one and generate the table from the same code.
438
+
439
+ Apex: the frame where the velocity crosses zero, here `$0400 / $0020 =
440
+ 32` updates; the table's flat top is wider than one frame because the
441
+ position moves less than a pixel per frame there. Landing: the first
442
+ update where the position reaches or passes the ground. With these
443
+ constants the 63 velocity steps sum to exactly zero, so the landing is
444
+ exact and the clamp does nothing; keep the clamp for constants where
445
+ the last step would carry the sprite below the ground.
446
+
447
+ ### The table
448
+
449
+ 63 bytes of sprite Y, one per jump frame, generated off-line by the same
450
+ two adds. The recipe embeds it as `arc_y[]`. A game that stores only the
451
+ table needs no fixed point at all for the jump; it needs the table
452
+ length and a frame index.
453
+
454
+ ### Divide by a constant
455
+
456
+ Converting 8.8 to pixels is a divide by 256, which is taking the high
457
+ byte. Halving a velocity is `cmp #$80 / ror hi / ror lo` (arithmetic
458
+ shift right of a 16-bit signed value, 2 + 5 + 5 = 12 cycles zero page,
459
+ rung 3). A divide by a constant that is not a power of two is a multiply
460
+ by its reciprocal in 8.8 using `table_multiply_8x8`, then the high byte:
461
+ `v / 3` is `v * $55 >> 8`, which is one part in 256 low because
462
+ `3 * $55 = 255`, so the result is up to one pixel short over `0..255`
463
+ (`255 * $55 >> 8` is 84, not 85). Use `$56` and accept an error the
464
+ other way, or a 16-bit reciprocal, if that matters (rung 3, checked in
465
+ Python over 0..255, not measured on the 6510).
466
+
467
+ ### Region
468
+
469
+ The table is in frames. On NTSC the same table plays about 20 percent
470
+ faster in wall-clock time (60 frames per second against 50);
471
+ `game-design-patterns.md` notes that gravity and speeds are tuned per
472
+ region for that reason. The recipe keeps one table and shows the frame
473
+ number so the two pictures can be compared frame for frame.
474
+
475
+ ### Variations
476
+
477
+ Velocity table, launch from any height. `arc_y[]` is absolute sprite Y
478
+ from one fixed ground, so a platformer cannot play it from a platform.
479
+ Store the per-frame velocity instead, `vy_tab[n] = V0 + (n + 1) * G`
480
+ clamped to a terminal velocity, and do `y += vy_tab[n]` from whatever
481
+ `y` the jump starts at; the caller decides what a landing is (a floor
482
+ under the sprite, a tile lookup). With `-$0380`, `+$0024` and terminal
483
+ `+$0210` the 52 entries sum to exactly zero, so a sprite that launches
484
+ from a floor is back on it exactly, and the terminal run at the end of
485
+ the table is what a longer fall holds. `recipes/oscar64/fixed-point-jump-velocity.md`
486
+ runs two sprites from floors 64 pixels apart off one table and checks
487
+ the exact landing on the 6510 every jump; the constants were picked in
488
+ Python for the zero sum, and the pair `-3.5` with `+$28` does not give
489
+ one for any terminal velocity from 1.5 to 4.0 in steps of 1/32.
490
+
491
+ ### Recipes
492
+
493
+ - `recipes/oscar64/fixed-point-jump.md`
494
+ - `recipes/oscar64/fixed-point-jump-velocity.md`
495
+
496
+ ### The measuring program
497
+
498
+ This section belongs to all three techniques above; it sits under the
499
+ last one only because the extractor reads every H2 as a technique. Not
500
+ built by the listing gate (technique pages are not compiled); it was
501
+ built and run by hand with the commands below. Both the multiply and the
502
+ add are `__noinline` functions whose body is `__asm volatile`, so the
503
+ timed and checked code is the instruction sequence shown above and not
504
+ the compiler's version of it.
505
+
506
+ ```
507
+ oscar64 -tm=c64 -O2 -o=fpcheck.prg fpcheck.c
508
+ GSETTINGS_SCHEMA_DIR=/opt/homebrew/share/glib-2.0/schemas timeout 120 x64sc -default -warp +sound +autostart-delay-random -autostartprgmode 1 -limitcycles 70000000 -exitscreenshot fpcheck.png -autostart fpcheck.prg
509
+ ```
510
+
511
+ Screen after the run, decoded from the PNG with the character ROM:
512
+
513
+ ```
514
+ mul cs 0f09 exp 0f09 miss 00000
515
+ add cs 8aa1 exp 8aa1 miss 00000
516
+
517
+ cyc x100 empty 04904 mul 10104 add 07504
518
+
519
+ pass
520
+ ```
521
+
522
+ Timing is CIA1 timer B, force-loaded from `$FFFF`, counting phi2, read
523
+ after 100 calls; `$D011` has DEN clear during the timed loops so no
524
+ badline or sprite fetch stalls the CPU (with the display on, the same
525
+ loops read 5,205 to 5,472 for the empty case depending on where the
526
+ loop sat relative to the badlines).
527
+
528
+ ```c
529
+ // fpcheck.c -- exhaustive check of the quarter-square multiply and the
530
+ // 8.8 add, plus a CIA timer B cycle measurement of each core.
531
+ #include <c64/vic.h>
532
+ #include <c64/cia.h>
533
+
534
+ #define SCREEN ((char *)0x0400)
535
+ #define COLOUR ((char *)0xd800)
536
+
537
+ #define EXPECT_MUL 0x0F09 // from the Python fold over all 65,536 products
538
+ #define EXPECT_ADD 0x8AA1 // from the Python fold over all 65,536 sums
539
+
540
+ char sqr_lo[512], sqr_hi[512], nsq_lo[512], nsq_hi[512];
541
+ #pragma align(sqr_lo, 256)
542
+ #pragma align(sqr_hi, 256)
543
+ #pragma align(nsq_lo, 256)
544
+ #pragma align(nsq_hi, 256)
545
+
546
+ char mul_a, mul_b;
547
+ unsigned mul_r;
548
+ int add_a, add_b, add_r;
549
+
550
+ static void build_tables(void)
551
+ {
552
+ unsigned q = 0;
553
+ for (unsigned i = 0; i < 512; i++)
554
+ {
555
+ sqr_lo[i] = q & 0xff;
556
+ sqr_hi[i] = q >> 8;
557
+ q += (i + 1) >> 1; // q(i+1) = q(i) + floor((i+1)/2)
558
+ }
559
+ for (unsigned i = 0; i < 512; i++)
560
+ {
561
+ unsigned k = i < 255 ? 255 - i : i - 255;
562
+ nsq_lo[i] = sqr_lo[k];
563
+ nsq_hi[i] = sqr_hi[k];
564
+ }
565
+ }
566
+
567
+ __noinline void qmul(void)
568
+ {
569
+ __asm volatile
570
+ {
571
+ lda mul_a
572
+ sta m1 + 1
573
+ sta m2 + 1
574
+ eor #$ff
575
+ sta m3 + 1
576
+ sta m4 + 1
577
+ ldy mul_b
578
+ sec
579
+ m1: lda sqr_lo, y
580
+ m3: sbc nsq_lo, y
581
+ sta mul_r
582
+ m2: lda sqr_hi, y
583
+ m4: sbc nsq_hi, y
584
+ sta mul_r + 1
585
+ }
586
+ }
587
+
588
+ __noinline void fpadd(void)
589
+ {
590
+ __asm volatile
591
+ {
592
+ clc
593
+ lda add_a
594
+ adc add_b
595
+ sta add_r
596
+ lda add_a + 1
597
+ adc add_b + 1
598
+ sta add_r + 1
599
+ }
600
+ }
601
+
602
+ static inline unsigned fold(unsigned cs, unsigned r)
603
+ {
604
+ cs = (cs << 1) | (cs & 0x8000 ? 1 : 0);
605
+ cs ^= r;
606
+ return cs + 0x0d;
607
+ }
608
+
609
+ static const char hexg[16] = { 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,1,2,3,4,5,6 };
610
+
611
+ static void put_hex(char row, char col, unsigned v)
612
+ {
613
+ char *p = SCREEN + 40 * row + col;
614
+ p[0] = hexg[(v >> 12) & 15]; p[1] = hexg[(v >> 8) & 15];
615
+ p[2] = hexg[(v >> 4) & 15]; p[3] = hexg[v & 15];
616
+ }
617
+
618
+ static void put_dec(char row, char col, unsigned v)
619
+ {
620
+ char *p = SCREEN + 40 * row + col;
621
+ for (signed char i = 4; i >= 0; i--) { p[i] = 0x30 + v % 10; v /= 10; }
622
+ }
623
+
624
+ static void put_str(char row, char col, const char *s)
625
+ {
626
+ char *p = SCREEN + 40 * row + col;
627
+ while (*s) { char c = *s++; p++[0] = (c >= 'a' && c <= 'z') ? c - 'a' + 1 : (c == ' ' ? 0x20 : c); }
628
+ }
629
+
630
+ volatile char sink;
631
+
632
+ static unsigned time_loop(void (*fn)(void))
633
+ {
634
+ cia1.crb = 0x00;
635
+ cia1.tb = 0xffff;
636
+ cia1.crb = 0x11; // start, force load, count phi2
637
+ for (char i = 0; i < 100; i++) { fn(); sink = i; }
638
+ cia1.crb = 0x00;
639
+ return 0xffff - cia1.tb;
640
+ }
641
+
642
+ void nothing(void) { }
643
+
644
+ int main(void)
645
+ {
646
+ for (unsigned i = 0; i < 1000; i++) { SCREEN[i] = 0x20; COLOUR[i] = 1; }
647
+ vic.color_back = 0; vic.color_border = 0;
648
+ __asm { sei }
649
+
650
+ build_tables();
651
+
652
+ unsigned cs = 0, miss = 0;
653
+ mul_a = 0;
654
+ do {
655
+ mul_b = 0;
656
+ do {
657
+ qmul();
658
+ if (mul_r != (unsigned)mul_a * mul_b) miss++;
659
+ cs = fold(cs, mul_r);
660
+ } while (++mul_b);
661
+ } while (++mul_a);
662
+ put_str(0, 0, "mul cs exp miss");
663
+ put_hex(0, 7, cs); put_hex(0, 16, EXPECT_MUL); put_dec(0, 26, miss);
664
+ bool ok = (cs == EXPECT_MUL) && miss == 0;
665
+
666
+ cs = 0; miss = 0;
667
+ unsigned i = 0;
668
+ do {
669
+ unsigned j = 0;
670
+ do {
671
+ add_a = i * 0x101; add_b = j * 0x101; fpadd();
672
+ if (add_r != add_a + add_b) miss++;
673
+ cs = fold(cs, add_r);
674
+ } while (++j < 256);
675
+ } while (++i < 256);
676
+ put_str(1, 0, "add cs exp miss");
677
+ put_hex(1, 7, cs); put_hex(1, 16, EXPECT_ADD); put_dec(1, 26, miss);
678
+ ok = ok && (cs == EXPECT_ADD) && miss == 0;
679
+
680
+ vic.ctrl1 = 0x0b; // DEN off: no badlines while timing
681
+ vic_waitFrame();
682
+ unsigned t0 = time_loop(nothing);
683
+ unsigned tm = time_loop(qmul);
684
+ unsigned ta = time_loop(fpadd);
685
+ vic.ctrl1 = 0x1b;
686
+ put_str(3, 0, "cyc x100 empty mul add");
687
+ put_dec(3, 15, t0); put_dec(3, 25, tm); put_dec(3, 35, ta);
688
+
689
+ put_str(5, 0, ok ? "pass" : "fail");
690
+ vic.color_border = ok ? 5 : 2;
691
+ for (;;)
692
+ ;
693
+ return 0;
694
+ }
695
+ ```
696
+
697
+ The Python side of the checksum:
698
+
699
+ ```python
700
+ def fold(cs, r):
701
+ cs = ((cs << 1) | (cs >> 15)) & 0xFFFF
702
+ cs ^= r & 0xFFFF
703
+ return (cs + 0x0D) & 0xFFFF
704
+
705
+ cs = 0
706
+ for a in range(256):
707
+ for b in range(256):
708
+ cs = fold(cs, a * b) # -> 0x0F09
709
+ cs = 0
710
+ for i in range(256):
711
+ for j in range(256):
712
+ cs = fold(cs, (i * 0x101 + j * 0x101) & 0xFFFF) # -> 0x8AA1
713
+ ```
714
+
715
+ A plain rotate-and-xor fold was tried first and gave `0x0000` for the
716
+ add sweep: the domain is symmetric in `i` and `j` and the xor cancelled.
717
+ The `+ 13` breaks that. sim6502 was not used; `dotnet` runs on the
718
+ machine but the harness above answers the question in one VICE run and
719
+ its result is a screen the character ROM can decode.
720
+
721
+ ## lfsr_random — Linear-feedback shift register random numbers
722
+
723
+ **Complexity:** low
724
+ **Region:** both
725
+ **Uses registers:** D41B, D412, D40E, D40F, D418, DC04, DC05, DC0E
726
+ **Cost:** cycles_per_frame=14, bytes_code=1947
727
+ **Cost basis:** arithmetic
728
+ **Cost measured on:** oscar64-lfsr-random (one 8-bit step, screen blanked; bytes are the whole PRG)
729
+ **Claims:** sid_voice_3 (init), sid_voice_3_readback (init), sid_filter_volume (init), cia1_timer_a (reads)
730
+ **Claims basis:** derived-listing
731
+
732
+ ### Why
733
+
734
+ A game needs cheap pseudo-random bytes for spawn positions, noise
735
+ pixels and starfields, and it needs the sequence to differ from one
736
+ play to the next. A linear-feedback shift register (LFSR) gives the
737
+ bytes in a dozen cycles with no table. The seed is what makes each game
738
+ different, and the C64 has three sources for it: SID voice 3 noise, a
739
+ CIA timer, and the moment the player first touches the controls.
740
+
741
+ ### How
742
+
743
+ Galois form, shifting right. Shift the state right one bit; if the bit
744
+ that fell out was 1, XOR the tap mask into the state. The state after
745
+ each step is the output.
746
+
747
+ - 8-bit: taps `$B8` (x^8 + x^6 + x^5 + x^4 + 1). Period measured at 255
748
+ from the three seeds the recipes ran (`$9219`, `$7A80`, `$BEEF`): the
749
+ recipe walks the full cycle from its live seed and counts (rung 1,
750
+ VICE x64sc 3.10). The full-cycle argument below covers the rest.
751
+ - 16-bit: taps `$B400` (x^16 + x^14 + x^13 + x^11 + 1). Period measured
752
+ at 65535 the same way (rung 1).
753
+
754
+ ```
755
+ step8: lda s8 step16: lsr s16+1
756
+ lsr ror s16
757
+ bcc + bcc +
758
+ eor #$b8 lda s16+1
759
+ + sta s8 eor #$b4
760
+ sta s16+1
761
+ +
762
+ ```
763
+
764
+ Seeding from SID voice 3. Write `$FFFF` to voice 3's frequency
765
+ (`$D40E/$D40F`), gate the noise waveform (`$D412 = $81`), and set bit 7
766
+ of `$D418` (3OFF) so voice 3 is silent while the volume nibble a music
767
+ player owns is left alone; a game ORs the bit into its own `$D418`
768
+ shadow rather than storing a fresh value. Then read `$D41B` twice, a
769
+ few thousand cycles apart (the recipe calls an empty function 100 times
770
+ between the reads, about 3,700 cycles by its own timing figure of 9,476
771
+ cycles for 256 calls), for a 16-bit seed. In the recipe 255 of 255
772
+ consecutive `$D41B` read pairs differed (rung 1), the reads a C loop
773
+ iteration apart, so the register is moving at every read. In a headless
774
+ VICE run the two reads land on the same values every time (the seed was
775
+ `$9219` on PAL in every run, `$7A80` on NTSC), because the emulator's
776
+ noise register starts from a fixed state and the program runs the same
777
+ number of cycles to the read; that the seed varies on a real machine,
778
+ where power-on state and load timing differ, is not measured here
779
+ (rung 4). The seed is the noise register's state, not a clock, so it is
780
+ only as unpredictable as the time between power-on and the read.
781
+ On a SID replacement that cannot read back `$D41B`, such as the
782
+ SwinSID, the seed can be constant or low-entropy (an inference from C64-Wiki
783
+ and SIDDetector-II, not measured here); mix in the CIA timer read at a
784
+ moment the player chose, or the player-input
785
+ count below, which do not depend on the SID. See the pitfall
786
+ `sid_replacement_d41b_unreadable` in `pitfalls/sid.md`.
787
+
788
+ Seeding from a CIA timer. `$DC04/$DC05` is CIA1 timer A, which the
789
+ KERNAL leaves free-running for its jiffy interrupt. Read it as a 16-bit
790
+ value and XOR it into the seed. Read at a fixed point after boot it is
791
+ as reproducible as the SID read (the recipe prints `$251C` on PAL every
792
+ run); read at a moment the player chose it is a good seed.
793
+
794
+ Seeding from player input. Step the LFSR once per frame, or count
795
+ frames, while the title screen waits for the first fire press
796
+ (`joystick_edge_detect` in `techniques/input.md` gives the press). The
797
+ count of frames the player took is the seed, and the timer read at that
798
+ moment adds sixteen bits of sub-frame phase. The recipe cannot show this
799
+ because a headless run has no player; the text here is the design, not
800
+ a measurement.
801
+
802
+ The all-zero state. A Galois LFSR maps state 0 to state 0: nothing falls
803
+ out, nothing is XORed in, and every output is zero for ever. Check the
804
+ seed and replace zero with a constant before the first step (the recipe
805
+ uses `$ACE1`, and `$01` for the 8-bit register when its byte is zero).
806
+ Two `$D41B` reads are both `$00` rarely, but the check costs four
807
+ instructions and the failure is a game that never varies again. See the
808
+ pitfall `lfsr_zero_state_lockup` in `pitfalls/cpu.md`.
809
+
810
+ ### Why it works
811
+
812
+ The tap mask is a primitive polynomial over GF(2), so the shift-and-XOR
813
+ map is a permutation of the 2^n - 1 non-zero states in one cycle. That
814
+ is why the period is 255 and 65535 exactly and why a byte histogram over
815
+ one period is flat. The recipe counts the low byte of every 16-bit state
816
+ over the full period: each value 256 times, except `$00` 255 times,
817
+ because state `$0000` is the one that is never visited (rung 1; the
818
+ same figures come from the Python model). For the 8-bit register the
819
+ same argument gives every non-zero byte once and zero never.
820
+
821
+ A flat histogram is not independence. Successive states of a
822
+ right-shifting register are the previous state shifted right with the
823
+ taps folded into a few high bits, so the low byte of state n+1 is the
824
+ low byte of state n shifted right with one new bit at the top. The
825
+ recipe's first bytes show it: `86 43 A1 D0 E8 74 3A` is a right shift
826
+ each step. For a byte a player could not guess by eye, take the high
827
+ byte XOR the low byte, or step the register eight times per byte at
828
+ eight times the cost. Noise pixels and spawn tables do not care; the
829
+ recipe's mosaic paints the low nibble directly and every one of the 16
830
+ colours lands between 43 and 53 times over 760 cells (measured off the
831
+ PAL screenshot).
832
+
833
+ The SID noise source is itself a 23-bit LFSR clocked by voice 3's
834
+ oscillator; `hardware/sid-reference.md` (`$D41B`) has its readback
835
+ rules, including that a held TEST bit drifts it to all ones and never to
836
+ zero. `hardware/c64-registers-reference.md` lists the voice 3 and CIA
837
+ registers.
838
+
839
+ ### Variations
840
+
841
+ - 8-bit only: the whole state is the byte, 13 to 14 cycles a step, 255
842
+ bytes before repeating; enough for a starfield, not for a level
843
+ generator.
844
+ - Fibonacci form (XOR several state bits into the new bit) gives the same
845
+ sequences at a higher cost; on the 6510 the Galois form is the one to
846
+ use because the feedback is a single `eor`.
847
+ - Step several times per read, or XOR two registers of coprime period,
848
+ when the shift structure of consecutive bytes would show.
849
+
850
+ ### Cycle budget
851
+
852
+ Measured with CIA1 timer A in the recipe: force-load `$FFFF`, 256 calls
853
+ through a function pointer, read the count, with DEN clear so no badline
854
+ interrupts the count (rung 1). Empty loop 9,476 cycles; 8-bit step
855
+ 12,933; 16-bit step 14,441. Per step: 13.5 cycles for the 8-bit register
856
+ (13 without the tap, 14 with, absolute addressing) and 19.4 for the
857
+ 16-bit one (15 without, 24 with; half the steps take the tap). In zero
858
+ page the same sequences cost 11 to 12 and 13 to 20 (rung 3, from the
859
+ instruction table, not measured).
860
+
861
+ ### Recipes
862
+
863
+ - `recipes/oscar64/lfsr-random.md` — seed from `$D41B`, mosaic, period,
864
+ checksum and histogram self-check, timing.
865
+ - `recipes/oscar64/lfsr-random-seed2.md` — the same listing with a
866
+ fixed seed, to show a different seed gives a different picture.
867
+
868
+ The checksum fold in those pages is `chk = ((chk ^ value) * 5 + 1) &
869
+ 0xFFFF`, folded from state 1 round the whole cycle so the expected
870
+ value does not depend on the live seed; it is not the rotate fold used
871
+ by `fpcheck.c` above.
872
+
873
+ ## compare_16bit_and_signed — 16-bit, signed and ranged compares
874
+
875
+ **Complexity:** low
876
+ **Region:** both
877
+ **Uses registers:** (none)
878
+ **Cost:** cycles_per_frame=20
879
+ **Cost basis:** measured-vice
880
+ **Cost measured on:** kickassembler-compare-16bit-signed (one call, signed 16-bit with overflow)
881
+
882
+ ### Why
883
+
884
+ `CMP` compares one unsigned byte. A tile collision compares a 16-bit
885
+ world coordinate against a boundary, a fixed-point mover compares a
886
+ signed velocity against a limit, and a bounds check asks whether a
887
+ value lies inside a range. Each needs a sequence of instructions and a
888
+ particular flag read afterwards, and the obvious sequences are wrong
889
+ at the edges: a bare `BMI` after a subtract gives the wrong answer for
890
+ one pair in four. Every flag and cycle figure below was measured in
891
+ VICE x64sc 3.10 by `recipes/kickassembler/compare-16bit-signed.md`,
892
+ which prints the flags on the boundary pairs and checks every idiom
893
+ over full sweeps against a Python model.
894
+
895
+ ### How
896
+
897
+ **Unsigned 16-bit.** Compare the high bytes; if they differ they decide,
898
+ otherwise compare the low bytes:
899
+
900
+ ```asm
901
+ lda a_hi
902
+ cmp b_hi
903
+ bne done // high bytes differ: their flags are the answer
904
+ lda a_lo
905
+ cmp b_lo // high bytes equal: the low bytes decide
906
+ done: // C clear: a < b. C set: a >= b. Z set: a == b
907
+ ```
908
+
909
+ After the first `CMP`, `C` set means `a_hi >= b_hi` and `Z` set means
910
+ the high bytes are equal. `BNE` skips the low bytes when they are not.
911
+ At `done`, `Z` can only be set by the low-byte `CMP` after the high
912
+ bytes matched, so it means the whole words are equal. `$0100` against
913
+ `$00FF` ends with `C` set and `Z` clear from the first `CMP` alone;
914
+ `$00FF` against `$0100` ends with `N` set and `C` clear. For "`a > b`"
915
+ test `C` set and `Z` clear; for "`a <= b`" swap the operands.
916
+
917
+ **Signed 8-bit.** Subtract, then correct the sign when the subtract
918
+ overflowed:
919
+
920
+ ```asm
921
+ lda a
922
+ sec
923
+ sbc b
924
+ bvc ok // V clear: N is the sign of a - b
925
+ eor #$80 // V set: the sign wrapped, flip it back
926
+ ok: // N set: a < b (signed). N clear: a >= b
927
+ ```
928
+
929
+ Measured flags on the boundary pairs (`N V Z C`, `.` for clear):
930
+
931
+ | a | b | after `SBC` | after fix-up | idiom says | bare `BMI` says | truth |
932
+ |---|---|---|---|---|---|---|
933
+ | `$80` (-128) | `$7F` (127) | `. V . C` | `N V . C` | less | not less | less |
934
+ | `$7F` (127) | `$80` (-128) | `N V . .` | `. V . .` | not less | less | not less |
935
+ | `$00` | `$00` | `. . Z C` | `. . Z C` | not less | not less | not less |
936
+ | `$FF` (-1) | `$00` | `N . . C` | `N . . C` | less | less | less |
937
+ | `$00` | `$FF` (-1) | `. . . .` | `. . . .` | not less | not less | not less |
938
+ | `$80` | `$80` | `. . Z C` | `. . Z C` | not less | not less | not less |
939
+ | `$7F` | `$7F` | `. . Z C` | `. . Z C` | not less | not less | not less |
940
+ | `$9C` (-100) | `$64` (100) | `. V . C` | `N V . C` | less | not less | less |
941
+
942
+ `-128 - 127` is `-255`, which does not fit in a byte; the byte result is
943
+ `$01`, `N` clear, and `V` set says so. `EOR #$80` makes it `$81` and
944
+ `EOR` sets `N` from its result, so `N` is the true sign. The bare `BMI`
945
+ is wrong on exactly the rows where `V` is set. Over all 65,536 pairs it
946
+ disagrees with the corrected form on 16,384, one quarter (measured;
947
+ the Python model gives the same count). A value that never crosses
948
+ the overflow boundary hides the fault: two positions within 127 of
949
+ each other compare correctly either way, and the bug appears the first
950
+ time a velocity or an offset is large and of the other sign.
951
+
952
+ **Signed 16-bit.** Set the borrow from the low bytes with `CMP`, then
953
+ subtract the high bytes and apply the same fix-up:
954
+
955
+ ```asm
956
+ lda a_lo
957
+ cmp b_lo // C = no borrow from the low bytes
958
+ lda a_hi
959
+ sbc b_hi
960
+ bvc ok
961
+ eor #$80
962
+ ok: // N set: a < b (signed)
963
+ ```
964
+
965
+ Measured on the boundary pairs: `$7FFF` against `$8000` ends `. V . .`
966
+ with `N` clear (32,767 is not less than -32,768) where the unsigned
967
+ form ends `N . . .` with `C` clear (as unsigned, `$7FFF` is less);
968
+ `$8000` against `$7FFF` ends `N V . C`, less, where the unsigned form
969
+ says not less; equal values end `. . Z C` in both forms; `$0000`
970
+ against `$FFFF` ends `. . Z .`, not less, and shows that `Z` at the end
971
+ of the signed form is the high byte's alone, not equality. Test equality
972
+ with the unsigned form.
973
+
974
+ **Ranged, unsigned 8-bit.** Is `x` in `[lo, lo + w)`:
975
+
976
+ ```asm
977
+ lda x
978
+ sec
979
+ sbc lo
980
+ cmp w // C clear: inside. C set: outside
981
+ ```
982
+
983
+ `x - lo` modulo 256 is below `w` exactly when `lo <= x < lo + w`, as
984
+ long as `lo + w` does not exceed 256: an `x` below `lo` wraps to at
985
+ least `256 - lo`, which is at least `w`. One `BCC inside` acts on it.
986
+ Measured over every `x` against every `lo` with `w = 32` (clamped near
987
+ the top): matches the model. The two-compare form, `cmp lo / bcc out /
988
+ cmp hi / bcs out`, needs no width and no wrap condition, and is the
989
+ one to use when `hi` is what you hold.
990
+
991
+ ### Why it works
992
+
993
+ `CMP` is a subtract that discards the result and does not touch `V`
994
+ (measured: the flag column after `CMP` alone shows the `V` the previous
995
+ instruction left, which is why the recipe clears it first). It sets `C`
996
+ when no borrow was needed, that is when `A >= operand` unsigned, and `Z`
997
+ when the two are equal; `N` after `CMP` is bit 7 of a discarded
998
+ difference and means nothing about order. `SBC` keeps the result and
999
+ does set `V`: the two's complement sign of `a - b` is the order only
1000
+ when the difference fits in seven bits plus sign, and `V` is the
1001
+ report that it did not. Flipping bit 7 when `V` is set restores the
1002
+ sign because overflow in either direction flips it exactly once.
1003
+
1004
+ ### The C compiler's view
1005
+
1006
+ Oscar64 (`-O2`, read from its `.asm` listing; the four functions were
1007
+ marked `__noinline` so they stayed separate, since `-O2` had inlined
1008
+ them into `main` and dropped three whose result was overwritten)
1009
+ emits the same high-byte-first `CMP / BNE / CMP` chain for `unsigned
1010
+ int` and turns the carry into a value with `LDA #0 / ROL / EOR #1`. For
1011
+ `int` it uses the chain too, but when the high bytes differ it does not
1012
+ use `SBC` and `V`: it exclusive-ors the two high bytes and tests the
1013
+ sign of that. If the signs agree (`BPL`) the unsigned carry is the
1014
+ answer; if they differ the negative one is the smaller, and the carry
1015
+ from the unsigned compare already says which is negative. For `signed
1016
+ char` it emits `CMP / BEQ / EOR b / BCC / BMI`, the same sign-agreement
1017
+ test. For `x >= lo && x < hi` on `unsigned char` it emits `CMP lo / BCS
1018
+ / CMP hi` with the `LDA #0 / ROL / EOR #1` tail. A C `int` compare is
1019
+ correct at the boundary; what costs is the value it materialises when
1020
+ all you wanted was a branch, and `-O2` folds that away when the result
1021
+ feeds an `if`.
1022
+
1023
+ ### Variations
1024
+
1025
+ - **Three-byte and 16.8 compares** extend the unsigned chain one byte
1026
+ from the top, or carry the `CMP` borrow through two `SBC`s for the
1027
+ signed form (rung 3, not measured here).
1028
+ - **Signed against a constant** needs no `SBC`: `lda v / cmp #lim` is
1029
+ correct when both are in the same half, and `eor #$80` on both sides
1030
+ (`lda v / eor #$80 / cmp #(lim ^ $80)`) turns any signed compare
1031
+ into an unsigned one, the trick the Oscar64 `signed char` code is
1032
+ a variant of.
1033
+ - **Equality only** is `lda a_lo / cmp b_lo / bne no / lda a_hi / cmp
1034
+ b_hi / bne no`; the order of bytes does not matter for it.
1035
+
1036
+ ### Cycle budget
1037
+
1038
+ One call, operands absolute, no result branch, net of an empty
1039
+ `JSR / RTS`, identical on PAL and NTSC:
1040
+
1041
+ | Idiom | Cycles |
1042
+ |---|---|
1043
+ | unsigned 16-bit, high bytes differ | 11 |
1044
+ | unsigned 16-bit, high bytes equal | 18 |
1045
+ | signed 8-bit | 13 (no overflow) / 14 (overflow) |
1046
+ | signed 16-bit | 19 (no overflow) / 20 (overflow) |
1047
+ | range test | 14 |
1048
+
1049
+ Each equals its instruction-table sum. The two signed rows depend on
1050
+ the path: when the subtract does not overflow the `BVC` is taken for 3
1051
+ and the `EOR` is skipped; when it does, the `BVC` falls through for 2
1052
+ and the `EOR #$80` costs 2 more. The recipe times the overflow pairs,
1053
+ so its screen shows 14 and 20. Add the branch that acts on the
1054
+ result: 2 not taken, 3 taken, 4 taken across a page (`pitfalls/cpu.md`,
1055
+ `branch_page_cross_extra_cycle`). Zero-page operands save one cycle per
1056
+ access (rung 3). The `**Cost:**` line carries the signed 16-bit figure,
1057
+ the worst of the five, as one call per frame.
1058
+
1059
+ ### Recipes
1060
+
1061
+ - `recipes/kickassembler/compare-16bit-signed.md` — every idiom over
1062
+ the boundary pairs with flags on screen, full sweeps against a
1063
+ Python checksum, the bare `BMI` miss count, and the timings.
1064
+
1065
+ ## isqrt_16bit — Integer square root of a 16-bit value
1066
+
1067
+ **Complexity:** low
1068
+ **Region:** both
1069
+ **Uses registers:** (none)
1070
+ **Cost:** cycles_per_frame=869
1071
+ **Cost basis:** measured-vice
1072
+ **Cost measured on:** kickassembler-sqrt-atan2 (one call, worst input)
1073
+
1074
+ ### Why
1075
+
1076
+ A distance. A homing missile, a proximity trigger, a guard that
1077
+ notices the player inside a radius, a spring force that weakens with
1078
+ range: each wants `sqrt(dx*dx + dy*dy)`, and the 6510 has no square
1079
+ root. Often the game does not need the root itself, only a comparison
1080
+ against a radius, and the variation below does that without one. When
1081
+ the distance is used as a number, to scale a speed or index a volume
1082
+ table, this routine gives the floor of the root of any 16-bit value
1083
+ in under 900 cycles with no table. The figures on this technique were
1084
+ measured with CIA1 timer A, not the timer B the page's introduction
1085
+ names, in `recipes/kickassembler/sqrt-atan2.md` (rung 1, VICE x64sc
1086
+ 3.10, PAL and NTSC identical).
1087
+
1088
+ ### How
1089
+
1090
+ Restoring shift-and-subtract, the same shape as `division_8_16bit`
1091
+ above but taking two bits of the input per pass and testing a trial
1092
+ value that grows with the answer. Keep a 16-bit remainder and an
1093
+ 8-bit root, both zero. Eight passes: shift the top two bits of the
1094
+ input into the bottom of the remainder; form the trial
1095
+ `4 * root + 1`; double the root; if the remainder is at least the
1096
+ trial, subtract it and set the root's new bottom bit. After eight
1097
+ passes the root is `floor(sqrt(n))` and the remainder is
1098
+ `n - root * root`. The trial reaches 509 and the remainder 1,019,
1099
+ so both need two bytes and the compare is a 16-bit one, high bytes
1100
+ first, low bytes only when they tie; the branch into the subtract
1101
+ arrives with the carry set on both paths, so the `SBC` is exact with
1102
+ no `SEC`. The recipe's listing is the reference form; it was checked
1103
+ for the exact root on 35 cases against `math.isqrt` and for
1104
+ `rem <= 2 * root`, which is `n < (root + 1)^2`, on all 65,536 inputs
1105
+ on the machine.
1106
+
1107
+ A table-assisted form was considered and not used. A 256-entry table
1108
+ gives the root of an 8-bit value directly, but a 16-bit input needs
1109
+ either a 64 KB table or a two-stage estimate and a correction step
1110
+ with a multiply, and the loop is simpler, needs no memory and is fast
1111
+ enough for a routine called a few times a frame.
1112
+
1113
+ ### Why it works
1114
+
1115
+ Each pass decides one bit of the root from the top, keeping the
1116
+ invariant `n_seen = root^2 + rem` over the bits consumed so far. If
1117
+ the next root bit is 1 the root becomes `2 * root + 1` and its square
1118
+ grows by `(2 * root + 1)^2 - (2 * root)^2 = 4 * root + 1`, which is
1119
+ the trial; the bit is 1 exactly when the remainder, now holding two
1120
+ more bits of `n`, can absorb that. It is long division with a
1121
+ divisor that is rebuilt from the quotient so far.
1122
+
1123
+ ### Variations
1124
+
1125
+ - **Compare squares, no root.** For "is the target within radius r"
1126
+ compute `dx*dx + dy*dy` with `table_multiply_8x8` (two multiplies,
1127
+ 52 cycles each with the tables in place) and compare the 16-bit sum
1128
+ against `r*r` held as a constant. About 130 cycles by the
1129
+ instruction table (rung 3, not measured here), and exact. This is
1130
+ the common case in a game; take the root only when the distance is
1131
+ used as a number.
1132
+ - **Octagonal estimate.** `max + min / 2` over `|dx|` and `|dy|` is
1133
+ within about 12 per cent of the true distance and costs a compare
1134
+ and a shift (rung 4, not measured here). Good enough to rank targets
1135
+ by nearness.
1136
+ - **8-bit input.** For `n` below 256 a 256-byte table of roots is one
1137
+ indexed load; the loop above with four passes over an 8-bit input
1138
+ is the same routine at half the cost (rung 3, not measured here).
1139
+
1140
+ ### Cycle budget
1141
+
1142
+ Body only, operands in zero page, net of `JSR` and `RTS`, measured
1143
+ over every input: 869 cycles at worst, first reached at 65025, which
1144
+ is 255 squared; 65535 costs 862. The instruction-table model of the
1145
+ loop gives the same two figures and 869 as its maximum, so the loop is
1146
+ about 105 cycles a pass plus 13 of setup. The Cost line carries the
1147
+ worst case; one call is 4.4 per cent of a PAL frame. Keep the loop in
1148
+ one page: a layout with its branch across a page boundary measured
1149
+ 876, seven taken branches at one cycle each.
1150
+
1151
+ ### Recipes
1152
+
1153
+ - `recipes/kickassembler/sqrt-atan2.md` — the root and the atan2
1154
+ below, 35 root cases against a Python model, the remainder
1155
+ invariant on every input, and the worst-case timing sweep on
1156
+ screen.
1157
+
1158
+ ## atan2_8bit — Aim angle from a signed offset, 256 units a turn
1159
+
1160
+ **Complexity:** medium
1161
+ **Region:** both
1162
+ **Uses registers:** (none)
1163
+ **Requires:** division_8_16bit
1164
+ **Cost:** cycles_per_frame=381
1165
+ **Cost basis:** measured-vice
1166
+ **Cost measured on:** kickassembler-sqrt-atan2 (one call, worst pair)
1167
+
1168
+ ### Why
1169
+
1170
+ A turret that turns toward the player, an enemy that fires along the
1171
+ line to the ship, a homing shot: each needs the angle of the offset
1172
+ `(dx, dy)` from itself to the target, in the same units as its sine
1173
+ table so the angle can be turned straight back into a velocity. A
1174
+ byte angle, 256 units a turn, is that unit on this machine. The
1175
+ routine here takes signed 8-bit `dx` and `dy` and returns the angle
1176
+ to within one unit, measured over every one of the 65,536 pairs by
1177
+ the host model and confirmed on the machine over 36 cases in
1178
+ `recipes/kickassembler/sqrt-atan2.md` (rung 1, VICE x64sc 3.10). The
1179
+ convention is 0 for `+dx`, 64 for `+dy`, so with screen y growing
1180
+ downward the angle runs clockwise as seen on the screen.
1181
+
1182
+ ### How
1183
+
1184
+ Three steps. Fold: take `|dx|` and `|dy|` by testing bit 7 of each
1185
+ input with `BPL` and negating, remember the two signs, and compare
1186
+ the magnitudes; the smaller over the larger is a ratio between 0 and
1187
+ 1, which is the first octant. Look up: the ratio index is
1188
+ `floor(min * 256 / max)`, an 8-bit quotient from an eight-pass
1189
+ shift-and-subtract divide, the loop of `division_8_16bit` with the
1190
+ dividend's high byte equal to `min` and its low byte zero; because
1191
+ `max` is at most 128 the remainder never needs the ninth-bit guard
1192
+ the general loop carries. The index reads a 256-byte table whose
1193
+ entry `i` is `round(atan(i / 256) * 256 / 2pi)`, 0 to 32; the recipe
1194
+ quotes the Python that prints it. Unfold: if `|dy|` was the larger
1195
+ the angle is `64 - a`; if `dx` was negative, `128 - a`; if `dy` was
1196
+ negative, `256 - a`. Equal magnitudes are exactly 32 and are tested
1197
+ for, since the ratio 256 does not fit the index; `(0, 0)` returns 0.
1198
+
1199
+ The inputs must be true 8-bit differences. A game that forms `dx` as
1200
+ `target_x - x` with `SEC / SBC` over positions more than 127 apart
1201
+ gets a wrapped byte whose sign is wrong, and the fold then answers
1202
+ for the opposite half of the circle. Clamp the difference, or take
1203
+ it in 16 bits and use the high byte's sign with a clamped low byte
1204
+ (`pitfalls/cpu.md`, `signed_compare_bmi_overflow`).
1205
+
1206
+ ### Why it works
1207
+
1208
+ `atan2` over the whole plane is eight copies of `atan` over one
1209
+ octant, related by reflection in the axes and the diagonal, and each
1210
+ reflection is a subtraction from a constant: `64 - a` across the
1211
+ diagonal, `128 - a` across the vertical axis, `-a` across the
1212
+ horizontal. The octant needs only `atan` of a ratio in `[0, 1)`,
1213
+ which is a 256-entry table when the ratio is quantised to 1/256. The
1214
+ angle changes by less than 0.2 units across one ratio step, so the
1215
+ truncated index costs less than that and the rounding of the table
1216
+ entry costs at most half a unit; over every pair the largest error is
1217
+ 1 unit, on 3,968 of the 65,536 pairs. Those pairs are spread across
1218
+ the whole octant, not gathered at the axes: counted by tenths of the
1219
+ ratio `min / max`, the first three tenths from the axis hold 1,680 of
1220
+ them and the last three before the diagonal 688 (the generator's
1221
+ model, rung 3).
1222
+
1223
+ ### Variations
1224
+
1225
+ - **Sixteen directions, no table, no divide.** The sign bits and the
1226
+ magnitude compare give the octant; one more compare, `min * 2 < max`
1227
+ (a shift and a `CMP`), splits each octant at 26.6 degrees, which is
1228
+ `atan(1/2)`, so the two sectors are 26.6 and 18.4 degrees wide, not
1229
+ equal. An equal split at 22.5 degrees needs `min / max < 0.414`,
1230
+ which `min * 2 + min / 2 < max` gives closely: two shifts, an add and
1231
+ the `CMP`, a few cycles more. Sixteen sectors from three compares,
1232
+ about 40 cycles for the unequal form by the instruction table
1233
+ (rung 3, not measured here), enough for a sprite with sixteen facing
1234
+ frames or a shot that picks one of sixteen velocity pairs.
1235
+ - **Smaller table.** Index with `ratio >> 2` for a 64-byte table or
1236
+ `ratio >> 3` for 32 bytes; the error grows with the step and was
1237
+ not measured here.
1238
+ - **Angle to velocity.** Feed the result to a sine table with 256
1239
+ entries a turn (`sine_table_generation`, `techniques/cpu-cycle-tricks.md`)
1240
+ for `dy` and the same table 64 entries on for `dx`; the angle unit
1241
+ was chosen so no scaling sits between the two.
1242
+
1243
+ ### Cycle budget
1244
+
1245
+ Body only, zero-page operands, net of `JSR` and `RTS`, measured over
1246
+ every pair: 381 cycles at worst, first reached at `dx = -127`,
1247
+ `dy = -128`, a pair that negates both inputs, takes the second-octant
1248
+ path and subtracts on most divide passes. The divide is about 290 of
1249
+ that; the fold and unfold about 90 (rung 3, split from the
1250
+ instruction table; the total is the measured figure). The Cost line
1251
+ carries the worst case, 1.9 per cent of a PAL frame per call. The
1252
+ same page-crossing caution as the root applies: the recipe holds both
1253
+ routines in one page with an `.assert`.
1254
+
1255
+ ### Recipes
1256
+
1257
+ - `recipes/kickassembler/sqrt-atan2.md` — the fold, divide and table
1258
+ as listed, 36 angle cases over the axes and every octant against a
1259
+ Python model, and the worst-case timing sweep over all 65,536 pairs.
1260
+
1261
+ ## procedural_seed_universe — A reproducible galaxy from three 16-bit seeds
1262
+
1263
+ **Complexity:** low
1264
+ **Region:** both
1265
+ **Uses registers:** (none)
1266
+ **Cost:** cycles_per_frame=733
1267
+ **Cost basis:** measured-vice
1268
+ **Cost measured on:** kickassembler-wireframe-ships (one system per call, display on)
1269
+ **Claims:** none
1270
+ **Claims basis:** derived-listing
1271
+
1272
+ ### Why
1273
+
1274
+ A trading or exploration game wants hundreds of named places, and the
1275
+ machine has no room to store them. Elite's answer is to store six bytes
1276
+ and regenerate every system from them on demand. The same seeds always
1277
+ give the same galaxy, so nothing but the seeds and the player's
1278
+ position needs saving.
1279
+
1280
+ ### How
1281
+
1282
+ Keep three 16-bit seeds `s0`, `s1`, `s2`. One twist is a
1283
+ Tribonacci-style step, 16-bit with carries out of bit 15 dropped:
1284
+
1285
+ ```text
1286
+ s0' = s1
1287
+ s1' = s2
1288
+ s2' = s0 + s1 + s2
1289
+ ```
1290
+
1291
+ A system's data comes from bits of its seeds, and four twists move the
1292
+ seeds on to the next system. Elite's rules for galaxy 1 (Mark Moxon's
1293
+ deep dives):
1294
+
1295
+ - Start seeds `s0 = $5A4A`, `s1 = $0248`, `s2 = $B753` (Tibedied).
1296
+ - Galactic x is the high byte of `s1`; y is the high byte of `s0`
1297
+ shifted right once, because the chart is half as tall as it is wide.
1298
+ - The name is two-letter pairs. Before each of the first three twists,
1299
+ bits 0-4 of the high byte of `s2` pick a pair from a 32-entry table;
1300
+ 0 adds nothing. If bit 6 of the low byte of `s0` is set, the fourth
1301
+ twist adds a fourth pair, for up to 8 letters. One table entry has a
1302
+ silent second letter.
1303
+ - A galactic jump rotates each of the six seed bytes left by one bit, so
1304
+ eight jumps return to galaxy 1: 8 galaxies of 256 systems.
1305
+
1306
+ In a game, keep the current system's seeds, twist forwards to walk the
1307
+ galaxy, and restart from the galaxy's seeds to go back.
1308
+
1309
+ ### Why it works
1310
+
1311
+ The twist is a linear recurrence over 16-bit integers, so it is fully
1312
+ determined by its six bytes and cheap on a 6502: two 16-bit adds and
1313
+ two moves. The name routine consumes the same twists
1314
+ that advance to the next system, so one routine yields the name and the
1315
+ next seeds.
1316
+
1317
+ Moxon gives a worked example that checks an implementation: Lave's
1318
+ seeds are `s0 = $AD38`, `s1 = $149C`, `s2 = $151D`, its name pairs are
1319
+ tokens 149 (LA) and 150 (VE), and it is a short name (bit 6 of `$38` is
1320
+ clear). The recipe's generator, original code following those rules,
1321
+ produces LAVE as the eighth system of galaxy 1 with those seeds, at
1322
+ x = 20, y = 86 (`$AD >> 1`). Its first eight names, TIBEDIED, QUBE,
1323
+ LELEER, BIARGE, XEQUERIN, TIRAOR, RABEDIRA, LAVE, match its Python model
1324
+ on PAL and NTSC. The seed and bit rules are Elite's as Moxon documents
1325
+ them. The token string is data from Elite (Ian Bell and David Braben,
1326
+ 1984) as Moxon lists it at QQ16. Index 0 is written `..` because it is
1327
+ never used, and the silent letter is written `.`.
1328
+
1329
+ ### Variations
1330
+
1331
+ - **Your own world.** Keep the twist and choose your own fields: a
1332
+ planet's economy, tech level or government from other seed bits
1333
+ (Elite does this too; Moxon's "Generating system data").
1334
+ - **More or fewer twists.** Four twists a system is Elite's choice. Any
1335
+ fixed count gives a reproducible sequence; fewer cost less. How the
1336
+ count changes the look of the galaxy was not measured here.
1337
+ - **Contrast with an LFSR.** `lfsr_random` above steps one register and
1338
+ suits noise and dice. The seed twist is the same idea with a 48-bit
1339
+ state and data read straight from the state bits.
1340
+
1341
+ ### Cycle budget
1342
+
1343
+ One system (coordinates, name, four twists) measured 733 cycles at
1344
+ worst over the first eight systems, in VICE x64sc on PAL and NTSC with
1345
+ the display on, CIA1-timed around the call. A twist is 76 cycles with
1346
+ its `JSR` and `RTS`, by the instruction table, so four twists are about
1347
+ 300 of that. The Cost line is one system per call. Walking all 256
1348
+ systems of a galaxy at that rate is about 188,000 cycles (arithmetic),
1349
+ nearly ten PAL frames: a galaxy chart is drawn over several frames, or
1350
+ its x and y are cached in 512 bytes.
1351
+
1352
+ ### Recipes
1353
+
1354
+ - `recipes/kickassembler/wireframe-ships.md`: generates and prints the
1355
+ first eight systems of galaxy 1 and checks names and coordinates
1356
+ against a Python model.
1357
+
1358
+ ### Sources
1359
+
1360
+ - Mark Moxon, "Twisting the system seeds":
1361
+ https://elite.bbcelite.com/deep_dives/twisting_the_system_seeds.html
1362
+ - Mark Moxon, "Generating system names":
1363
+ https://elite.bbcelite.com/deep_dives/generating_system_names.html
1364
+ - Mark Moxon, "Generating system data":
1365
+ https://elite.bbcelite.com/deep_dives/generating_system_data.html
1366
+ - Mark Moxon, C64 Elite variable QQ16 (the two-letter tokens):
1367
+ https://elite.bbcelite.com/c64/main/variable/qq16.html
1368
+
1369
+ ## basic_rom_float_calls — The BASIC ROM's floating-point routines, called from machine code
1370
+
1371
+ **Complexity:** low
1372
+ **Region:** both
1373
+ **Uses registers:** (none)
1374
+ **Requires:** cpu_io_port_bank
1375
+ **Cost:** cycles_per_frame=1079, zp_bytes=26
1376
+ **Cost basis:** measured-vice
1377
+ **Cost measured on:** kickassembler-basic-float-calls (one FMULT call, display off; the zero-page span $57 to $70 the calls use)
1378
+
1379
+ ### Why
1380
+
1381
+ A 40-bit float library for free: five-byte add, subtract, multiply,
1382
+ divide, square root, powers, logs, trig, integer conversion in both
1383
+ directions and a decimal printer, all in the BASIC ROM that a `SYS`
1384
+ leaves banked in. When speed does not matter, a level editor's
1385
+ statistics, a high-score table's percentage, a one-off table built at
1386
+ start-up, a trainer's or a tool's arithmetic, it is not worth writing.
1387
+ It is worth nothing in a frame loop: the multiply alone is a twentieth
1388
+ of a PAL frame and the root is more than two frames (measured, below).
1389
+ The figures on this technique were measured with CIA1 timer A in
1390
+ `recipes/kickassembler/basic-float-calls.md` (rung 1, VICE x64sc 3.10,
1391
+ PAL and NTSC identical), and every address was read from the ROM with
1392
+ the monitor's `d` command before it was used; the dump is on that page.
1393
+
1394
+ These are BASIC ROM routines, not KERNAL ones. They have no jump-table
1395
+ entry and no vector, so they are not KERNAL routine nodes in this
1396
+ knowledge base and the `uses_kernal` line of a recipe does not name
1397
+ them; the recipe lists the KERNAL `CHROUT` it prints with and nothing
1398
+ else. Their addresses are fixed by the BASIC ROM revision (901226-01
1399
+ here) and do not move with a KERNAL swap.
1400
+
1401
+ ### How
1402
+
1403
+ Bank the BASIC ROM in: `$01 = $37`, which is the value at `SYS`. The
1404
+ routines, with the first instruction the monitor showed at each and the
1405
+ calling convention:
1406
+
1407
+ | Name | Address | First instruction | Convention |
1408
+ |---|---|---|---|
1409
+ | `GIVAYF` | `$B391` | `LDX #$00` | A = high byte, Y = low byte of a signed 16-bit integer; FAC = that value |
1410
+ | `MOVFM` | `$BBA2` | `STA $22` | A = low, Y = high of a five-byte float; FAC = it |
1411
+ | `MOVMF` | `$BBD4` | `JSR $BC1B` | X = low, Y = high of five bytes; rounds FAC and stores it there |
1412
+ | `CONUPK` | `$BA8C` | `STA $22` | A/Y as `MOVFM`; ARG = it. The four arithmetic entries call it first |
1413
+ | `FADD` | `$B867` | `JSR $BA8C` | FAC = mem(A/Y) + FAC |
1414
+ | `FSUB` | `$B850` | `JSR $BA8C` | FAC = mem(A/Y) - FAC |
1415
+ | `FMULT` | `$BA28` | `JSR $BA8C` | FAC = mem(A/Y) * FAC |
1416
+ | `FDIV` | `$BB0F` | `JSR $BA8C` | FAC = mem(A/Y) / FAC (the divisor is the one in FAC) |
1417
+ | `FSQR` | `$BF71` | `JSR $BC0C` | FAC = FAC ^ 0.5: FAC to ARG, `MOVFM` of `80 00 00 00 00` at `$BF11`, then the power routine at `$BF7B` |
1418
+ | `FOUT` | `$BDDD` | `LDY #$01` | PETSCII string of FAC at `$0100`, zero-terminated; returns A = `$00`, Y = `$01`, the pointer |
1419
+ | `LINPRT` | `$BDCD` | `STA $62` | prints the unsigned 16-bit integer A (high), X (low) through `CHROUT` |
1420
+
1421
+ The pattern for a sum is: get the first operand into memory as five
1422
+ bytes (a constant in the listing, or `GIVAYF` then `MOVMF` to a
1423
+ scratch), get the second into FAC (`GIVAYF` or `MOVFM`), call the
1424
+ arithmetic entry with A/Y pointing at the memory operand, and read FAC
1425
+ or `MOVMF` it out. For `x / y` put `y` in FAC and point A/Y at `x`.
1426
+ `FOUT` writes into the bottom of the stack page; the string is
1427
+ overwritten by the next `FOUT` and, since it starts at `$0100`, by any
1428
+ stack that grows deep enough, so copy it or print it at once. `FOUT`
1429
+ also alters FAC.
1430
+
1431
+ A five-byte float in memory is an exponent byte in excess-128 form
1432
+ (`$81` is 2^0, `$00` is the value zero) followed by a 32-bit
1433
+ big-endian mantissa whose top bit is implied set and whose bit 7 in
1434
+ the first byte carries the sign instead (0 positive). So 1 is
1435
+ `81 00 00 00 00`, 2 is `82 00 00 00 00`, 10 is `84 20 00 00 00`, and
1436
+ 0.1 is `7D 4C CC CC CD`, which the recipe derives and which BASIC's own
1437
+ `1 / 10` reproduced byte for byte. The recipe's `0.1 + 0.2` prints
1438
+ `.3` and `2^24 + 1` prints `16777217`: the mantissa is 32 bits, so the
1439
+ integer that a 24-bit single-precision format loses is exact here.
1440
+
1441
+ The calls own zero page `$57` to `$70`, BASIC's work area (the KERNAL
1442
+ keeps its own variables from `$90` up, by the memory map, rung 4).
1443
+ Over the recipe's whole sequence the bytes that changed were `$57` to
1444
+ `$5D`, `$61` to `$66` (FAC), `$69` to `$6B` and `$6D` (ARG) and `$6F`;
1445
+ `$5E` to `$60`, `$67`, `$68`, `$6C`, `$6E` and `$70` did not change for
1446
+ those inputs, which is not a promise that they never do. A program that
1447
+ keeps its own variables in that span loses them on the first call; one
1448
+ that returns to BASIC afterwards has clobbered nothing BASIC did not
1449
+ expect.
1450
+
1451
+ ### Why it works
1452
+
1453
+ BASIC's expression evaluator is the same code with a parser in front:
1454
+ every operator ends as one of these entries with one operand in FAC and
1455
+ the other unpacked into ARG by `CONUPK`. Calling the entries directly
1456
+ skips the tokeniser, the variable lookup and the type checks, and gets
1457
+ the arithmetic at the ROM's own speed. The ROM is visible to the CPU
1458
+ only while bit 0 of `$01` is set with bit 1 also set (`$37`, or `$33`
1459
+ with the character ROM at `$D000`); with it clear a `JSR` fetches
1460
+ whatever the RAM under `$A000` holds. In the recipe's control run that
1461
+ was a zero byte, so the first call executed `BRK`, and the KERNAL's
1462
+ `BRK` handler ran IOINIT, which writes `$E7` to `$01` and banks the ROM
1463
+ back in, cleared the screen and warm-started BASIC: the program
1464
+ vanished into a `READY.` prompt with its verdict byte untouched. The
1465
+ failure is a silent restart, not a wrong number.
1466
+
1467
+ ### Cycle budget
1468
+
1469
+ One call each, the input shown, display off, net of a 21-cycle empty
1470
+ call, PAL and NTSC identical: `FMULT` 1,079 (2 * 2), `FDIV` 2,409
1471
+ (355 / 113), `FSQR` 43,752 (sqrt 2), `FOUT` 7,412 (1.41421356). The
1472
+ Cost line carries the multiply as the one call a frame a game might
1473
+ plausibly make; a root is 2.2 PAL frames and does not belong inside
1474
+ one. With the display on the same `FSQR` read 45,975 and `FOUT` 7,969:
1475
+ the CIA counts the cycles badlines take from the CPU, so time these
1476
+ calls with DEN off or accept the stall in the figure. No worst case was
1477
+ sought; the costs vary with the operands' exponents and mantissas and
1478
+ `FOUT` with the digit count.
1479
+
1480
+ ### Variations
1481
+
1482
+ - **Print an integer.** `LINPRT` at `$BDCD`, A = high, X = low, prints
1483
+ the unsigned 16-bit value through `CHROUT` with no leading space, as
1484
+ a line number is printed. The recipe prints its four cycle counts
1485
+ with it. It goes through `FOUT` and so costs at least `FOUT`'s
1486
+ 7,412 (arithmetic from the measured `FOUT`; `LINPRT` itself was not
1487
+ timed).
1488
+ - **Random numbers.** `RND` is at `$E097`, in the KERNAL ROM image
1489
+ (the BASIC interpreter's tail lives there), so it needs both ROMs
1490
+ in. Its first instructions are `JSR $BC2B` (sign of FAC) then `BMI`,
1491
+ `BNE` and `JSR $FFF3` (IOBASE), which is the three-way split of the
1492
+ published listings, negative reseeds, positive steps, zero reads the
1493
+ CIA timers (the roles rung 4, the instructions rung 1). Put
1494
+ the argument in FAC and call it; the result is a float in FAC in
1495
+ [0, 1). The seed's location was not measured here.
1496
+ - **Subtract and the missing ones.** `FSUB` at `$B850` is verified
1497
+ above and is `mem - FAC`. Powers, logs and trig were not exercised
1498
+ here; the technique gives no address for them.
1499
+ - **Integer out.** For a result back as an integer, the ROM's
1500
+ float-to-integer conversions were not verified in this session; the
1501
+ measured route is `FOUT` and parsing the digits, or `MOVMF` and
1502
+ reading the mantissa against the exponent.
1503
+
1504
+ ### Pitfalls
1505
+
1506
+ - `pitfalls/banking.md`, `ram_under_rom_traps`: a program that stores
1507
+ data at `$A000` to `$BFFF` while the ROM is in writes to the RAM
1508
+ underneath and reads the ROM back; and one that banks the ROM out to
1509
+ use that RAM cannot call these routines until it banks it in again,
1510
+ which the recipe's control shows ends in a `BRK`.
1511
+
1512
+ ### Recipes
1513
+
1514
+ - `recipes/kickassembler/basic-float-calls.md`: the four sums on
1515
+ screen, the string compares, the ROM dump at every entry, the
1516
+ zero-page before-and-after, the four timings and the banked-out
1517
+ control.
1518
+
1519
+ ### Sources
1520
+
1521
+ - The BASIC ROM image 901226-01 and KERNAL 901227-03 as shipped with
1522
+ VICE 3.10, read with the monitor's `d` command (rung 1). The routine
1523
+ names are the conventional labels from the published ROM
1524
+ disassemblies (rung 4); the addresses and first instructions are not.