badline 0.1.0 → 0.2.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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +167 -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 +88 -23
  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 +131 -15
  51. data/lib/badline/cia/interrupt_register.rb +88 -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 +109 -74
  55. data/lib/badline/color_memory.rb +14 -5
  56. data/lib/badline/computer.rb +68 -8
  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 +171 -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 +108 -167
  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 +33 -0
  85. data/lib/badline/kernal_trap/drive.rb +239 -0
  86. data/lib/badline/kernal_trap/file.rb +4 -24
  87. data/lib/badline/kernal_trap/load.rb +2 -2
  88. data/lib/badline/kernal_trap/routine.rb +34 -0
  89. data/lib/badline/kernal_trap/save.rb +1 -1
  90. data/lib/badline/kernal_trap/serial.rb +139 -0
  91. data/lib/badline/kernal_trap.rb +4 -0
  92. data/lib/badline/keyboard.rb +40 -16
  93. data/lib/badline/media.rb +48 -10
  94. data/lib/badline/roms/eapi/LICENSE.md +15 -0
  95. data/lib/badline/roms/eapi/README +41 -0
  96. data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
  97. data/lib/badline/sid/decimator.rb +46 -0
  98. data/lib/badline/sid/envelope.rb +232 -0
  99. data/lib/badline/sid/filter.rb +213 -0
  100. data/lib/badline/sid/voice.rb +61 -0
  101. data/lib/badline/sid/waveform/combined.rb +126 -0
  102. data/lib/badline/sid/waveform/fast_forward.rb +105 -0
  103. data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
  104. data/lib/badline/sid/waveform.rb +289 -0
  105. data/lib/badline/sid.rb +274 -7
  106. data/lib/badline/status.rb +32 -27
  107. data/lib/badline/storage/crt_file.rb +18 -2
  108. data/lib/badline/storage/disk_image.rb +52 -5
  109. data/lib/badline/storage/host_directory.rb +18 -4
  110. data/lib/badline/storage/sid_file.rb +274 -0
  111. data/lib/badline/storage/song_lengths.rb +65 -0
  112. data/lib/badline/storage/t64.rb +67 -0
  113. data/lib/badline/storage/tap.rb +68 -0
  114. data/lib/badline/storage.rb +19 -0
  115. data/lib/badline/time_of_day.rb +105 -52
  116. data/lib/badline/version.rb +1 -1
  117. data/lib/badline/vic/bank.rb +14 -6
  118. data/lib/badline/vic/border_mask.rb +76 -0
  119. data/lib/badline/vic/collisions.rb +104 -0
  120. data/lib/badline/vic/color_patches.rb +33 -0
  121. data/lib/badline/vic/display_state.rb +92 -29
  122. data/lib/badline/vic/graphics_mode.rb +78 -53
  123. data/lib/badline/vic/graphics_shifter.rb +140 -0
  124. data/lib/badline/vic/register_log.rb +63 -0
  125. data/lib/badline/vic/registers.rb +3 -0
  126. data/lib/badline/vic/sequencer.rb +163 -137
  127. data/lib/badline/vic/sequencer_output.rb +116 -0
  128. data/lib/badline/vic/sprite/internal_bus.rb +49 -0
  129. data/lib/badline/vic/sprite/shifter.rb +129 -0
  130. data/lib/badline/vic/sprite.rb +275 -65
  131. data/lib/badline/vic/sprites.rb +220 -58
  132. data/lib/badline/vic.rb +373 -49
  133. data/lib/badline.rb +5 -0
  134. data/vendor/.gitkeep +0 -0
  135. metadata +103 -4
@@ -0,0 +1,1117 @@
1
+ # Pinned behaviour
2
+
3
+ These timing rules were derived empirically against a named test, not from
4
+ a datasheet. Each entry states the rule, the test that forced it and, where
5
+ there is one, the fast spec that guards it. Specs carrying a guard say
6
+ `Pinned by <test>` in a comment.
7
+
8
+ The constraint list is the artifact; the implementation is not. When you
9
+ change code a rule governs, re-derive the rule against its pinning test. A
10
+ green suite is not enough, because a suite can stay green while the rule
11
+ breaks: a spec guard catches the rule's own failure mode, and a baseline
12
+ only catches the rows that happen to move.
13
+
14
+ - [CPU interrupt recognition](#cpu-interrupt-recognition)
15
+ - [CPU JAM](#cpu-jam)
16
+ - [CPU unstable stores under DMA](#cpu-unstable-stores-under-dma)
17
+ - [CPU ANE constant](#cpu-ane-constant)
18
+ - [VIC raster IRQ phase](#vic-raster-irq-phase)
19
+ - [VIC mid-line register visibility](#vic-mid-line-register-visibility)
20
+ - [VIC sprite display](#vic-sprite-display)
21
+ - [VIC sprite collisions](#vic-sprite-collisions)
22
+ - [VIC border and idle state](#vic-border-and-idle-state)
23
+ - [VIC bad line and DMA](#vic-bad-line-and-dma)
24
+ - [VIC graphics pipeline](#vic-graphics-pipeline)
25
+ - [VIC phi1 bus](#vic-phi1-bus)
26
+ - [VIC light pen](#vic-light-pen)
27
+ - [CIA 6526 timer pipeline](#cia-6526-timer-pipeline)
28
+ - [CIA serial shift register](#cia-serial-shift-register)
29
+ - [6510 I/O port](#6510-io-port)
30
+ - [SID oscillator](#sid-oscillator)
31
+ - [SID register writes](#sid-register-writes)
32
+ - [SID data bus](#sid-data-bus)
33
+ - [SID envelope](#sid-envelope)
34
+ - [`.sid` tune banking](#sid-tune-banking)
35
+
36
+ ## CPU interrupt recognition
37
+
38
+ - `poll` runs at the head of every `CPU#cycle!`, before that cycle's
39
+ micro-operation, and shifts `irq && !I` and the NMI latch through a
40
+ two-stage pipeline (`pending ← sample ← line`). The instruction boundary
41
+ consumes `pending` *before* its own poll, which is the
42
+ second-to-last-cycle sample (64doc: "2 or more cycles before the end").
43
+ `end_sequence` latches that value into `@boundary_irq`/`@boundary_nmi` as
44
+ the instruction ends. That is the same read, because nothing but `poll`
45
+ moves `pending`.
46
+ - A taken same-page branch sets `@skip_poll`, skipping one poll, so the
47
+ interrupt must arrive before clock 1. The CLI/SEI/PLP delays fall out of
48
+ sampling `!I` at poll time.
49
+ - An NMI before cycle 4 hijacks a BRK or IRQ sequence: the vector swaps at
50
+ the `@nmi_pending` check before the vector fetch, and the B flag stays on
51
+ the stack. Interrupt sequences, BRK included, clear the pipeline at their
52
+ end, so the handler's first instruction always runs.
53
+ - The sequence is a true 7 cycles. The CPU does *not* clear `@irq` on
54
+ service, because the line belongs to the device.
55
+ - Pinned by Lorenz `irq` and `nmi` (`nmi` subtest `00/5` for the pipeline
56
+ clear).
57
+ - A cycle the VIC stalls through BA (`CPU#stall!`) keeps sampling the lines
58
+ but doesn't advance the pipeline: `irq_sample ||= irq && !I` and
59
+ `nmi_sample ||= nmi`, OR-ed with the sample it held, while `pending`
60
+ doesn't shift and `@skip_poll` stays owed to the next real cycle. The I
61
+ used is the one the stalled step leaves when it comes from the opcode: a
62
+ stalled CLI execute cycle masks with I = 0 and a stalled SEI with I = 1.
63
+ Every other step uses the current I. PLP too: its pulled I only arrives
64
+ with the stalled stack read.
65
+ - Each piece is forced by an `interrupts/irqdma` case. A NOP stalled on
66
+ its last cycle with the IRQ rising in the stall is taken after that NOP
67
+ (test 1). An SEI whose fetch sampled the IRQ, stalled on its execute
68
+ cycle, is still taken after the SEI (`||=`), while an IRQ rising in the
69
+ SEI stall is masked (test 7, `$d015=03`). A CLI stalled with the IRQ
70
+ rising is taken after the CLI (test 7, `$d015=01`). A PLP stalled on its
71
+ stack read pulling I = 1 is taken after the PLP (test 6, `$d015=40`
72
+ offset 106). Consuming `@skip_poll` in the stall breaks test 5.
73
+ - Pinned by `interrupts/irqdma` (all 16 rows) and Lorenz `irq` and `nmi`.
74
+ - Spec guard: *interrupts sampled while stalled by the VIC* in
75
+ [`cpu_spec.rb`](../spec/badline/cpu_spec.rb), one example per piece.
76
+ - Spec guard: the *interrupt recognition timing* group in
77
+ [`cpu_spec.rb`](../spec/badline/cpu_spec.rb), one example per quirk.
78
+
79
+ ## CPU JAM
80
+
81
+ - JAM reads the opcode, a dummy byte, `$FFFF`, `$FFFE` and `$FFFE`, then
82
+ `$FFFF` on every cycle until reset. It never reaches an instruction
83
+ boundary, so a pending IRQ or NMI is never taken.
84
+ - Pinned by `CPU/cpujam` (the halt) and `jamirq`/`jamnmi`.
85
+ - Spec guard: the *JAM* group in
86
+ [`cpu_spec.rb`](../spec/badline/cpu_spec.rb).
87
+
88
+ ## CPU unstable stores under DMA
89
+
90
+ - A cycle the VIC holds the CPU through BA is not a CPU cycle.
91
+ `Computer#cycle!` calls `CPU#stall!` instead of `CPU#cycle!`, which
92
+ records `@cycles`. The interrupt pipeline's `pending` stage doesn't
93
+ advance, but the line sample does (see *CPU interrupt recognition*).
94
+ - SHA, SHX, SHY and SHS/TAS drop the `& (H+1)` from the stored value when
95
+ the CPU is stalled **immediately before the dummy read**, the
96
+ second-to-last cycle (VICE x64sc's `LOAD_CHECK_BA_LOW_DUMMY`). A stall at
97
+ any earlier cycle leaves it in. The high byte of a page-crossing target
98
+ is still `value & (H+1)` either way.
99
+ - The rule is that one cycle, not "RDY went low during the instruction".
100
+ In the `*4`/`*5` timing tables the drop falls exactly one position after
101
+ each cycle-steal dip and on no other position. A whole-instruction rule
102
+ would drop on the positions after it too.
103
+ - Pinned by `CPU/sha`, `CPU/shxy` and `CPU/shs`, variants 2–5, with
104
+ variant 1 as the stall-free guard. They measure against VIC BA timing:
105
+ the `*2`/`*3` variants (sprite DMA) and `shxy4`/`shyx4`/`shx-test`
106
+ need sprite BA at `54 + 2n`, and the `*4`/`*5` variants (sprite and
107
+ character DMA) also need the bad-line and sprite-DMA-end BA to match.
108
+ - Spec guard: *SHX stalled by the VIC* in
109
+ [`cpu_spec.rb`](../spec/badline/cpu_spec.rb).
110
+
111
+ ## CPU ANE constant
112
+
113
+ - ANE computes `A = (A | CONST) & X & imm`. `CONST` varies from chip to
114
+ chip; `CPU.new` takes it as `ane_constant:` and defaults to the C64
115
+ 6510's `$EF`, VICE's value. A stall between the opcode and operand
116
+ fetches clears bits 0 and 4 of it (`CONST & $EE`).
117
+ - Pinned by `CPU/ane` (`ane`, `ane-border` and `ane-none`), which fails any
118
+ constant without bits 0 and 1 set and a high nybble of `$4`, `$5`, `$E`
119
+ or `$F`. The stall variant follows VICE: the testprog only displays the
120
+ RDY-cycle result, so no exit code pins it.
121
+ - SingleStepTests recorded an NMOS 6502 whose constant is `$EE`, so
122
+ `test/test_cpu.rb` builds its CPU with `ane_constant: 0xee` and checks
123
+ `$8B` as strictly as every other opcode.
124
+ - Spec guard: *ANE* in [`cpu_spec.rb`](../spec/badline/cpu_spec.rb).
125
+
126
+ ## VIC raster IRQ phase
127
+
128
+ - The raster compare happens at the line wrap (the end of the old line's
129
+ last cycle), except on line 0, which compares at column 0. This is Bauer
130
+ 3.12's "cycle 0 of every line, cycle 1 of line 0".
131
+ - The bad line compare picks up the same wrap (see
132
+ [VIC bad line and DMA](#vic-bad-line-and-dma)). It was re-derived against
133
+ the tests below when that landed.
134
+ - Pinned by `greydot`, `ss-*-color` and `den01-49-*`, with `denrsel-s0` as
135
+ the guard that must keep passing. This rule is coupled to
136
+ [CPU interrupt recognition](#cpu-interrupt-recognition), so recalibrate
137
+ the two together.
138
+ - Spec guard: *with the compare at the line wrap* and *with line 0 as the
139
+ target* in [`vic_spec.rb`](../spec/badline/vic_spec.rb). They are the
140
+ only examples that separate this phase from a uniform column-0 compare.
141
+ - `$d011` bit 7 and `$d012` follow the same phase. Every line reads as the
142
+ new line from the CPU cycle paired with column 62 of the old one. Line 0
143
+ reads a cycle later: that cycle still reads 311. VICE resets the counter
144
+ at cycle 2 of line 0 but increments it at cycle 1 of every other line.
145
+ - Pinned by `split-tests/lightpen`, whose `$d011`/`$d012` pages match
146
+ `dump6569` byte for byte only with the delay. Without it, index `$47`
147
+ reads `$1b`/`$00` where the chip reads `$9b`/`$37`, and the test exits
148
+ `$ff`.
149
+ - Spec guard: *when the counter wraps to line 0* in
150
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
151
+ - The compare latches the flag only on a change from no match to match,
152
+ and it runs again after every `$d011`/`$d012` write as well as at each
153
+ line step (VICE x64sc compares in every cycle). A write after column 61
154
+ is left to the line step, so a target moved to the next line there
155
+ still matches when the line steps: the match holds and nothing latches.
156
+ A write that moves the target onto the current line latches at once.
157
+ - Pinned by `rasterirq_hold` (11672 px → pass). It goes back to 11672 px
158
+ when the line step latches on any match, and to 345 px when writes
159
+ don't run the compare, which also breaks `greydot` (1576 px), because
160
+ the stale match state then swallows a later edge.
161
+ - The latch on a write that moves the target onto the current line is
162
+ VICE's behaviour. No test pins it: `rasterirq_hold` and `greydot` still
163
+ pass when a write only updates the match state.
164
+ - Spec guard: *with the target stepped along with the raster line* and
165
+ *when a write moves the target onto the current line* in
166
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
167
+
168
+ ## VIC mid-line register visibility
169
+
170
+ - Sprite registers written mid-line are logged against
171
+ `(write cycle + 1) * 8` and take hold after a per-signal delay: **+1 px**
172
+ for the colors ($d025/$d026/$d027–$d02e), **+6 px** for priority
173
+ ($d01b) and **+7 px** for the sequencer inputs ($d000–$d010, $d01c,
174
+ $d01d). The color delay is the same +1 px that `greydot` pins for
175
+ $d020–$d024 below.
176
+ - Pinned by the `spritesplit` staircases. `ss-hires-color`/`ss-mc-color*`
177
+ fix the color delay and `ss-pri*` the priority one (only the bands where
178
+ the sprite sits on an odd X can tell 6 from 7).
179
+ `ss-hires-mc`/`ss-mc-hires`/`ss-*exp*` fix the sequencer one.
180
+ - These were +9/+14/+15 until the sprite BA windows moved a column
181
+ earlier (see *VIC sprite display*). `spritesplit` syncs on a raster IRQ
182
+ whose line starts a sprite DMA. With the window where `spritesteal` puts
183
+ it, that line's stall catches a read it used to miss, so the CPU loses 5
184
+ cycles there, not 4, and every write in the staircase lands one cycle
185
+ later. The old delays were 8 px of that phase error on top of the real
186
+ delay.
187
+ - Spec guard: *mid-line write delays* in
188
+ [`vic/sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb), one example
189
+ per path, each failing on a one-pixel change.
190
+ - $d020–$d024 writes become visible 1 px into the next column.
191
+ `ColorPatches` restores the boundary pixel at `finish_line`.
192
+ - Pinned by `greydot`.
193
+ - Spec guard:
194
+ [`vic/color_patches_spec.rb`](../spec/badline/vic/color_patches_spec.rb).
195
+ - `apply_border` restores a pre-composite snapshot (`BorderMask`) instead of
196
+ repainting with the end-of-line $d020, so mid-line border splits survive
197
+ sprite compositing.
198
+
199
+ ## VIC sprite display
200
+
201
+ - Sprite display starts at **Y+1**: the Y match turns DMA on, and the first
202
+ row renders on the next line.
203
+ - DMA compares at cycles 55/56 (**VIC columns 53/54**, with BA rebuilt
204
+ mid-line). Display turns on at cycle 58 (column 57), **but only while both
205
+ MxE and Y still match**. That is Bauer §3.8 rule 6 plus the enable bit his
206
+ wording omits. A write to either register between the compares and cycle
207
+ 58 keeps DMA running invisibly.
208
+ - Pinned by all five `spriteenable` rows. `3` and `5` move Y and `4`
209
+ clears MxE. `1` and `2` write $d015 *between* the two compares, which
210
+ only lands on that column pair.
211
+ - Each sprite's BA window is five columns, three ahead of its two s-access
212
+ columns, stepping two columns per sprite from column 54
213
+ (`SPRITE_BA_WINDOWS`). `ba_low?` is asked after the VIC has advanced, so
214
+ the CPU cycle that follows VIC column 53 is the first one sprite 0 halts.
215
+ From sprite 3 on, the window runs past the end of the line and splits: the
216
+ tail falls on the line whose compare started the fetch, and the head on
217
+ the next one. One sprite therefore costs the CPU 5 cycles, and sprites 0–3
218
+ together cost 11. A write cycle still completes under BA.
219
+ - Pinned by `spritesteal`, whose `sprite_steal_table` (`core.asm:759`)
220
+ lists the stolen cycles for each sprite alone and all eight together,
221
+ one cycle at a time, on the line that starts the DMA.
222
+ - The `spriteenable` raster markers land on their printed `x`/`y` columns
223
+ only with these windows *and* the bad-line stall in *VIC bad line and
224
+ DMA*: on their line 51, a bad line meets the head of the sprite 3
225
+ window, and the CPU gets 9 cycles between the two.
226
+ - Spec guard: *sprite DMA cycle stealing (#ba_low?)* in
227
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
228
+ - A DMA started on the *second* compare leaves sprite 0 a column short of
229
+ AEC, because sprite 0 alone has its accesses immediately after the
230
+ compares. The first of its three s-accesses therefore reads back the $ff
231
+ the CPU is still driving.
232
+ - Pinned by `spriteenable2`.
233
+ - Spec guard: *the first s-access of a new DMA* in
234
+ [`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
235
+ - Rows come from MCBASE/MC, not from a line counter (Bauer §3.8). MC
236
+ steps once per s-access, so it stands three past MCBASE after a row's
237
+ fetch. At Bauer cycle 16 (**VIC column 14**) MCBASE takes MC while the
238
+ expansion flip-flop is set, and MC reloads from MCBASE at cycle 58. The
239
+ end-of-sprite compare runs a column later, at column 15, where the BA
240
+ columns are rebuilt. The sprite ends only when MCBASE lands on
241
+ **exactly** 63, so a crunched sprite steps over it and runs on through
242
+ the rest of its block.
243
+ - Pinned by `spritecrunch2-09`, whose $d017 clears land in the CPU cycle
244
+ after column 14. The reference repeats one row for 48 lines, so MCBASE
245
+ has already moved when the flip-flop is set. Moving MCBASE with the
246
+ compare at column 15 fails it (804 px).
247
+ - The expansion flip-flop is set by the Y match that starts the DMA, and
248
+ set **at once** by a $d017 write that clears MxYE, not at a column hook
249
+ (VICE `d017_store`). At Bauer cycle 56 (**VIC column 54, after the second
250
+ Y compare**) it inverts for each sprite with DMA running and MxYE set.
251
+ - Pinned by `spritecrunch2-25`–`29`. Their $d017 sets land from the CPU
252
+ cycle after column 48 to the one after column 55, one column later
253
+ every 8 lines, so they straddle the inversion. Inverting at column 53
254
+ fails all five (228–472 px), and inverting ahead of the compare breaks
255
+ `spritedma/d017-54` and `d017-57` as well.
256
+ - **Sprite crunch**: a $d017 write that clears MxYE in Bauer cycle 15 (the
257
+ CPU cycle after VIC column 13) while the flip-flop is reset steps MC to
258
+ `(0x2a & (MCBASE & MC)) | (0x15 & (MCBASE | MC))`, which column 14 then
259
+ hands to MCBASE. From MCBASE $00 that is $01, from $01 or $04–$06 it is
260
+ $05, from $03 it is $07, as the `spritecrunch` readme's table has it.
261
+ - Pinned by `spritecrunch-3b/3c/3d-00` (the same program, whose clear
262
+ lands in that cycle once), `spritecrunch2-08` and `sequencer-bug`. There
263
+ the crunch on line 52 steps MCBASE from $03 to $07, off the multiples of
264
+ three, so the expanded sprites wrap through their block and run for 84
265
+ lines instead of 42. Without the formula, or with the window a column either side,
266
+ all five fail (`sequencer-bug` 8064 px, 384 px a column early).
267
+ - `spritedma/d017-54` and `d017-57` pass with and without it.
268
+ - Spec guard: *sprite crunch* in
269
+ [`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb), one example
270
+ per row of the readme's table.
271
+ - Sprite pixels come from a per-pixel sequencer, not from a decoded row. A
272
+ live X comparator fires one pixel before the sprite's first pixel, the
273
+ expansion flip-flop gates the shift, and in multicolor a two-bit latch
274
+ reloads on every second shift. Both flip-flops idle while their register
275
+ bit is clear, so the first pixel after a mid-sprite $d01c/$d01d change
276
+ repeats the last latched value, and the pairs restart on the pixel after
277
+ that.
278
+ - Pinned by all 17 `spritesplit` tests, and `ss-xpos` for the comparator
279
+ in particular.
280
+ - X coordinates ≥ $1f8 never match the VIC's 504-step X counter, so those
281
+ sprites stay dark. The `spritegap` dumps show this boundary.
282
+ - Spec guard: *X comparator* in
283
+ [`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
284
+ - Each sprite's s-accesses **reload its shift register** at raster pixel
285
+ K = 459 + 16*m (mod 504). That is late in the line for sprites 0–2 and
286
+ early in the next one for 3–7.
287
+ - A sprite still shifting at K loses the rest of its row. Its output
288
+ holds its last pixel from K through **K+6**, then goes dark.
289
+ - A comparator hit in K..K+11 shows nothing.
290
+ - A hit from K+12 on shows the row the reload brought. For sprites 0–2
291
+ that is the *next* line's row, a line early. For sprites 3–7 it is the
292
+ current row, and a hit ahead of their K shows the previous line's row.
293
+ - The row can be shown once on each side of K, so a sprite moved past
294
+ the beam fires a second time on the same line.
295
+ - Pixels that run past the end of the line are drawn at the start of the
296
+ next one.
297
+ - Pinned by `split-tests/spritescan`, a byte-exact dump over all eight
298
+ sprites, 512 X positions and four patterns. `spritex/testsuite`
299
+ (entries 14–16: 469/470 dead, 471 = K+12 fires again),
300
+ `spritex/demusinterruptus` and `spritegap2` pass on it too.
301
+ - Spec guard: *the reload* in
302
+ [`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
303
+ - The hold's length is pinned by `spritefetchbug/test-136-2a`, whose
304
+ X-expanded sprite 0 sits at X = $136 and is still shifting at 459. Its
305
+ reference holds the last pixel through 465. Holding it for one pixel
306
+ fails the test by 134 px. `spritescan` passes either way, because it
307
+ records only whether a collision happened at each X position.
308
+ - A multicolor pair that the latch loads on **K−1**, the last pixel before
309
+ the reload, keeps only its high bit, as a hi-res pixel does: %11 shows the
310
+ sprite's own color and %01 is transparent. The hold then repeats that
311
+ pixel.
312
+ - Pinned by `spritefetchbug/test-136-2a`. Its X-expanded multicolor
313
+ sprite at $136 loads its last pair on pixel 458. Without the rule the
314
+ test fails by 168 px. The readme lists 136, 13a, 13e, 142, 146 and 14e
315
+ as the positions where the bug stops being multicolor. Those are the X
316
+ positions ≡ 2 mod 4, where an X-expanded sprite's pair loads land on
317
+ 458.
318
+ - Spec guard: *a multicolor pair loaded on the pixel before the reload*
319
+ in [`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
320
+ - Sprites 3–7 run their s-accesses at the start of the line, so on the line
321
+ whose compare starts their DMA, those accesses ran with the DMA still
322
+ off. The shift register loads what the VIC saw anyway. The first and third
323
+ bytes come from the VIC's internal bus in phi2, which reads $ff unless
324
+ the CPU reads or writes a VIC register in that cycle, in which case it
325
+ holds that byte. The middle byte is the idle phi1 fetch at $3fff. A hit
326
+ after the display turns on at cycle 58 (raster pixel 460 on, X ≥ $164)
327
+ shows this row on the same line.
328
+ - Pinned by `sbsprf24-164`. Its sprite 6 at $164 shows `BYTE_S0`, the
329
+ ghost byte and `BYTE_S2` on line $7a, which the program puts on the bus
330
+ with the dummy read and the write of `sta $d000,y` in Bauer cycles 7
331
+ and 8. Without the rule the test goes back from 42 to 51 px. With $ff
332
+ in place of the bus bytes it is 49 px, and with $ff in place of the
333
+ ghost byte, 46. `sbsprf24-163`, one pixel to the left, shows nothing
334
+ on that line.
335
+ - `Sprite::InternalBus` samples $3fff as the line starts rather than in
336
+ the sprite's own cycle, which `sbsprf24` can't tell apart.
337
+ - Spec guard: *the s-accesses before the DMA starts* in
338
+ [`vic/sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb).
339
+ - On the line that shows its last row, where MCBASE reached 63 and the DMA
340
+ ended at cycle 16, the sprite loses its display at cycle 58: no hit from
341
+ raster pixel **460** on starts it, though one already shifting runs on,
342
+ and it shows no row on the line after. VICE x64sc does the same, pending
343
+ bits cleared at xpos $164.
344
+ - Pinned by `spritegap3`'s collision log, where every pair stops at X =
345
+ $164 whatever the lower sprite.
346
+ - MCBASE reaching 63 at cycle 16 stops only the **DMA**. The display turns
347
+ off at cycle 58, and only if the DMA is still off. A Y match at the
348
+ cycle 55/56 compare on the last row's line restarts the DMA under a
349
+ display that is still on, so the new run shows even though Y no longer
350
+ matches at cycle 58.
351
+ - Pinned by `spriterestart`.
352
+ - Spec guard: *when Y matches only at the compare on the last row's line*
353
+ in [`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
354
+ - The DMA end also drops the sprite's BA tail on that line, because the
355
+ BA columns are rebuilt at cycle 16. Pinned by `CPU/sha*`, `shs*` and
356
+ `shxy*`, variants 4 and 5.
357
+ - Spec guard: *sprite BA on the line its DMA ends* in
358
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
359
+ - The Y comparator is **eight bits** wide, so a coordinate of 0–55 matches a
360
+ second time on PAL lines 256–311 and starts a second DMA run there.
361
+ - Pinned by `spritey`, whose reference collides on every one of the 312
362
+ lines.
363
+ - Spec guard: *the eight-bit Y compare* in
364
+ [`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
365
+
366
+ ## VIC sprite collisions
367
+
368
+ - $d01e/$d01f latch **as the beam crosses each sprite pixel**, not at the
369
+ end of the line. A read reports the pixels drawn before its own cycle and
370
+ nothing after them. The VIC runs ahead of the CPU inside a machine cycle,
371
+ so the column the read lands on has not latched yet.
372
+ - Pinned by `sprite-sprite-collision-cycle` and
373
+ `sprite-gfx-collision-cycle`, which step the sprite one pixel per
374
+ subtest across that boundary.
375
+ - The reset a read asserts outlives the read by **12 pixels**. Pixels drawn
376
+ under it never reach the register, so two reads four cycles apart report
377
+ a 20-pixel window instead of the 32 pixels between them. The two
378
+ registers reset independently.
379
+ - Pinned by `spritevssprite`'s 20-column bands, which place the boundary
380
+ to the pixel.
381
+ - The comparator runs wherever the sprites do, including the vertical
382
+ blank, where there is no line to paint them over.
383
+ - Pinned by `spritey`. Its sprites carry a single lit pixel on their first
384
+ row, so they only collide on the line after the Y match: line 1 for a
385
+ coordinate of 0.
386
+ - The $D019 collision bits rise on the **cycle that draws the colliding
387
+ pixel**, on the edge out of an empty register, whether or not $D01A
388
+ enables them. The fold keeps up with the beam while an enabled collision
389
+ IRQ is unlatched, and a $D019 read folds first, so neither waits for the
390
+ end of the line.
391
+ - Pinned by `irq-ack-vicii`'s sprite-sprite half. Its `STA $D019` row
392
+ acknowledges the flag before the CPU takes the IRQ (`-`) at exactly the
393
+ fourth of six delays. Folding a cycle early moves the `-` to the third,
394
+ folding a cycle late moves it to the fifth, and folding only at the end
395
+ of the line loses it. The read fold matters only with the IRQ disabled,
396
+ which no testprog covers; it follows VICE, which sets the bit
397
+ regardless of $D01A.
398
+ - Spec guard: *when the beam crosses the colliding pixel* in
399
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
400
+ - Spec guard: *#collide_upto* in
401
+ [`vic/sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb). Its first
402
+ four examples each fail on a one-pixel change.
403
+
404
+ ## VIC border and idle state
405
+
406
+ - The vertical border compares run in every cycle of a line, as VICE x64sc
407
+ runs them. The top compare (line 51 with RSEL set, 55 with it clear, and
408
+ DEN set) resets the flip-flop at once. The bottom compare (251 or 247)
409
+ only arms it, and the armed state takes hold at the line's first cycle
410
+ and at the left window edge (Bauer §3.9 rules 2–5). Between writes
411
+ nothing changes, so the VIC compares at each line's first cycle and after
412
+ each `$d011` write.
413
+ - The line's first cycle is the previous line's column 62 (Bauer cycle 1,
414
+ the column the bad line compare also gives to the next line), and it
415
+ compares the line about to start. A `$d011` write after column 61 is
416
+ compared there, against the next line. A write after column 62 has
417
+ missed it.
418
+ - Pinned by `denrsel-s0`/`-s1`: s1 clears RSEL and DEN one cycle later
419
+ than s0, just after line 51's first-cycle compare, so the border
420
+ opens. Pinned with them by `denrsel-1`/`-2`/`-s2`, `den10-51-1` (all
421
+ 64000–87040 px → pass), `vborder-33-08`/`-09`, `vborder2-22` and
422
+ `vborder2-64`, which fail again when column 62 compares the line
423
+ ending. `border-bm-idle` (8057 → 9 px) goes back too.
424
+ - A write is compared in the next column, so RSEL touching a bottom
425
+ compare line mid-line closes the border from the next line.
426
+ - Pinned by `vborder2-63` and `vborder-32-08`/`-09`, which fail when
427
+ only the line start and the left edge compare, and `vborder-33-08`
428
+ and `vborder2-36`, which move further.
429
+ - The 40-column left compare runs in column 15 (Bauer 17), a column
430
+ before the column that draws its pixel, so a `$d011` write after
431
+ column 15 misses it. The 38-column one (Bauer 18) runs in column 16.
432
+ - Pinned by `vborder2-36` (320 px → pass), which fails alone when the
433
+ compare sees `$d011` as of column 16. `vborder2-35` is the other side
434
+ of the pair.
435
+ - Spec guard: *vertical border flip-flop* in
436
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb), one context per rule,
437
+ each failing under its knock-out, and *vertical border flip-flop* in
438
+ [`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb) for
439
+ the armed bottom compare and the 40-column left compare.
440
+ - The side border compares see CSEL a pixel late, as the colour registers
441
+ do. The 40-column compares fall on the first pixel of their group (raster
442
+ x 128 on the left, 448 on the right), and that pixel still sees CSEL as
443
+ the column before had it. The 38-column compares fall on a group's last
444
+ pixel (135 and 439), so they see a write made in the CPU cycle before
445
+ the group. A CSEL clear in the CPU cycle after column 55 therefore misses
446
+ the 38-column compare and still meets the 40-column one: the border
447
+ closes.
448
+ - Pinned by `vicii_reg_timing` (71 px → pass, and 78 → 7 px for `-a5`
449
+ and `-ff`), whose line 232 clears CSEL there and shows a closed right
450
+ border. Lagging the 38-column compares as well breaks `border-bm-ysh`,
451
+ `border-bm-ysh2`, `border-mcbm` and `hvborder1` (71–73 px each), which
452
+ clear CSEL in the cycle after column 53 and close at 439.
453
+ - Spec guard: *closes the right border when CSEL clears in the compare's
454
+ column* and *closes the right border at the 38-column compare in the
455
+ column CSEL clears* in
456
+ [`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb).
457
+ - The border colour shows where the **main** flip-flop is set, and only
458
+ there. The vertical flip-flop keeps the main one from clearing at the
459
+ left compare and withholds the graphics data, but it does not paint
460
+ border itself. So with the side border held open, the lines inside the
461
+ vertical border show zero data in the latched colours (VICE x64sc
462
+ `draw_border8`).
463
+ - Pinned by `hvborder2`, `border-bm-ysh`, `border-bm-ysh2` and
464
+ `border-mcbm`, which go back to 8134, 5575, 4598 and 5595 px when
465
+ either flip-flop paints border (knock-outs measured before the sprite
466
+ shifter landed; all four pass since the
467
+ [graphics pipeline](#vic-graphics-pipeline) rules).
468
+ - Spec guard: *shows the vertical border only through the main
469
+ flip-flop* in
470
+ [`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb).
471
+ - An idle-state g-access reads $3fff, or $39ff with ECM set, and the byte
472
+ is painted through the current mode with a zero screen byte and colour
473
+ nibble (foreground black, background per mode). A guard keeps
474
+ closed-border lines on the bulk path.
475
+ - Pinned by `ss-pri*`, whose diffs collapsed from ~85k px to 220–440 px.
476
+ - Spec guard: *renders the idle byte at $3fff in black* in
477
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
478
+ - A column with no g-access (outside columns 14–53), or one whose
479
+ g-access falls while the vertical border stays closed for the rest of
480
+ the line, shifts out **zero data**. It is painted through the current
481
+ mode with the screen byte and colour nibble the last g-access latched:
482
+ a display-state one latches its buffer cell, an idle one latches 0/0,
483
+ and the value carries across lines. So a zero pixel is $d021 in the text
484
+ and multicolour bitmap modes, the kept screen byte's low nibble in
485
+ standard bitmap, the ECM background it selects, and black in the
486
+ invalid modes (VICE x64sc `draw_graphics8`).
487
+ - Pinned by `sbsprf24-163`/`-164` (401/428 → 34/51 px),
488
+ `spritefetchbug` (302 → 174), `hvborder1` (191 → 43) and
489
+ `vicii_reg_timing` (8865 → 1773), which decoded memory at VC there
490
+ before. The kept colours are pinned by
491
+ `border-bm-ysh`/`-ysh2`, which go back to 5580/4741 px with 0/0
492
+ instead.
493
+ - The top compare line counts as open for the whole line, because the
494
+ vertical flip-flop only clears at the left compare, two columns after
495
+ the first g-accesses are sampled. Without that, every picture loses
496
+ the start of its first line (`greydot`, `dmadelay`, `dentest`).
497
+ - Spec guard: *a column with no g-access* in
498
+ [`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb).
499
+ - The XSCROLL bleed at column 0 takes its pixels from the group before
500
+ it, the zero-data group, rather than filling with $d021. Only
501
+ `border-bm-ysh2` separates the two (+6 px).
502
+
503
+ ## VIC bad line and DMA
504
+
505
+ - The display state counts columns two ahead of Bauer's cycles: column
506
+ `c` is his cycle `c + 2`. This is the frame the CPU sees, where the
507
+ bad-line stall `bascan` pins starts in the CPU cycle after column 10
508
+ (Bauer 12). It is also VICE x64sc's order, where the VIC's logic for a
509
+ cycle runs before the CPU's bus access in it. A `$d011` write in Bauer
510
+ cycle `n` runs after column `n − 2` and is first compared in column
511
+ `n − 1`, which is Bauer `n + 1`.
512
+ - The bad line condition is compared in **every** column. The last column
513
+ of a line (Bauer cycle 1 of the next) compares against the line about to
514
+ start.
515
+ - A match enters display state **in its own column**, not when AEC falls.
516
+ - Pinned by 12 of the `dmadelay` rows, `screenpos`, `fldscroll-20-60`
517
+ and `colorfetchbug/bitmap`, which all fail when display state waits
518
+ for AEC.
519
+ - The first match in columns 10–52 (Bauer 12–54) pulls BA low, and the VIC
520
+ owns the bus three columns later. The c-accesses run in columns 13–52
521
+ (Bauer 15–54) for as long as the condition stands, so a condition
522
+ withdrawn mid-line stops them. The ones before AEC read `$ff` off a bus
523
+ the CPU still drives.
524
+ - Pinned by `flibug/blackmail*`. Their `$d011` writes land in column 12
525
+ (Bauer 14), so the match comes in column 13 and three cells read
526
+ `$ff`, as in the reference.
527
+ - The g-accesses run in columns 14–53 (Bauer 16–55), in the first half
528
+ of the column, ahead of that column's compare. Their pixels leave the
529
+ sequencer two columns later (`VIC::GRAPHICS_DELAY`), so cell 0 still
530
+ draws at column 16.
531
+ - Pinned by `dmadelay` `test*-18`/`-1a`, `screenpos` and
532
+ `colorfetchbug/bitmap`, which fail when the compare runs first.
533
+ - VC and VMLI reload in column 12 (Bauer 14). The row counter resets there
534
+ **only** if the condition stands in that column, so a row opened later
535
+ keeps the row counter it had. It steps in column 56 (Bauer 58). This
536
+ replaces the curve-fitted 11–15 reset window, and matches Bauer and
537
+ VICE.
538
+ - Pinned by `colorfetchbug` (its bad lines start at Bauer 17), the
539
+ `dmadelay` `test*-17`/`-18`/`-19`/`-1a` rows, `flibug/blackmail*` and
540
+ `screenpos`, which all fail when a later match resets the counter.
541
+ - The CPU halts on the bad line condition **as it stands**, not on the
542
+ latched match. `ba_low?` holds it for the columns BA covers, 10–52, which
543
+ it sees as `@column` 11–53 because it is asked after the VIC advances.
544
+ That is 43 cycles, the gap Bauer leaves between bad-line BA (cycle 12)
545
+ and sprite 0's BA (cycle 55). A condition that goes away mid-line
546
+ releases the CPU.
547
+ - Pinned by `split-tests/bascan`, a per-cycle dump of when the stall
548
+ first catches a CIA timer read. Its `$d012` reads are the same dump's
549
+ check that the raster sync itself did not move.
550
+ - The end is pinned too. Re-measured in this frame, a 45-cycle stall
551
+ (CPU halted through the cycle after column 54) breaks `bascan`
552
+ itself, all five `colorfetchbug` rows, `flibug/blackmail*`,
553
+ `spriteenable3`–`5` and several `vborder*` rows.
554
+ - Spec guard: *#ba_low?* in [`vic_spec.rb`](../spec/badline/vic_spec.rb).
555
+ - The DEN latch is level-sensitive across the raster counter's increment,
556
+ so its window runs from the last column of line 47 through the last
557
+ column of line 48. The wrap column counts for both the line ending and
558
+ the line starting. Derived against `dentest`'s `den01-48-*`, `den01-49-*`
559
+ and `den10-48-*`, which bracket both edges one cycle at a time.
560
+ - A condition still standing at column 56 puts the logic straight back
561
+ into display state after the counter wraps (Bauer 3.7.2 step 5), so RC
562
+ rolls 7 → 0 instead of leaving the line idle. This is what holds an FLI
563
+ picture together.
564
+ - VC and VMLI advance per g-access in display state, and VCBASE takes VC at
565
+ the wrap. A row that opens late therefore carries its shortfall into the
566
+ next one instead of a fixed +40.
567
+ - All 21 `dmadelay` rows pass under these rules, and they sweep the match
568
+ across the whole line. `D011Test/disable-bad` pins the too-late match.
569
+ - Spec guard: [`vic/display_state_spec.rb`](../spec/badline/vic/display_state_spec.rb),
570
+ one group per rule.
571
+ - The colour nibble of a c-access before AEC is the low nibble of the byte
572
+ at the CPU's PC, which is the opcode it is halted on, as VICE reads it.
573
+ `Computer` hands the VIC a lambda for it (`VIC#open_bus=`), called only
574
+ on those accesses. A bare VIC reads colour RAM instead.
575
+ - Pinned by `flibug/blackmail*` and `colorfetchbug/main*`, whose bug
576
+ cells take their colour from the halted opcode.
577
+ - Spec guard: *an FLI match in column 13* in
578
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
579
+ - These rows can't be read as pixel counts. The sweep became readable by
580
+ OCRing each reference PNG against `lib/badline/roms/character.rom` and
581
+ matching every display row back to its offset in screen memory, so a diff
582
+ reads as "row 0 starts 40 cells in" instead of "11,376 px". Rebuild that
583
+ as a scratch script before touching these tests again.
584
+
585
+ ## VIC graphics pipeline
586
+
587
+ The column frame is the one in [VIC bad line and DMA](#vic-bad-line-and-dma).
588
+ A register write in the CPU cycle after column `c - 1` is seen by column
589
+ `c`, and a g-access in column `c` draws in column `c + 2`. The rules follow
590
+ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
591
+
592
+ - The g-access reads its byte **in its own column**, through the mode,
593
+ `$d018` and VIC bank of that cycle, not when the sequencer draws it two
594
+ columns later. `VIC#fetch_graphics` reads it, and `GraphicsMode` only
595
+ paints.
596
+ - Pinned by `gfxfetch` (224 px → pass), which flips the character data
597
+ between the g-access and the draw, and `fetchsplit` (2939 → 2890 px
598
+ without it), which splits `$d018` and `$dd00` mid-line.
599
+ - Spec guard: *reads the byte in its own column* in
600
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
601
+ - The g-access still sees BMM for one column after it falls: it addresses
602
+ with `$d011` OR-ed with the BMM bit of the column before
603
+ (`VIC::FETCH_HOLD`). When BMM changes and the access moves from RAM onto
604
+ the character ROM, the low address byte comes from the old mode and the
605
+ rest from the new one.
606
+ - The hold is pinned by `vicii_reg_timing` (pass → 56 px without it, and
607
+ 7 → 63 px for `-a5` and `-ff`). The address mix is pinned by
608
+ `modesplit` (48 → 202 px) and `videomode-v`, `-x` and `-y` (5/10/1 →
609
+ 13/14/9 px).
610
+ - Spec guard: *addresses with a BMM that fell in the same column* and
611
+ *mixes the addresses when BMM falls onto the character ROM* in
612
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
613
+ - ECM is held for that column too, but only when the access it leaves,
614
+ addressed through the old mode, read the character ROM
615
+ (`VIC::FETCH_HOLD_ROM`). A falling ECM reaches a RAM access at once.
616
+ - Pinned by `modesplit` (348 → 48 px). Its section 2 drops ECM in text
617
+ mode with the characters in the ROM, and its section 1 goes from
618
+ ECM text on the ROM to bitmap in RAM. Both need the mask held.
619
+ `videomode-v` makes the second move too (6 → 5 px).
620
+ - The RAM side is pinned by `vicii_reg_timing`, whose ECM row drops ECM
621
+ in text mode with the characters in RAM: holding ECM there as well
622
+ takes it from pass to 32 px (7 → 39 px for `-a5` and `-ff`), and
623
+ `videomode-z`, a `$7b` → `$3b` fall in RAM, from pass to 3 px.
624
+ `videomode-x` makes the same fall in RAM and would prefer the hold
625
+ (10 → 2 px), but its readme says its reference doesn't match every 6569
626
+ capture. VICE holds BMM only.
627
+ - Spec guard: *drops a falling ECM at once when the access left RAM* and
628
+ *holds a falling ECM when the access left the character ROM* in
629
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
630
+ - The byte a group draws loads into the shift register at pixel XSCROLL,
631
+ and that XSCROLL is the one the **column before** saw. It is latched in
632
+ each g-access column with the vertical border open (VICE
633
+ `xscroll_pipe`), so a `$d016` write shows a column later than a colour
634
+ or mode write in the same cycle.
635
+ - Pinned by `sbsprf24-163`/`-164` (34/42 px → pass, 40/44 without it),
636
+ `modesplit` (48 → 144) and `vicii_reg_timing` (pass → 720), and by
637
+ `border-bm-idle`, `border-bm-ysh` and `border-mcbm`.
638
+ - Spec guard: *loads the byte at the XSCROLL the column before saw* in
639
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
640
+ - A mode change takes hold **inside** the group. ECM and BMM rising show
641
+ at pixel 4 and falling at pixel 6, so a change that clears one bit while
642
+ it sets the other shows the invalid mode's black on pixels 4 and 5. MCM
643
+ changes the colour lookup at pixel 4 but how the shift register is read
644
+ only at pixel 7, where a rising MCM also resets the multicolour
645
+ flip-flop. The mode applies to the pixels as they leave the shift
646
+ register, so the boundaries stay put whatever XSCROLL is.
647
+ - `VIC::GraphicsShifter` runs these groups pixel by pixel: a group where
648
+ the mode or the load point changes, and the group after it. Every other
649
+ group paints whole bytes, which comes to the same pixels.
650
+ - Pinned by `modesplit` (48 → 1222 px painting whole groups, 428 with
651
+ MCM read at pixel 4), the `videomode` rows and `vicii_reg_timing`
652
+ (pass → 274).
653
+ - `videomode2` and `videomode-y` disagree on where a falling BMM shows:
654
+ pixel 6 in `videomode2`, pixel 5 in `videomode-y` and in `modesplit`'s
655
+ ECM+BMM → ECM split. The readme says these delays vary with the chip
656
+ and its temperature. badline keeps VICE's pixel 6, which passes
657
+ `videomode2` and leaves `videomode-y` 1 px off and all 48 px left in
658
+ `modesplit`, one pixel on each of its first-section lines.
659
+ - An MCM that falls out of the invalid ECM+MCM text mode is a pixel
660
+ later on both counts. The lookup stays black through pixel 4 and
661
+ changes at pixel 5, and the pairs are read through pixel 7, with
662
+ hi-res reads starting at the next group's pixel 0.
663
+ - Pinned by `videomode1` and `videomode-z` (2 px each → pass), whose
664
+ illegal → ECM text split drops MCM. With the fall at pixels 4 and 7,
665
+ the reference's black pixel 4 and last-pair foreground on pixel 7
666
+ are both lost. Applying the late timing to every falling MCM breaks
667
+ `videomode2`, `vicii_reg_timing`, `modesplit` and `videomode-v`,
668
+ `-w`, `-x` and `-y`.
669
+ - Spec guard: *takes an MCM falling out of ECM+MCM a pixel late* in
670
+ [`vic/graphics_shifter_spec.rb`](../spec/badline/vic/graphics_shifter_spec.rb).
671
+ - Spec guard: [`vic/graphics_shifter_spec.rb`](../spec/badline/vic/graphics_shifter_spec.rb),
672
+ one example per pixel, and *a mode change inside a group* in
673
+ [`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb).
674
+ - The pixels XSCROLL keeps from the previous byte take the **current**
675
+ colour registers: a `$d021`–`$d024` write repaints that byte before it
676
+ shows. The `ColorPatches` +1 px still applies on top.
677
+ - Pinned by `modesplit` (48 → 114 px without it) and
678
+ `vicii_reg_timing` (pass → 212). `colorsplit` (64 px → pass) and
679
+ `spritefetchbug/test-136-2a` (8 px → pass) go back only when both this
680
+ and the XSCROLL latch are knocked out.
681
+ - Spec guard: *a background write under XSCROLL* in
682
+ [`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb).
683
+
684
+ ## VIC phi1 bus
685
+
686
+ - A CPU read of open I/O ($DE00–$DFFF) returns the byte the VIC fetched in
687
+ the phi1 half of the same cycle, and a colour RAM read takes its upper
688
+ nibble from it. `VIC#phi1_data` works the byte out when it is asked for,
689
+ from the VIC's state at that moment. The CPU cycle after column `c` is
690
+ Bauer cycle `c + 2` (the display-state frame in
691
+ [VIC bad line and DMA](#vic-bad-line-and-dma)), which is `@column + 1`
692
+ once the VIC has advanced. Each Bauer cycle has a fixed access, as in
693
+ VICE `cycle_phi1_fetch`:
694
+ - 1–10 and 58–63: two cycles per sprite from sprite 0 at 58, the
695
+ p-access at `screen_base + $3f8 + n` and then the middle s-access,
696
+ `pointer * 64 + MC + 1` with DMA on and `$3fff` with it off.
697
+ - 11–15: refresh at `$3f00 | REF`. REF is `$ff` at line 0 and steps
698
+ down once per refresh access, five per line.
699
+ - 16–55: the g-access of the column just run, read at column time, not
700
+ when the sequencer draws it two columns later. In display state that
701
+ is the address the sequencer uses, with the VC and VMLI the access
702
+ stepped past (`& $39ff` with ECM). In idle state it is `$3fff`, or
703
+ `$39ff` with ECM, whatever the vertical border does.
704
+ - 56–57: `$3fff`, with or without ECM.
705
+ - Pinned by `phi1timing`, which reads $DEAD once per cycle across a
706
+ line in idle state with ECM set. A one-cycle shift of the frame either
707
+ way fails it on 19 of its 63 columns. The refresh counter, display-state
708
+ g-accesses and the DMA s-access follow VICE: `phi1timing` fills
709
+ $3f00–$3ffe with one value and runs with DEN clear and no sprites.
710
+ - Spec guard: *#phi1_data* in [`vic_spec.rb`](../spec/badline/vic_spec.rb).
711
+
712
+ ## VIC light pen
713
+
714
+ - CIA1 PB4 level changes (`CIA#on_port_b4_change`) drive
715
+ `VIC#lightpen_level`. The first falling edge per frame latches LPX/LPY one
716
+ cycle later (plus the 6569's 2 half-pixel offset) and raises the `$D019`
717
+ bit 3 IRQ.
718
+ - Triggers on the last line are consumed without latching (except at cycle
719
+ 0). A line held low across frame start retriggers with a fixed LPX of
720
+ `$d1`.
721
+ - Calibrated byte-exact against the `split-tests/lightpen` `dump6569`
722
+ reference, as `makeref` corrects it: the test fixes up the tail bytes of
723
+ pre-R03 dumps before comparing. All five pages match, the raster-read
724
+ pages included (see [VIC raster IRQ phase](#vic-raster-irq-phase)).
725
+ - The 6569's offset is 2 half-pixels and the 8565's is 1, so
726
+ `lp-trigger/test2new`, which wants the 8565, would fail by design.
727
+ badline models only the 6569, and `bin/testbench` skips `vicii-new` rows.
728
+ - Pinned by `lplatency`, `lp-trigger`, and the `fldscroll` tests, which sync
729
+ through the light pen instead of the double IRQ.
730
+
731
+ ## CIA 6526 timer pipeline
732
+
733
+ - Start and stop go through two stages. A force load lands one tick after
734
+ it becomes visible, then swallows a pulse and lands after that tick's
735
+ count. The timer underflows on reaching zero, with the reload on the next
736
+ tick. Other rules: a zero counter underflows prematurely; draining to zero
737
+ on stop does not raise the flag; zero latches chain; a one-shot lingers
738
+ when cleared at t-1; PB6/PB7 toggle only on a start transition.
739
+ - Old-CIA IR delay: IR rises 1 cycle after the flag, or 2 cycles after a
740
+ mask write hits a pending flag, and an ICR read cancels the pending
741
+ assert.
742
+ - Old-CIA acknowledge: an ICR read releases the interrupt line at once, but
743
+ its IR acknowledge lands a cycle late. A read on the next cycle still
744
+ sees IR (`$80`) if it was set at the first read, or was due to rise on
745
+ the next cycle. The line stays released either way, and the cycle after
746
+ that IR reads clear. Pinned by `CIA/dd0dtest/dd0dtest` tests 0c, 0d
747
+ and 0e: the dummy read of `inc $dd0d,x` acknowledges, and the real read
748
+ one cycle later sees `$80`, so the RMW writes `$80`/`$81` back and the
749
+ mask survives, where `$00`/`$01` would clear timer A's mask bit.
750
+ - Old-CIA mask cancel: a write that masks every pending source on the
751
+ cycle the flag rises cancels the IR assert only if an ICR read happened
752
+ two cycles earlier. Without that read, IR still rises. Pinned by
753
+ `dd0dtest` test 11 (`inc $dd0d,x` reads at F-2 and writes the clearing
754
+ `$01` at F). This is VICE `ciacore.c`'s `CIA_IRQ_ACK_1` branch (its
755
+ NOTE_1).
756
+ - Timer B bug (6526 only): a timer B underflow on the cycle right after an
757
+ ICR read raises the flag, and IR if armed, but the next ICR read drops
758
+ the TB bit unseen. Timer A has no such bug. Pinned by
759
+ `CIA/ciavarious/cia3` K/L, `cia3a` D/H, `cia4` X, `cia8` A/C/F/J/L
760
+ (the readme's old-versus-new CIA cells) and `CIA/cia-timer/cia-timer-oldcias`,
761
+ and ported from VICE's `CIA_IM_TBB`.
762
+ - The modelled revision is the **6526**, not the 6526A, matching
763
+ `Lorenz.d81`. That is all the `(*1)` cells of `cia1ta`/`cia1tb` measure,
764
+ and it is an ICR difference, not a counter one. Those cells read the ICR
765
+ on the very cycle the underflow flag rises, so the source bit is up while
766
+ IR is not: they read `$01`/`$02`, where a 6526A reads `$81`/`$82`. Counter
767
+ readback is identical on both revisions. `Lorenznew.d81` expects the
768
+ 6526A and must not be mixed in.
769
+ - Timer B's cascade decodes CRB, not CRA. Every count source (ø2, a CNT
770
+ edge, a cascaded timer A underflow) drives the same two-stage
771
+ count-enable pipeline, so a timer A underflow decrements timer B two
772
+ cycles later, and switching the source mid-flight leaves up to two
773
+ enables in the pipe. `cia1tab` is what rules out feeding the underflow
774
+ straight in. With both latches at 2, it wants timer B to read `00` for
775
+ exactly two cycles, with the flag, the PB7 toggle and the reload all
776
+ landing on the cycle after: the premature underflow of a zero counter,
777
+ one pipeline stage ahead of the decrement.
778
+ - Pinned by the Lorenz `irq` header, `cia1tb123`, `cia2tb123`, `cia1pb6`,
779
+ `cia1pb7`, `cia2pb6`, `cia2pb7`, `flipos`, `oneshot`, `cntdef`, `loadth`,
780
+ `icr01`, `cia1tab`, `imr`, `cputiming`, `cia1ta`, `cia1tb`, `cia2ta` and
781
+ `cia2tb`.
782
+ - Both timers power on with latch and counter at `$ffff`, as VICE's
783
+ `ciat_reset` does. The KERNAL never writes CIA2's timer latches, so a
784
+ program that writes only the high byte while the timer is stopped loads
785
+ the counter with `$00ff`, not zero. A zero there underflows as soon as the
786
+ timer starts and raises a spurious NMI. Pinned by
787
+ `interrupts/branchquirk/branchquirk-nmiold` (first cell only) and
788
+ `CPU/Acid800/cpu_bugs` (NMI lands before the BRK instead of hijacking it).
789
+ - Spec guard: [`cia/timer_spec.rb`](../spec/badline/cia/timer_spec.rb) runs
790
+ the eight `(*1)` cells and the `cia1tab` table. It fails if the IR delay
791
+ is dropped. Its *power-on state* group guards the `$ffff` reset.
792
+ [`cia_spec.rb`](../spec/badline/cia_spec.rb)'s *6526 interrupt
793
+ acknowledge* and *6526 timer B bug* groups guard the three ICR rules
794
+ above, on a bare CIA.
795
+
796
+ ## CIA serial shift register
797
+
798
+ - The register counts itself empty on the 15th timer A underflow, when the
799
+ eighth bit reaches SP, not on the 16th that raises CNT over it. The
800
+ serial ICR flag rises 4 cycles after that 15th underflow. VICE waits for
801
+ the 16th, which is the "4 cycle delay" in `cia-sdr-delay`'s readme.
802
+ - A byte waiting in the data register loads from that same point
803
+ (`@steps <= 1`) and goes out on the next underflow, so CNT stays low
804
+ between the two bytes. Waiting for the 16th costs a whole timer period,
805
+ the 49 cycles `cia-sdr-load` measured.
806
+ - An in-flight level runs through a delay line. It rises 1 cycle after a
807
+ bit lands on SP, drops at 2 and rises again at 3. The eighth bit skips
808
+ straight to 3. The level drops 4 cycles after CNT rises over the bit.
809
+ - A CRA bit 6 change is not symmetric. Switching the port to input tears
810
+ the transmission down. If the register is busy, from 4 cycles after the
811
+ first bit reaches SP until it reports empty over the eighth, the byte is
812
+ flagged gone. The same change latches the in-flight level, and switching
813
+ back to output flags the byte gone if the latch is set. Reading the live
814
+ level at the output change instead leaves it stuck high whenever a byte
815
+ never finishes, and the single-baud `cia?-sdr-icr-*` rows catch that.
816
+ - A zero timer A latch holds the underflow line asserted rather than
817
+ pulsing it. A waiting byte is still picked up on the level, but every
818
+ later half-step needs a fresh edge, so the byte stops after one bit, CNT
819
+ stays low and the flag never rises.
820
+ - Pinned by the `CIA/shiftregister` rows below. Each was checked by
821
+ breaking the rule and rerunning the rows:
822
+ - Empty at the 15th: `cia-sdr-delay`, `cia-sdr-init`, `cia-sdr-load`,
823
+ `cia-sp-test-oneshot-old` and the `-3`/`-19`/`-39` `cia-sdr-icr` rows.
824
+ - Loading from the same point: `cia-sdr-load` alone.
825
+ - The in-flight delay line: the `-3`/`-19`/`-39` `cia-sdr-icr` rows.
826
+ - The latched level: every single-baud `cia-sdr-icr` row, `-0`
827
+ included.
828
+ - The busy report on the switch to input: only
829
+ `cia1-sdr-icr-test2-0_7f` and `cia2-sdr-icr-test2-0_7f`.
830
+ - The zero-latch stall: the `-0` `cia-sdr-icr` rows.
831
+ - Nothing pins whether the delay line keeps moving while timer A is
832
+ stopped. It is clocked every cycle, and freezing it passes every row too.
833
+ - Don't read `cia-sdr-icr/generate.c` as the spec. Its `reset1` sets
834
+ `delaysetsdr1 = baud` for baud > 3, but `cia-sdr-icr-v3.asm`, which is
835
+ what runs, leaves it at 3 for every baud ≥ 3 (`lda #$03 / cpx #$03 /
836
+ bcs +`). That byte is the difference between the first rule and VICE's
837
+ behaviour. `generate-test2.c` states the rule in a comment: "SP INT is
838
+ raised 4 cycles after TA INT".
839
+ - The twelve `-4485` rows other than `-4485-0` are `expect:error`: they
840
+ pass because the model does not match the 4485-batch CIA's reference.
841
+ Baud 0 behaves the same on that batch, so the two `-4485-0` rows must
842
+ pass outright.
843
+ - An offline replay that starts each baud on a fresh CIA cannot see the
844
+ state one baud carries into the next through the mode-change latch. Run
845
+ the test2 sweeps for real (about 5 min each) after changing that path.
846
+ - Spec guard: the "serial port in output mode" block in
847
+ [`cia_spec.rb`](../spec/badline/cia_spec.rb) covers the flag timing, the
848
+ waiting byte, both mode-change reports and the zero-latch stall.
849
+
850
+ ## 6510 I/O port
851
+
852
+ - DDR at `$00`/`$01`: inputs are pulled up, bit 5 reads low, and bits 3, 6
853
+ and 7 float.
854
+ - Pinned by Lorenz `mmu` and `cpuport`.
855
+ - DDR and data both power on at `$00`, so `$00` reads `$00` and `$01` reads
856
+ `$17` until the KERNAL sets them.
857
+ - Pinned by `CPU/cpuport/initvalue.crt`, which runs from a cartridge
858
+ before the KERNAL does.
859
+ - Spec guard: *when powered on* in
860
+ [`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
861
+
862
+ ## SID oscillator
863
+
864
+ - The phase accumulator powers on at `$555555` (all bits high, with the odd
865
+ ones stored inverted) and survives reset. `SID/osc3-wave0` only reads the
866
+ documented `$00`/`$ff` because of it.
867
+ - Pinned by `SID/oscinit` (all three).
868
+ - Spec guard: *leaves the SID's accumulators alone* in
869
+ [`computer_spec.rb`](../spec/badline/computer_spec.rb). The testbench
870
+ only runs `SID/oscinit` from power-on, so only the spec catches a reset
871
+ that rebuilds the voices.
872
+ - Ring modulation substitutes the triangle's MSB with
873
+ `!Saw & ((!V3 & Ring) ^ bit23)`, where `V3` is the modulating voice's MSB.
874
+ That is an XNOR where reSID uses an XOR.
875
+ - Pinned by `SID/ringmod`, which expects OSC3 to read `$ff` with both
876
+ oscillators stopped at zero.
877
+ - The noise LFSR is 23 bits wide. It feeds bit 0 back from bits 22 ^ 17.
878
+ Its eight output taps are bits 20, 18, 14, 11, 9, 5, 2 and 0, driving
879
+ waveform bits 11 down to 4 (Dag Lem's diagram in `SID/noise-reset_new`).
880
+ It powers on at `$7ffffe`, which is what makes `SID/oscinit`'s
881
+ `noiseinit` read `$fe`.
882
+ - Each rise of accumulator bit 19 shifts the LFSR two cycles later, in two
883
+ phases (libresidfp's shift pipeline). The cycle after the rise is phase
884
+ 1: the register bits float, so a combined waveform pulls nothing down and
885
+ its output is latched instead. The cycle after that is phase 2: the
886
+ latched output is written over the taps, then the register shifts.
887
+ Phase 2 writes back by the test bit release rule below, with the old and
888
+ new waveform the same: noise combined with anything but pulse alone.
889
+ Setting the test bit drops a shift in flight.
890
+ - Pinned by `SID/noisewriteback`'s `noise_writeback_test2` (both chips).
891
+ It releases the test bit into noise+triangle, which pulls every tap low,
892
+ then sets the frequency to `$ffff`. Bit 19 rises on the 9th cycle and
893
+ the read lands on the 11th, the first output after the shift has filled
894
+ the taps from the bits below: `$14` on the 6581 and `$12` on the 8580,
895
+ whose OSC3 reads the triangle a cycle late. Shifting on the rise itself
896
+ lets the triangle pull the new taps down first, and the read is `$10`.
897
+ The test pins the delay only. Phase 1's float differs from pulling down
898
+ only when pulse+noise is selected across it, and nothing pins that.
899
+ - Spec guard: *two cycles after accumulator bit 19 rises* and *around a
900
+ shift* in [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb),
901
+ and *carries a noise shift pending across a span edge* in
902
+ [`sid_spec.rb`](../spec/badline/sid_spec.rb): a catch-up that
903
+ fast-forwards across a rise carries the shift still in flight into the
904
+ next span.
905
+ - The test bit does not clear the LFSR. It stalls it halfway through a
906
+ shift with bit 22 forced high. While the bit is held, every bit bleeds up
907
+ to `$7fffff`: after `$950000` cycles on the 8580 (`SID/bitfade`'s
908
+ `delaynoise` on a real 8580; VICE's `~8000` there is its own emulation)
909
+ and after `$80000` on the 6581. On release, one bit clocks in.
910
+ - Pinned by `SID/resid-test`'s `oscsample0`/`oscsample1` (both chips).
911
+ They hold the test bit about `$8800` cycles between their eight noise
912
+ runs, and the real chips' dumps read each run on from the register the
913
+ last one left, so the bleed has to take longer. The old `$8000` fails
914
+ all four rows. The 6581 value is not measured anywhere: it only has to
915
+ fall between that hold and the second `SID/noise-reset_old` allows the
916
+ 6581 (60 frames, about `$120000` cycles), and `$80000` sits between
917
+ them. The scored tests that wait for the bleed hold the bit far longer
918
+ (`resid-test/noisetest` about `$1680000`, `waveforms-80` and
919
+ `noise_writeback_test1` about `$f60000`), so the 8580's `$950000`
920
+ passes them too. On a real chip the bits rise one at a time, at a rate
921
+ that varies with the chip's temperature (`SID/wf12nsr`'s
922
+ `quicktest.prg` on a 6581), which nothing scored depends on.
923
+ - Spec guard: *bleeding through a held test bit* in
924
+ [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb). Before it does, the old waveform's output is
925
+ written back only for some waveform changes. Noise has to have been
926
+ combined before the release and still be selected after it. A change to
927
+ noise alone writes nothing back unless all four waveforms were selected
928
+ before. A change to pulse+noise writes nothing back, nor does a change
929
+ from pulse+noise to sawtooth+noise. On the 6581, trading triangle for
930
+ sawtooth or back writes nothing back. The rule follows libresidfp's
931
+ `do_writeback`. What is written is the writeback shape below, not the
932
+ OSC3 read. One more case is not in that rule: on the 6581, a change that
933
+ keeps only pulse+noise of the old waveform (`D`/`E`/`F`→`C`, `D`→`E`)
934
+ writes the three lowest noise lines low (`$f8`).
935
+ - Pinned by `SID/wb_testsuite` (the `9`/`A`/`D`/`E`→`8` rows on both
936
+ chips, and the 6581's `9`↔`A`, `9`/`A`→`C` and `D`→`A` rows) and by
937
+ `SID/noisewriteback`'s `noise_writeback_test1`. The 6581's `$f8` case
938
+ is pinned by its `D`→`C`, `D`→`E`, `E`→`C` and `F`→`C` rows, and
939
+ dropping it fails all four. The 8580's `C`→`A` row pins the
940
+ pulse+noise to sawtooth+noise exception: without it the release writes
941
+ the 8580's `$fc` and the row fails.
942
+ - Known conflict: the 6581's `F`→`8` row expects no writeback, but
943
+ `SID/noiselfsrinit`'s `simple` and `scan` zero the register with
944
+ `$f8`/`$80` pairs, which needs `F`→`8` to write back. The row stays
945
+ failing.
946
+ - Spec guard: *as the test bit falls* in
947
+ [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
948
+ - A combined waveform shorts the shapers onto the lines the oscillator reads
949
+ back. A low top bit reaches the accumulator MSB through the sawtooth
950
+ switch and clears it (`SID/osc_topbit`, all three). With noise selected,
951
+ the result is written into the LFSR, where a bit pulled low never comes
952
+ back. Together these run Dag Lem's fast LFSR reset exactly as documented:
953
+ three `$b8`/`$b0` pairs zero the register, and 18 `$88`/`$80` pairs set
954
+ bits 0–17.
955
+ - The shape of a combined waveform without noise comes from a fitted model
956
+ (`SID::Waveform::Combined`), not from ANDing the shapers: each line is
957
+ high when its own drive and its neighbours', weighted by distance, clear
958
+ a threshold. `bin/sidwavefit` fits it to `SID/resid-test`'s oscsample
959
+ dumps and `bin/sidwavecheck` scores it. A low pulse grounds every line.
960
+ Noise is ANDed over triangle and sawtooth mixes; pulse+noise is below.
961
+ - Not pinned by an exit code: oscsample only scores the single
962
+ waveforms, and no scored test reads a combined shape without noise.
963
+ - Spec guard: [`sid/waveform/combined_spec.rb`](../spec/badline/sid/waveform/combined_spec.rb)
964
+ checks spot values against the dumps.
965
+ - Noise in a combined waveform writes its lines back into the LFSR by a
966
+ lower threshold than OSC3 reads them by. Two rules follow, both fitted to
967
+ the tests' own reference data (`SID::Waveform::NoiseWriteback`):
968
+ - With triangle or sawtooth, OSC3 reads the plain AND, but a line left
969
+ high between two low ones is written low. `SID/noisewriteback`'s
970
+ `noise_writeback_test2` reads such lone lines (`$14` is output bits 8
971
+ and 6), and `SID/wf12nsr`'s noise+triangle and noise+sawtooth rows need
972
+ them written low: at the end of the `$ffff` period the real register
973
+ holds `$020100`, and a writeback of the plain AND leaves `$024100`.
974
+ - Pulse+noise with the pulse high loses its lowest noise lines, next to
975
+ the four below them that nothing drives. The 8580 reads `$f8` of a full
976
+ register and writes `$fc` back, the only pair among `$f8`, `$fc` and the
977
+ full noise that passes `SID/wf12nsr`'s pulse+noise row. That `$fc` is what
978
+ `SID/wb_testsuite`'s `C`→`9` and `C`→`E` rows need written at release.
979
+ The 6581 reads `$fc` (the readme of `SID/wf12nsr`, VICE bug #1037,
980
+ unscored) and writes nothing back: its `8`/`9`/`A`/`B`→`C` rows run
981
+ pulse+noise and leave the register alone.
982
+ - Pinned by `SID/wf12nsr` (wf9/wfa on both chips, wfc on the 8580) and
983
+ the `SID/wb_testsuite` rows above. Knock-outs: writing back the plain
984
+ AND fails wf9 and wfa on both chips; on the 8580, reading and writing
985
+ `$fc` fails wfc, and reading and writing `$f8`, or the full noise,
986
+ fails wfc and the `C`→`9`/`C`→`E` rows; applying the lone-line rule to
987
+ the read as well makes `noise_writeback_test2` read `$00` on both
988
+ chips.
989
+ - Not fitted: the 6581's pulse+noise row of `SID/wf12nsr`. The program
990
+ never sets voice 3's pulse width, and the 6581 reference reads as if
991
+ the pulse was low when the test bit fell, grounding every line. From
992
+ power-on the width is zero and the pulse high. Forcing a width of
993
+ `$800` before the row makes it pass.
994
+ - Spec guard: *writes a lone line of a noise combination back low*, *with
995
+ pulse+noise on the 8580* and *as the test bit falls* in
996
+ [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
997
+ - The pulse comparator's output reaches the lines a cycle after the
998
+ accumulator it compared, on both chips. Setting the test bit forces it
999
+ high at once.
1000
+ - Pinned by `SID/waveforms`' `waveforms-40` (both chips) and the pulse
1001
+ rows of `SID/resid-test`'s `oscsample1` dumps, where the width of `$100`
1002
+ first reads high at phase `$101` on the 6581, and at `$100` on the 8580
1003
+ whose OSC3 reads the phase itself a cycle late. Comparing on time fails
1004
+ both `waveforms-40` rows.
1005
+ - Spec guard: *reaches the output a cycle after the accumulator it
1006
+ compared* in [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
1007
+ - Waveform 0 leaves the DAC input floating. It holds the last value a shaper
1008
+ drove onto it and drains to `$000` after `$4000` cycles.
1009
+ - Pinned by `SID/osc3-wave0`. `SID/oscinit`'s `allinit` pins the power-on
1010
+ `$00`, before anything has driven the line.
1011
+ - The 8580 delays the triangle and sawtooth shapers by half a cycle. OSC3
1012
+ latches in the first phase of the clock, so it reads them a whole cycle
1013
+ late, while pulse and noise still reach the lines on time (the pulse with
1014
+ its own cycle of lag, above). The audio output is not delayed. This
1015
+ follows libsidplayfp's `tri_saw_pipeline`.
1016
+ - Pinned by `SID/detect`'s `detect-2-new`. It releases the test bit into
1017
+ a `$ffff` sawtooth and reads OSC3 four cycles later: `3` on the 6581,
1018
+ `2` on the 8580.
1019
+ - Spec guard: *#osc3 on the 8580* in
1020
+ [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb) and
1021
+ *the 8580* in [`sid_spec.rb`](../spec/badline/sid_spec.rb).
1022
+
1023
+ ## SID register writes
1024
+
1025
+ - The 6581 latches a register write one cycle late. That falls out of the
1026
+ bus order, not from any delay line inside `SID`. `Computer#cycle!` clocks
1027
+ the SID ahead of the CPU, so a write on cycle N first reaches the DSP on
1028
+ cycle N+1, while a read on cycle N sees N cycles of clocking. Moving
1029
+ `sid.cycle!` after `cpu.cycle!` breaks this.
1030
+ - Pinned by `SID/writedelay`, which reads OSC3 four cycles after releasing
1031
+ the test bit and expects the pulse already high.
1032
+ - Spec guard: *SID writes against the clock order* in
1033
+ [`computer_spec.rb`](../spec/badline/computer_spec.rb). It runs an
1034
+ `STA $d400` and checks that the DSP has not clocked the new frequency on
1035
+ the store's own cycle, over both the synthesizing and the idle-replay
1036
+ paths.
1037
+
1038
+ ## SID data bus
1039
+
1040
+ - Reading a write-only or unconnected register returns the latch without
1041
+ draining it. reSID halves what is left of the charge on such a read,
1042
+ which would cut the 6581 measurement below to `$52`–`$7d`.
1043
+ - Pinned by `SID/bitfade`'s `delayfrq0`, which polls `$d400` every nine
1044
+ cycles while it waits and still measures the full TTL: `$1d02` here
1045
+ against `~$01d00` on a real 6581, and `$a2005` for the 8580 against
1046
+ VICE's `~$a2000`.
1047
+
1048
+ ## SID envelope
1049
+
1050
+ - The envelope follows reSID's rate-counter model: a 15-bit counter compared
1051
+ against a per-nibble period, and a second level-dependent divider (`$ff`→1,
1052
+ `$5d`→2, `$36`→4, `$1a`→8, `$0e`→16, `$06`→30) that the attack phase
1053
+ bypasses and resets. Zero freezes the envelope until the next gate edge.
1054
+ - Lowering the rate period below the running counter sends the counter the
1055
+ long way round through `2^15`. Hard restarts depend on this ADSR delay
1056
+ bug.
1057
+ - Pinned by `SID/envelope` (`testADSRDelayBug`, `testFlip00toFF`,
1058
+ `testFlipFFto00`, `lft-adsr-test`) and `SID/exp_counter_reset`.
1059
+ - Each stage runs a cycle or more behind the one feeding it, after reSID
1060
+ 1.0's single-cycle pipeline (VICE's `resid/envelope.h`), not reSID 0.16's
1061
+ step-on-match:
1062
+ - The rate counter compares against `PERIODS` (8, 31, 62, …) *before* it
1063
+ counts. A match holds the counter for a cycle and resets it to 0 on the
1064
+ next, so the period is one cycle longer than the comparison value.
1065
+ - An attack step lands two cycles after that reset. A decay or release
1066
+ step with the divider at 1 also lands two cycles after it, through a
1067
+ one-cycle divider stage. With the divider above 1, it takes one cycle
1068
+ more.
1069
+ - ENV3 reads the counter as it stood at the start of the cycle, one cycle
1070
+ behind the audio path.
1071
+ - A rising gate runs the decay state and decay rate for one cycle and
1072
+ enters attack on the second. A step already set off by a pending reset
1073
+ or divider still lands, as an attack step, two cycles after the edge
1074
+ (four with the divider above 1), and a divider one cycle from landing
1075
+ holds the attack off a cycle more.
1076
+ - A falling gate switches the rate over on its second cycle, or its third
1077
+ with a step in flight. Out of decay it switches a cycle sooner.
1078
+ - Pinned by `SID/env_test`, all seven. The readme says they pass on a
1079
+ real 6581 and 8580. Knock-outs, each failing the listed tests:
1080
+ attack step one cycle after the reset (all seven), divider stage
1081
+ always one cycle (all seven), ENV3 reading the live counter (all
1082
+ seven), a rising gate ignoring the pending step (`ra_0000`, `ra_0100`,
1083
+ `adra_1`, `adra_2`), attack on the first cycle after the edge
1084
+ (`ra_0100`, `adra_1`, `adra_2`), no decay rate on that first cycle
1085
+ (`ra_0100`), and a falling gate always taking two cycles (`ar_1`,
1086
+ `ar_2`). `SID/envelope`, `SID/exp_counter_reset` and the four
1087
+ `resid-test/env*` programs pass either way.
1088
+ - Spec guard: *gate edge*, *attack*, *#env3*, *exponential divider*,
1089
+ *gate falling with a step in flight* and *gate rising as the rate
1090
+ counter matches* in
1091
+ [`sid/envelope_spec.rb`](../spec/badline/sid/envelope_spec.rb). Its
1092
+ *#fast_forward* examples hold the batched catch-up to the same timing:
1093
+ between steps a span jumps from one match to the next, and the cycles
1094
+ around each step run whole.
1095
+
1096
+ ## `.sid` tune banking
1097
+
1098
+ - A tune's init and play routines must run with the ROMs banked to match
1099
+ the address they live at, following libsidplayfp's iomap: `$37` below
1100
+ `$a000`, `$36` under BASIC, `$34` in the `$d000` I/O window, `$35` under
1101
+ the KERNAL. Restore `$01` afterwards so the caller's banking survives.
1102
+ This entry is the rule, and it should outlive whichever code carries it.
1103
+ - Derived against six OneLoad64 tunes (Galway, Tel, Gray, Dunn, Cooksey).
1104
+ `SIDFile#bank_for` is the one copy of the map: `Driver` wraps both calls
1105
+ in a `$01` save/bank/restore, and `BarePlayer#dispatch` pokes it before
1106
+ handing the CPU the stub.
1107
+ - Spec guard, at both ends: *a tune living under the BASIC ROM* in
1108
+ [`audio/renderer_spec.rb`](../spec/badline/audio/renderer_spec.rb) (a
1109
+ fixture at `$a000` that reads peak=0 without the `$01` write), and the
1110
+ *#driver for a tune under BASIC* and *#bank_for* groups in
1111
+ [`storage/sid_file_spec.rb`](../spec/badline/storage/sid_file_spec.rb).
1112
+ - The boot stub never returns to its caller: after `cli` it spins on
1113
+ `jmp *` (PSID and RSID). BASIC's READY loop reuses zero page `$19`–`$21`,
1114
+ which a tune owns. Wizball (Ocean Loader 1) keeps its music-enable flag
1115
+ in `$19` and goes silent when BASIC writes `$0a` there.
1116
+ - Spec guard:
1117
+ [`storage/sid_file/driver_spec.rb`](../spec/badline/storage/sid_file/driver_spec.rb).