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
@@ -15,6 +15,11 @@ module Badline
15
15
  Array.new(8) { |i| (data >> (6 - (i & ~1))).allbits?(0b10) }.freeze
16
16
  end.freeze
17
17
 
18
+ # The same patterns as masks, all bits set for a foreground pixel.
19
+ HIRES_MASK = Array.new(256) do |data|
20
+ Array.new(8) { |i| data.anybits?(1 << (7 - i)) ? -1 : 0 }.freeze
21
+ end.freeze
22
+
18
23
  NO_FG = HIRES_FG[0]
19
24
 
20
25
  module Hires
@@ -23,117 +28,137 @@ module Badline
23
28
  colors = seq.cur_colors
24
29
  return colors.fill(background) if data.zero?
25
30
 
26
- i = 0
27
- while i < 8
28
- colors[i] = data.anybits?(1 << (7 - i)) ? color : background
29
- i += 1
30
- end
31
+ mask = HIRES_MASK[data]
32
+ flip = color ^ background
33
+ colors[0] = background ^ (flip & mask[0])
34
+ colors[1] = background ^ (flip & mask[1])
35
+ colors[2] = background ^ (flip & mask[2])
36
+ colors[3] = background ^ (flip & mask[3])
37
+ colors[4] = background ^ (flip & mask[4])
38
+ colors[5] = background ^ (flip & mask[5])
39
+ colors[6] = background ^ (flip & mask[6])
40
+ colors[7] = background ^ (flip & mask[7])
31
41
  end
32
42
  end
33
43
 
34
- # Decodes 2-bit pixel pairs into double-wide pixels. The colour for each
35
- # pair is supplied by the block.
44
+ # Decodes 2-bit pixel pairs into double-wide pixels, each pair indexing
45
+ # the four colours in the mode's palette.
36
46
  module Multicolor
37
47
  def paint_pairs(data, seq)
38
48
  seq.cur_fg = PAIR_FG[data]
39
49
  colors = seq.cur_colors
40
- i = 0
41
- while i < 8
42
- colors[i] = yield((data >> (6 - (i & ~1))) & 0b11)
43
- i += 1
44
- end
50
+ palette = @palette
51
+ colors[0] = colors[1] = palette[data >> 6]
52
+ colors[2] = colors[3] = palette[(data >> 4) & 0b11]
53
+ colors[4] = colors[5] = palette[(data >> 2) & 0b11]
54
+ colors[6] = colors[7] = palette[data & 0b11]
45
55
  end
46
56
  end
47
57
 
58
+ # Each mode paints the byte a g-access read with the screen byte and
59
+ # colour nibble it latched. The VIC reads the byte itself, in the
60
+ # g-access column. #fg gives the foreground mask #paint would leave,
61
+ # without the colours.
48
62
  class Text
49
63
  include Hires
50
64
 
51
- def decode(screencode, color, _cell, row, seq)
52
- registers = seq.registers
53
- data = seq.bank.peek(registers.char_base + (screencode * 8) + row)
54
- paint_hires(data, color, registers.background, seq)
65
+ def paint(data, _screencode, color, seq)
66
+ paint_hires(data, color, seq.registers.background, seq)
55
67
  end
68
+
69
+ def fg(data, _screencode, _color) = HIRES_FG[data]
56
70
  end
57
71
 
58
72
  class MulticolorText
59
73
  include Hires
60
74
  include Multicolor
61
75
 
62
- def decode(screencode, color, _cell, row, seq)
76
+ def initialize
77
+ @palette = Array.new(4, 0)
78
+ end
79
+
80
+ def paint(data, _screencode, color, seq)
63
81
  registers = seq.registers
64
- data = seq.bank.peek(registers.char_base + (screencode * 8) + row)
65
82
  if color.anybits?(0x08)
66
- paint_pairs(data, seq) do |pair|
67
- multicolor_pixel(pair, color, registers)
68
- end
83
+ palette = @palette
84
+ palette[0] = registers.background(0)
85
+ palette[1] = registers.background(1)
86
+ palette[2] = registers.background(2)
87
+ palette[3] = color & 0x07
88
+ paint_pairs(data, seq)
69
89
  else
70
90
  paint_hires(data, color & 0x07, registers.background, seq)
71
91
  end
72
92
  end
73
93
 
74
- private
75
-
76
- def multicolor_pixel(pair, color, registers)
77
- case pair
78
- when 0b00 then registers.background(0)
79
- when 0b01 then registers.background(1)
80
- when 0b10 then registers.background(2)
81
- else color & 0x07
82
- end
83
- end
94
+ def fg(data, _screencode, color) = color.anybits?(0x08) ? PAIR_FG[data] : HIRES_FG[data]
84
95
  end
85
96
 
86
97
  class ExtendedBackgroundText
87
98
  include Hires
88
99
 
89
- def decode(screencode, color, _cell, row, seq)
90
- registers = seq.registers
91
- background = registers.background((screencode >> 6) & 0b11)
92
- data = seq.bank.peek(registers.char_base + ((screencode & 0x3f) * 8) + row)
100
+ def paint(data, screencode, color, seq)
101
+ background = seq.registers.background((screencode >> 6) & 0b11)
93
102
  paint_hires(data, color, background, seq)
94
103
  end
104
+
105
+ def fg(data, _screencode, _color) = HIRES_FG[data]
95
106
  end
96
107
 
97
108
  class Bitmap
98
109
  include Hires
99
110
 
100
- def decode(screencode, _color, cell, row, seq)
101
- data = seq.bank.peek(seq.registers.bitmap_base + (cell * 8) + row)
111
+ def paint(data, screencode, _color, seq)
102
112
  foreground = (screencode >> 4) & 0x0f
103
113
  background = screencode & 0x0f
104
114
  paint_hires(data, foreground, background, seq)
105
115
  end
116
+
117
+ def fg(data, _screencode, _color) = HIRES_FG[data]
106
118
  end
107
119
 
108
120
  class MulticolorBitmap
109
121
  include Multicolor
110
122
 
111
- def decode(screencode, color, cell, row, seq)
112
- registers = seq.registers
113
- data = seq.bank.peek(registers.bitmap_base + (cell * 8) + row)
114
- paint_pairs(data, seq) do |pair|
115
- multicolor_pixel(pair, screencode, color, registers)
116
- end
123
+ def initialize
124
+ @palette = Array.new(4, 0)
117
125
  end
118
126
 
119
- private
120
-
121
- def multicolor_pixel(pair, screencode, color, registers)
122
- case pair
123
- when 0b00 then registers.background(0)
124
- when 0b01 then (screencode >> 4) & 0x0f
125
- when 0b10 then screencode & 0x0f
126
- else color & 0x0f
127
- end
127
+ def paint(data, screencode, color, seq)
128
+ palette = @palette
129
+ palette[0] = seq.registers.background(0)
130
+ palette[1] = (screencode >> 4) & 0x0f
131
+ palette[2] = screencode & 0x0f
132
+ palette[3] = color & 0x0f
133
+ paint_pairs(data, seq)
128
134
  end
135
+
136
+ def fg(data, _screencode, _color) = PAIR_FG[data]
129
137
  end
130
138
 
131
139
  class Null
132
- def decode(_screencode, _color, _cell, _row, seq)
140
+ def paint(_data, _screencode, _color, seq)
133
141
  seq.cur_fg = NO_FG
134
142
  seq.cur_colors.fill(0)
135
143
  end
144
+
145
+ def fg(_data, _screencode, _color) = NO_FG
136
146
  end
147
+
148
+ NULL_MODE = Null.new
149
+
150
+ # Indexed by ECM/BMM/MCM; the three invalid combinations decode to
151
+ # black.
152
+ MODES = [
153
+ Text.new, # 000 standard text
154
+ MulticolorText.new, # 001 multicolour text
155
+ Bitmap.new, # 010 standard bitmap
156
+ MulticolorBitmap.new, # 011 multicolour bitmap
157
+ ExtendedBackgroundText.new, # 100 ECM text
158
+ NULL_MODE, # 101 invalid
159
+ NULL_MODE, # 110 invalid
160
+ NULL_MODE # 111 invalid
161
+ ].freeze
137
162
  end
138
163
  end
139
164
  end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class VIC < Cycleable
5
+ # The graphics shift register, pixel by pixel, for the groups where a
6
+ # mode or XSCROLL change lands (VICE x64sc `draw_graphics`). The
7
+ # sequencer paints whole bytes everywhere else, which comes to the same
8
+ # thing while nothing changes.
9
+ #
10
+ # The byte a group draws loads at pixel XSCROLL. ECM and BMM take hold at
11
+ # pixel 4 as they rise and at pixel 6 as they fall (the 6569's colour
12
+ # latency). MCM takes hold at pixel 4 for the colour lookup but only at
13
+ # pixel 7 for how the register is read, where a rising MCM also resets
14
+ # the multicolour flip-flop. An MCM that falls out of the invalid
15
+ # ECM+MCM mode is a pixel later on both counts: the lookup changes at
16
+ # pixel 5 and the read at the next group's pixel 0. The mode bits are
17
+ # the sequencer's: ECM 4, BMM 2, MCM 1.
18
+ class GraphicsShifter
19
+ ECM_BMM = 0b110
20
+ ECM_MCM = 0b101
21
+ BMM = 0b010
22
+
23
+ # The group #draw painted, and which of its pixels are foreground.
24
+ attr_reader :colors, :fg
25
+
26
+ def initialize(registers)
27
+ @registers = registers
28
+ @colors = Array.new(8, 0)
29
+ @fg = Array.new(8, false)
30
+ @gbuf = 0
31
+ @vbuf = 0
32
+ @cbuf = 0
33
+ @flop = 0
34
+ @pixel = 0
35
+ @mode_lookup = 0
36
+ @mode_read = 0
37
+ @late_mcm = false
38
+ @late_read = false
39
+ end
40
+
41
+ # Rebuilds the state at the end of a group that painted whole bytes:
42
+ # the byte loaded at pixel shift under an unchanging mode.
43
+ def prime(data, screencode, color, shift, mode)
44
+ @mode_lookup = mode
45
+ @mode_read = mode & 1
46
+ @late_read = false
47
+ load(data, screencode, color)
48
+ (8 - shift).times { read_pixel }
49
+ end
50
+
51
+ # Draws one group, loading the byte at pixel shift.
52
+ def draw(data, screencode, color, shift, mode)
53
+ i = 0
54
+ while i < 8
55
+ step_mode(i, mode)
56
+ load(data, screencode, color) if i == shift
57
+ pixel = read_pixel
58
+ @colors[i] = lookup(pixel)
59
+ @fg[i] = pixel >= 2
60
+ i += 1
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def step_mode(pixel, mode)
67
+ case pixel
68
+ when 0 then end_late_read
69
+ when 4 then step_lookup(mode)
70
+ when 5 then @mode_lookup &= ~1 if @late_mcm
71
+ when 6 then @mode_lookup &= mode | 1
72
+ when 7 then step_read
73
+ end
74
+ end
75
+
76
+ def step_lookup(mode)
77
+ @late_mcm = @mode_lookup.allbits?(ECM_MCM) && mode.nobits?(1)
78
+ @mode_lookup = (@mode_lookup & ~1) | (mode & 1) | (@late_mcm ? 1 : 0) | (mode & ECM_BMM)
79
+ end
80
+
81
+ def step_read
82
+ return @late_read = true if @late_mcm
83
+
84
+ mcm = @mode_lookup & 1
85
+ @flop = 0 if mcm == 1 && @mode_read.zero?
86
+ @mode_read = mcm
87
+ end
88
+
89
+ def end_late_read
90
+ return unless @late_read
91
+
92
+ @mode_read = 0
93
+ @late_read = false
94
+ end
95
+
96
+ def load(data, screencode, color)
97
+ @gbuf = data
98
+ @vbuf = screencode
99
+ @cbuf = color & 0x0f
100
+ @flop = 1
101
+ end
102
+
103
+ def read_pixel
104
+ multicolor = multicolor?
105
+ if @mode_read == 1 && multicolor
106
+ @pixel = @gbuf >> 6 if @flop == 1
107
+ elsif @gbuf.anybits?(0x80)
108
+ @pixel = multicolor && @mode_read.zero? ? 2 : 3
109
+ else
110
+ @pixel = 0
111
+ end
112
+ @gbuf = (@gbuf << 1) & 0xff
113
+ @flop ^= 1
114
+ @pixel
115
+ end
116
+
117
+ def multicolor? = @mode_lookup.anybits?(BMM) || @cbuf.anybits?(0x08)
118
+
119
+ def lookup(pixel)
120
+ case @mode_lookup
121
+ when 0 then pixel < 2 ? @registers.background : @cbuf
122
+ when 1 then pixel == 3 ? @cbuf & 0x07 : @registers.background(pixel)
123
+ when 2 then pixel < 2 ? @vbuf & 0x0f : @vbuf >> 4
124
+ when 3 then multicolor_bitmap(pixel)
125
+ when 4 then pixel < 2 ? @registers.background(@vbuf >> 6) : @cbuf
126
+ else 0
127
+ end
128
+ end
129
+
130
+ def multicolor_bitmap(pixel)
131
+ case pixel
132
+ when 0 then @registers.background
133
+ when 1 then @vbuf >> 4
134
+ when 2 then @vbuf & 0x0f
135
+ else @cbuf
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class VIC < Cycleable
5
+ # Mid-line writes to the sprite registers, keyed by the pixel at which
6
+ # they become visible. Compositing replays the line through the log, so
7
+ # each pixel is drawn with the register values that were in effect when
8
+ # the VIC reached it.
9
+ class RegisterLog
10
+ def initialize(registers)
11
+ @registers = registers
12
+ @entries = []
13
+ @seed = []
14
+ @values = Array.new(2**6, 0)
15
+ @cursor = 0
16
+ @next_x = 0
17
+ end
18
+
19
+ # The pixel at which the next logged write takes effect.
20
+ attr_reader :next_x
21
+
22
+ def empty? = @entries.empty?
23
+ def length = @entries.length
24
+
25
+ def clear
26
+ return if @entries.empty?
27
+
28
+ @entries.clear
29
+ @seed.clear
30
+ end
31
+
32
+ def log(pixel_x, reg, old, value)
33
+ @seed[reg] = old if @seed[reg].nil?
34
+ @entries << [pixel_x, reg, value]
35
+ end
36
+
37
+ def prepare
38
+ @entries.sort_by! { |entry| entry[0] }
39
+ end
40
+
41
+ # Restart the cursor before the line's first pixel, with every logged
42
+ # register back at the value it held when the line started.
43
+ def rewind
44
+ @values.length.times { |reg| @values[reg] = @registers[reg] }
45
+ @seed.each_with_index { |value, reg| @values[reg] = value unless value.nil? }
46
+ @cursor = 0
47
+ @next_x = @entries.empty? ? Float::INFINITY : @entries[0][0]
48
+ end
49
+
50
+ def [](reg) = @values[reg]
51
+
52
+ # Apply every write visible at or before pixel_x.
53
+ def advance(pixel_x)
54
+ while @cursor < @entries.length && @entries[@cursor][0] <= pixel_x
55
+ entry = @entries[@cursor]
56
+ @values[entry[1]] = entry[2]
57
+ @cursor += 1
58
+ end
59
+ @next_x = @cursor < @entries.length ? @entries[@cursor][0] : Float::INFINITY
60
+ end
61
+ end
62
+ end
63
+ end
@@ -74,6 +74,9 @@ module Badline
74
74
  class Registers
75
75
  include IntegerHelper
76
76
 
77
+ # The raw register bytes, for the per-column reads on the hot path.
78
+ attr_reader :bytes
79
+
77
80
  def initialize
78
81
  @bytes = Array.new(2**6, 0)
79
82
  @irq_line = false