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,1144 @@
1
+ ---
2
+ category: music
3
+ chip: SID
4
+ ---
5
+
6
+ <!-- doc-type: technique-reference -->
7
+
8
+ # SID Music and Audio Techniques
9
+
10
+ The MOS 6581/8580 SID chip is the reason C64 music has its own aesthetic identity. Three voices, a shared analog filter, and a handful of hardware quirks add up to an instrument that decades of composers have explored in depth. The techniques in this document cover the mechanical foundation — how to set frequency, waveform, and envelope per voice; how to wire the filter; how the init/play convention works; why the two chip revisions sound different; and how to push into territory beyond simple three-voice music: 4-bit and 8-bit sample playback, two-chip stereo setups, and the emulator fidelity gaps you need to know about when testing recipes.
11
+
12
+ All SID registers $D400-$D418 are write-only. The chip cannot be read back; code must maintain software shadow copies when it needs to modify individual bits. The four read-only registers ($D419-$D41C) return paddle inputs and voice 3 status; they are not discussed here. For the full register map see [docs/hardware/sid-reference.md](../hardware/sid-reference.md).
13
+
14
+ The Oscar64 canonical interface for this chip lives in `c64/sid.h`, which defines the `struct SID` layout (three `Voice` structs followed by filter registers) and the `sid` macro expanding to `(*((struct SID *)0xd400))`. Frequency macros `SID_FREQ_PAL(f)` and `SID_FREQ_NTSC(f)` compute the 16-bit register value from a Hz argument using fixed-point arithmetic.
15
+
16
+ ---
17
+
18
+ ## sid_voice_setup — Frequency / waveform / ADSR per voice
19
+
20
+ **Complexity:** low
21
+ **Region:** both
22
+ **Uses registers:** D400, D401, D402, D403, D404, D405, D406, D407, D408, D409, D40A, D40B, D40C, D40D, D40E, D40F, D410, D411, D412, D413, D414
23
+
24
+ ### Why
25
+
26
+ Playing a musical note on the SID requires configuring seven registers per voice: two bytes of frequency, two bytes of pulse width, one control byte selecting the waveform and triggering the envelope, and two bytes encoding the four ADSR envelope parameters. Understanding this layout is the prerequisite for every other SID technique — filter routing, play routines, and digi all layer on top of it.
27
+
28
+ ### How
29
+
30
+ Voice N (N = 1, 2, 3) occupies seven consecutive registers starting at base $D400 + 7*(N-1):
31
+
32
+ | Offset | Register | Function |
33
+ |--------|----------|----------|
34
+ | +0 | FRELO | Frequency low byte (F7-F0) |
35
+ | +1 | FREHI | Frequency high byte (F15-F8) |
36
+ | +2 | PWLO | Pulse width low byte (PW7-PW0) |
37
+ | +3 | PWHI | Pulse width high nibble (PW11-PW8, bits 7-4 unused) |
38
+ | +4 | VCREG | Control: waveform select + GATE/SYNC/RING/TEST |
39
+ | +5 | ATDCY | Attack rate (bits 7-4) / Decay rate (bits 3-0) |
40
+ | +6 | SUREL | Sustain level (bits 7-4) / Release rate (bits 3-0) |
41
+
42
+ The 16-bit frequency value F sets the oscillator pitch. The SID phase accumulator is 24 bits wide; every system clock cycle F is added to it. The audible frequency in Hz is:
43
+
44
+ ```
45
+ f = F * Phi2 / 2^24
46
+ ```
47
+
48
+ where `Phi2` is the system clock: 985248 Hz on PAL, 1022727 Hz on NTSC. Inverting for a target note frequency:
49
+
50
+ ```
51
+ F_PAL = f * 16777216 / 985248 ~= f * 17.0284
52
+ F_NTSC = f * 16777216 / 1022727 ~= f * 16.4044
53
+ ```
54
+
55
+ (Earlier figures of 17.0288 and 16.4046 here were arithmetic slips; the quotients are 17.02842 and 16.40439.) The PAL register value for A4 (440 Hz) is $1D45 (7492.5, rounded up); note that `sid.h`'s `SID_FREQ_PAL(440)` truncates to $1D44, one step below. Most SID players ship a 96-entry table (8 octaves × 12 semitones) with precomputed PAL and NTSC values rather than computing at runtime.
56
+
57
+ In Oscar64, using `c64/sid.h`:
58
+
59
+ ```c
60
+ #include <c64/sid.h>
61
+
62
+ // Play A4 on voice 1 with sawtooth waveform, medium attack
63
+ void play_a4_voice1(void)
64
+ {
65
+ // Zero out voice 1 first (good habit)
66
+ sid.voices[0].freq = 0;
67
+ sid.voices[0].pwm = 0;
68
+ sid.voices[0].ctrl = 0;
69
+ sid.voices[0].attdec = 0;
70
+ sid.voices[0].susrel = 0;
71
+
72
+ // Frequency: A4, PAL
73
+ sid.voices[0].freq = SID_FREQ_PAL(440);
74
+
75
+ // Pulse width: 50% square wave (only relevant when RECT bit set)
76
+ sid.voices[0].pwm = 0x0800;
77
+
78
+ // ADSR: attack 16ms, decay 48ms, sustain level 9/15, release 300ms
79
+ sid.voices[0].attdec = SID_ATK_16 | SID_DKY_48;
80
+ sid.voices[0].susrel = (9 << 4) | SID_DKY_300;
81
+
82
+ // Control: sawtooth waveform, GATE on -> starts attack phase
83
+ sid.voices[0].ctrl = SID_CTRL_SAW | SID_CTRL_GATE;
84
+ }
85
+
86
+ // Release note (start release phase)
87
+ void release_voice1(void)
88
+ {
89
+ // Keep waveform bits, clear GATE
90
+ sid.voices[0].ctrl = SID_CTRL_SAW; // GATE bit not set
91
+ }
92
+ ```
93
+
94
+ The VCREG control byte bit layout (all three voices share this format):
95
+
96
+ | Bit | Name | Meaning |
97
+ |-----|------|---------|
98
+ | 7 | NOISE | Enable noise waveform (23-bit LFSR output) |
99
+ | 6 | PULSE/RECT | Enable pulse waveform (duty cycle from PW11-PW0) |
100
+ | 5 | SAW | Enable sawtooth waveform |
101
+ | 4 | TRI | Enable triangle waveform |
102
+ | 3 | TEST | Reset accumulator to zero and hold; the noise LFSR stops shifting and keeps its contents (it is not reset — an earlier version of this row said it was) |
103
+ | 2 | RING | Ring-modulate triangle with previous voice's oscillator MSB |
104
+ | 1 | SYNC | Hard-sync accumulator to previous voice's oscillator MSB transitions |
105
+ | 0 | GATE | 0 = release phase; 1 = attack-decay-sustain phase |
106
+
107
+ Setting GATE starts the attack. Clearing GATE starts the release from the current envelope level. Waveform bits can be ORed together; the output is the bitwise AND of each enabled waveform's 12-bit value. See the [sid-reference](../hardware/sid-reference.md#d404--vcreg1--voice-1-control-register-w) for combined-waveform behavior.
108
+
109
+ ### Why it works
110
+
111
+ The phase accumulator advances by F every cycle. When it overflows the 24-bit range it wraps, completing one oscillator period. The waveform generator derives its output from the accumulator's upper bits: the sawtooth is the top 12 bits directly; the triangle folds them symmetrically; the pulse compares the top 12 bits against PW11-PW0 and outputs either $FFF or $000. The noise waveform takes eight bits from a 23-bit LFSR that is shifted each time bit 19 of the accumulator rises — sixteen shifts per oscillator period, not one per period (measured in VICE x64sc reSID: at F=$1000 the $D41B value holds for about 256 cycles between changes, where once-per-period clocking would hold it for 4,096; over 65,536 back-to-back polls it changed 5,617 times against roughly 313 expected from MSB clocking; an earlier version of this sentence said the LFSR was clocked by the accumulator's MSB). The ADSR envelope generator multiplies the waveform output by the current envelope level (0-$FF), giving notes their amplitude shape.
112
+
113
+ ### Variations
114
+
115
+ **Pulse-width modulation (PWM) pads.** Route an LFO into the pulse-width registers while holding the PULSE waveform. Voice 3's oscillator output ($D41B) is the standard LFO source; read it each frame and write the result to the target voice's PWHI register. Produces the classic SID "wobbling pad" timbre.
116
+
117
+ **Ring modulation.** Set the RING bit alongside TRI. The triangle's MSB is XORed with the previous voice's oscillator MSB (voice 1 modulates against voice 3; voice 2 against voice 1; voice 3 against voice 2). Produces inharmonic bell-like tones. The modulator voice must have a non-zero frequency but does not need to be gated or audible.
118
+
119
+ **Oscillator sync.** Set the SYNC bit. When the modulator voice's accumulator MSB rises, this voice's accumulator resets. Sweep this voice's frequency while holding the modulator steady for a classic sync sweep sound.
120
+
121
+ **Noise drums.** Set the NOISE bit with a short attack, zero sustain, and short release. Each gate-on starts a percussive burst. Setting TEST briefly before each hit restarts the oscillator from accumulator zero, so the pitched part of the drum and the LFSR's clocking phase are locked; it does not re-seed the noise, which resumes from wherever the LFSR stopped (an earlier version said TEST re-seeded the LFSR). Only a TEST held for about two PAL frames on a 6581 leaves the register at a known all-ones state — see the sid-reference TEST entry; on an 8580 that takes seconds.
122
+
123
+ ### Cycle budget
124
+
125
+ Voice setup writes are not time-critical — they happen before the note sounds. The IRQ overhead for a play routine that updates all three voices and the filter is approximately 9 registers × 4 cycles per STA = 36 cycles minimum, plus subroutine overhead and frequency-table lookups. At 50 Hz (PAL) a frame is 19656 cycles; a minimal three-voice update consumes under 0.3% of available cycles.
126
+
127
+ ### Recipes
128
+
129
+ - `recipes/oscar64/sid-music-player.md`
130
+
131
+ ---
132
+
133
+ ## sid_filter_routing — Filter cutoff / resonance / voice-routing setup
134
+
135
+ **Complexity:** medium
136
+ **Region:** both
137
+ **Uses registers:** D415, D416, D417, D418
138
+ **Requires:** sid_voice_setup
139
+
140
+ ### Why
141
+
142
+ The SID's analog multi-mode filter is what gives it harmonic flexibility beyond a raw oscillator. Without filter work, three-voice SID tunes sound flat and mechanical. The filter lets composers carve high-frequency content off bass voices, sweep a resonant peak across a lead, or build a vowel-formant by combining low-pass and high-pass modes. Understanding the four filter registers is the bridge from "playing notes" to "making SID music sound like SID music."
143
+
144
+ ### How
145
+
146
+ The filter occupies four registers at $D415-$D418:
147
+
148
+ **$D415 — CUTLO: Filter cutoff low bits.**
149
+ Bits 7-3 are unused (writes ignored). Bits 2-0 (FC2-FC0) are the low three bits of the 11-bit cutoff value. Many tunes leave this at zero and treat $D416 as an 8-bit cutoff.
150
+
151
+ **$D416 — CUTHI: Filter cutoff high bits.**
152
+ Bits 7-0 (FC10-FC3) are the high 8 bits of the 11-bit cutoff. The combined value is:
153
+ ```
154
+ cutoff11 = ($D416 << 3) | ($D415 & 7)
155
+ ```
156
+ Range 0-2047. On 6581 the Hz response is non-linear and chip-dependent (see `sid_8580_vs_6581_differences`). On 8580 it is linear.
157
+
158
+ **$D417 — RESON: Resonance and routing.**
159
+
160
+ | Bits | Name | Meaning |
161
+ |------|------|---------|
162
+ | 7-4 | RES | Resonance Q, 0 (none) to 15 (maximum) |
163
+ | 3 | FILTEX | Route external audio input through filter |
164
+ | 2 | FILT3 | Route voice 3 through filter |
165
+ | 1 | FILT2 | Route voice 2 through filter |
166
+ | 0 | FILT1 | Route voice 1 through filter |
167
+
168
+ **$D418 — SIGVOL: Filter mode, voice-3 mute, master volume.**
169
+
170
+ | Bits | Name | Meaning |
171
+ |------|------|---------|
172
+ | 7 | 3OFF | Disconnect voice 3 from audio (oscillator still runs) |
173
+ | 6 | HP | High-pass filter mode (12 dB/oct) |
174
+ | 5 | BP | Band-pass filter mode (6 dB/oct) |
175
+ | 4 | LP | Low-pass filter mode (12 dB/oct) |
176
+ | 3-0 | VOL | Master volume (0 = silent, 15 = full) |
177
+
178
+ LP, BP, and HP bits can be combined: LP+HP produces a notch filter; LP+BP gives a wider band-pass; setting all three is rarely useful in practice.
179
+
180
+ Oscar64 example — route voice 1 through a resonant low-pass sweep:
181
+
182
+ ```c
183
+ #include <c64/sid.h>
184
+
185
+ void init_filter_sweep(void)
186
+ {
187
+ // Route voice 1 only through filter; voices 2-3 bypass
188
+ sid.resfilt = (12 << 4) | SID_FILTER_1; // resonance 12, voice 1 filtered
189
+
190
+ // Low-pass mode, master volume 15
191
+ sid.fmodevol = SID_FMODE_LP | 15;
192
+
193
+ // Initial cutoff: mostly closed (low frequency)
194
+ // ffreq low byte -> $D415 (bits 2-0), high byte -> $D416 (FC10-FC3)
195
+ sid.ffreq = 0x4000; // $D415 = 0, $D416 = $40 (cutoff11 = $200)
196
+ }
197
+
198
+ // Call this from a raster IRQ or main loop to sweep the filter
199
+ void update_filter_cutoff(unsigned cutoff11)
200
+ {
201
+ // cutoff11: 0-2047
202
+ // ffreq is the 16-bit register: low byte = D415, high byte = D416
203
+ // but D415 only uses bits 2-0; the upper 5 bits of the low byte are ignored
204
+ sid.ffreq = (cutoff11 & 7) | ((cutoff11 >> 3) << 8);
205
+ // equivalent to: D415 = cutoff11 & 7, D416 = cutoff11 >> 3
206
+ }
207
+ ```
208
+
209
+ An earlier version of this example wrote `sid.ffreq = 0x0200` and `(cutoff11 >> 3) | ((cutoff11 & 7) << 13)`; compiled with `oscar64 -O2 -n`, those put $02 (not $40) in $D416 and sent the eight high cutoff bits to $D415, where bits 7-3 are ignored, so every cutoff it wrote was wrong. The values above compile to `$D415 = cutoff11 & 7`, `$D416 = cutoff11 >> 3` as the comment says.
210
+
211
+ Note: the `SID` struct in `c64/sid.h` declares `ffreq` as `volatile unsigned`, so one C assignment compiles to two separate 8-bit stores — the 6502 has no 16-bit store (an earlier version of this note said "a single instruction"). Oscar64 currently emits the low byte ($D415) first, then the high byte ($D416), but C does not guarantee that order. Neither the SID datasheet nor this knowledge base's SID reference documents any write-order requirement or latching of the cutoff pair: each byte takes effect as it is written, so the filter briefly sees a mixed old/new value between the two stores. That transient lasts a handful of cycles and is inaudible in practice; the order does not matter for correctness.
212
+
213
+ ### Why it works
214
+
215
+ All three voices (and the external audio input if FILTEX is set) share a single analog filter. Voices with their FILT bit clear bypass the filter entirely and go straight to the volume DAC at full bandwidth. Voices with FILT set pass through the filter's LP/BP/HP network before reaching the DAC. The analog filter's cutoff frequency is set by the 11-bit value; resonance emphasizes a narrow band around the cutoff frequency, producing the classic "ringing" or "squealing" filter sound. The 6581 filter is a switched-capacitor design with strongly non-linear cutoff response; the 8580 uses a different cell design with near-linear response.
216
+
217
+ Changing $D416 while voices are playing produces a live filter sweep — this is how SID tracker filter automation works. Because the filter is analog and there is no sample clock, sweeps are continuous.
218
+
219
+ ### Variations
220
+
221
+ **Notch filter for pad sounds.** LP+HP simultaneously (bits 4 and 6 set in $D418) creates a notch filter: all frequencies pass except a null band around the cutoff. Useful for creating formant-like vowel textures on sustained pad voices.
222
+
223
+ **Filter as LFO target.** Route voice 3 as the LFO source ($D41B output read each frame) and write its output scaled into $D416. The filter then tracks the LFO, producing a cyclic filter sweep without CPU intervention beyond the per-frame copy.
224
+
225
+ **High resonance as pseudo-oscillator.** At maximum resonance (15) the 8580 filter self-oscillates around the cutoff frequency, producing a sine-like tone. Some SID composers exploit this as a fourth "voice" by routing a silent voice through the filter and relying on self-oscillation. Less predictable on 6581 (individual chips vary in how close to self-oscillation they reach at RES=15).
226
+
227
+ **Voice muting via filter.** Setting $D418 to filter mode bits but VOL=0 silences all output; restoring VOL fades all voices back. This is cleaner than gating voices individually when you need a fade-out, but still avoid a sudden VOL jump (see pitfall `$D418 popping` in [sid-reference.md](../hardware/sid-reference.md#pitfalls)).
228
+
229
+ ---
230
+
231
+ ## sid_play_routine_pattern — The init+play subroutine convention
232
+
233
+ **Complexity:** low
234
+ **Region:** both
235
+ **Uses registers:** D400, D401, D402, D403, D404, D405, D406, D407, D408, D409, D40A, D40B, D40C, D40D, D40E, D40F, D410, D411, D412, D413, D414, D415, D416, D417, D418
236
+ **Requires:** sid_voice_setup
237
+ **Cost:** cycles_per_frame=327, irq_slots=1
238
+ **Cost basis:** measured-vice
239
+ **Cost measured on:** oscar64-sfx-engine (the recipe's stub tune; a real player costs several times more)
240
+ **Claims:** sid_voice_1-3 (owns), sid_filter_volume (owns)
241
+ **Claims basis:** estimated
242
+
243
+ ### Why
244
+
245
+ Every C64 SID tune — whether produced by a dedicated tracker (GoatTracker, SidFactory II, defMON) or hand-coded — exposes exactly two entry points to the host: an `init` subroutine that sets up the tune and a `play` subroutine that must be called once per frame. This two-entry-point contract is how SID players, emulators, and game engines integrate music without needing to understand the tune's internals. Learning this pattern is the entry point for driving SID tune files from Oscar64 code.
246
+
247
+ ### How
248
+
249
+ The canonical contract:
250
+
251
+ - **init(A = song_index):** Call once. Accumulator A selects which subtune to play (0-based). The routine initializes all SID registers, sets up internal player state, and returns. Multiple calls to init (with the same or different song index) must be safe — a well-written player zeroes or resets all state on every init call.
252
+ - **play():** Call once per frame (typically from a raster IRQ at line 0 or wherever the game places its audio IRQ). The routine reads the current frame count from internal state, computes the SID register values for this frame, writes them to $D400-$D418, and returns. The play routine must not corrupt the CPU registers it uses without saving and restoring them; well-written players save A, X, Y on the stack and restore before returning.
253
+
254
+ The Claims line rests on this contract: a player writes all three voices and $D415-$D418 every frame. The recipes that implement the pattern here do not show it. `sid-music-player.md` plays voice 2 only, `simple-shmup.md` plays its music on voice 1, and `cracktro-template.md` calls a stub that returns at once. The basis is therefore `estimated`.
255
+
256
+ In assembly the pattern is:
257
+
258
+ ```asm
259
+ ; Call init with song index 0
260
+ lda #0
261
+ jsr TUNE_INIT_ADDR
262
+
263
+ ; In raster IRQ (called each frame):
264
+ play_music:
265
+ jsr TUNE_PLAY_ADDR
266
+ ; ...other IRQ work...
267
+ ```
268
+
269
+ In Oscar64, the play routine is a C function pointer called from the raster IRQ:
270
+
271
+ ```c
272
+ #include <c64/vic.h>
273
+ #include <c64/sid.h>
274
+
275
+ // External assembly SID tune symbols
276
+ extern void sid_tune_init(byte song);
277
+ extern void sid_tune_play(void);
278
+
279
+ __interrupt void raster_irq(void)
280
+ {
281
+ // Acknowledge raster IRQ
282
+ vic.intr_ctrl = 1;
283
+
284
+ // Call SID play routine
285
+ sid_tune_play();
286
+
287
+ // ...rest of frame work...
288
+ }
289
+
290
+ int main(void)
291
+ {
292
+ // Set up raster IRQ at line 0
293
+ // ... VIC IRQ setup ...
294
+
295
+ // Init SID tune, select song 0
296
+ sid_tune_init(0);
297
+
298
+ // Main loop
299
+ for (;;)
300
+ {
301
+ // Game logic, not audio
302
+ }
303
+ return 0;
304
+ }
305
+ ```
306
+
307
+ (An earlier version of this fragment wrote `vic.irq = 1` and `void main(void)`; neither compiles — `vic.h` names the field `intr_ctrl`, and Oscar64's `crt.c` declares `int main`, so `void main` is refused with "Function declaration differs".)
308
+
309
+ **PSID and RSID file formats.** SID tune files (.SID) carry the `init` and `play` addresses in a fixed header:
310
+
311
+ - Bytes $06-$07: `data_offset` — offset of the tune body from the start of the file ($007C for v2)
312
+ - Bytes $08-$09: `load_address` — where to load the tune data (0 = the body's first two bytes hold the address, little-endian, and are not part of the code)
313
+ - Bytes $0A-$0B: `init_address` — entry point for init
314
+ - Bytes $0C-$0D: `play_address` — entry point for play (0 = init installs its own IRQ handler, raster or CIA)
315
+ - Bytes $0E-$0F: `songs` — total number of subtunes
316
+ - Bytes $10-$11: `start_song` — default subtune (1-based)
317
+
318
+ All header words are big-endian. An earlier version of this list was shifted one word — it called $06-$07 the load address, $08-$09 init, $0A-$0B play, and read `songs` and `start_song` from single bytes $0F and $10, the second of which is the always-zero high byte of a big-endian word; the offsets above agree with [formats/c64-file-formats.md](../formats/c64-file-formats.md).
319
+
320
+ PSID (most common) is the player-driven variant: the player itself calls init and then play on a VBI or CIA tick, and before each call it sets $01 from the routine's address ($37 below $A000, $36 below $D000, $35 at $E000 and above — KERNAL banked out — and $34 in the $D000 page), so a PSID tune cannot assume the KERNAL is mapped in and should be self-contained. RSID ("Real SID") is the opposite: the tune gets the C64 power-on environment as-is — $01 = $37 with KERNAL and BASIC ROMs banked in, CIA 1 timer A interrupting at 60 Hz — and must configure the hardware and install its own interrupt handler, so play_address, load_address (in the header) and speed are all 0 and init must live in RAM at or above $07E8. KERNAL and BASIC use is legitimate in RSID (the RSID-only BASIC flag even runs the tune as a BASIC program). An earlier version of this paragraph had the emphasis backwards, saying PSID tunes may call the KERNAL and RSID tunes may not. Most emulator players handle both; extracting a tune into an Oscar64 project is simpler with PSID because you call init/play yourself. See formats/c64-file-formats.md for the header.
321
+
322
+ ### Why it works
323
+
324
+ The play routine is called once per frame (every 20 ms on PAL, every 16.7 ms on NTSC). That gives the player 50 opportunities per second on PAL (about 60 on NTSC) to rewrite any of the 25 registers — at most 1,250 writes a second, not the 15,600 an earlier version of this sentence claimed (312 × 50 counts raster lines, not register writes). The player's job each frame is to advance its internal sequencer by one tick (or by a fraction of a tick if the tune runs at a sub-frame rate), compute any pitch slides, vibrato, or arpeggio values for each voice, and write the result to SID. Because SID registers are write-only and take effect immediately, the writes can happen at any point in the frame without synchronization — the SID does not have a "register latch" mode that defers application.
325
+
326
+ The reason for placing the call inside a raster IRQ rather than the main loop is timing stability. A main loop with variable per-frame work produces jitter in the audio write timing. The raster IRQ fires at a fixed line number every frame, guaranteeing the play routine runs at the same point in every frame regardless of what the main loop is doing.
327
+
328
+ ### Variations
329
+
330
+ **CIA-timed play.** Some tunes embed their own CIA timer IRQ setup and call the play routine on a sub-frame rate (e.g. 50 Hz for the music while the game runs at 25 Hz). The `.play_address` field in PSID can be $0000, indicating the tune installs its own IRQ. Standard SID players handle this case.
331
+
332
+ **Multi-speed tunes.** Certain trackers call the play routine 2× or 4× per frame (CIA timers) to achieve smoother vibrato and faster arpeggios than 50 Hz allows. A 4× tune calls play 200 times per second on PAL.
333
+
334
+ **Overlay (co-call) pattern.** A game's existing raster IRQ chain calls the SID play routine as one step in a multi-step handler. The play routine returns normally and execution continues with sprite positioning, scroll updates, and so on. This is the standard Oscar64 game structure.
335
+
336
+ ### Recipes
337
+
338
+ - `recipes/oscar64/sid-music-player.md`
339
+ - `recipes/oscar64/sfx-engine.md` (a table-driven effect borrowing a voice from a play routine that writes all three, and giving it back; register-level checksum, nobody has listened)
340
+
341
+ ---
342
+
343
+ ## sid_8580_vs_6581_differences — Chip revision differences
344
+
345
+ **Complexity:** low
346
+ **Region:** both
347
+ **Uses registers:** D404, D405, D406, D40E, D40F, D412, D415, D416, D417, D418, D41B
348
+
349
+ ### Why
350
+
351
+ The C64 shipped with two distinct SID revisions across its production life: the 6581 (1982 through approximately 1986) and the 8580 (1987 through 1992). Despite sharing the same register interface and the same musical capabilities on paper, the two chips sound noticeably different in practice. Code that sounds excellent on a 6581 may sound wrong on an 8580 and vice versa. Understanding the three main differences is necessary to write cross-compatible music code or to deliberately target one revision.
352
+
353
+ ### How
354
+
355
+ #### Filter cutoff curve
356
+
357
+ The 6581 filter uses a switched-capacitor design with a strongly non-linear cutoff-versus-register-value curve. The curve is roughly sigmoidal on a logarithmic frequency scale, and it varies significantly between individual chips from different manufacturing batches. A $D416 value of $40 that places the cutoff at ~1 kHz on one 6581 may produce ~600 Hz on another. Music tuned for a specific 6581 often sounds slightly wrong on a different 6581 of the same nominal revision, let alone on an 8580.
358
+
359
+ The 8580 filter has a near-linear cutoff curve and is consistent between chips. A $D416 sweep that sounds even-stepped on an 8580 sounds compressed at the low end and spread at the high end on a 6581.
360
+
361
+ Standard practice: SID players ship two filter-cutoff tables (one per chip revision) and detect which revision is present at startup.
362
+
363
+ #### ADSR bug
364
+
365
+ Both chips share a hardware quirk in the envelope rate counter: if you write a smaller rate value to $D405 or $D406 than the 15-bit internal rate counter has already counted past for the current phase, the counter must wrap through its full 15-bit range (up to 32768 cycles, approximately 33 ms at PAL) before the envelope generator acts on the new rate. This causes new notes to play at the wrong envelope shape until the counter wraps. The standard workaround is the "hard restart" sequence (see the `Programming patterns` section in [sid-reference.md](../hardware/sid-reference.md#hard-restart-adsr-bug-workaround)):
366
+
367
+ ```asm
368
+ // Hard restart: three steps over two frames, starting 2 frames before the note
369
+ hard_restart:
370
+ lda #0
371
+ sta $D405 // AD = 0 (attack 2ms, decay 6ms — fastest possible)
372
+ lda #$F0
373
+ sta $D406 // SR = $F0 (sustain max, release 0 — no decay hang)
374
+ lda ctrl_shadow
375
+ and #$FE
376
+ sta $D404 // clear GATE bit to start release
377
+ rts
378
+ // ... 1 frame later ...
379
+ hard_restart_test:
380
+ lda #$09
381
+ sta $D404 // TEST+GATE: reset oscillator, start attack
382
+ rts
383
+ // ... 1 frame later (the note frame) ...
384
+ hard_restart_note:
385
+ lda real_ad
386
+ sta $D405
387
+ lda real_sr
388
+ sta $D406
389
+ lda real_ctrl_with_gate
390
+ sta $D404 // release TEST; GATE + waveform, correct values
391
+ rts
392
+
393
+ ctrl_shadow: .byte 0 // software copy of $D404 (write-only register)
394
+ real_ad: .byte 0
395
+ real_sr: .byte 0
396
+ real_ctrl_with_gate: .byte 0
397
+ ```
398
+
399
+ An earlier version of this listing had only two steps — it went from the gate-off frame straight to the note frame, dropping the TEST+GATE frame that sid-reference.md's hard-restart pattern puts between them — and its comment said "2 frames" while the body waited one; it also read a `$D404_shadow` label that no assembler accepts. The three-step form above mirrors the reference. The 6581 exhibits the ADSR bug more visibly at certain rate combinations; the 8580 is slightly less severe in some cases, but the bug exists on both and should always be worked around.
400
+
401
+ #### $D418 sample replay (digi) difference
402
+
403
+ The 6581 has a measurable DC offset at the master volume DAC. Writing varying 4-bit values to $D418 bits 3-0 modulates this offset and produces audible clicks, enabling 4-bit PCM playback (see `digi_4bit`). The 6581's DC-offset amplitude is large enough to produce clear speech and sampled sound at multi-kHz rates.
404
+
405
+ The 8580 cleaned up the DAC design; the DC offset is nearly absent. The same $D418 write sequence produces volume levels that are too small to hear without hardware assistance. The standard hardware fix is a 330-740 kΩ resistor between SID pin 26 (EXT IN) and either GND (pin 14) or +5 V, which injects a signal into the filter path that the DAC can modulate. Without this resistor, software-only digi on a stock 8580 requires different techniques (see `digi_8bit_hard_restart`).
406
+
407
+ #### Combined waveforms
408
+
409
+ Enabling more than one waveform bit simultaneously produces a bitwise AND of the waveform outputs. On the 6581 this combined output is quieter and exhibits noise artifacts at the zero-crossing points. On the 8580 the combined output is louder and cleaner. Tunes that rely on TRI+PULSE for warm pad sounds or TRI+SAW for soft brass will sound distinctly louder and brighter on 8580 than on 6581. The exact bit patterns produced by combined waveforms depend on the chip revision — some combinations produce a fundamentally different harmonic spectrum between revisions.
410
+
411
+ #### Voltage and capacitor differences
412
+
413
+ This is a hardware concern, not a software one, but it affects anyone testing against real hardware: the 6581 requires +12 V Vdd and uses 470 pF filter capacitors. The 8580 runs on +9 V and uses 22 nF capacitors. The 6582 designation is an 8580 in a different package. Swapping chips without changing the power rail and capacitors results in audibly wrong filter behavior or chip damage.
414
+
415
+ ### Variations
416
+
417
+ **Chip detection at runtime.** Runtime detection cannot observe the filter from software: the only readable voice-3 registers are $D41B (oscillator) and $D41C (envelope), and both sit before the filter in the signal path, so no cutoff, mode or routing write changes what they return (see sid-reference.md, Filter signal flow; an earlier version of this paragraph said the filter's effect could be measured through $D41C). The standard detection routine instead uses $D41B: write $FF to $D412, $D40E and $D40F, then write $20 to $D412 (sawtooth, TEST and GATE cleared) and read $D41B immediately — the value differs between revisions because the two chips reset and restart the accumulator differently. In VICE 3.10 reSID the read returns 3 for the 6581 model and 2 for the 8580 model; treat the real-hardware values as the same but unverified here. Some SID players autodetect; others expose a settings toggle. Detection is imprecise (some SIDs answer ambiguously) — use it only to select between precomputed cutoff tables, and offer a settings toggle as the fallback.
418
+
419
+ **Per-chip optimization.** Scene-quality SID music is often composed explicitly for one chip revision. The composer notes the target in the HVSC (High Voltage SID Collection) metadata (`STIL.txt` or the SID file header `SID model` field). Accept that cross-revision playback will sound different.
420
+
421
+ ---
422
+
423
+ ## digi_4bit — 4-bit digi playback via $D418 volume
424
+
425
+ **Complexity:** high
426
+ **Region:** both
427
+ **Uses registers:** D418
428
+ **Demands:** continuous_interrupts
429
+
430
+ ### Why
431
+
432
+ The C64 has no dedicated PCM audio hardware. The SID chip was designed as a synthesizer, not a sample player. Yet some of the most memorable audio moments in C64 history — Ghostbusters speech, Arkanoid title track, Mahoney's "Musik Run/Stop" sampled instruments — are PCM playback at what sounds like reasonable audio quality. The mechanism is a hardware accident in the 6581: the master volume register doubles as a 4-bit DAC for anyone willing to write to it fast enough. This technique is how C64 demos and games play speech and percussion samples.
433
+
434
+ ### How
435
+
436
+ The $D418 register's lower nibble (VOL, bits 3-0) sets the master output volume. On the 6581 the DAC that drives the audio output pin has a measurable DC offset; changing this nibble between values produces an audible click proportional to the step size. Streaming a series of 4-bit values to $D418 at a regular rate reproduces PCM audio at 4-bit resolution.
437
+
438
+ Sample rate is determined by the IRQ frequency: any timer that fires and writes a new nibble to $D418 contributes one sample. The practical range on PAL is:
439
+
440
+ - **Low rate (4-8 kHz):** One IRQ every 123-246 cycles. This is approximately every 2-4 raster lines. Sufficient for speech (e.g. GoatTracker `ADSR bug` workaround timing) and simple percussion. The handler below costs roughly 25-35 cycles per sample plus the 7-cycle interrupt entry and 6-cycle RTI, so at one IRQ every 2-4 lines it leaves well over half the CPU free. (An earlier version of this bullet said "~20-30 IRQs per raster line is achievable"; a PAL line is 63 cycles and entry plus RTI alone cost 13, so at most four empty interrupts fit in one line.)
441
+ - **Higher rate (up to ~15.6 kHz):** One IRQ per raster line. PAL: 985,248 cycles/s ÷ 63 cycles/line ≈ 15,639 samples/s (312 lines × 50.125 Hz). NTSC: 1,022,727 ÷ 65 ≈ 15,734 samples/s (263 × 59.826 Hz). An earlier version put the PAL clock at "63 × 312 × 50 = 982080" — that product is 982,800, and the clock is 985,248 Hz. This is the theoretical ceiling for raster-line-based digi; practical implementations are limited by the IRQ overhead and the need for the main program to do anything else.
442
+
443
+ A minimal 4-bit digi IRQ (KickAssembler syntax; `sample_ptr` must be a zero-page pair because `(zp),y` has no absolute form, and KickAssembler assembles `(label),y` with a non-zero-page label silently and reads the wrong pointer):
444
+
445
+ ```asm
446
+ // Setup: set SID master volume to mid-range, all voices gated off
447
+ // Sample data: array of bytes, each byte = two 4-bit samples packed as hi|lo nibble
448
+ // nibble_hi: flag byte, 0 = output low nibble, nonzero = output high nibble
449
+ // sample_ptr: zero-page pointer to the current sample byte
450
+ .label sample_ptr = $FB
451
+
452
+ digi_irq:
453
+ pha
454
+ tya
455
+ pha
456
+ ldy #0
457
+ lda nibble_hi
458
+ bne output_hi
459
+ // Low nibble
460
+ lda (sample_ptr),y
461
+ and #$0F
462
+ sta $D418
463
+ inc nibble_hi
464
+ jmp digi_irq_done
465
+ output_hi:
466
+ lda (sample_ptr),y
467
+ lsr
468
+ lsr
469
+ lsr
470
+ lsr
471
+ sta $D418
472
+ lda #0
473
+ sta nibble_hi
474
+ inc sample_ptr // advance to next byte
475
+ bne digi_irq_done
476
+ inc sample_ptr+1 // carry into high byte
477
+ digi_irq_done:
478
+ pla
479
+ tay
480
+ pla
481
+ // ... ACK IRQ (CIA or VIC as appropriate) ...
482
+ rti
483
+
484
+ nibble_hi: .byte 0
485
+ ```
486
+
487
+ An earlier version of this listing loaded the sample with `lda (sample_ptr)` — zero-page indirect with no index register, which is a 65C02 addressing mode the 6510 does not have (KickAssembler: "'lda' doesn't support INDIRECT mode"); the 6510's indirect loads are `(zp,X)` and `(zp),Y` only, hence the `ldy #0` and the extra Y save/restore.
488
+
489
+ The upper nibble (bits 7-4 of $D418) contains the filter mode and voice-3 mute bits; the sample writes should preserve those bits or accept that the filter mode is overwritten on every sample byte. A common approach for digi that coexists with music is to set the filter mode to a fixed value and OR it with each sample nibble.
490
+
491
+ On Oscar64, the play-routine pattern applies: an `__interrupt` function writes `sid.fmodevol = filter_shadow | next_sample_nibble;` on each IRQ tick, where `filter_shadow` is a software copy of the intended bits 7-4 — $D418 cannot be read back (a read returns the last byte written to any SID register, measured in VICE reSID on both models), so `sid.fmodevol & 0xF0`, which an earlier version of this sentence used, is not the filter mode but the high nibble of whatever the music routine last wrote.
492
+
493
+ ### Why it works
494
+
495
+ The 6581 SID's output stage sums contributions from the three voice signals, the filter, and the master DAC. The master DAC is a simple resistor-ladder circuit driven by the four VOL bits. Changes to these bits produce a stepped analog output; because the output capacitor cannot instantaneously slew to a new voltage, each step produces a brief current transient that is audible as a click. Streaming clicks at audio rates produces the perception of continuous audio through the same mechanism as any PCM DAC — the ear integrates the rapid changes into a perceived waveform.
496
+
497
+ The technique works specifically because the 6581's DAC has a non-zero DC offset: the output at VOL=0 is not at the same voltage as at VOL=15 — there is an absolute shift that drives current through the output coupling capacitor. The 8580 corrected this: its output at VOL=0 and VOL=15 are symmetric around the bias point, dramatically reducing the click amplitude.
498
+
499
+ ### Variations
500
+
501
+ **Digi mixed with SID music.** Some skilled composers run three-voice SID music while simultaneously playing digi samples. The SID voices occupy the synthesizer path; digi drives the volume register. This requires the music play routine to not write $D418 (or to write only the filter-mode bits and leave the lower nibble to the digi routine). Rob Hubbard and Martin Galway pioneered this combination.
502
+
503
+ **Sample rate selection.** PAL gives more cycles per frame (19656) vs NTSC (17095), so PAL can sustain a higher sample rate before competing IRQs are starved. Most classic digi tunes were composed for PAL systems.
504
+
505
+ **NTSC consideration.** On NTSC (59.826 Hz frame rate), the same raster-line-based sample rate gives 1,022,727 ÷ 65 ≈ 15,734 samples/sec (263 × 59.826 Hz) — slightly higher than PAL's 15,639 (an earlier version said 263 × 60 = 15,780, using a rounded frame rate). However, the shorter frame (17095 cycles) leaves fewer cycles for the main program.
506
+
507
+ **8580 hardware fix.** A 330-740 kΩ resistor between SID pin 26 (EXT IN) and ground restores audible digi on 8580 by feeding back signal through the filter input. The exact resistor value affects the amplitude and frequency response of the digi; values around 470 kΩ are common.
508
+
509
+ ---
510
+
511
+ ## digi_8bit_hard_restart — Hard-restart digi and high-resolution sample techniques
512
+
513
+ **Complexity:** scene-tier
514
+ **Region:** both
515
+ **Uses registers:** D418, D404, D405, D406
516
+ **Demands:** continuous_interrupts
517
+ **Requires:** sid_voice_setup
518
+
519
+ ### Why
520
+
521
+ 4-bit digi via $D418 gives 16 amplitude levels — adequate for speech, marginal for music. Scene-quality productions achieving more than 4-bit effective resolution exist, and they do so by exploiting the SID's voice control logic rather than just the volume register. The "hard restart digi" family of techniques drives the envelope generator into a state where the audio output is effectively controlled by a pulse-width write rather than a volume nibble, giving access to a larger DAC range.
522
+
523
+ ### How
524
+
525
+ The technique family exploits the relationship between the ADSR bug, the TEST bit, and the pulse-width register on the 8580.
526
+
527
+ **Background: the ADSR bug as a DAC.** When the ADSR bug stalls the envelope at a fixed level, the voice output is `oscillator_output * envelope_level`. If the oscillator is held in a fixed state (via TEST or by using a DC-level waveform), the envelope level itself becomes the DAC value. The ADSR envelope counter is 8 bits of effective resolution ($00-$FF), giving 256 amplitude levels — 8-bit PCM.
528
+
529
+ **Hard-restart digi (Hermit method, basic form):**
530
+
531
+ For each sample byte (8-bit, one per IRQ tick):
532
+ 1. Gate off the voice. Write $D405 = 0, $D406 = 0 (fastest rates). No register write resets the 15-bit rate counter — it only clears when it reaches the current rate period, so a rate lowered below where the counter already sits must wrap the full range first (the ADSR bug, see above; an earlier version of this step said the write "triggers the ADSR rate counter reset", which is the opposite of the bug). This loop is safe because it writes rate 0 on every tick and never lowers the rate mid-count; a music-player hard restart gets the same guarantee by writing AD/SR = 0 a frame or two before the gate.
533
+ 2. Set TEST bit ($D404 bit 3) to hold the oscillator at zero output.
534
+ 3. Write the 8-bit sample value into... the envelope? Not directly — the envelope value is not writable. Instead, the trick is to set a specific ATTACK value such that the envelope ramps from 0 to the target value in exactly one sample period.
535
+
536
+ No working hard-restart / envelope-DAC listing is given here: the 8-bit envelope path described above and below is described, not demonstrated. An earlier version of this entry carried a "Hermit method" fence for voice 1 that did not do what its comments said — it loaded the sample byte and then overwrote A with `lda #$00` / `lda #$08`, so its `and #$0F` / `sta $D418` stored the constant $08 on every tick, it never set GATE, and it never acknowledged the interrupt; an agent copying it got silence at fixed volume 8. What follows instead is an explicitly plain 4-bit $D418 player that takes 8-bit sample bytes, so that the per-tick structure a hard-restart routine would also need — fetch, scale, write, acknowledge, restore — is shown by something that assembles. It uses the voice-1 registers only through the `filter_shadow` convention of `digi_4bit`; the entry still requires `sid_voice_setup` because the envelope technique it describes is built on the per-voice $D404-$D406 layout.
537
+
538
+ ```asm
539
+ // Plain 4-bit $D418 player, one 8-bit sample byte per tick (high nibble used).
540
+ // This is NOT the hard-restart / envelope-DAC routine; see the text above.
541
+ digi_8bit_irq:
542
+ pha
543
+ tya
544
+ pha
545
+ // Load next sample byte (0-255)
546
+ ldy sample_idx
547
+ lda sample_data,y
548
+ inc sample_idx
549
+ // Keep the high nibble: a 4-bit $D418 digi wants bits 7-4, not 3-0
550
+ lsr
551
+ lsr
552
+ lsr
553
+ lsr
554
+ ora filter_shadow // software copy of $D418 bits 7-4 ($D418 cannot be read back)
555
+ sta $D418
556
+ lda $DC0D // acknowledge CIA 1 timer IRQ (use dec $D019 for a raster source)
557
+ pla
558
+ tay
559
+ pla
560
+ rti
561
+
562
+ sample_idx: .byte 0
563
+ filter_shadow: .byte 0
564
+ sample_data: .fill 256, 0
565
+ ```
566
+
567
+ The full 8-bit envelope technique requires precisely timed gate sequences spanning multiple IRQ slots. The envelope counter increments once per specific number of cycles depending on the ATTACK rate value 0 (one increment every ~8-9 cycles at PAL clock: PAL φ2 = 985248 Hz, attack-rate-0 spec = 2 ms to peak, 256 envelope steps → ~7.8 µs/step ≈ 8 φ2 cycles/step by the datasheet figure; measured in VICE reSID, both 6581 and 8580 models, ENV3 rises 3 levels per 27 cycles, i.e. 9 cycles per step, and reaches 255 about 2,080 cycles ≈ 2.1 ms after gate-on. An earlier revision said ~15 cycles per step, which is double the value its own arithmetic gives.) Counting exactly enough IRQ cycles to arrive at the target amplitude requires very precise IRQ timing — this is why this is classified scene-tier. The Hermit technique in its simplest deployable form produces 5-6 effective bits; the most refined variants (Mahoney's "Musik Run/Stop") achieve perceptual quality close to 8 bits.
568
+
569
+ **Mahoney technique (8580-specific).** Mahoney's 8580 digi routine (released as "Musik Run/Stop", 2014 — title and year from published descriptions, not verified against the release or any document on this machine) drives the 8580 to high-resolution audio by combining the envelope, the volume DAC and the filter's resonance gain. The exact Mahoney and Hermit register sequences are not documented here. An earlier version of this entry named Jan Lund Thomsen and 1994 and called the release "Musik Runs in the Family", while the same page credited Mahoney two paragraphs earlier — two people for one work; the name and year have been dropped rather than resolved, since nothing on this machine settles them. That version also gave a four-step recipe built on holding voice 3 in PULSE+TEST and driving its pulse width as a DAC, followed by a paragraph contradicting it; that mechanism does not work (see the PWM digi variation below) and has been removed.
570
+
571
+ ### Why it works
572
+
573
+ The core insight is that the SID has multiple analog signal paths that can be driven by digital writes at different resolutions. The $D418 volume register gives 4 bits directly. The pulse comparator output is binary ($000 or $FFF), and with TEST set it is forced to $FFF whatever PW holds, so the extra resolution in these techniques comes from the envelope and the volume DAC, not from PW (an earlier version of this sentence said the pulse width gave "12 bits of comparator control" with the oscillator locked via TEST; it does not). Combining the envelope and volume paths gives more effective resolution. The filter resonance adds gain at the cutoff frequency, boosting low-amplitude signals to audible levels on the 8580 where the raw DAC change is too small to hear without it.
574
+
575
+ ### Variations
576
+
577
+ **PWM digi (8580 software-only) — does not work as once described here, and what was described here was not Harsfalvi's method.** The pulse-width-modulation digi of the late 1990s holds no TEST bit: it runs the pulse waveform at `$FFFF` and rewrites the pulse width at the sample rate, and it works on both chip models; it is built and measured under `pwm_digi` below (an earlier version of this paragraph, up to 2026-09-23, called the TEST-bit variant "PWM digi" and drew the conclusion that a pulse-width DAC does not work, which is true only with TEST held). With TEST set the pulse output is held at full scale regardless of PW: measured in VICE reSID on both models, OSC3 reads $FF for PW = $000, $080, $800 and $FFF alike, so PW cannot act as a DAC while TEST is held (an earlier version of this variation said modulating PWHI under TEST changed the DC level, and the How section above said the TEST-locked output was $000; both were wrong). The usable software-only 8580 form is the test-bit DC digi in [sid-reference.md](../hardware/sid-reference.md): PULSE+TEST+GATE ($49) on all three voices as constant full-scale sources through their envelopes, with $D418 as the 4-bit DAC. The exact Mahoney/Hermit sequences are not documented here.
578
+
579
+ **Test-bit digi.** Rapidly toggle the TEST bit at audio frequency. The duty cycle of the toggling produces an average DC level that the filter and volume DAC amplify. Produces lower effective resolution but requires only one bit manipulation per sample.
580
+
581
+ ---
582
+
583
+ ## pwm_digi — Pulse-width-modulation digi: the sample rides the duty cycle
584
+
585
+ **Complexity:** high
586
+ **Region:** both
587
+ **Uses registers:** D400, D401, D402, D403, D404, D405, D406, D418, DD04, DD05, DD06, DD07, DD0D, DD0E, DD0F
588
+ **Demands:** continuous_interrupts
589
+ **Requires:** sid_voice_setup
590
+ **Cost:** cycles_per_frame=4774
591
+ **Cost basis:** arithmetic
592
+ **Cost measured on:** kickassembler-pwm-digi (per-sample work at a 128-cycle period, PAL)
593
+
594
+ ### Why
595
+
596
+ A third way to play a sample, beside the `$D418` volume nibble of `digi_4bit` and the envelope tricks of `digi_8bit_hard_restart`. It needs no volume-register write per sample, no 6581 DAC offset and no 8580 quirk: one voice plays the pulse waveform as fast as the SID can, and the program rewrites the pulse width from the sample. The duty cycle carries the sample and the output stage averages it. It is the method Levente Harsfalvi described in the late 1990s for playing samples on the 8580, where `$D418` digi is nearly silent (name and decade from the candidate list this entry was written against and from memory of the published description; the description itself was not read here, so what follows is the mechanism as built and measured, not a claim about his register sequence). It leaves the volume register to the music.
597
+
598
+ ### How
599
+
600
+ 1. Set the voice up as `sid_voice_setup` describes: frequency `$FFFF`, attack 0, decay 0, sustain 15, release 0, then control `$41` (pulse, gate on) and leave the gate on. Master volume 15.
601
+ 2. Choose a sample period in cycles and run a timer at it (CIA 2 timer A in the recipe, so the KERNAL's jiffy timer on CIA 1 is untouched). 128 cycles gives 7,697 samples a second on PAL and 7,990 on NTSC.
602
+ 3. On every timer tick write the sample into the pulse width: sample bits 7 to 4 into `$D402` bits 7 to 4, sample bits 3 to 0 into `$D403` bits 3 to 0. Two pre-split tables make that two loads and two stores.
603
+ 4. In a program that does anything else, the tick is a timer interrupt every sample period, which is the `continuous_interrupts` demand; the recipe polls the CIA flag under `sei` instead, so it can prove its rate with timer B.
604
+
605
+ The carrier cannot be put above the audible band. `$FFFF` is the top of the frequency register and gives `65535 × 985,248 ÷ 16,777,216 = 3,848.6 Hz` on PAL (3,995 Hz on NTSC), one pulse period every 256.004 cycles. Every sample rate up to about half of that is usable; the recipe's 128-cycle period puts two pulse-width writes into each pulse period. The pulse width is 12 bits and the sample 8, so the sample sits in bits 11 to 4, and nothing is lost by that: at `$FFFF` the top twelve bits of the accumulator step by 16 each cycle, so the low four bits of the pulse width are below the phase step, and the 8-bit sample already uses every duty level the carrier can resolve in one period, 4,096 ÷ 16 = 256 of them.
606
+
607
+ ### Why it works
608
+
609
+ The pulse comparator is a one-bit output: high while the top twelve bits of the phase accumulator are at or above the pulse width. With the envelope parked at sustain 15 the voice output is that bit at full scale, and its mean over one pulse period is the duty cycle times full scale. The mixer, the output amplifier and whatever follows the audio pin cannot follow a 3.85 kHz square wave exactly; what comes through at audio rates is the running mean, which is the sample. No register read, no TEST bit and no envelope timing is involved, which is why the write is two plain stores. An earlier version of the `digi_8bit_hard_restart` entry described a pulse-width DAC that held TEST set; that is not this method, and it does not work, since TEST forces the comparator output high whatever the pulse width holds (measured there).
610
+
611
+ Measured in VICE x64sc 3.10 reSID, PAL, from a WAV the emulator wrote (nobody listened): the strongest raw component is at 3,848.6 Hz, the carrier; after a moving average over one carrier period the dominant component is 240.5 Hz, which is the recipe's synthesised tone, `985,248 ÷ 128 ÷ 32 = 240.54 Hz`; its second harmonic is about 42 dB down. The carrier line sits about 7 dB below the tone line, with sidebands at ±240.5 Hz about 8 dB below it (whole decibels, since the window and segment chosen move these by up to a decibel; the recipe page states the method): the carrier is audible as a whistle, and a filter (the SID's own low-pass on the voice, or the listener's speaker) is what removes it. The figures are the same on the 6581 and 8580 models except level: the 8580 model's averaged signal is 0.74 of the 6581's (2,494 against 3,358 RMS in 16-bit units), about 2.6 dB quieter. Real chips were not measured.
612
+
613
+ ### Variations
614
+
615
+ **Filtered carrier.** Route the voice through the SID filter in low-pass mode with the cutoff below the carrier, so the whistle is attenuated on chip. Not measured here; on the 6581 the filter's cutoff range and gain vary by chip (`sid_filter_chip_variation`).
616
+
617
+ **Interrupt-driven.** A CIA timer IRQ every sample period, the handler doing the two loads and two stores and acknowledging the CIA, is the form a game uses. Entry and `rti` add the usual 7 + 6 cycles plus register saves on top of the loop body, and any other interrupt source adds jitter to the write times; the recipe's polled loop under `sei` is the jitter-free floor.
618
+
619
+ **Loudness on real chips.** 6581 against 8580 loudness on hardware is not measured here; the reSID figure above is the emulator's.
620
+
621
+ ### Cycle budget
622
+
623
+ Per sample the recipe's loop is a flag poll (`lda $DD0D`, `and #1`, `beq`) and then `lda abs,x` / `sta $D402` / `lda abs,x` / `sta $D403` / `inx` / `bne`, 31 cycles on the passing path by the instruction table: 8 for the poll, 21 for the rest, and one more for each `lda abs,x` because the recipe's two tables start two bytes before a page boundary, so every index from 2 upward crosses it (an earlier version of this paragraph said 32; a `.align $100` on the tables gives 29). The Cost line above is 31 times the samples in a PAL frame, `19,656 ÷ 128 = 154` (rounded up), and its basis is arithmetic; the loads and stores without the poll were timed in VICE at 21.1 cycles a sample, which is the table's 21 plus the outer counter once per 256, so the figure is not far from a measurement. What else was measured: at 128 cycles a sample the loop kept exact pace on both models (timer B agreed with the sample count to the underflow); at every period from 28 to 36 cycles that was tried it did not, with timer B running 4 to 17 per cent ahead of the sample count; and at 36 cycles with the display blanked it kept exact pace. The polled floor with the screen on is the badline, not the loop: the poll clears the flag, a badline holds the CPU for 40 to 43 cycles between two polls, and two underflows in that gap count as one sample, so any period below roughly 31 + 43 cycles loses samples at a rate that scales with the run's length in frames (an earlier version of this paragraph said the floor was not explained; the recipe page has the sweep, the blanked rows and the arithmetic). At 128 cycles a sample, 97 cycles of every 128 are free for the rest of the program, less the badline stall on one line in eight while the display is on.
624
+
625
+ ### Recipes
626
+
627
+ - `recipes/kickassembler/pwm-digi.md`
628
+
629
+ ---
630
+
631
+ ## sidfx_layered_chip — Two-SID setups
632
+
633
+ **Complexity:** scene-tier
634
+ **Region:** both
635
+ **Uses registers:** D400, D401, D402, D403, D404, D405, D406, D407, D408, D409, D40A, D40B, D40C, D40D, D40E, D40F, D410, D411, D412, D413, D414, D415, D416, D417, D418
636
+
637
+ ### Why
638
+
639
+ Three voices is a severe constraint for anyone who wants chord harmonies, polyphonic melody, rhythm and bass simultaneously, or sample playback coexisting with full-voice music. Hardware expansion cards that install a second SID chip are common in the advanced demo scene and among serious SID musicians. Understanding how two-SID setups work — and where the convention places the second chip — is necessary for reading scene-quality SID music source and for writing code that gracefully degrades on stock hardware.
640
+
641
+ **Scope notice:** Stock C64 does not have a second SID. This technique is hardware-mod-only. The register addresses and detection logic documented here describe the dominant convention; actual stock-C64 output ignores $D420 (it falls within the SID mirror range, see below). Recipes for this project target stock C64 only; this section is reference material.
642
+
643
+ ### How
644
+
645
+ The first SID is always at $D400-$D418 (stock C64 mapping). The second SID address depends on the expansion hardware:
646
+
647
+ | Hardware | Second SID address | Notes |
648
+ |----------|--------------------|-------|
649
+ | C64 Reloaded MK2 | Configurable, common: $D420 | Default factory setting |
650
+ | SID Symphony cartridge | $DE00 | Cartridge port expansion I/O |
651
+ | Ultimate II+ | Configurable | Settings in Ultimate menu |
652
+ | SidCard v2 | $D500 | Rare; 1980s hardware |
653
+
654
+ **$D420 vs. mirroring.** The stock C64 SID is mirrored every 32 bytes through $D7FF (the SID has only five address inputs, A0-A4, and the C64's I/O address decoding selects it for the whole $D400-$D7FF range, so $D420, $D440, $D460... all address the same chip; an earlier version said "bits 5-0", which would be a 64-byte period). On boards with a hardware second SID, the chip select is modified so $D420 addresses the second chip instead of mirroring the first. Code that writes $D420 on stock hardware writes to the first SID voice 1 registers (same as writing $D400). Graceful degradation is therefore automatic on stock hardware — two-SID music defaults to single-SID behavior, just with duplicate writes.
655
+
656
+ **Detecting second SID presence.** $D400-$D418 are write-only; $D419-$D41C read live state (paddles, OSC3, ENV3). A read of a write-only address does not float and is not the last VIC-II fetch (an earlier version of this paragraph said both): the SID drives the bus with the last byte written to any of its 32 addresses (or last read from $D419-$D41C), fading to $00 after roughly 7k cycles on a 6581 and roughly 660k on an 8580 (measured in VICE reSID; see [sid-reference.md](../hardware/sid-reference.md)). So writing a value to $D420 and reading $D420 back returns that value whether or not a second chip is there — with one chip $D420 is a mirror of $D400. A read-back test has to read the FIRST chip: write $AA to $D401, write $55 to $D421, then read $D401 promptly — a single SID returns $55 (the mirror write was its last write), a second chip at $D420 leaves the first chip's held byte at $AA. This works in VICE with a second SID configured; on real add-on boards the result depends on the board's address decoding and the read must land well inside the 6581's fade window, so treat it as a hint and expose a user toggle.
657
+
658
+ An Oscar64 approach:
659
+
660
+ ```c
661
+ #include <c64/sid.h>
662
+
663
+ // Map second SID at $D420 (C64 Reloaded default)
664
+ #define sid2 (*((struct SID *)0xd420))
665
+
666
+ // Init both SIDs
667
+ void init_both_sids(void)
668
+ {
669
+ // Zero first SID
670
+ byte *p = (byte *)0xd400;
671
+ for (byte i = 0; i <= 0x18; i++) p[i] = 0;
672
+ sid.fmodevol = 0x0F; // volume 15, no filter
673
+
674
+ // Zero second SID (writes to $D400 on stock hardware, harmless)
675
+ byte *q = (byte *)0xd420;
676
+ for (byte i = 0; i <= 0x18; i++) q[i] = 0;
677
+ sid2.fmodevol = 0x0F;
678
+ }
679
+ ```
680
+
681
+ **Composing for two SIDs.** Two-SID tunes use six voices total. Conventional assignment: SID1 voices 1-3 for melody + bass, SID2 voices 1-3 for chords + extra percussion or samples. GoatTracker 2.x supports stereo SID output with configurable second-chip address. SidFactory II has native two-SID editing. HVSC has no `stereo` flag (an earlier version of this sentence said it had); a second SID is indicated by a non-zero `secondSIDAddress` byte at header offset $7A (PSID v3+), holding the middle byte of $Dxx0 — valid values $42-$7F and $E0-$FE, even only, e.g. $42 = $D420, $50 = $D500, $E0 = $DE00 — with the third SID's address at $7B in v4.
682
+
683
+ ### Why it works
684
+
685
+ A second SID chip, properly wired into the bus, responds to its assigned address range like any peripheral. The audio outputs of both chips connect to the same output mixer (either summed in hardware or via separate left/right audio channels for stereo). The CPU writes to each chip independently using its respective address range. The play routine simply doubles the number of SID writes per frame: init, update, and gate operations run for each voice on each chip.
686
+
687
+ ### Variations
688
+
689
+ **Mono two-SID summed.** Both chips output to the same audio line. Six voices sound richer than three but the mix can be muddy at full volume. Composers reduce per-voice amplitude by setting lower sustain levels.
690
+
691
+ **Stereo two-SID.** C64 Reloaded MK2 and some cartridges route the two chips to separate left/right outputs. HVSC stereo SID files are mixed for left/right separation. Playback on systems with only mono output sums both channels.
692
+
693
+ **$DE00 expansion-port SID.** The SID Symphony cartridge maps the second SID to $DE00 (Cartridge port expansion area 1). This address does not conflict with any stock C64 hardware and is detectable more reliably than $D420 since it does not overlap the mirror range. Tunes targeting SID Symphony set the second address to $DE00 in their header.
694
+
695
+ ---
696
+
697
+ ## sidasid_emulation_notes — How SID emulators differ from real silicon
698
+
699
+ **Complexity:** medium
700
+ **Region:** both
701
+ **Uses registers:** D415, D416, D417, D418
702
+
703
+ ### Why
704
+
705
+ Every Oscar64 SID recipe will be tested first in VICE, not on real hardware. VICE ships reSID (version 3.x in recent VICE releases), which is the most accurate publicly-available SID emulation, but "most accurate" is not "bit-perfect." Knowing where reSID diverges from real silicon prevents debugging phantom issues where a recipe sounds wrong on hardware but fine in the emulator, or vice versa.
706
+
707
+ ### How
708
+
709
+ #### Filter cutoff curve
710
+
711
+ The most significant deviation is the 6581 filter cutoff curve. Real 6581 chips vary between manufacturing batches; reSID approximates the curve from measurements of a handful of chips with a polynomial model. The result:
712
+
713
+ - At low cutoff values ($D416 < $20), reSID opens the 6581 filter more than most real chips. A patch with a closed low-pass filter sounds brighter in reSID than on hardware.
714
+ - At high cutoff values ($D416 > $C0), reSID is close to real hardware behavior.
715
+ - Mid-range ($D416 $20-$B0) is where the polynomial fit is most uncertain; filter sweep effects may have slightly different sweep rates.
716
+
717
+ The 8580 filter curve in reSID is close to real hardware and more reliable for testing than the 6581 model.
718
+
719
+ **Practical recommendation:** Compose and test with the emulated chip set to 8580. The 8580 model is more accurate and more consistent across physical chips. Ship with a note in the SID header indicating the target chip revision.
720
+
721
+ #### ADSR bug timing
722
+
723
+ The ADSR bug in reSID (v3.x) is implemented but with slightly different rate-counter timing than real hardware. On some specific rate transitions the "wrap" delay is a few cycles longer or shorter than measured on real 6581 chips. This matters for:
724
+
725
+ - Very fast arpeggios where the hard restart's AD=0 write races the next gate.
726
+ - Digi routines that rely on exact envelope-level timing.
727
+
728
+ In practice the difference is rarely audible in music playback. For digi timing, always test against real hardware before claiming cycle-exact behavior.
729
+
730
+ #### 8580 digi click suppression
731
+
732
+ reSID 3.x correctly models the 8580's reduced DC offset: $D418 volume writes produce nearly silent output in the 8580 emulation, matching real hardware. Earlier versions of reSID (v0.16-era) incorrectly made 8580 digi audible. If you test digi recipes with older VICE versions you may hear output that is absent on real 8580 hardware.
733
+
734
+ #### Combined waveforms
735
+
736
+ reSID uses lookup tables derived from chip measurements to model combined waveforms. The tables were measured from a small number of chips; combined waveform output on chips outside the measurement set may differ. The 6581 combined waveform model in particular is based on older measurements; the 8580 model is generally more reliable.
737
+
738
+ #### Cycle-exact register writes
739
+
740
+ reSID is not cycle-exact in VICE's default mode (it runs with a cycle granularity of 1 SID clock = 1 CPU clock, which is correct, but the audio output is buffered and interpolated). This is accurate enough for all music techniques. The cycle granularity difference matters only for techniques like oscillator-sync sweeps that involve writes on specific sub-cycle boundaries — real hardware may produce a slightly different transition glitch than reSID predicts.
741
+
742
+ #### $D41B/$D41C sampling
743
+
744
+ NOP takes two cycles, not one (an earlier version of this paragraph prescribed "a one-cycle NOP"). Whether VICE's $D41B/$D41C reads are offset from silicon by a cycle is not something we have measured; what is measured (VICE 3.10, reSID) is that reads are clocked per CPU cycle — with F=$FFFF each extra NOP between a TEST-clear and an OSC3 read advances the value by exactly 2 — so inserting a NOP only reads a later value, it does not remove any offset. The caveat that actually bites in headless testing: with `-sound -sounddev dummy`, warp on or off, $D41B and $D41C do not advance between CPU reads (OSC3 read a constant $55 across 80,000 cycles of a running sawtooth; ENV3 stayed $00 through an attack), and with `+sound` they return changing but meaningless values (ENV3 non-monotonic during an attack, different on every run). A test that reads OSC3/ENV3 must run with a real sound sink: `-sound -sounddev wav -soundarg out.wav` or `-sound -sounddev dump -soundarg out.txt` (no audio device needed); both showed OSC3 advancing and ENV3 reaching $FF normally.
745
+
746
+ ### Why it works
747
+
748
+ reSID is an analog-circuit simulation using a mix of analytical models (for the digital sections) and empirical lookup tables (for the non-linear analog sections). The digital parts — oscillators, LFSR, gate logic, rate counters — are modeled exactly. The non-linear parts — the 6581 filter and the 6581/8580 waveform combiners — use polynomial fits or lookup tables from physical measurements. Accuracy is highest where the chip behavior is linear and consistent across revisions; it degrades where behavior is non-linear, chip-dependent, or temperature-sensitive.
749
+
750
+ ### Variations
751
+
752
+ **Testing against multiple VICE SID backends.** Current VICE builds expose a single SID engine: `-sidengine 1` (reSID). FastSID is a compile-time option that has been off by default since VICE 3.5 (`--with-fastsid`, marked deprecated), so a stock install rejects `-sidengine 0`; the separate reSID-fp engine that VICE 2.1 added is no longer offered as a selectable engine (an earlier version of this paragraph listed `fastsid`, `reSID` and `reSID-fp` as three runtime choices; `x64sc -help` on VICE 3.10 offers only ReSID). Choose the chip with `-sidmodel 0` (6581), `1` (8580) or `2` (8580 + digiboost), and trade speed for accuracy inside reSID with `-residsamp 0` (fast) … `3` (fast resampling) rather than by switching engines. For verifying SID techniques, use reSID. For final hardware validation, test on a real C64 with both 6581 and 8580 if possible, or use the 1541 Ultimate II+ with its SID emulation mode as an intermediate step.
753
+
754
+ **HVSC SID compatibility metadata.** The High Voltage SID Collection tags each tune with the target SID model (6581/8580/both) in the `.SID` file header's 16-bit big-endian `flags` word at $76-$77: bits 4-5 of byte $77 give the first SID's model (00 unknown, 01 6581, 10 8580, 11 both). Bits 0-1 are the MUS-data and PlaySID/BASIC flags and bits 2-3 the video standard, so mask `byte[$77] >> 4 & 3`, not `& 3` (an earlier version of this sentence put the first SID's model in bits 0-1 and the second's in bits 2-3, which would classify MUS files as 6581). In PSID v3+ bits 6-7 give the second SID's model (00 = same as the first), and in v4 bits 8-9 (low bits of byte $76) give the third's. See [c64-file-formats.md](../formats/c64-file-formats.md). STIL.txt adds human-readable notes. When ingesting SID files into a game, check this field to select the appropriate per-chip frequency and filter tables.
755
+
756
+ **GoatTracker's chip selection.** GoatTracker 2 (readme v2.72) has one SID model setting for the whole editor, and it chooses what the emulation plays: the `-E` option (`0` = 6581, `1` = 8580, default 6581) or SHIFT+F8 to switch. An instrument's nine parameters include no chip choice, so a tune written on one model sounds different on the other, filters most of all (the readme advises testing filtered tunes on a real C64 or a HardSID card). Since v2.07 the packer writes the PAL/NTSC and 6581/8580 flags of the PSID v2NG header (version history). Load the `.sid` into VICE with `-sidmodel` set to match. (An earlier version of this paragraph said GoatTracker 2.x has a per-instrument SID model toggle and exports different filter tables per chip; the readme describes neither. Source: GoatTracker 2 `readme.txt` v2.72, https://sourceforge.net/projects/goattracker2/, mirrored at https://github.com/leafo/goattracker2/blob/master/readme.txt.)
757
+
758
+ ---
759
+
760
+ ## sfx_engine_beside_music — Sound-effect engine beside a music player
761
+
762
+ **Complexity:** medium
763
+ **Region:** both
764
+ **Uses registers:** D400, D401, D402, D403, D404, D405, D406, D407, D408, D409, D40A, D40B, D40C, D40D, D40E, D40F, D410, D411, D412, D413, D414, D418
765
+ **Requires:** sid_play_routine_pattern, sid_voice_setup
766
+ **Cost:** cycles_per_frame=258, cycles_per_frame_typical=50, irq_slots=1
767
+ **Cost basis:** measured-vice
768
+ **Cost measured on:** oscar64-sfx-engine (a frame the engine owns the voice)
769
+ **Claims:** sid_voice_2 (shares)
770
+ **Claims basis:** derived-listing
771
+
772
+ Nobody on this machine has listened to anything in this entry. Every claim
773
+ below is register-level: what bytes reach which SID register in which
774
+ order, measured in VICE x64sc 3.10 with a trace checkpoint and a checksum.
775
+ Whether an effect built this way sounds right is not established here.
776
+
777
+ ### Why
778
+
779
+ A game needs gun shots and explosions while the tune keeps playing, and a
780
+ tune's play routine writes all three voices every frame
781
+ (`sid_play_routine_pattern`). Writing an effect's registers once, as
782
+ `simple-shmup.md` does, works only until the player's next frame overwrites
783
+ them. An engine that owns one voice for the life of the effect, runs after
784
+ the player each frame, and stops when the effect ends, gives effects that
785
+ survive the player and a tune that comes back on its own.
786
+
787
+ ### How
788
+
789
+ **The table.** An effect is a header and a list of rows, one row per frame.
790
+ The header holds a priority byte and the ADSR pair, written when the effect
791
+ starts. Each row holds the 16-bit frequency, the 16-bit pulse width and the
792
+ control byte for that frame. The list ends with a terminator row; the recipe
793
+ uses control `$FF`, a value no row wants because it combines every waveform
794
+ with TEST and GATE. The engine keeps a pointer to the current row, writes it,
795
+ advances, and on reaching the terminator releases the voice.
796
+
797
+ **Priority.** Each effect carries a small priority number. A request to
798
+ start an effect is refused if an effect is running and the new one's
799
+ priority is lower; equal or higher priority cuts the running effect and
800
+ starts the new one from its first row. Equal priority restarting is what a
801
+ repeated shot wants. One byte per effect and two comparisons is the whole
802
+ scheme; count refusals in a debug counter so a level whose effects never
803
+ sound has a number to show.
804
+
805
+ **Taking the voice and handing it back.** Two cases, decided by the
806
+ player:
807
+
808
+ - A player that writes all three voices every frame (most tracker exports,
809
+ and the stub tune in the recipe): call the player first and the engine
810
+ second, every frame, in that order. While the engine owns the voice it
811
+ re-pokes all seven of the voice's registers after the player has written
812
+ its own, including AD and SR, which the effect meant to write "once at
813
+ start" but the player has just overwritten. The SID keeps the last write,
814
+ so the engine's values stand. Handing back is the engine not writing: the
815
+ frame after the terminator, the player's own writes are the last ones and
816
+ the tune's voice is back. No restore, no shadow copy.
817
+ - A player with a voice mask (a byte telling it which voices to leave
818
+ alone): set the mask bit when an effect starts, clear it when the
819
+ terminator is reached, and the engine can write ADSR once and rows of
820
+ frequency, pulse width and control only. Whether a given player has such a
821
+ mask is a property of that player's source, not of the SID; the recipe
822
+ does not assume one.
823
+
824
+ **Gating and restart.** The engine inherits whatever envelope state the
825
+ tune left in the voice. The sid-reference hard-restart entry's classic
826
+ sequence ends with a TEST+GATE (`$09`) frame and then the real waveform
827
+ with GATE one frame later; the recipe uses those two frames, without the
828
+ AD=0/SR=$F0 frame before them. (That page's "test-bit restart" variant is
829
+ TEST alone, cleared on the gate frame, and is not what the recipe does.)
830
+ The recipe makes the `$09` frame the first row of every effect, so the oscillator
831
+ restarts from zero and the attack starts from a known state, and its last
832
+ gated row drops GATE so the tune's first gate-on after the hand-back begins
833
+ a fresh attack instead of joining a sustain. Nothing more than the SID page
834
+ already documents is claimed: the ADSR bug and the two-frame classic hard
835
+ restart are covered there, and an effect that needs them uses the same rows.
836
+
837
+ The per-frame order in the interrupt or the frame loop:
838
+
839
+ ```asm
840
+ frame_tick:
841
+ jsr tune_play // the player writes all three voices
842
+ jsr sfx_update // the engine re-pokes the borrowed voice: last write wins
843
+ rts
844
+ tune_play: rts // stand-ins so the fragment assembles alone
845
+ sfx_update: rts
846
+ ```
847
+
848
+ ### Why it works
849
+
850
+ The SID has no register latch or double buffer: each write takes effect
851
+ when it lands, and the register holds the last byte written until the next
852
+ write. Two writers to one voice in one frame therefore resolve by order,
853
+ not by conflict, and a fixed call order is a complete arbitration. AD and
854
+ SR are rate settings for the envelope generator, so rewriting the same
855
+ value mid-envelope should change nothing; that is what makes the re-poke
856
+ safe. This is stated from the register's function (rung 4, not measured
857
+ here as audio); what is measured is that the bytes land in order. The
858
+ trace in `recipes/oscar64/sfx-engine.md` shows the tune's four bytes for
859
+ `$D408-$D40B` landing on raster lines 261 to 262 and the engine's four on
860
+ 267 to 268 in the same frame, and in the frame after the terminator only the
861
+ tune's four.
862
+
863
+ ### Variations
864
+
865
+ **Two effect voices.** Run two engine slots, one per borrowed voice, with
866
+ the priority rule per slot and a rule for which slot a new effect takes
867
+ (the free one, else the lower-priority one). The tune loses two voices while
868
+ both are busy.
869
+
870
+ **Effects on the tune's quietest voice.** Pick the borrowed voice per tune
871
+ rather than fixing it: the voice a tune uses for a hi-hat or an echo costs
872
+ less to lose than its bass. This is a composer's choice, not a code change.
873
+
874
+ **Row compression.** A row per frame is simple and costs five bytes; a
875
+ falling sweep can be one row plus a per-frame delta applied by the engine,
876
+ at the cost of a second table format. Do this only when the tables are
877
+ measured to be the problem.
878
+
879
+ ### Cycle budget
880
+
881
+ Measured in VICE x64sc 3.10 with CIA1 timer A around each call, interrupts
882
+ off, in `recipes/oscar64/sfx-engine.md` (rung 1): the engine costs 263
883
+ cycles on a frame it owns the voice (seven stores, four byte copies for the
884
+ checksum, and the row advance) and 55 cycles when idle, both including the
885
+ harness's 5 cycles of start/stop overhead. The stub tune's play routine
886
+ costs 332, or 327 net of that overhead, which is the figure on the Cost
887
+ line above. The engine's own Cost line carries 258 (263 less the 5) as its worst frame and 50 (55 less the 5,
888
+ idle) as its typical figure. Against a PAL frame of 19,656 cycles the engine is about 1.3 %
889
+ active and 0.25 % idle (arithmetic). A real player's play routine is
890
+ typically several times the stub; its figure is the player's, not this
891
+ technique's.
892
+
893
+ ### Recipes
894
+
895
+ - `recipes/oscar64/sfx-engine.md`
896
+
897
+ ---
898
+
899
+ ## sfx_in_player — Sound effects inside the music player: voice stealing, priority and hand-back
900
+
901
+ **Complexity:** medium
902
+ **Region:** both
903
+ **Uses registers:** D400, D401, D402, D403, D404, D405, D406, D407, D408, D409, D40A, D40B, D40C, D40D, D40E, D40F, D410, D411, D412, D413, D414, D415, D416, D417, D418
904
+ **Requires:** sid_play_routine_pattern, sid_voice_setup
905
+ **Cost:** cycles_per_frame=493
906
+ **Cost basis:** arithmetic
907
+ **Cost measured on:** kickassembler-sfx-in-player (increment over the player, worst effect frame)
908
+
909
+ Every claim below is register-level: what the player put in its shadow of
910
+ the SID, and so in the SID, measured in VICE x64sc 3.10 by the recipe's
911
+ own checks. Nobody on this machine has listened to it.
912
+
913
+ ### Why
914
+
915
+ `sfx_engine_beside_music` is a second routine, called after the player,
916
+ that re-pokes a voice the player has just written. Most games instead give
917
+ the music player itself an effect slot per voice. The player then knows a
918
+ voice is taken, skips its own writes there, and puts the music back when
919
+ the effect ends. There is one call per frame and one writer per register.
920
+ GoatTracker 2's packed player with sound-effect support works this way
921
+ (`goattracker_player_api`, below), and so does the sound code in
922
+ Cadaver's c64gameframework (source read, not run here).
923
+
924
+ ### How
925
+
926
+ **One slot per voice.** Each voice has an effect number (0 for none) and
927
+ a position in that effect's data. Every frame the player advances the
928
+ music on all three voices, owned or not. Then, per voice, either the music
929
+ or the effect's next row writes the voice. Advancing the music under an
930
+ effect keeps the tune in time, and at hand-back the music's frequency and
931
+ gate are already right for that frame.
932
+
933
+ **The request: one start per frame, higher number wins.** The game calls
934
+ an entry point with an effect number. The entry point stores it in a
935
+ pending byte only if it is at least the number already pending. The player
936
+ takes the pending effect at the top of its next call and clears the byte.
937
+ Two requests in one frame start one effect, the higher. c64gameframework's
938
+ `QueueSfx` uses the same rule.
939
+
940
+ **Priority on the voice.** A pending effect takes its voice if its number
941
+ is the same as or higher than the effect already there; a lower one is
942
+ refused. Equal numbers restart, which a repeated shot wants. The number is
943
+ the priority, so number the effect table in order of importance. Which
944
+ voice an effect gets is a design choice: a fixed voice per effect (the
945
+ recipe), a voice the caller names (GoatTracker 2), or a round-robin search
946
+ from the channel after the last one used, taking the first voice whose
947
+ running effect is the same or lower (c64gameframework, source read, not
948
+ run).
949
+
950
+ **Hand-back.** On the frame an effect's data ends, the player must
951
+ re-apply the music voice's instrument: AD, SR, pulse width, waveform and
952
+ gate. Many players, the recipe's among them, write AD, SR and pulse width
953
+ only on a note's first frame. Without a restore, the voice keeps the effect's envelope and pulse
954
+ width until the music's next note: a held pad comes back with the effect's
955
+ decay, or silent if the effect's sustain level was 0. In the recipe, a
956
+ harness build without the restore failed all three hand-back checks that
957
+ fell mid-note (rung 1). The gate needs care too. If the effect's last row
958
+ leaves GATE set and the music wants GATE set, there is no 0-to-1 edge, so
959
+ no new attack: the music continues from whatever level the effect's
960
+ envelope reached. End every effect with a GATE-clear row, as the recipe
961
+ does, and the music's next gated frame starts a fresh attack. The same
962
+ rule applies at the start: an effect that takes a voice mid-note with
963
+ GATE set gets no attack and runs from the music envelope's level. Give
964
+ the effect a GATE-clear first frame, or a test-bit hard restart.
965
+
966
+ **Ghost registers.** The player writes a shadow of `$D400`-`$D418` in RAM
967
+ and one loop copies all 25 bytes to the SID at the end of the call, `$18`
968
+ down to `$00`. This gives four things:
969
+
970
+ - One writer. Music and effect both write the shadow; only the copy writes
971
+ the SID.
972
+ - A readable copy. The SID's registers are write-only
973
+ (`sid_write_only_registers` in `pitfalls/sid.md`), so the shadow is the
974
+ only place to read what the SID holds. The recipe's checks read it.
975
+ - Fixed write order and spacing. A voice's seven writes land within 98
976
+ cycles (7 × 14, arithmetic), in the same order, whatever path the player
977
+ took. In an unbuffered player the gap between the AD and SR writes and
978
+ the GATE write depends on the code path, and the ADSR bug depends on
979
+ where the envelope's rate counter stands when a new rate is written
980
+ (`sid_adsr_bug_8580` in `pitfalls/sid.md`). The GoatTracker 2 readme's
981
+ remedies for ADSR bugs in unbuffered players include making the
982
+ note-init code take more cycles, buffered writes, and hard-restart
983
+ attack parameter F for a different write order. A fixed order and
984
+ spacing makes the timing the same every time rather than removing the
985
+ bug. That is the mechanism as stated; no audio was measured here (rung 4).
986
+ - A price. The copy costs 351 cycles every frame (instruction table,
987
+ shadow within one page), which is why the readme says buffered writes
988
+ take "more memory & rastertime".
989
+
990
+ Two cautions from the GoatTracker 2 readme, not measured here. With its
991
+ alternative hard restart (attack parameter F) the copy must write each
992
+ voice's waveform, frequency and pulse width, then ADSR last, not in the
993
+ plain descending order. And music and effects share the shadow: after an
994
+ effect ends, a music note that sets no pulse width of its own plays with
995
+ the effect's. A hand-back that rewrites the pulse width, as the recipe's
996
+ does, avoids it.
997
+
998
+ ### Why it works
999
+
1000
+ A SID register holds the last byte written; there is no latch. With one
1001
+ writer per register per frame, music and effect never race for a voice.
1002
+ The hand-back is right because the music path rewrites the instrument on
1003
+ the hand-back frame and the copy delivers it in that frame.
1004
+
1005
+ ### Variations
1006
+
1007
+ **Copy first, compute second.** At the top of the interrupt, copy the
1008
+ shadow the last call left, then run the player for the next frame. The SID
1009
+ writes then land a fixed number of cycles after the interrupt, whatever
1010
+ the player does, at the cost of one frame's delay (20 ms on PAL;
1011
+ arithmetic).
1012
+
1013
+ **Effects only while the music is off.** A second entry point that drops
1014
+ the request while a tune plays, for footsteps and menu clicks
1015
+ (c64gameframework's `QueueSfxNoMusic`).
1016
+
1017
+ **Beside or inside.** `sfx_engine_beside_music` works with a player binary
1018
+ you cannot change and needs no restore code, because the player rewrites
1019
+ the voice every frame. Its cost is a second set of writes to the voice.
1020
+ This technique needs the player's source, or a player built with effect
1021
+ support, and a hand-back written into it.
1022
+
1023
+ ### Cycle budget
1024
+
1025
+ Measured in VICE x64sc 3.10 with CIA1 timer A around each play call in the
1026
+ recipe (rung 1), identical on PAL and NTSC because the call runs below the
1027
+ display with no sprites: 774 cycles on a frame with no effect and no note
1028
+ start, 1,058 on the worst music-only frame (a note starts on all three
1029
+ voices), and 1,200 on the worst effect frame (a note starts on all three
1030
+ voices, one voice is handed back and another voice starts an effect).
1031
+ The Cost line is the technique's own work: the 142-cycle increment of
1032
+ the worst effect frame over the worst music-only frame plus the 351-cycle
1033
+ shadow copy, 493 (arithmetic from measured figures and the instruction
1034
+ table). A real player's cost replaces the recipe's music part (1,058
1035
+ minus 351 on its worst frame); the effect work adds about 142. 351 cycles of each figure are the shadow copy. The
1036
+ worst effect frame is 142 cycles over the worst music-only frame. The
1037
+ entry point costs 25 to 34 cycles per request including the `JSR`, by the
1038
+ instruction table, 6 of them the recipe's `dropped` counter where it
1039
+ runs. 1,200 cycles is 6.1 % of a PAL frame of 19,656 and
1040
+ 7.0 % of an NTSC frame of 17,095 (arithmetic).
1041
+
1042
+ ### Recipes
1043
+
1044
+ - `recipes/kickassembler/sfx-in-player.md`
1045
+
1046
+ ### Sources
1047
+
1048
+ - https://github.com/leafo/goattracker2/blob/master/readme.txt
1049
+ (GoatTracker 2 v2.72 readme, section 5.1, and warning 6 in section 1.1
1050
+ on ADSR bugs; upstream https://sourceforge.net/projects/goattracker2/).
1051
+ - https://github.com/cadaver/c64gameframework (MIT): `sound.s`
1052
+ (`QueueSfx`, `QueueSfxNoMusic`, per-channel `chnSfxNum`) and `raster.s`
1053
+ (the round-robin channel search). Read for facts; no code is taken from it.
1054
+
1055
+ ---
1056
+
1057
+ ## goattracker_player_api — GoatTracker 2 packed player: calls, zero page, sound effects and ghost registers
1058
+
1059
+ **Complexity:** low
1060
+ **Region:** both
1061
+ **Requires:** sid_play_routine_pattern
1062
+
1063
+ Everything in this entry is from the GoatTracker 2 v2.72 readme (rung 4
1064
+ here). Neither GoatTracker nor its relocator is installed on this machine,
1065
+ so no packed player was built, run or timed here. The readme gives the
1066
+ jump table and the options; it does not give the packed player's internal
1067
+ layout, and this entry does not either.
1068
+
1069
+ ### Why
1070
+
1071
+ A game that ships GoatTracker 2 music and wants sound effects from the same
1072
+ player needs the calling convention, the memory it takes, and the effect
1073
+ data format. It is the ready-made form of `sfx_in_player`.
1074
+
1075
+ ### How
1076
+
1077
+ The packer/relocator is F9 in the editor. It asks for the playroutine
1078
+ options, a start address, a zero-page address (two consecutive locations)
1079
+ and a file format: PRG, BIN or SID. It strips unused patterns, instruments,
1080
+ table entries and player code. A pattern over 64 rows may fail to relocate,
1081
+ and no packed pattern may exceed 256 bytes.
1082
+
1083
+ | Call | Registers | Address |
1084
+ |---|---|---|
1085
+ | Init a subtune | `A` = subtune, from 0 | `start` |
1086
+ | Play one frame | none | `start+3` |
1087
+ | Start an effect (sound-effect support on) | `A` = effect address low, `Y` = high, `X` = channel: 0, 7 or 14 for channels 1 to 3 | `start+6` |
1088
+ | Set master volume (volume support on) | `A` = 0 to 15 | `start+6`, or `start+9` with sound-effect support |
1089
+
1090
+ The volume call shares its location with the tune's `DXY` master-volume
1091
+ command, so the two clash. With "store author-info" on, the author string
1092
+ sits at `start+$20` to `start+$3F`, and a timing mark (a `DXY` with a
1093
+ parameter of `$10` or more) is copied into `start+$3F` when played.
1094
+
1095
+ **Priority.** Fixed by effect address: an effect higher in memory is never
1096
+ interrupted by one lower in memory. Lay the effect data out in order of
1097
+ importance. The caller picks the channel.
1098
+
1099
+ **Options that change the memory and the timing.** Buffered SID writes
1100
+ collect a channel's registers and write them in one go at the end of that
1101
+ channel's frame: "more memory & rastertime", more stable sound. Sound-effect
1102
+ support implies buffered writes. "Use zeropage ghostregs" writes a
1103
+ zero-page copy of the SID instead of the SID, and the game copies it after
1104
+ every play call with a reverse loop from `X = $18` to 0 into `$D400,X`; this
1105
+ also lets the player and its data sit under the I/O area. With the
1106
+ alternative hard restart (attack parameter F) that copy must write each
1107
+ channel's waveform, frequency and pulse width, then ADSR last. The 1- and
1108
+ 2-channel relocator optimisation cannot be combined with sound effects or
1109
+ ghost registers.
1110
+
1111
+ **Effect data.**
1112
+
1113
+ | Offset | Content |
1114
+ |---|---|
1115
+ | +0 | Attack/Decay |
1116
+ | +1 | Sustain/Release |
1117
+ | +2 | Pulse width with its nybbles swapped (`$800` in the editor is stored `$08`); written to both `$D402` and `$D403` |
1118
+ | +3 | Wavetable: `$00` ends the effect, `$01`-`$81` are waveforms, `$82`-`$DF` are absolute notes D-0 to B-7. A waveform may be left out when unchanged; a note may not |
1119
+
1120
+ INS2SND2 converts a GoatTracker instrument to this format. It refuses an
1121
+ effect over 128 bytes, relative notes, the notes C-0 and C#0, and waveforms
1122
+ above `$81`, and it drops the instrument's pulse modulation and filter.
1123
+
1124
+ ### Cycle budget
1125
+
1126
+ Not measured here. The readme points to its example programs and says "No
1127
+ promises!". Measure a packed player with the recipe's method (CIA timer
1128
+ around `JSR start+3`, below the badlines) before planning a frame around
1129
+ it.
1130
+
1131
+ ### Recipes
1132
+
1133
+ - No recipe yet. `recipes/kickassembler/sfx-in-player.md` is an original
1134
+ player of the same shape, not GoatTracker's.
1135
+
1136
+ ### Sources
1137
+
1138
+ - https://github.com/leafo/goattracker2/blob/master/readme.txt
1139
+ (GoatTracker 2 v2.72 readme, sections 5, 5.1 and 6.3, and the v2.34
1140
+ change note; upstream https://sourceforge.net/projects/goattracker2/).
1141
+
1142
+ ---
1143
+
1144
+ *Cross-references: [docs/hardware/sid-reference.md](../hardware/sid-reference.md) for the full register map, ADSR table, programming patterns, and pitfalls. [docs/recipes/oscar64/sid-music-player.md](../recipes/oscar64/sid-music-player.md) for a working Oscar64 implementation of sid_voice_setup + sid_play_routine_pattern.*