badline 0.1.0 → 0.2.1

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 (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +185 -0
  3. data/CLAUDE.md +217 -0
  4. data/CODE_OF_CONDUCT.md +92 -0
  5. data/CONTRIBUTING.md +51 -0
  6. data/README.md +224 -31
  7. data/doc/pinned-behaviour.md +1117 -0
  8. data/exe/badline +22 -2
  9. data/exe/badline-sid +34 -0
  10. data/lib/badline/address_bus.rb +96 -25
  11. data/lib/badline/audio/aiff.rb +36 -0
  12. data/lib/badline/audio/bare_player.rb +129 -0
  13. data/lib/badline/audio/cli.rb +133 -0
  14. data/lib/badline/audio/console.rb +75 -0
  15. data/lib/badline/audio/jukebox.rb +82 -0
  16. data/lib/badline/audio/machine_player.rb +57 -0
  17. data/lib/badline/audio/options.rb +137 -0
  18. data/lib/badline/audio/pcm_writer.rb +57 -0
  19. data/lib/badline/audio/playback.rb +108 -0
  20. data/lib/badline/audio/renderer.rb +85 -0
  21. data/lib/badline/audio/sdl_sink.rb +102 -0
  22. data/lib/badline/audio/wav.rb +23 -0
  23. data/lib/badline/audio.rb +14 -0
  24. data/lib/badline/cartridge/action_replay.rb +111 -0
  25. data/lib/badline/cartridge/atomic_power.rb +24 -0
  26. data/lib/badline/cartridge/bank.rb +81 -0
  27. data/lib/badline/cartridge/comal80.rb +48 -0
  28. data/lib/badline/cartridge/dinamic.rb +34 -0
  29. data/lib/badline/cartridge/easy_flash.rb +122 -0
  30. data/lib/badline/cartridge/epyx_fastload.rb +71 -0
  31. data/lib/badline/cartridge/final_cartridge3.rb +77 -0
  32. data/lib/badline/cartridge/flash.rb +265 -0
  33. data/lib/badline/cartridge/freezer.rb +31 -0
  34. data/lib/badline/cartridge/fun_play.rb +38 -0
  35. data/lib/badline/cartridge/g_mod2.rb +62 -0
  36. data/lib/badline/cartridge/game_system.rb +40 -0
  37. data/lib/badline/cartridge/kcs_power.rb +72 -0
  38. data/lib/badline/cartridge/mach5.rb +34 -0
  39. data/lib/badline/cartridge/magic_desk.rb +6 -3
  40. data/lib/badline/cartridge/ocean.rb +11 -12
  41. data/lib/badline/cartridge/pagefox.rb +56 -0
  42. data/lib/badline/cartridge/retro_replay.rb +276 -0
  43. data/lib/badline/cartridge/rex_utility.rb +33 -0
  44. data/lib/badline/cartridge/rgcd.rb +48 -0
  45. data/lib/badline/cartridge/simons_basic.rb +40 -0
  46. data/lib/badline/cartridge/standard.rb +3 -19
  47. data/lib/badline/cartridge/super_games.rb +36 -0
  48. data/lib/badline/cartridge/westermann.rb +34 -0
  49. data/lib/badline/cartridge/zaxxon.rb +50 -0
  50. data/lib/badline/cartridge.rb +132 -16
  51. data/lib/badline/cia/interrupt_register.rb +102 -0
  52. data/lib/badline/cia/serial.rb +219 -0
  53. data/lib/badline/cia/timer.rb +43 -31
  54. data/lib/badline/cia.rb +138 -88
  55. data/lib/badline/color_memory.rb +14 -5
  56. data/lib/badline/computer.rb +97 -12
  57. data/lib/badline/control_ports.rb +54 -9
  58. data/lib/badline/cpu/addressing.rb +131 -0
  59. data/lib/badline/cpu/microcode.rb +169 -0
  60. data/lib/badline/cpu/operations.rb +88 -0
  61. data/lib/badline/cpu/stack_operations.rb +82 -0
  62. data/lib/badline/cpu.rb +109 -168
  63. data/lib/badline/cycleable.rb +3 -0
  64. data/lib/badline/datasette.rb +81 -0
  65. data/lib/badline/debug_register.rb +28 -0
  66. data/lib/badline/gui/application.rb +79 -18
  67. data/lib/badline/gui/gamepads.rb +97 -0
  68. data/lib/badline/gui/joy_map.rb +15 -7
  69. data/lib/badline/gui.rb +1 -0
  70. data/lib/badline/input/mouse1351.rb +41 -0
  71. data/lib/badline/input/paddles.rb +36 -0
  72. data/lib/badline/input.rb +4 -0
  73. data/lib/badline/instruction_set/arithmetic.rb +16 -21
  74. data/lib/badline/instruction_set/bitwise.rb +17 -22
  75. data/lib/badline/instruction_set/branch.rb +20 -19
  76. data/lib/badline/instruction_set/flag.rb +7 -7
  77. data/lib/badline/instruction_set/illegal.rb +14 -22
  78. data/lib/badline/instruction_set/inc_dec.rb +8 -10
  79. data/lib/badline/instruction_set/stack.rb +5 -57
  80. data/lib/badline/instruction_set/transfer.rb +9 -9
  81. data/lib/badline/instruction_set.rb +9 -31
  82. data/lib/badline/interrupts.rb +104 -0
  83. data/lib/badline/joystick.rb +9 -9
  84. data/lib/badline/kernal_trap/channel.rb +51 -0
  85. data/lib/badline/kernal_trap/drive/status.rb +49 -0
  86. data/lib/badline/kernal_trap/drive.rb +277 -0
  87. data/lib/badline/kernal_trap/file.rb +5 -25
  88. data/lib/badline/kernal_trap/load.rb +86 -24
  89. data/lib/badline/kernal_trap/routine.rb +51 -0
  90. data/lib/badline/kernal_trap/save.rb +46 -14
  91. data/lib/badline/kernal_trap/serial.rb +171 -0
  92. data/lib/badline/kernal_trap.rb +4 -0
  93. data/lib/badline/keyboard.rb +40 -16
  94. data/lib/badline/media.rb +48 -10
  95. data/lib/badline/memory.rb +5 -0
  96. data/lib/badline/roms/eapi/LICENSE.md +15 -0
  97. data/lib/badline/roms/eapi/README +41 -0
  98. data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
  99. data/lib/badline/sid/decimator.rb +46 -0
  100. data/lib/badline/sid/envelope.rb +232 -0
  101. data/lib/badline/sid/filter.rb +213 -0
  102. data/lib/badline/sid/voice.rb +61 -0
  103. data/lib/badline/sid/waveform/combined.rb +126 -0
  104. data/lib/badline/sid/waveform/fast_forward.rb +105 -0
  105. data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
  106. data/lib/badline/sid/waveform.rb +289 -0
  107. data/lib/badline/sid.rb +274 -7
  108. data/lib/badline/status.rb +251 -23
  109. data/lib/badline/storage/crt_file.rb +18 -2
  110. data/lib/badline/storage/d64_image.rb +6 -0
  111. data/lib/badline/storage/d71_image.rb +4 -0
  112. data/lib/badline/storage/d81_image.rb +4 -0
  113. data/lib/badline/storage/disk_image.rb +99 -13
  114. data/lib/badline/storage/host_directory.rb +30 -4
  115. data/lib/badline/storage/sid_file.rb +274 -0
  116. data/lib/badline/storage/song_lengths.rb +65 -0
  117. data/lib/badline/storage/t64.rb +67 -0
  118. data/lib/badline/storage/tap.rb +68 -0
  119. data/lib/badline/storage.rb +19 -0
  120. data/lib/badline/time_of_day.rb +105 -52
  121. data/lib/badline/version.rb +1 -1
  122. data/lib/badline/vic/bank.rb +14 -6
  123. data/lib/badline/vic/border_mask.rb +76 -0
  124. data/lib/badline/vic/collisions.rb +104 -0
  125. data/lib/badline/vic/color_patches.rb +33 -0
  126. data/lib/badline/vic/display_state.rb +92 -29
  127. data/lib/badline/vic/graphics_mode.rb +78 -53
  128. data/lib/badline/vic/graphics_shifter.rb +140 -0
  129. data/lib/badline/vic/register_log.rb +63 -0
  130. data/lib/badline/vic/registers.rb +3 -0
  131. data/lib/badline/vic/sequencer.rb +163 -137
  132. data/lib/badline/vic/sequencer_output.rb +116 -0
  133. data/lib/badline/vic/sprite/internal_bus.rb +49 -0
  134. data/lib/badline/vic/sprite/shifter.rb +129 -0
  135. data/lib/badline/vic/sprite.rb +275 -65
  136. data/lib/badline/vic/sprites.rb +220 -58
  137. data/lib/badline/vic.rb +389 -55
  138. data/lib/badline.rb +5 -0
  139. data/vendor/.gitkeep +0 -0
  140. metadata +104 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee3a30a07439b0a0dbffe352b5667945721a7bc84fc594b62e79e007d255b801
4
- data.tar.gz: 2737e2a0dc04a81d264506aa406dc9cb3a661f37fdc3ec5135976ce5a654d558
3
+ metadata.gz: 7150f883c604da9d4f9bd4712412a020f9352dbbdba08a5051fee95e7f8bce30
4
+ data.tar.gz: cac44823e8c8afcc4637ad40383da5c624f51d29a60a45084b2ba6b81d6ae68e
5
5
  SHA512:
6
- metadata.gz: d450522ea11e666044fceff4d1c0ab24f3bfb384c04d71914c3eb8a4bd62319911da97873101a6e1a759b053cdac2a7c421aaa4d7d16a55dc30eb05e5ce01a8d
7
- data.tar.gz: 4cc7bdaa7cfdc414b0e1e8e5104115a8d5ff465428492165ca9880222cbbd56d06e45db9625f51b18f57956c5a9b86e51e442d42504b70c63ba2058c6bc1a091
6
+ metadata.gz: d09600dbf17cfa3df1d32e6ca5b8b25cd6f3cb012fd54c0cc0f7a4f700d98aaa45e9b0ba6cde95fa6c37e7310368f692e75c0f332f55fad84c5181a07df5da61
7
+ data.tar.gz: e3b2796713ab7701ab0a536276b6e845bb1751586c7706bdbe19a918129bb978927aef395062c98331c493af515a8ef77455446ca21289e434360753bf1aa89a
data/CHANGELOG.md ADDED
@@ -0,0 +1,185 @@
1
+ # Changelog
2
+
3
+ ## [0.2.1](https://github.com/elektronaut/badline/compare/v0.2.0...v0.2.1) (2026-09-23)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * decode U commands by the 1541's user table index ([4d37a92](https://github.com/elektronaut/badline/commit/4d37a928f9109d8214294fdac21bce25f201549b))
9
+ * detect disk image error tables by exact image size ([baa2091](https://github.com/elektronaut/badline/commit/baa20918b345723003ff61936c791f476e478fe8))
10
+ * detect the error table on 42-track D64 images ([9d8be41](https://github.com/elektronaut/badline/commit/9d8be41ffd23f0984f26e8e2c3ec0ba692629df9))
11
+ * end a SAVE the host directory can't write like a failed drive write ([4d15efe](https://github.com/elektronaut/badline/commit/4d15efe815d809fb7eba84cc9d59aebb4964f4ad))
12
+ * fail writes to a disk image like a write-protected 1541 ([c4ccb46](https://github.com/elektronaut/badline/commit/c4ccb46382166a896875a4f081c13db7841aaf7d))
13
+ * hand LOADs that reach below $0334 to the ROM's byte loop ([948b816](https://github.com/elektronaut/badline/commit/948b8163d0ba16d4eb4c71ee03ab4fe59297ba56))
14
+ * power-cycle the machine when a cartridge is attached ([2e1d7a4](https://github.com/elektronaut/badline/commit/2e1d7a472f00e0ccbd7a3245db7c685162e40786))
15
+ * print the KERNAL's SAVING message from the SAVE trap ([c912986](https://github.com/elektronaut/badline/commit/c912986e1702ba9907a097aaebd62b36e83b1fe5))
16
+ * print the KERNAL's SEARCHING and LOADING messages from the LOAD trap ([fc7e719](https://github.com/elektronaut/badline/commit/fc7e719e0856b8eddafbb96402893570cf757842))
17
+ * report the DOS version on the command channel at drive power-on ([016ad3f](https://github.com/elektronaut/badline/commit/016ad3f884a2fb1160f30372acd1ae972d0b9702))
18
+ * reset the virtual drive on the U-command reset forms ([a558314](https://github.com/elektronaut/badline/commit/a558314e7f116a28c8341ca82acce65c94469b9d))
19
+ * stop LOADs and file reads at a block the error table marks bad ([457be18](https://github.com/elektronaut/badline/commit/457be18351af7241a26f76631ec9bfad0ce2306e))
20
+
21
+ ## [0.2.0](https://github.com/elektronaut/badline/compare/v0.1.0...v0.2.0) (2026-09-23)
22
+
23
+
24
+ ### Features
25
+
26
+ * add the Action Replay, Retro Replay and Final Cartridge III freezers ([7f4a8ba](https://github.com/elektronaut/badline/commit/7f4a8ba83aee493f22e1bcf8eec03b786ec8b119))
27
+ * add the KCS Power Cartridge (type 2) ([18c3fec](https://github.com/elektronaut/badline/commit/18c3feca3308a8bbe850a3618a7a9fac0eb0df8b))
28
+ * add the Retro Replay's flash mode on the Am29F010 ([4ad4001](https://github.com/elektronaut/badline/commit/4ad40013f5c563a3b94a951881f4b19b70d332e3))
29
+ * **audio:** default the render length from HVSC's song lengths ([5ab7678](https://github.com/elektronaut/badline/commit/5ab76785770bb6386618b531b225b19f477c4cb9))
30
+ * **audio:** render .sid tunes to WAV and AIFF offline ([913c927](https://github.com/elektronaut/badline/commit/913c9274fbf7571d190024a95b9aa0d52ffb5400))
31
+ * bundle EAPI and swap it into EasyFlash cartridges ([421fed1](https://github.com/elektronaut/badline/commit/421fed11d80fcd98a37a69b38a86de56d88c5e2c))
32
+ * carry the Lorenz chain onto Disk4.d64 ([cec8f18](https://github.com/elektronaut/badline/commit/cec8f1867a49d907133f2d9c2b8610aa0ea25a9c))
33
+ * **cartridge:** 15 more CRT hardware types, EasyFlash and Epyx FastLoad included ([8c58b02](https://github.com/elektronaut/badline/commit/8c58b023c2b3b4949a9f401b4c332bed177e78e2))
34
+ * **cartridge:** bank-switching mappers ([4a3c3ee](https://github.com/elektronaut/badline/commit/4a3c3eebc578c03859a86cda39c1f423d6c138f3))
35
+ * **cartridge:** EasyFlash ([d57f5c3](https://github.com/elektronaut/badline/commit/d57f5c3aa117eb8a50f958d8e30b3ccffa962212))
36
+ * **cartridge:** Epyx FastLoad ([6c9a5a4](https://github.com/elektronaut/badline/commit/6c9a5a4a6016335ed2fd4d015a99f02814864eb2))
37
+ * **cartridge:** Pagefox ([6c80f47](https://github.com/elektronaut/badline/commit/6c80f472638cdaed40df0152e0a3b74214c1ed6b))
38
+ * **cartridge:** Simons' BASIC, Westermann, Rex Utility and Mach 5 ([1ece033](https://github.com/elektronaut/badline/commit/1ece0338fb7b2128bca530d9cd92a5da6906f2cd))
39
+ * **cia:** model the CNT pin, serial port and TOD divider ([dd6d17a](https://github.com/elektronaut/badline/commit/dd6d17a02a2421e88de2d642852adf448aa393fd))
40
+ * **cia:** trigger the light pen from control port 1's fire line ([a4059d2](https://github.com/elektronaut/badline/commit/a4059d268a3402d1324f770edb35293aacdfcdb4))
41
+ * **cpu:** let SH* and ANE see VIC stalls ([84bc62b](https://github.com/elektronaut/badline/commit/84bc62b912c07892db2921104b1677e00e0deeeb))
42
+ * **cpu:** model the dummy reads on internal cycles ([a34c37b](https://github.com/elektronaut/badline/commit/a34c37bd79ddb0c749aedafe65e1455cd4df9a38))
43
+ * **cpu:** per-cycle interrupt polling with VIC raster-IRQ recalibration ([056e235](https://github.com/elektronaut/badline/commit/056e23554efb7026f3b58d3a25868fbc20c5c442))
44
+ * fit and score the combined waveform model against real-chip dumps ([232cab2](https://github.com/elektronaut/badline/commit/232cab2a7088204f8eb10c962264cc94ac872786))
45
+ * **gui:** add host gamepads and pointer input modes ([3fc5904](https://github.com/elektronaut/badline/commit/3fc5904d9cdf11b6bdd3461cca7d11f77200184b))
46
+ * **gui:** offer the mouse and paddles on either control port ([4d678e5](https://github.com/elektronaut/badline/commit/4d678e50c51131d0f00b12f1832875b654adcf21))
47
+ * **input:** add joystick 1 on CIA 1 port B ([05465b4](https://github.com/elektronaut/badline/commit/05465b4f5d3e0642dcb1967d1e2419c8c7647c2d))
48
+ * **input:** add paddles, a 1351 mouse and the CIA 1 POT mux ([85e079f](https://github.com/elektronaut/badline/commit/85e079f86a451fefaf11c2aa7cd9127bd538003a))
49
+ * **keyboard:** scan the matrix in both directions ([6a258db](https://github.com/elektronaut/badline/commit/6a258db51b813ddbb9f99f0727d1a9b7c65502b6))
50
+ * play .sid tunes live in badline-sid ([9ad32ed](https://github.com/elektronaut/badline/commit/9ad32edde74a8321f4d0ea81007b4e6b2051e976))
51
+ * **rake:** resume a killed testbench suite run with RESUME=1 ([d907434](https://github.com/elektronaut/badline/commit/d907434763760f62c8295c39e60ec795311528cc))
52
+ * **rake:** run the Lorenz chain as four stretches ([a7be7e1](https://github.com/elektronaut/badline/commit/a7be7e1efe022700190ad88042af781e6038c80e))
53
+ * rename badline-render to badline-sid ([74a832f](https://github.com/elektronaut/badline/commit/74a832fc682e2ae117eec529d35289a59acacbd8))
54
+ * rename badline-render to badline-sid, with -o and -s options ([68ff4c0](https://github.com/elektronaut/badline/commit/68ff4c01bed0571122145b28785b9a70e31698e2))
55
+ * **render:** add --filter-chunk to trade filter exactness for speed ([d5e037c](https://github.com/elektronaut/badline/commit/d5e037cfb0ebdfd8a448b3977e1715573da6cc0e))
56
+ * Retro Replay flash mode ([a4e444f](https://github.com/elektronaut/badline/commit/a4e444f215b0920f24c4d98e06f9b2ea65b6d9ff))
57
+ * run the testbench's cartridge rows as testbench-carts ([2003835](https://github.com/elektronaut/badline/commit/2003835226e4d7e11363356bcd2c978a6d91f986))
58
+ * run the testbench's cartridge rows with --carts ([3f9ec9c](https://github.com/elektronaut/badline/commit/3f9ec9c6cd8bc7ab2b70fd578fde0831ed42a0bc))
59
+ * select the 8580 and connect the paddle fire buttons ([05f8d1c](https://github.com/elektronaut/badline/commit/05f8d1cbd90212f7a30f44df51bee7d05a5e1a4e))
60
+ * **sid:** add the 6581 synthesis core ([3748c99](https://github.com/elektronaut/badline/commit/3748c994cdd708c1937de8757b8e49c23c04f4fe))
61
+ * **sid:** back-fill the idle DSP and add the 8580 chip model ([b0f8fef](https://github.com/elektronaut/badline/commit/b0f8fef6c57578fdced09de7a2853e6d6cf222e9))
62
+ * **sid:** fitted combined waveform shapes and a one-cycle pulse lag ([c9df98a](https://github.com/elektronaut/badline/commit/c9df98ab6e31619c5a3c36f776cf8ed2824be18e))
63
+ * **sid:** model combined waveform feedback and the floating waveform-0 DAC ([a0d4286](https://github.com/elektronaut/badline/commit/a0d4286b237d7919e0009c4d88ea703cc3337345))
64
+ * **sid:** model register read semantics and the paddle inputs ([ef00ef9](https://github.com/elektronaut/badline/commit/ef00ef9a34d5bac696f1b8c47618c403847a1987))
65
+ * **sid:** model the board's RC filter and the 8580 cutoff curve ([f6038ac](https://github.com/elektronaut/badline/commit/f6038ac54188f4a0a9cdf5ff3d97b5ea668682c6))
66
+ * **sid:** pick the subtune to play with --song ([f896627](https://github.com/elektronaut/badline/commit/f896627db8f1f83fe6328d415ef999d2924ff0dd))
67
+ * **sid:** select the 8580 from a tune's header or --sid ([9b1267a](https://github.com/elektronaut/badline/commit/9b1267a0643c062e512eaeef7a3ad356dc7a2827))
68
+ * **sid:** shape combined waveforms from a fitted line-coupling model ([c12f707](https://github.com/elektronaut/badline/commit/c12f7074daba13664066d8e8346dea05bf5f1d5e))
69
+ * step between songs and pause from the terminal in badline-sid ([a1facca](https://github.com/elektronaut/badline/commit/a1facca9d87d7e6be040e2ff3e88f3617ba4b6f8))
70
+ * **storage:** play .sid tunes through a raster-timed player stub ([75ec9fc](https://github.com/elektronaut/badline/commit/75ec9fce5104d95c8bfc39067f62962f9f9eda3f))
71
+ * **storage:** play .tap datasette streams ([d9dc69e](https://github.com/elektronaut/badline/commit/d9dc69eef1ad9c4fbd65290127cee4d210045717))
72
+ * **storage:** serve .t64 archives from mounted directories ([f03a988](https://github.com/elektronaut/badline/commit/f03a988a3482952cad459bf71962c235fec2e728))
73
+ * **storage:** serve .t64 tape archives through the LOAD trap ([7e4faa7](https://github.com/elektronaut/badline/commit/7e4faa7183ae7446caf07f6103e2aced9f39f6c1))
74
+ * **storage:** serve device 8 over trapped serial bus primitives ([4a6ec70](https://github.com/elektronaut/badline/commit/4a6ec7006bdcdc459e3cfc76a379ca2bfc249edc))
75
+ * terminal display for badline-sid playback ([edc37ac](https://github.com/elektronaut/badline/commit/edc37ac758d8e1760475d16b4e89c1e077467870))
76
+ * **testbench:** keep finished rows when a run is killed, and --resume from them ([3155e41](https://github.com/elektronaut/badline/commit/3155e41bf7171c94993f870c60ffb349e6e80a9b))
77
+ * **test:** guard the headless suites with tracked baselines ([7bc4df0](https://github.com/elektronaut/badline/commit/7bc4df05dfaeb89b24d05e2962a85d9a7295df54))
78
+ * **test:** select the SID model in bin/sidtests ([dcc55d0](https://github.com/elektronaut/badline/commit/dcc55d00fce90744c0d21d6797d9d4f5c4a27d0e))
79
+ * **vic:** cycle-accurate border, sprite timing, idle graphics and light pen ([e67ee00](https://github.com/elektronaut/badline/commit/e67ee0082229b292d230d5ea15b4b9a68201a881))
80
+ * **vic:** model bad-line DMA delay and the BA-to-AEC fetch gap ([529b5ca](https://github.com/elektronaut/badline/commit/529b5caad882dc306313e9cd2d6edbf8956a1e75))
81
+ * **vic:** replay sprite registers through a per-pixel sequencer ([91be311](https://github.com/elektronaut/badline/commit/91be3115fc1701b3a3894ab2e2a560fdf3705642))
82
+ * write EasyFlash and GMod2 flash through the Am29F040 command set ([690e6f2](https://github.com/elektronaut/badline/commit/690e6f256071018f4413cacdd126eaee55c7f784))
83
+
84
+
85
+ ### Bug Fixes
86
+
87
+ * cartridge-visible power-on state and 16K PLA mapping ([d971f72](https://github.com/elektronaut/badline/commit/d971f72105a09f70c10646a1388ac52b3d03d680))
88
+ * **cia:** count the shift register empty at the eighth bit ([a1d2fd1](https://github.com/elektronaut/badline/commit/a1d2fd1fa3a7f72a02adf98f1c8874c64b39cf1f))
89
+ * **cia:** count timer B's cascade input through the enable pipeline ([c7d6439](https://github.com/elektronaut/badline/commit/c7d6439495a5ffb790fe6c79a500a4cec6d996e9))
90
+ * **cia:** load the timer latch on the underflow tick ([2be77c1](https://github.com/elektronaut/badline/commit/2be77c152d1572763dac70a27e62cf6d57d38bba))
91
+ * **cia:** model the 6526 ICR acknowledge timing and timer B bug ([7422b13](https://github.com/elektronaut/badline/commit/7422b1380445625e13a787773ca8b3296f4aa429))
92
+ * **cia:** port the TOD clock's digit counters, alarm compare and divider ([c301ec5](https://github.com/elektronaut/badline/commit/c301ec5140d37a38f559d254bce44d0e264087d9))
93
+ * **cia:** port the TOD digit counters, alarm compare and divider ([5ad8000](https://github.com/elektronaut/badline/commit/5ad8000be779792f6d5b98ff9e6af21f64a95b01))
94
+ * **cia:** power the timer latches and counters on at $ffff ([611fdb6](https://github.com/elektronaut/badline/commit/611fdb61f4c839555549cd44420d8ee06a53636d))
95
+ * **cpu:** halt on JAM until reset ([c262d3e](https://github.com/elektronaut/badline/commit/c262d3eb4c1bdf1325169e8c10d762bc7bcd4326))
96
+ * **cpu:** keep sampling the interrupt lines while the VIC stalls the CPU ([d313090](https://github.com/elektronaut/badline/commit/d313090436c8397a61dd18d86b80d18aaf41507f))
97
+ * **crt:** ignore trailing bytes too short for a CHIP header ([bbb9b09](https://github.com/elektronaut/badline/commit/bbb9b09aa71dea4fa457f3b3cafd33fe93e99e22))
98
+ * give the SID's noise combinations their own writeback shape ([b87da71](https://github.com/elektronaut/badline/commit/b87da7196f7ffe6853fc007c10b2b2f07051071d))
99
+ * **input:** wire the host mouse buttons to the paddle fire lines ([171ae1e](https://github.com/elektronaut/badline/commit/171ae1e3a8db4a5a29eb1ff1621026e056bdebef))
100
+ * keep the SID's accumulators across a reset ([e066262](https://github.com/elektronaut/badline/commit/e06626225374214c5db3f058e78fb3f3571b32b6))
101
+ * **keyboard:** add the missing F5 key to the matrix ([702a0bb](https://github.com/elektronaut/badline/commit/702a0bbd520a47232d1aa677d25aef6108c44426))
102
+ * map RAM at $d000 in 16K mode while only LORAM is set ([01aaff0](https://github.com/elektronaut/badline/commit/01aaff07fb1d61d0f1ed627360f93bbd6b4f98ad))
103
+ * map the Nordic Replay's RAM at $A000 while frozen in Ultimax mode ([3a2215c](https://github.com/elektronaut/badline/commit/3a2215c6e0b69a908f5da2b63ef013dcf27b6a91))
104
+ * open SEQ and USR files on a disk image by their type ([8a0c0b0](https://github.com/elektronaut/badline/commit/8a0c0b055acaeead2ab5eb10ab6992e5a59dbcaa))
105
+ * pace CIA-timed PSID songs off CIA 1 timer A ([a7702fc](https://github.com/elektronaut/badline/commit/a7702fcca61a027a8bfbe6b231f3ddf5267f0f62))
106
+ * pace CIA-timed PSID songs off CIA 1 timer A on the bare rig ([dd8ec20](https://github.com/elektronaut/badline/commit/dd8ec20926c96c0b587aa74fca0a099cbdf4aa0d))
107
+ * pipeline the SID envelope as reSID 1.0 does ([25505ce](https://github.com/elektronaut/badline/commit/25505ced0e202e6b54d9f42d442ce604f541d5b8))
108
+ * power the CIA ports on with every register cleared ([20c12e8](https://github.com/elektronaut/badline/commit/20c12e8a4d85d3c94f630d697f59061c500415eb))
109
+ * power the CPU port on with both registers cleared ([8ba1be9](https://github.com/elektronaut/badline/commit/8ba1be9882cddc69bea9a85b8118a00faa1b3eda))
110
+ * read open bus from the unmapped Ultimax address space ([efcadf6](https://github.com/elektronaut/badline/commit/efcadf6ec742fff40f671e361e3805e8b3c03eb4))
111
+ * read the VIC's phi1 byte from unconnected I/O ([0dda282](https://github.com/elektronaut/badline/commit/0dda2821773b44b63d7181a89c6429087c1b4432))
112
+ * refuse multi-SID and MUS-flag .sid files ([f3c7da6](https://github.com/elektronaut/badline/commit/f3c7da645da714f2409ecbdf50357cb92411d115))
113
+ * report the error table's read errors on block reads ([065f62c](https://github.com/elektronaut/badline/commit/065f62c25811c403f49d094ff1984617e4ffce80))
114
+ * report unreadable media without a backtrace ([9c75c79](https://github.com/elektronaut/badline/commit/9c75c797607d02319ddb77a6a569cc89ea4cf91f))
115
+ * reset the bank-switching mappers' registers on RES ([39c071d](https://github.com/elektronaut/badline/commit/39c071d29bcf9f21e9b8d17e2665f52c86a66811))
116
+ * reset the CIAs, SID and CPU port when a cartridge is attached ([8264966](https://github.com/elektronaut/badline/commit/826496674243ea6d2661d89fc83ee46b3a791ff0))
117
+ * return open bus in colour RAM's upper nibble ([6e8348e](https://github.com/elektronaut/badline/commit/6e8348eae92014c1a48f8ac93ce14c0a88a92eb4))
118
+ * run the chip-agnostic SID testprogs on the 8580 as well ([77918a8](https://github.com/elektronaut/badline/commit/77918a82fc617a65fd6f62adaa82c9d86266e6c9))
119
+ * serve M-W and M-R from drive RAM and run read jobs ([81561dc](https://github.com/elektronaut/badline/commit/81561dc4a3eeda648439bbb65bb6d9dca8995574))
120
+ * serve the DOS B-R block read on the virtual drive ([cdeb085](https://github.com/elektronaut/badline/commit/cdeb08579f0dea239f6fe7aba8ef613e832918a0))
121
+ * **sid:** bank the ROMs out around a tune's init and play ([b664987](https://github.com/elektronaut/badline/commit/b664987a0016a6423365a069703041950007b0a5))
122
+ * **sid:** carry fractional bits through the filter integrators ([73bac1c](https://github.com/elektronaut/badline/commit/73bac1c81ed1d19bcfefe34987d8db2df1386558))
123
+ * **sid:** feed the RC network the filter's fractional mix ([9e670c0](https://github.com/elektronaut/badline/commit/9e670c0bb23373a007049356df5537127a61afa4))
124
+ * **sid:** keep a playing tune from returning to BASIC ([de49325](https://github.com/elektronaut/badline/commit/de49325026588a584fc71a144d1c58338780b56f))
125
+ * **sid:** let the test bit bleed the noise LFSR as slowly as real chips do ([3d95639](https://github.com/elektronaut/badline/commit/3d95639c51b8ec802790d1811a902d95910c44bc))
126
+ * **sid:** reach the output with the pulse a cycle after its compare ([b1bf3eb](https://github.com/elektronaut/badline/commit/b1bf3eb4f8d37dd9182943e2b0388da485aefe67))
127
+ * **sid:** read the 8580's triangle and sawtooth a cycle late on OSC3 ([96ece2b](https://github.com/elektronaut/badline/commit/96ece2bb7489ced2979b785015839c25701e20e5))
128
+ * **sid:** shift the noise LFSR two cycles after bit 19 rises ([b143001](https://github.com/elektronaut/badline/commit/b1430016117333706b3b159e02ce6a0a509c8f9e))
129
+ * **sid:** write the LFSR back on test release only for some waveform changes ([0ca3afb](https://github.com/elektronaut/badline/commit/0ca3afb02ce3873b6a3c6204ee6a044f626bd6da))
130
+ * stop testbench shards when the runner is killed ([0c05c3e](https://github.com/elektronaut/badline/commit/0c05c3e69c115bdb439f70949771876426f9491b))
131
+ * strip the type and mode fields from LOAD and SAVE names ([f09aab3](https://github.com/elektronaut/badline/commit/f09aab3bbf9dbb68267f4ec9d438b98669bbf499))
132
+ * **vic:** 40-column side border compares see CSEL a pixel late ([b9a1904](https://github.com/elektronaut/badline/commit/b9a19043b256cb2990914c7ce09dc1d28f7a9807))
133
+ * **vic:** colour the c-accesses before AEC from the halted CPU's bus ([4661fa3](https://github.com/elektronaut/badline/commit/4661fa34b6663ba7eed7acb6c35e7d7656717d44))
134
+ * **vic:** compare the vertical border in every cycle of its line ([fcde974](https://github.com/elektronaut/badline/commit/fcde974487320ea1ebee69ede2e5104f0775b045))
135
+ * **vic:** crunch sprites on a $d017 clear in cycle 15 ([11d15ef](https://github.com/elektronaut/badline/commit/11d15ef499314b2011ca71cda5746a23c5ea0ec5))
136
+ * **vic:** delay the line 0 raster read by a cycle ([3f4fddc](https://github.com/elektronaut/badline/commit/3f4fddc378b08af2127401497e1a3ee7d07686ae))
137
+ * **vic:** hold a falling ECM for a g-access that leaves the character ROM ([4a4a9b0](https://github.com/elektronaut/badline/commit/4a4a9b0cd1d8b71a2d640cf590bdae71593a3246))
138
+ * **vic:** hold a falling ECM for the g-access that leaves the character ROM ([ffe9a6f](https://github.com/elektronaut/badline/commit/ffe9a6fcf0e44a4457e8bd100009d980bde47439))
139
+ * **vic:** latch sprite collisions per pixel and match Y in eight bits ([8495ed8](https://github.com/elektronaut/badline/commit/8495ed80ddd22555979b5ea310b10416119c1126))
140
+ * **vic:** latch the raster IRQ only as the compare comes to match ([62ccc11](https://github.com/elektronaut/badline/commit/62ccc114420fe8837fbb3794e316bd22a1440bf5))
141
+ * **vic:** let the 40-column side border compares see CSEL a pixel late ([65fe47f](https://github.com/elektronaut/badline/commit/65fe47fda7acbc90304c181e01818147ace711e0))
142
+ * **vic:** model the sprite reload, its dead window and the display end ([e36219e](https://github.com/elektronaut/badline/commit/e36219e8ce7032fd9cd7e387ec6a14e4d1454001))
143
+ * **vic:** move bad-line and sprite BA to the cycles the CPU sees them ([b8c8583](https://github.com/elektronaut/badline/commit/b8c8583343975a2f04224e58a6d5edce488d459a))
144
+ * **vic:** raise the collision IRQ on the cycle that draws the pixel ([e81090b](https://github.com/elektronaut/badline/commit/e81090bf0944ba33074e6c8a7121335cb45f9e0d))
145
+ * **vic:** re-phase the display state to Bauer's cycles in the CPU's frame ([95381ef](https://github.com/elektronaut/badline/commit/95381ef765353bcbbb6b69962e9f7b23fa76018e))
146
+ * **vic:** read g-accesses in their column and shift graphics per pixel ([4cf1841](https://github.com/elektronaut/badline/commit/4cf1841351eee05cbddc979071afdd46460bfe71))
147
+ * **vic:** read line 311 from $d011/$d012 until line 0's compare cycle ([01fb629](https://github.com/elektronaut/badline/commit/01fb6291768c50c3d0fdfa137c9ffb6bfc379802))
148
+ * **vic:** run the vertical border and raster IRQ compares in every cycle ([8149255](https://github.com/elektronaut/badline/commit/8149255559cf406545d4c14bc59d54b21fe6afb2))
149
+ * **vic:** shift out zero data where the sequencer gets no g-access ([f88100f](https://github.com/elektronaut/badline/commit/f88100fdb00101898f4b92a3a52670bf4b2862e5))
150
+ * **vic:** sprite crunch and the expansion flip-flop timing ([171bd30](https://github.com/elektronaut/badline/commit/171bd3005ef836f869f08f47a415f1a0b6f744a3))
151
+ * **vic:** sprite reload hold, K-1 multicolor pair and s-accesses without DMA ([de60342](https://github.com/elektronaut/badline/commit/de60342f8de9515ee9adade4437c39f07afc6252))
152
+ * **vic:** sprite reload hold, K-1 pair and s-accesses without DMA ([30b7ec9](https://github.com/elektronaut/badline/commit/30b7ec99432f21ccdd48d2687896b3ff7995ca05))
153
+ * **vic:** take an MCM falling out of ECM+MCM a pixel late ([f9bfa21](https://github.com/elektronaut/badline/commit/f9bfa217cde58cd7cd98bb66dba6b630662736ee))
154
+ * **vic:** work out the phi1 bus byte per cycle ([cdda146](https://github.com/elektronaut/badline/commit/cdda1465133338141a07240e3e508b82d1ef412e))
155
+ * **vic:** wrap sprite BA past the line end and gate display on MxE ([3d8173c](https://github.com/elektronaut/badline/commit/3d8173c35413bdb07863973dfd464eb741f4d28a))
156
+
157
+
158
+ ### Performance Improvements
159
+
160
+ * **cia:** cache the timer's settled and empty states ([9deeadd](https://github.com/elektronaut/badline/commit/9deeadd384d25833652c3bcfb7b4d9f1d2344563))
161
+ * **cia:** skip the idle shift register ([a6ba3d4](https://github.com/elektronaut/badline/commit/a6ba3d4f589c12c881d296c4f4298ea580c7f0c2))
162
+ * **cia:** trim the per-cycle CIA work ([5b727c6](https://github.com/elektronaut/badline/commit/5b727c666a66b2d714eb5f4235d639582c76967d))
163
+ * compare finished VIC lines against a per-line copy ([0682d6a](https://github.com/elektronaut/badline/commit/0682d6a77c6ee83026bbf81743b7394c935fbdc3))
164
+ * compile Status flag accessors as plain methods ([8c1bdbe](https://github.com/elektronaut/badline/commit/8c1bdbe9c96e9a9d294685c1d509100414d22306))
165
+ * **cpu:** replace the fiber with a microcode sequencer ([1d6f760](https://github.com/elektronaut/badline/commit/1d6f760ac0bd7c4d5b5ffd1b9257ac2da792ee72))
166
+ * drop per-cycle method calls from the CIA and CPU hot paths ([a8a2843](https://github.com/elektronaut/badline/commit/a8a284324b650e06f12621fa45854bdf8cdbf60b))
167
+ * drop two cfunc calls from the VIC's per-cycle paths ([7fe1f6b](https://github.com/elektronaut/badline/commit/7fe1f6b92eb88d93282204cabe60a51168d5715d))
168
+ * let a headless run leave the VIC's colours unpainted ([34d1c58](https://github.com/elektronaut/badline/commit/34d1c588928ad0196db27755b7c2bdf3c48784c0))
169
+ * let headless runs leave the VIC's colours unpainted ([3154996](https://github.com/elektronaut/badline/commit/3154996b49aac8b2e96ed09132df4824e3d42944))
170
+ * look up graphics columns and test sprite codes with zero? ([6d5fc93](https://github.com/elektronaut/badline/commit/6d5fc9389b20c25bb0f74e7b283f695a54d67ce2))
171
+ * paint hires pixels from a per-byte mask table ([42de4fe](https://github.com/elektronaut/badline/commit/42de4fe1b57bcf955d6332a15272352da87abe4e))
172
+ * paint multicolour pairs from a palette and hires pixels from the fg mask ([d57f709](https://github.com/elektronaut/badline/commit/d57f709e147167140800c3391129e9816cf18665))
173
+ * paint multicolour pairs from a per-group palette ([95f633c](https://github.com/elektronaut/badline/commit/95f633c717710f47a6ade79cecb057db433125c2))
174
+ * run exitcode testbench rows and Lorenz with the VIC unpainted ([6f84e35](https://github.com/elektronaut/badline/commit/6f84e358e9e5c1a3200a40898d22d1485c94f853))
175
+ * run the SID testprogs with the VIC unpainted ([78e8c75](https://github.com/elektronaut/badline/commit/78e8c75a566284ef618aadf4655774079011a680))
176
+ * set Status flags without renormalizing the value ([d2d3033](https://github.com/elektronaut/badline/commit/d2d3033fe137786f03e937a7e2c689c9f58ce677))
177
+ * **sid:** age the bus latch without a method call per cycle ([3ea58b1](https://github.com/elektronaut/badline/commit/3ea58b10ec97985374f5c14fde278688a44949d1))
178
+ * **sid:** batched synthesis with a pull API for audio ([cd2fb42](https://github.com/elektronaut/badline/commit/cd2fb42afcb6ac65e815e98b9981262489ee3674))
179
+ * **sid:** catch the DSP up a span at a time ([4157da0](https://github.com/elektronaut/badline/commit/4157da096d98fe63aaf55c3bbffcf5cf999418ae))
180
+ * **sid:** fast-forward a combined waveform that writes nothing back ([11f9837](https://github.com/elektronaut/badline/commit/11f9837ab74ca979bac882e9443e164eb6098cf6))
181
+ * **sid:** unroll the per-cycle voice loops ([7be0ee9](https://github.com/elektronaut/badline/commit/7be0ee9ab9c7806fcc5b94a59df64099f7d40cda))
182
+ * **test:** boot once and fork a child per test in bin/testbench and bin/sidtests ([7db72e0](https://github.com/elektronaut/badline/commit/7db72e0a6844b30931f7fb73c8196afe86ad5137))
183
+ * unroll the multicolour pair loop ([a0a6f4e](https://github.com/elektronaut/badline/commit/a0a6f4eba30f8caa6f224b3e62b1910329d47839))
184
+ * **vic:** answer the bad-line BA check from the last column's compare ([aa1e983](https://github.com/elektronaut/badline/commit/aa1e983710f000b0c3a544430408ac5c768fba5c))
185
+ * **vic:** stop allocating per column in the sequencer and border restore ([1eeac57](https://github.com/elektronaut/badline/commit/1eeac57f836d791b5c9674bd286716f8284a1afa))
data/CLAUDE.md ADDED
@@ -0,0 +1,217 @@
1
+ # Badline
2
+
3
+ A cycle-accurate Commodore 64 emulator in Ruby. `README.md` covers usage,
4
+ supported media and what is emulated, and `CONTRIBUTING.md` covers setup.
5
+ This file covers how to work on the code.
6
+
7
+ ## Orientation
8
+
9
+ `lib/badline/` holds one file per chip or subsystem, and namespaced groups
10
+ live in subdirectories:
11
+
12
+ - **Machine**: `computer.rb` clocks everything once per cycle (`cycle!`:
13
+ VIC, both CIAs, SID and the datasette, then the CPU unless the VIC holds
14
+ BA low). `address_bus.rb` handles banking and page-table dispatch
15
+ - **CPU**: `cpu.rb`, `instruction.rb` and `instruction_set/`, plus
16
+ `interrupts.rb`
17
+ - **VIC-II**: `vic.rb` and `vic/`, covering the sequencer, sprites, graphics
18
+ modes, border and register timing
19
+ - **CIA**: `cia.rb` and `cia/` (timers, serial), plus `time_of_day.rb`
20
+ - **SID**: `sid.rb` and `sid/`. `audio/` plays and renders tunes for
21
+ `exe/badline-sid`
22
+ - **Media and host I/O**: `storage/` (disk, tape and cartridge image
23
+ formats), `cartridge/` (mappers), `kernal_trap/` (the LOAD/SAVE and IEC
24
+ traps that stand in for a drive), `media.rb` (attach and autostart),
25
+ `datasette.rb`, and `gui/` and `input/` (SDL front end, controllers)
26
+
27
+ Timing-critical code lives in `cpu`, `interrupts`, `vic`, `cia` and `sid` and
28
+ in the order `Computer#cycle!` clocks them. Changes there move the test
29
+ suites below. `gui/` changes move none of them.
30
+
31
+ Namespaced groups live in `lib/badline/<namespace>/`, with a sibling
32
+ `lib/badline/<namespace>.rb` that requires the members. `lib/badline.rb`
33
+ requires only the namespace file.
34
+
35
+ ### Oracles
36
+
37
+ | Oracle | Runner | Covers |
38
+ | --- | --- | --- |
39
+ | rspec (`spec/`) | `bundle exec rspec` | Unit behaviour, all of `lib/` |
40
+ | SingleStepTests 65x02 | `rake test` (100 sampled cases per opcode) | CPU, per-cycle bus traces |
41
+ | Wolfgang Lorenz suite | `bin/lorenz` | CPU, CIA, interrupts |
42
+ | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, and cartridges with `--carts` |
43
+ | VICE SID testprogs | `bin/sidtests` | SID |
44
+ | CIA offline grids | `bundle exec rspec --tag slow spec/badline/cia` | CIA timers and shift register, against a bare CIA in about 2 min |
45
+
46
+ The CIA offline grids replay Lorenz's `cia1ta` and `cia1tb` sweeps (about
47
+ 8 s) and the `cia-sdr-icr` loop for every timer A latch (about 2 min),
48
+ checked against each test's own results. On the Lorenz chain, a failing
49
+ timer test takes about 25 minutes.
50
+
51
+ `bin/benchmark` measures emulation speed after boot, and `bin/profile` shows
52
+ where that time goes. `test/baselines/README.md`
53
+ documents the baseline format, the suites and how rows are compared.
54
+
55
+ ## Driving the emulator headlessly
56
+
57
+ `exe/badline <media>` opens the SDL window, which is no use for checking
58
+ work. Drive a `Badline::Computer` from Ruby instead, as the `bin/` runners do:
59
+
60
+ ```ruby
61
+ computer = Badline::Computer.new
62
+ Badline::Media.attach(computer, path) # PRG, disk, tape, CRT or directory; autostarts
63
+ computer.on_init { computer.type_text("print 6*7\r") } # after boot (~2.5M cycles)
64
+ 3_500_000.times { computer.cycle! }
65
+ computer.address_bus.peek(0x0400) # screen RAM starts at $0400
66
+ ```
67
+
68
+ - `computer.capture_output` traps CHROUT and records what the machine prints
69
+ - `computer.install_debug_register { |code| ... }` receives the `$D7FF`
70
+ exit code that testprogs report through (`$00` pass, `$ff` fail)
71
+ - To test a BASIC snippet, convert it with `petcat -w2 -o out.prg in.txt`
72
+
73
+ ## Working in parallel
74
+
75
+ Several sessions work in this repo at once. Each one has its own git
76
+ worktree and owns a different set of files.
77
+
78
+ - **One branch per unit of work**, cut from a freshly fetched `origin/main`,
79
+ in a worktree under `.claude/worktrees/` (gitignored). A lane can keep
80
+ its worktree between tasks: when `git status --short` is clean, start
81
+ the next unit with `git fetch && git switch -c <branch> origin/main`
82
+ - **Edit only the files your task names.** Other sessions own the rest, and
83
+ edits outside your set collide with theirs. If you need a change
84
+ elsewhere, report it as a follow-up and don't make it
85
+ - The external suites under `vendor/` are gitignored, so a new worktree has
86
+ only `vendor/.gitkeep`. From the worktree root, symlink the main
87
+ checkout's suites into it:
88
+ `for d in 65x02 VICE-testprogs; do ln -sfn ../../../../vendor/$d vendor/$d; done`.
89
+ The main checkout also has `vendor/OneLoad64-Games-Collection-v5`, which
90
+ `rake vendor:checkout` doesn't fetch. Link it the same way only for
91
+ `.sid` or media work. Don't re-run `rake vendor:checkout` there, and
92
+ don't write into `vendor/`
93
+ - The roadmap is `Plan.local.md` in the main checkout. It is untracked and
94
+ not in the public repo. When a planner session assigned your task, the
95
+ planner owns that file: read it and don't edit it. Send the planner your
96
+ results instead: what changed, suite scores before and after, and anything
97
+ worth recording (pinned rules, follow-ups)
98
+ - **Stop only your own processes, by PID.** Never kill by name or
99
+ pattern (`pkill -f`, `killall`, `pgrep … | kill`): other worktrees
100
+ run the same commands, and a pattern stops their runs too. Kill `$!`
101
+ for a job you backgrounded, or a PID you noted when you started it.
102
+ If you have to look one up, match on your worktree's absolute path
103
+ and check the result before you kill anything
104
+ - `bin/testbench` passes TERM and INT on to its shards, and the
105
+ `rake regression:*` tasks pass them on to the runner, so killing the
106
+ PID you started stops the whole run. An interrupted run exits
107
+ non-zero and writes no results or baseline
108
+ - A background task that ends with exit code 144 got SIGTERM, most likely
109
+ from another session's pattern kill, not a timeout. There is no
110
+ run-length limit
111
+
112
+ ## Testing and baselines
113
+
114
+ - `rake vendor:checkout` fetches SingleStepTests and VICE-testprogs into
115
+ `vendor/`
116
+ - `bundle exec rspec`: line coverage is about 95%. `spec/spec_helper.rb`
117
+ fails a whole-suite run (every spec file, no filters) below 90%, while
118
+ single-file and filtered runs skip the floor. `:slow` specs are excluded
119
+ by default: run them with `bundle exec rspec --tag slow`
120
+ - `rake test`: SingleStepTests. Run it for any CPU change
121
+
122
+ The headless suites are expensive. `bin/testbench` forks over 4 shards by
123
+ default. `SHARDS=N rake regression:<suite>` or `bin/testbench --shards N`
124
+ overrides that, capped by the core count, but keep the default, because
125
+ other worktrees share the machine. Whole runs at 4 shards on an M-series
126
+ laptop take 15 min for `testbench` (`VICII/`), 11 for `testbench-cia`, 1 for
127
+ `testbench-interrupts`, 37 for `testbench-irqdma`, 11 for
128
+ `testbench-cpu` and 2 for `testbench-carts`. `sid` takes about 12 min. `lorenz` chains itself and takes
129
+ about 2.5 h on CI whole. `rake regression:lorenz-1` to `lorenz-4` run it as
130
+ four stretches of about 40 min each, and they can run side by side.
131
+ `test/baselines/README.md` has the full table. A killed `bin/testbench` run
132
+ leaves its finished rows in `<results>.progress`, and running the same
133
+ command again with `--resume` carries on from them.
134
+
135
+ **Never run a full suite to check your work or to record it.** A full run
136
+ repeats CI on your machine, several times over when worktrees overlap, and
137
+ the rows your change can't reach tell you nothing about it.
138
+
139
+ - Iterate on a subset: `ruby --yjit bin/testbench <filter>` (an id
140
+ substring such as `VICII/spritegap`, and `--list` shows the ids),
141
+ `ruby --yjit bin/sidtests <filter>`, or
142
+ `ruby --yjit bin/lorenz [image] [start_test] [max_cycles]` (resumes the
143
+ chain at a named test, and `--stop-after NAME` ends it once that test is
144
+ done). These take seconds to minutes
145
+ - When your change moves rows, re-record only those rows, in the same
146
+ change: `rake "regression:record:<suite>[filter,...]"` (quote it, because
147
+ zsh globs the brackets). It runs only the matching tests and splices
148
+ their rows into the baseline, and every other row keeps its verdict.
149
+ `lorenz` chains itself, so it takes a stretch of the chain instead:
150
+ `rake "regression:record:lorenz[first,last]"` resumes just ahead of
151
+ `first`, stops after `last` and leaves the `(suite)` row alone.
152
+ `[first,(suite)]` runs on to the end of the chain and records the
153
+ `(suite)` row too. Cover every test your change can reach, and explain
154
+ every moved row. Never re-record just to make a diff go away
155
+ - CI's nightly run owns the whole-suite verdict. The Regression workflow
156
+ runs `testbench`, `lorenz` and `sid` against `main` each night, and skips
157
+ the night when nothing relevant has changed since the last successful
158
+ nightly run. It also runs from the Actions tab on demand. It never runs
159
+ on push or on pull requests, so one verdict can cover a day's merges.
160
+ The `testbench-*` and `sid-8580` suites run from the Actions tab on
161
+ demand
162
+ - The one exception is a change whose reach you can't bound to a set of
163
+ filters, such as reordering `Computer#cycle!` or changing the LOAD trap
164
+ every suite loads through. Ask before running a full suite for it, and
165
+ don't start one on your own judgement
166
+
167
+ Pick the filters from the suites your change can move: VIC → `testbench`;
168
+ CPU, interrupts or timing → the matching `testbench-*` suite, plus
169
+ `rake test` for CPU; CIA → the slow CIA specs first, then the matching
170
+ `testbench-cia` rows; cartridge mappers, banking or power-on state →
171
+ `testbench-carts`; SID → `sid`, plus `sid-8580` for anything the 8580
172
+ model reaches (`bin/sidtests --sid 8580`). Lorenz isn't a per-change check:
173
+ its full chain runs nightly, and the planner assigns any row it moves. The
174
+ exception is code whose rule in `doc/pinned-behaviour.md` names Lorenz
175
+ tests: the interrupt polling, CPU port and CIA timer rules. Run just the
176
+ tests that rule names, one at a time, with
177
+ `ruby --yjit bin/lorenz --resume <test> --stop-after <test>`. A stretch
178
+ resumes on a fresh machine, so its cuts in the Rakefile have to stay before
179
+ `trap1`, where the tests start carrying state from one to the next. Both Lorenz
180
+ and testbench load through the LOAD trap and type through the keyboard
181
+ buffer, so storage, IEC and keyboard changes can move them too. A broken
182
+ trap stalls the Lorenz chain.
183
+
184
+ `ruby --yjit bin/benchmark` measures post-boot speed. Absolute numbers
185
+ depend on the machine and its load: parallel worktrees running suites can
186
+ cost 20% or more, and wall-clock variance is ±15% even when idle. For an
187
+ A/B, run `ruby --yjit bin/profile <idle|text|game|synth> --compare <sha>`.
188
+ It pins the base to a commit, because another worktree's fetch can move
189
+ `origin/main` mid-comparison. It also times in process CPU time, which holds
190
+ about ±3% under load. Without `--compare`, `bin/profile` samples with
191
+ stackprof and splits self time by subsystem. It drops stackprof's GC
192
+ pseudo-frames, which report lazy-sweep samples as GC time. `--ceiling` shows
193
+ how much faster the machine would run if each chip cost nothing.
194
+
195
+ ### Pinned behaviour
196
+
197
+ [`doc/pinned-behaviour.md`](doc/pinned-behaviour.md) holds timing rules for
198
+ the CPU, VIC, CIA and SID. Each was derived empirically against a named
199
+ test, and the specs that guard one say `Pinned by <test>` in a comment. When
200
+ you change code a pinned rule governs, re-derive the rule against its test
201
+ and update the doc if the rule moves. A green suite alone isn't enough,
202
+ because the suite can stay green while the rule breaks.
203
+
204
+ ## Git
205
+
206
+ - **Don't stage, commit, push or open a PR** unless your task says so in
207
+ those words. Wanting a PR, a finished feature or green CI doesn't count
208
+ as permission. Finish the work, report what changed, and stop. A dirty
209
+ tree is the expected end state
210
+ - Hunks are staged by hand during review, so a partially staged file is
211
+ deliberate. Use `git diff HEAD` to see everything
212
+ - Commit messages use Conventional Commits (`feat:`, `fix:`, `chore:`, …).
213
+ release-please derives version bumps and the changelog from the prefixes
214
+ - No `Co-Authored-By` or `Claude-Session` trailers, even where `git log`
215
+ shows them
216
+ - When a task explicitly asks for a PR, the branch is already from
217
+ `origin/main` (see above). Open it with `gh pr create --base main`
@@ -0,0 +1,92 @@
1
+
2
+ # Contributor Covenant 3.0 Code of Conduct
3
+
4
+ ## Our Pledge
5
+
6
+ We pledge to make our community welcoming, safe, and equitable for all.
7
+
8
+ We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
9
+
10
+ ## Encouraged Behaviors
11
+
12
+ While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language.
13
+
14
+ With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including:
15
+
16
+ 1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
17
+ 2. Engaging **kindly and honestly** with others.
18
+ 3. Respecting **different viewpoints** and experiences.
19
+ 4. **Taking responsibility** for our actions and contributions.
20
+ 5. Gracefully giving and accepting **constructive feedback**.
21
+ 6. Committing to **repairing harm** when it occurs.
22
+ 7. Behaving in other ways that promote and sustain the **well-being of our community**.
23
+
24
+
25
+ ## Restricted Behaviors
26
+
27
+ We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct.
28
+
29
+ 1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop.
30
+ 2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people.
31
+ 3. **Stereotyping or discrimination.** Characterizing anyone’s personality or behavior on the basis of immutable identities or traits.
32
+ 4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community.
33
+ 5. **Violating confidentiality**. Sharing or acting on someone's personal or private information without their permission.
34
+ 6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group.
35
+ 7. Behaving in other ways that **threaten the well-being** of our community.
36
+
37
+ ### Other Restrictions
38
+
39
+ 1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions.
40
+ 2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
41
+ 3. **Promotional materials**. Sharing marketing or other commercial content in a way that is outside the norms of the community.
42
+ 4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviors.
43
+
44
+
45
+ ## Reporting an Issue
46
+
47
+ Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm.
48
+
49
+ When an incident does occur, it is important to report it promptly. To report a possible violation, **contact the project maintainer at inge@elektronaut.no.**
50
+
51
+ Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution.
52
+
53
+
54
+ ## Addressing and Repairing Harm
55
+
56
+ ****
57
+
58
+ If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped.
59
+
60
+ 1) Warning
61
+ 1) Event: A violation involving a single incident or series of incidents.
62
+ 2) Consequence: A private, written warning from the Community Moderators.
63
+ 3) Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations.
64
+ 2) Temporarily Limited Activities
65
+ 1) Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation.
66
+ 2) Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members.
67
+ 3) Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over.
68
+ 3) Temporary Suspension
69
+ 1) Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation.
70
+ 2) Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions.
71
+ 3) Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted.
72
+ 4) Permanent Ban
73
+ 1) Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member.
74
+ 2) Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior.
75
+ 3) Repair: There is no possible repair in cases of this severity.
76
+
77
+ This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community.
78
+
79
+
80
+ ## Scope
81
+
82
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
83
+
84
+
85
+ ## Attribution
86
+
87
+ This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
88
+
89
+ Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
90
+
91
+ For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla’s code of conduct team](https://github.com/mozilla/inclusion).
92
+
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,51 @@
1
+ # Contributing
2
+
3
+ Bug reports and pull requests are welcome on
4
+ [GitHub](https://github.com/elektronaut/badline). Everyone participating
5
+ is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
6
+
7
+ ## Getting started
8
+
9
+ Badline needs SDL2, which is available from most package managers.
10
+
11
+ ```sh
12
+ brew install sdl2 # macOS
13
+ apt install libsdl2-dev # Debian/Ubuntu
14
+ ```
15
+
16
+ Install the dependencies and run the specs:
17
+
18
+ ```sh
19
+ bundle install
20
+ bundle exec rspec
21
+ ```
22
+
23
+ A run of the whole suite fails if line coverage drops below 90%. Runs of
24
+ single files or filtered examples skip that check. Specs tagged `:slow`
25
+ boot the whole machine and are skipped by default. Run them with
26
+ `bundle exec rspec --tag slow`.
27
+
28
+ The CPU is verified separately against the
29
+ [65x02 single step tests](https://github.com/SingleStepTests/65x02).
30
+ `rake test` checks the fixtures out into `vendor/65x02` first, which
31
+ takes a moment the first time:
32
+
33
+ ```sh
34
+ bundle exec rake test
35
+ ```
36
+
37
+ Check style before pushing:
38
+
39
+ ```sh
40
+ bundle exec rubocop
41
+ ```
42
+
43
+ ## Pull requests
44
+
45
+ - Add tests for any behavior you change.
46
+ - Write commit messages using
47
+ [Conventional Commits](https://www.conventionalcommits.org). The
48
+ changelog and releases are generated from them, so the `feat:` and
49
+ `fix:` prefixes decide what ends up in the next release.
50
+ - Leave the version and `CHANGELOG.md` alone. Both are updated
51
+ automatically when a release is cut.