badline 0.10.0 → 0.11.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/CLAUDE.md +5 -3
  4. data/README.md +60 -5
  5. data/doc/library.md +15 -1
  6. data/doc/pinned-behaviour.md +75 -0
  7. data/doc/snapshots.md +19 -3
  8. data/lib/badline/address_bus/fittings.rb +38 -0
  9. data/lib/badline/address_bus/pla.rb +38 -21
  10. data/lib/badline/address_bus/roms.rb +12 -5
  11. data/lib/badline/address_bus.rb +20 -2
  12. data/lib/badline/c128/banks.rb +35 -0
  13. data/lib/badline/c128/bus.rb +347 -0
  14. data/lib/badline/c128/bus_state.rb +53 -0
  15. data/lib/badline/c128/color_lines.rb +39 -0
  16. data/lib/badline/c128/cpu.rb +10 -0
  17. data/lib/badline/c128/keys.rb +35 -0
  18. data/lib/badline/c128/mmu.rb +182 -0
  19. data/lib/badline/c128/mmu_pages.rb +105 -0
  20. data/lib/badline/c128/model.rb +39 -0
  21. data/lib/badline/c128/modes.rb +48 -0
  22. data/lib/badline/c128/saved_state.rb +120 -0
  23. data/lib/badline/c128/vdc/memory.rb +54 -0
  24. data/lib/badline/c128/vdc/painter.rb +143 -0
  25. data/lib/badline/c128/vdc/raster.rb +97 -0
  26. data/lib/badline/c128/vdc/window.rb +215 -0
  27. data/lib/badline/c128/vdc.rb +268 -0
  28. data/lib/badline/c128.rb +320 -0
  29. data/lib/badline/chrout_trap.rb +3 -0
  30. data/lib/badline/computer/attachments.rb +4 -13
  31. data/lib/badline/computer/kernal_traps.rb +29 -0
  32. data/lib/badline/computer.rb +13 -8
  33. data/lib/badline/frontend/app.rb +34 -21
  34. data/lib/badline/frontend/boot.rb +31 -1
  35. data/lib/badline/frontend/controls.rb +11 -0
  36. data/lib/badline/frontend/keys.rb +28 -1
  37. data/lib/badline/frontend/machine_window.rb +16 -13
  38. data/lib/badline/frontend/menu_pages.rb +6 -1
  39. data/lib/badline/frontend/menu_ports.rb +1 -1
  40. data/lib/badline/frontend/pause_menu.rb +1 -0
  41. data/lib/badline/frontend/screen.rb +33 -8
  42. data/lib/badline/frontend/screens.rb +90 -0
  43. data/lib/badline/frontend/timeline.rb +15 -4
  44. data/lib/badline/frontend.rb +1 -0
  45. data/lib/badline/kernal_trap/file.rb +1 -1
  46. data/lib/badline/kernal_trap/layout.rb +58 -8
  47. data/lib/badline/kernal_trap/load.rb +1 -1
  48. data/lib/badline/kernal_trap/save.rb +1 -1
  49. data/lib/badline/kernal_trap/serial.rb +5 -0
  50. data/lib/badline/keyboard.rb +12 -1
  51. data/lib/badline/machine.rb +16 -7
  52. data/lib/badline/media.rb +8 -6
  53. data/lib/badline/model.rb +29 -12
  54. data/lib/badline/options/event.rb +5 -1
  55. data/lib/badline/options/family.rb +52 -0
  56. data/lib/badline/options/help.rb +5 -1
  57. data/lib/badline/options/table.rb +8 -2
  58. data/lib/badline/options/validation.rb +14 -1
  59. data/lib/badline/options.rb +20 -19
  60. data/lib/badline/roms/README +51 -0
  61. data/lib/badline/roms/c128/basic.rom +0 -0
  62. data/lib/badline/roms/c128/character.rom +0 -0
  63. data/lib/badline/roms/c128/kernal.rom +0 -0
  64. data/lib/badline/roms/c64gs.rom +0 -0
  65. data/lib/badline/roms/kernal-pet64.rom +0 -0
  66. data/lib/badline/snapshot/container.rb +2 -0
  67. data/lib/badline/snapshot/image.rb +14 -0
  68. data/lib/badline/snapshot/setup.rb +15 -10
  69. data/lib/badline/snapshot/state.rb +5 -4
  70. data/lib/badline/snapshot/vice.rb +2 -1
  71. data/lib/badline/snapshot.rb +9 -2
  72. data/lib/badline/version.rb +1 -1
  73. data/lib/badline/vic/bank.rb +2 -1
  74. data/lib/badline/vic/display_state.rb +12 -0
  75. data/lib/badline/vic/registers.rb +3 -0
  76. data/lib/badline/vic.rb +124 -9
  77. data/lib/badline.rb +1 -0
  78. metadata +27 -1
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ class VDC
6
+ # Paints the characters of a displayed line, R1 of them from the
7
+ # line's first dot, in text mode or, with R25 bit 7, bitmap mode.
8
+ #
9
+ # Within a character the pixels come from R22: bits 4-7 hold its width
10
+ # less one (the width itself in double-width mode), and bits 0-3 the
11
+ # last pixel it displays (one more in double-width mode); a value past
12
+ # the width displays all of it. R25 bits 0-3 scroll the line left by
13
+ # the width less one less their value: the first character's pattern
14
+ # loads that many pixels before the display starts, and the displayed
15
+ # pixels run from the load to the R22 bits 0-3 count, as the
16
+ # Programmer's Reference Guide's table for R25 has it. Pixels past a
17
+ # pattern's eighth or the displayed count are the background, or with
18
+ # R25 bit 5, the semigraphic mode, the last displayed pixel repeated.
19
+ #
20
+ # Rows of characters are R9 + 1 lines, of which lines 0 to R23 display
21
+ # the pattern and the rest the background. R24 bits 0-4 scroll the
22
+ # frame up by that many lines. Row r reads its characters from
23
+ # R12/R13 + r * (R1 + R27) and their attributes from R20/R21 likewise,
24
+ # with both start addresses latched as each frame begins.
25
+ #
26
+ # In text mode a character's pattern is 16 bytes in its set, or 32
27
+ # with R9 above 15, from R28 bits 5-7 (bits 6-7 with 32). With
28
+ # attributes, R25 bit 6, bits 0-3 of the attribute are the colour, bit
29
+ # 4 blinks the character into the background, bit 5 underlines it on
30
+ # R29's line, bit 6 reverses it and bit 7 takes it from the second
31
+ # set. Without them the colour is R26 bits 4-7. The cursor, at R14/R15,
32
+ # reverses lines R10 bits 0-4 up to R11 less one, wrapping past the end
33
+ # of the character, and R10 bits 5-6 hold it solid, off, or blinking
34
+ # at 1/16 or 1/32 of the frame rate. Characters blink at 1/16 of the
35
+ # frame rate, or 1/32 with R24 bit 5. R24 bit 6 reverses everything.
36
+ #
37
+ # In bitmap mode scan line n of the frame reads its bytes from
38
+ # R12/R13 + n * (R1 + R27), and the attribute of each character cell
39
+ # holds its foreground in bits 0-3 and background in bits 4-7, or
40
+ # without attributes R26 holds them the other way round. The cursor
41
+ # isn't shown.
42
+ class Window
43
+ def initialize(registers, memory)
44
+ @registers = registers
45
+ @memory = memory
46
+ @display_start = 0
47
+ @attribute_start = 0
48
+ @layout = Array.new(16, 0)
49
+ @pattern = 0
50
+ @foreground = 0
51
+ @background = 0
52
+ end
53
+
54
+ def latch_starts
55
+ regs = @registers
56
+ @display_start = (regs[12] << 8) | regs[13]
57
+ @attribute_start = (regs[20] << 8) | regs[21]
58
+ end
59
+
60
+ def save_state(out)
61
+ out.int(@display_start).int(@attribute_start)
62
+ end
63
+
64
+ def load_state(input)
65
+ @display_start = input.int
66
+ @attribute_start = input.int
67
+ end
68
+
69
+ def paint(line, row, row_line, frame)
70
+ regs = @registers
71
+ lay_out_cell
72
+ height = (regs[9] & 0x1f) + 1
73
+ content = (row * height) + row_line + (regs[24] & 0x1f)
74
+ char_line = content % height
75
+ @gap_line = char_line > (regs[23] & 0x1f)
76
+ @stride = regs[1] + regs[27]
77
+ @attributes_on = regs[25].anybits?(0x40)
78
+ @screen_reverse = regs[24].anybits?(0x40)
79
+ if regs[25].anybits?(0x80)
80
+ paint_bitmap(line, content, content / height)
81
+ else
82
+ paint_text(line, content / height, char_line, frame)
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ # The pattern bit each pixel of a character shows, 0 for the first,
89
+ # or -1 for the background.
90
+ def lay_out_cell
91
+ regs = @registers
92
+ double = regs[25].anybits?(0x10)
93
+ @dot = double ? 2 : 1
94
+ @cell = double ? [regs[22] >> 4, 1].max : (regs[22] >> 4) + 1
95
+ @layout = Array.new(@cell, -1) if @layout.length < @cell
96
+ scroll = regs[25] & 0x0f
97
+ load = (scroll + 1) % @cell
98
+ @shift = (@cell - 1 - scroll) % @cell
99
+ last_shown = (regs[22] & 0x0f) - (double ? 1 : 0)
100
+ shown = last_shown >= @cell ? @cell : ((last_shown - load) % @cell) + 1
101
+ shown = [shown, 8].min
102
+ semigraphic = regs[25].anybits?(0x20)
103
+ @cell.times { |pixel| @layout[pixel] = cell_bit(pixel, shown, semigraphic) }
104
+ end
105
+
106
+ def cell_bit(pixel, shown, semigraphic)
107
+ return pixel if pixel < shown
108
+
109
+ semigraphic ? shown - 1 : -1
110
+ end
111
+
112
+ def paint_text(line, char_row, char_line, frame)
113
+ regs = @registers
114
+ @screen = @display_start + (char_row * @stride)
115
+ @attributes = @attribute_start + (char_row * @stride)
116
+ tall = (regs[9] & 0x1f) > 15
117
+ @set = tall ? (regs[28] & 0xc0) << 8 : (regs[28] & 0xe0) << 8
118
+ @set_size = tall ? 0x2000 : 0x1000
119
+ @char_size = tall ? 32 : 16
120
+ @pattern_line = tall ? char_line : char_line & 0x0f
121
+ @underline = char_line == (regs[29] & 0x1f)
122
+ @blink_on = frame % (regs[24].anybits?(0x20) ? 32 : 16) < (regs[24].anybits?(0x20) ? 16 : 8)
123
+ @cursor = cursor_shown?(char_line, frame) ? (regs[14] << 8) | regs[15] : -1
124
+ each_cell(line) { |n| text_cell(n) }
125
+ end
126
+
127
+ def paint_bitmap(line, content, char_row)
128
+ @bitmap = @display_start + (content * @stride)
129
+ @attributes = @attribute_start + (char_row * @stride)
130
+ each_cell(line) { |n| bitmap_cell(n) }
131
+ end
132
+
133
+ def text_cell(index)
134
+ address = (@screen + index) & 0xffff
135
+ attribute = @attributes_on ? @memory.fetch((@attributes + index) & 0xffff) : 0
136
+ @pattern = text_pattern(@memory.fetch(address), attribute)
137
+ reverse = attribute.anybits?(0x40) ^ (address == @cursor) ^ @screen_reverse
138
+ foreground = @attributes_on ? attribute & 0x0f : @registers[26] >> 4
139
+ colours(foreground, @registers[26] & 0x0f, reverse)
140
+ end
141
+
142
+ def text_pattern(code, attribute)
143
+ return 0 if attribute.anybits?(0x10) && !@blink_on
144
+ return 0xff if attribute.anybits?(0x20) && @underline
145
+ return 0 if @gap_line
146
+
147
+ set = @set + (attribute.anybits?(0x80) ? @set_size : 0)
148
+ @memory.fetch((set + (code * @char_size) + @pattern_line) & 0xffff)
149
+ end
150
+
151
+ def bitmap_cell(index)
152
+ @pattern = @gap_line ? 0 : @memory.fetch((@bitmap + index) & 0xffff)
153
+ regs = @registers
154
+ if @attributes_on
155
+ attribute = @memory.fetch((@attributes + index) & 0xffff)
156
+ colours(attribute & 0x0f, attribute >> 4, @screen_reverse)
157
+ else
158
+ colours(regs[26] >> 4, regs[26] & 0x0f, @screen_reverse)
159
+ end
160
+ end
161
+
162
+ def colours(foreground, background, reverse)
163
+ @foreground = reverse ? background : foreground
164
+ @background = reverse ? foreground : background
165
+ end
166
+
167
+ # Paints the cells the display shows, one more when the line is
168
+ # scrolled, each after the block sets its pattern and colours.
169
+ def each_cell(line)
170
+ count = @registers[1] + (@shift.positive? ? 1 : 0)
171
+ right = @registers[1] * @cell
172
+ index = 0
173
+ while index < count
174
+ yield index
175
+ paint_cell(line, (index * @cell) - @shift, right)
176
+ index += 1
177
+ end
178
+ end
179
+
180
+ def paint_cell(line, left, right)
181
+ gap = @registers[26] & 0x0f
182
+ pixel = 0
183
+ while pixel < @cell
184
+ x_pos = left + pixel
185
+ line.fill(pixel_colour(@layout[pixel], gap), x_pos * @dot, @dot) if x_pos >= 0 && x_pos < right
186
+ pixel += 1
187
+ end
188
+ end
189
+
190
+ def pixel_colour(bit, gap)
191
+ return gap if bit.negative?
192
+
193
+ @pattern.anybits?(0x80 >> bit) ? @foreground : @background
194
+ end
195
+
196
+ def cursor_shown?(char_line, frame)
197
+ mode = (@registers[10] >> 5) & 3
198
+ return false if mode == 1
199
+ return false if mode == 2 && (frame % 16) >= 8
200
+ return false if mode == 3 && (frame % 32) >= 16
201
+
202
+ cursor_line?(char_line)
203
+ end
204
+
205
+ def cursor_line?(char_line)
206
+ first = @registers[10] & 0x1f
207
+ stop = @registers[11] & 0x1f
208
+ return char_line >= first && char_line < stop if first < stop
209
+
210
+ first > stop && (char_line >= first || char_line < stop)
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/c128/vdc/memory"
4
+ require "badline/c128/vdc/window"
5
+ require "badline/c128/vdc/painter"
6
+ require "badline/c128/vdc/raster"
7
+
8
+ module Badline
9
+ class C128
10
+ # The 8563 or 8568 VDC: the address register at $D600 and the data
11
+ # register at $D601, mirrored through $D6FF, in front of 37 internal
12
+ # registers (38 on the 8568), the VDC's own RAM (Memory) and its 80
13
+ # column RGBI display (Raster, Painter).
14
+ #
15
+ # A write to $D600 selects a register, and a read there gives the
16
+ # status: bit 7 ready for the next access, bit 5 in the vertical blank
17
+ # (outside the displayed rows), and bits 0-2 the version. $D601 reads
18
+ # and writes the selected register. A register past the last reads $FF.
19
+ # The light pen is left out.
20
+ #
21
+ # R18 and R19 hold the update address, high byte first, and a write to
22
+ # either reads the RAM there into R31. A read of R31 gives that byte,
23
+ # moves the address on and reads the next. A write of R31 stores the
24
+ # byte, moves the address on and reads the next. A write of R30 then
25
+ # repeats the last byte written over R30 more bytes (256 for 0), or with
26
+ # R24 bit 7 copies that many from R32/R33 on, and leaves the update
27
+ # address, and R32/R33 for a copy, after the last byte.
28
+ #
29
+ # The VDC runs on its own 16 MHz dot clock, a character clock every
30
+ # character width of dots (R22, doubled by R25 bit 4). The status reads
31
+ # not ready for ACCESS_CLOCKS character clocks after an access through
32
+ # R31 or a write of R18 or R19, and for one clock per byte a block write
33
+ # fills or two per byte it copies.
34
+ class VDC
35
+ include Addressable
36
+
37
+ STATUS_READY = 0x80
38
+ STATUS_VBLANK = 0x20
39
+ UPDATE_HIGH = 18
40
+ UPDATE_LOW = 19
41
+ WORD_COUNT = 30
42
+ DATA = 31
43
+ SOURCE_HIGH = 32
44
+ SOURCE_LOW = 33
45
+ DOT_CLOCK_HZ = 16_000_000
46
+
47
+ # Character clocks the status reads busy after an access through R31
48
+ # or a write of R18 or R19, fitted to the timings a flat C128 gave
49
+ # VDC/vdctiming (TimingResultsFlatC128.jpg).
50
+ ACCESS_CLOCKS = 30
51
+
52
+ # The version in the status bits: 1 for the 8563 R8 and R9, and 2 for
53
+ # the 8568.
54
+ VERSIONS = { mos8563: 1, mos8568: 2 }.freeze
55
+
56
+ # The 16 RGBI colours, red, green, blue and intensity from bit 3 down,
57
+ # as the Programmer's Reference Guide names them: 1100 is dark yellow.
58
+ PALETTE = Array.new(16) do |rgbi|
59
+ intensity = rgbi.anybits?(1) ? 0x55 : 0
60
+ red = (rgbi.anybits?(8) ? 0xaa : 0) + intensity
61
+ green = (rgbi.anybits?(4) ? 0xaa : 0) + intensity
62
+ blue = (rgbi.anybits?(2) ? 0xaa : 0) + intensity
63
+ (red << 16) | (green << 8) | blue
64
+ end.freeze
65
+
66
+ attr_reader :model, :registers
67
+
68
+ # Whether lines paint into the display as the raster passes them.
69
+ attr_reader :render
70
+
71
+ # +ram_kb+ is 16 or 64, and +clock_hz+ the CPU's clock the VDC is
72
+ # clocked against.
73
+ def initialize(model: :mos8563, ram_kb: 16, clock_hz: Region::PAL.clock_hz)
74
+ addressable_at(0xd600, length: 0x100)
75
+ @model = model
76
+ @version = VERSIONS.fetch(model)
77
+ @clock_hz = clock_hz
78
+ @registers = Array.new(model == :mos8568 ? 38 : 37, 0)
79
+ @memory = Memory.new(ram_kb)
80
+ @painter = Painter.new(@registers, @memory)
81
+ @raster = Raster.new(@registers, @painter)
82
+ @render = false
83
+ power_on!
84
+ end
85
+
86
+ # The dots a character takes, R22 bits 4-7 plus one, or R22 bits 4-7
87
+ # doubled in double-width mode.
88
+ def self.char_dots(registers)
89
+ width = registers[22] >> 4
90
+ registers[25].anybits?(0x10) ? [width, 1].max * 2 : width + 1
91
+ end
92
+
93
+ # The registers clear, the RAM clears and the raster starts a frame.
94
+ def power_on!
95
+ @registers.fill(0)
96
+ @memory.clear!
97
+ @memory.mode64 = false
98
+ @selected = 0
99
+ @read_latch = 0
100
+ @write_data = 0
101
+ @busy_until = 0
102
+ @phase = 0
103
+ @raster.reset!
104
+ @line_units = @raster.line_dots * @clock_hz
105
+ @painter.clear!
106
+ end
107
+
108
+ def ram = @memory.ram
109
+
110
+ def render=(on)
111
+ @render = on
112
+ @painter.clear!
113
+ end
114
+
115
+ # Everything the VDC holds but its display, which it paints again
116
+ # while it renders: the registers, the RAM, the data port and the
117
+ # raster. Whether it renders is the host's.
118
+ def save_state(out)
119
+ out.marker("VDC")
120
+ out.ints(@registers).blob(@memory.ram)
121
+ out.int(@selected).int(@read_latch).int(@write_data).int(@busy_until).int(@phase).int(@line_units)
122
+ @raster.save_state(out)
123
+ @painter.save_state(out)
124
+ end
125
+
126
+ def load_state(input)
127
+ input.marker("VDC")
128
+ input.ints_into(@registers)
129
+ input.blob_into(@memory.ram)
130
+ @memory.mode64 = @registers[28].anybits?(0x10)
131
+ @selected = input.int
132
+ @read_latch = input.int
133
+ @write_data = input.int
134
+ @busy_until = input.int
135
+ @phase = input.int
136
+ @line_units = input.int
137
+ @raster.load_state(input)
138
+ @painter.load_state(input)
139
+ end
140
+
141
+ # The frames the raster has finished, which the blinking counts.
142
+ def frame = @raster.frame
143
+
144
+ def display = @painter.display
145
+
146
+ def width = @painter.width
147
+
148
+ def height = @painter.height
149
+
150
+ def dirty_lines = @painter.dirty_lines
151
+
152
+ def clear_dirty_lines! = @painter.clear_dirty_lines!
153
+
154
+ def palette = PALETTE
155
+
156
+ # The [left, top, width, height] of the display a monitor shows.
157
+ def crop = @painter.crop(@raster.line_dots, @raster.frame_lines, @raster.sync_lines)
158
+
159
+ # The update address R18 and R19 hold.
160
+ def update_address = (@registers[UPDATE_HIGH] << 8) | @registers[UPDATE_LOW]
161
+
162
+ # Whether an access or a block operation is still under way.
163
+ def busy? = @phase < @busy_until
164
+
165
+ # One CPU cycle: the dot clock runs on, and a line ends once it has
166
+ # run a line's dots. The line's phase counts dots times the CPU's
167
+ # clock, DOT_CLOCK_HZ a cycle.
168
+ def cycle!
169
+ @phase += DOT_CLOCK_HZ
170
+ end_line if @phase >= @line_units
171
+ end
172
+
173
+ def peek(addr)
174
+ return status if offset_of(addr).even?
175
+ return read_ram if @selected == DATA
176
+
177
+ @selected < @registers.length ? @registers[@selected] : 0xff
178
+ end
179
+
180
+ def poke(addr, value)
181
+ return @selected = value & 0x3f if offset_of(addr).even?
182
+ return if @selected >= @registers.length
183
+
184
+ write_register(@selected, value)
185
+ end
186
+
187
+ private
188
+
189
+ def status
190
+ value = @version
191
+ value |= STATUS_READY unless busy?
192
+ value |= STATUS_VBLANK if @raster.vertical_blank?
193
+ value
194
+ end
195
+
196
+ def end_line
197
+ @phase -= @line_units
198
+ @busy_until -= @line_units
199
+ @raster.end_line(@render)
200
+ @line_units = @raster.line_dots * @clock_hz
201
+ end
202
+
203
+ def write_register(number, value)
204
+ @registers[number] = value
205
+ case number
206
+ when DATA then write_ram(value)
207
+ when WORD_COUNT then block(value.zero? ? 256 : value)
208
+ when UPDATE_HIGH, UPDATE_LOW then read_ahead
209
+ when 28 then @memory.mode64 = value.anybits?(0x10)
210
+ end
211
+ end
212
+
213
+ def read_ram
214
+ value = @read_latch
215
+ advance(1)
216
+ read_ahead
217
+ value
218
+ end
219
+
220
+ def write_ram(value)
221
+ @memory.store(update_address, value)
222
+ @write_data = value
223
+ advance(1)
224
+ read_ahead
225
+ end
226
+
227
+ def read_ahead
228
+ @read_latch = @memory.fetch(update_address)
229
+ busy(ACCESS_CLOCKS)
230
+ end
231
+
232
+ def block(count)
233
+ copy = @registers[24].anybits?(0x80)
234
+ copy ? copy_block(count) : fill_block(count)
235
+ @read_latch = @memory.fetch(update_address)
236
+ busy(copy ? count * 2 : count)
237
+ end
238
+
239
+ def fill_block(count)
240
+ address = update_address
241
+ count.times { |i| @memory.store(address + i, @write_data) }
242
+ advance(count)
243
+ end
244
+
245
+ def copy_block(count)
246
+ destination = update_address
247
+ source = (@registers[SOURCE_HIGH] << 8) | @registers[SOURCE_LOW]
248
+ count.times { |i| @memory.store(destination + i, @memory.fetch(source + i)) }
249
+ advance(count)
250
+ source = (source + count) & 0xffff
251
+ @registers[SOURCE_HIGH] = source >> 8
252
+ @registers[SOURCE_LOW] = source & 0xff
253
+ end
254
+
255
+ def advance(count)
256
+ address = (update_address + count) & 0xffff
257
+ @registers[UPDATE_HIGH] = address >> 8
258
+ @registers[UPDATE_LOW] = address & 0xff
259
+ end
260
+
261
+ # Holds the status busy for `clocks` character clocks after whatever
262
+ # is already under way.
263
+ def busy(clocks)
264
+ @busy_until = [@busy_until, @phase].max + (clocks * VDC.char_dots(@registers) * @clock_hz)
265
+ end
266
+ end
267
+ end
268
+ end