badline 0.11.1 → 0.12.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 (175) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/CLAUDE.md +8 -7
  4. data/CONTRIBUTING.md +7 -6
  5. data/README.md +12 -5
  6. data/doc/media.md +28 -1
  7. data/doc/pinned-behaviour.md +31 -1
  8. data/exe/badline-ruby +1 -1
  9. data/lib/badline/address_bus/saved_state.rb +4 -10
  10. data/lib/badline/address_bus.rb +8 -27
  11. data/lib/badline/audio/bare_player.rb +1 -1
  12. data/lib/badline/c128/bus.rb +17 -52
  13. data/lib/badline/c128/bus_state.rb +5 -5
  14. data/lib/badline/c128/drives.rb +35 -22
  15. data/lib/badline/c128/fast_serial.rb +1 -0
  16. data/lib/badline/c128/io_pages.rb +35 -0
  17. data/lib/badline/c128/mmu.rb +9 -7
  18. data/lib/badline/c128/modes.rb +17 -4
  19. data/lib/badline/c128/saved_state.rb +22 -0
  20. data/lib/badline/c128/z80.rb +49 -0
  21. data/lib/badline/c128/z80_pages.rb +118 -0
  22. data/lib/badline/c128/z80_turns.rb +106 -0
  23. data/lib/badline/c128.rb +21 -83
  24. data/lib/badline/cartridge/action_replay.rb +2 -2
  25. data/lib/badline/cartridge/comal80.rb +1 -7
  26. data/lib/badline/cartridge/dinamic.rb +0 -8
  27. data/lib/badline/cartridge/final_cartridge3.rb +6 -6
  28. data/lib/badline/cartridge/fun_play.rb +0 -8
  29. data/lib/badline/cartridge/game_system.rb +0 -8
  30. data/lib/badline/cartridge/kcs_power.rb +10 -7
  31. data/lib/badline/cartridge/mach5.rb +2 -4
  32. data/lib/badline/cartridge/magic_desk.rb +0 -8
  33. data/lib/badline/cartridge/ocean.rb +0 -8
  34. data/lib/badline/cartridge/pagefox.rb +2 -2
  35. data/lib/badline/cartridge/retro_replay.rb +7 -6
  36. data/lib/badline/cartridge/rex_utility.rb +2 -6
  37. data/lib/badline/cartridge/rgcd.rb +2 -2
  38. data/lib/badline/cartridge/simons_basic.rb +0 -5
  39. data/lib/badline/cartridge/super_games.rb +4 -2
  40. data/lib/badline/cartridge/westermann.rb +2 -6
  41. data/lib/badline/cartridge.rb +37 -9
  42. data/lib/badline/cia/fast_forward.rb +21 -3
  43. data/lib/badline/cia.rb +8 -5
  44. data/lib/badline/computer/attachments.rb +14 -95
  45. data/lib/badline/computer/common.rb +34 -0
  46. data/lib/badline/computer/kernal_traps.rb +65 -5
  47. data/lib/badline/computer/true_drives.rb +69 -0
  48. data/lib/badline/computer.rb +9 -68
  49. data/lib/badline/core.rb +4 -2
  50. data/lib/badline/cpu_port.rb +112 -0
  51. data/lib/badline/drive/core.rb +8 -33
  52. data/lib/badline/drive/fast_serial.rb +73 -0
  53. data/lib/badline/drive/idle.rb +1 -11
  54. data/lib/badline/drive/orbit.rb +2 -5
  55. data/lib/badline/drive/sleep.rb +0 -2
  56. data/lib/badline/drive/vias.rb +84 -0
  57. data/lib/badline/drive.rb +2 -0
  58. data/lib/badline/drive1541/disk.rb +14 -17
  59. data/lib/badline/drive1541/disk_state.rb +7 -15
  60. data/lib/badline/drive1541.rb +1 -0
  61. data/lib/badline/drive1571/wd1770.rb +94 -8
  62. data/lib/badline/drive1571.rb +8 -58
  63. data/lib/badline/drive1581/bus.rb +137 -0
  64. data/lib/badline/drive1581/cpu.rb +11 -0
  65. data/lib/badline/drive1581/disk.rb +135 -0
  66. data/lib/badline/drive1581/disk_state.rb +87 -0
  67. data/lib/badline/drive1581/mechanism.rb +117 -0
  68. data/lib/badline/drive1581/serial_port.rb +52 -0
  69. data/lib/badline/drive1581/slot.rb +78 -0
  70. data/lib/badline/drive1581/track.rb +100 -0
  71. data/lib/badline/drive1581/track_bytes.rb +75 -0
  72. data/lib/badline/drive1581/wd1772/registers.rb +89 -0
  73. data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
  74. data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
  75. data/lib/badline/drive1581/wd1772/state.rb +66 -0
  76. data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
  77. data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
  78. data/lib/badline/drive1581/wd1772/turn.rb +73 -0
  79. data/lib/badline/drive1581/wd1772.rb +216 -0
  80. data/lib/badline/drive1581.rb +233 -0
  81. data/lib/badline/frontend/app.rb +1 -3
  82. data/lib/badline/frontend/audio_device.rb +67 -0
  83. data/lib/badline/frontend/audio_sink.rb +16 -50
  84. data/lib/badline/frontend/boot.rb +24 -67
  85. data/lib/badline/frontend/buttons.rb +7 -7
  86. data/lib/badline/frontend/keys.rb +33 -8
  87. data/lib/badline/frontend/media_slots.rb +42 -0
  88. data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
  89. data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
  90. data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
  91. data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
  92. data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +5 -8
  93. data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
  94. data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
  95. data/lib/badline/frontend/menu/theme.rb +18 -0
  96. data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
  97. data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
  98. data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
  99. data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
  100. data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
  101. data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
  102. data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
  103. data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
  104. data/lib/badline/frontend/player/theme.rb +18 -0
  105. data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
  106. data/lib/badline/frontend/sound.rb +19 -38
  107. data/lib/badline/frontend/timeline.rb +3 -14
  108. data/lib/badline/frontend.rb +24 -20
  109. data/lib/badline/iec_bus.rb +14 -2
  110. data/lib/badline/kernal_trap/{file.rb → file_routine.rb} +1 -1
  111. data/lib/badline/kernal_trap/load.rb +1 -1
  112. data/lib/badline/kernal_trap/save.rb +1 -1
  113. data/lib/badline/kernal_trap.rb +1 -1
  114. data/lib/badline/machine/clocking.rb +50 -0
  115. data/lib/badline/machine.rb +63 -6
  116. data/lib/badline/media/boot_disk.rb +50 -0
  117. data/lib/badline/media/disk_list.rb +3 -3
  118. data/lib/badline/media/disk_set.rb +1 -1
  119. data/lib/badline/media/extensions.rb +33 -0
  120. data/lib/badline/media/not_disk.rb +8 -0
  121. data/lib/badline/media/program.rb +28 -0
  122. data/lib/badline/media/true_drive.rb +38 -17
  123. data/lib/badline/media/tune.rb +35 -0
  124. data/lib/badline/media/vic20_cartridge.rb +1 -7
  125. data/lib/badline/media/vic20_media.rb +38 -80
  126. data/lib/badline/media.rb +90 -81
  127. data/lib/badline/options/event.rb +4 -2
  128. data/lib/badline/options/family.rb +3 -9
  129. data/lib/badline/options/help.rb +2 -1
  130. data/lib/badline/roms/README +9 -0
  131. data/lib/badline/roms/dos1581.rom +0 -0
  132. data/lib/badline/sdl.rb +37 -20
  133. data/lib/badline/sid.rb +1 -1
  134. data/lib/badline/snapshot/machine_state.rb +9 -1
  135. data/lib/badline/snapshot/state.rb +6 -4
  136. data/lib/badline/snapshot.rb +11 -9
  137. data/lib/badline/storage/d64_image.rb +15 -7
  138. data/lib/badline/storage/disk_image/writing.rb +2 -2
  139. data/lib/badline/storage/disk_image.rb +1 -1
  140. data/lib/badline/storage/host_directory.rb +1 -1
  141. data/lib/badline/storage/p00.rb +8 -0
  142. data/lib/badline/storage/t64.rb +1 -1
  143. data/lib/badline/time_of_day.rb +3 -3
  144. data/lib/badline/version.rb +1 -1
  145. data/lib/badline/vic/bank.rb +1 -1
  146. data/lib/badline/vic/border_mask.rb +1 -1
  147. data/lib/badline/vic/collisions.rb +1 -1
  148. data/lib/badline/vic/color_patches.rb +1 -1
  149. data/lib/badline/vic/display_state.rb +1 -1
  150. data/lib/badline/vic/graphics_mode.rb +1 -1
  151. data/lib/badline/vic/graphics_shifter.rb +1 -1
  152. data/lib/badline/vic/register_log.rb +1 -1
  153. data/lib/badline/vic/registers.rb +1 -1
  154. data/lib/badline/vic/sequencer.rb +1 -1
  155. data/lib/badline/vic/sequencer_output.rb +1 -1
  156. data/lib/badline/vic/sequencer_state.rb +1 -1
  157. data/lib/badline/vic/sprite/internal_bus.rb +1 -1
  158. data/lib/badline/vic/sprite/saved_state.rb +1 -1
  159. data/lib/badline/vic/sprite/shifter.rb +1 -1
  160. data/lib/badline/vic/sprite/timing.rb +1 -1
  161. data/lib/badline/vic/sprite.rb +1 -1
  162. data/lib/badline/vic/sprites.rb +1 -1
  163. data/lib/badline/vic/sprites_state.rb +1 -1
  164. data/lib/badline/vic.rb +6 -5
  165. data/lib/badline/vic20/attachments.rb +15 -71
  166. data/lib/badline/vic20/saved_state.rb +2 -30
  167. data/lib/badline/vic20.rb +4 -37
  168. data/lib/badline/z80/core.rb +2 -0
  169. data/lib/badline/z80/saved_state.rb +69 -0
  170. metadata +61 -23
  171. data/lib/badline/cycleable.rb +0 -38
  172. /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
  173. /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
  174. /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
  175. /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
data/lib/badline/c128.rb CHANGED
@@ -4,11 +4,15 @@ require "badline/c128/model"
4
4
  require "badline/c128/mmu"
5
5
  require "badline/c128/vdc"
6
6
  require "badline/c128/mmu_pages"
7
+ require "badline/c128/io_pages"
8
+ require "badline/c128/z80_pages"
7
9
  require "badline/c128/color_lines"
8
10
  require "badline/c128/banks"
9
11
  require "badline/c128/bus_state"
10
12
  require "badline/c128/bus"
11
13
  require "badline/c128/cpu"
14
+ require "badline/c128/z80"
15
+ require "badline/c128/z80_turns"
12
16
  require "badline/c128/saved_state"
13
17
  require "badline/c128/keys"
14
18
  require "badline/c128/modes"
@@ -21,11 +25,12 @@ module Badline
21
25
  # the VDC and its 80 column display, and 128K of RAM, which the 8722 MMU
22
26
  # maps (C128::Bus).
23
27
  #
24
- # Built for C128 mode, it runs BASIC 7.0 and the C128 KERNAL from reset.
25
- # The C128's Z80 runs first and hands the bus to the 8502, but here the
26
- # 8502 starts at its reset vector, with the MMU as the Z80 leaves it.
27
- # The KERNAL goes to C64 mode as on the C128, with C= held at reset, a
28
- # C64 cartridge or GO64.
28
+ # Built for C128 mode, it powers on as the C128 does: the Z80 runs the
29
+ # boot code in its BIOS and hands the bus to the 8502, which then runs
30
+ # its reset vector into BASIC 7.0 and the C128 KERNAL. The KERNAL goes
31
+ # to C64 mode as on the C128, with C= held at reset, a C64 cartridge or
32
+ # GO64. MCR bit 0 hands the bus back and forth from then on, and the
33
+ # Z80 runs 2 T-states in each cycle the VIC leaves it the bus.
29
34
  #
30
35
  # Built for C64 mode, the default, it powers on in C64 mode, the state
31
36
  # the C128 KERNAL reaches when C= is held at power-on, and a reset comes
@@ -39,12 +44,15 @@ module Badline
39
44
  class C128
40
45
  include IntegerHelper
41
46
  include KeyboardBuffer
47
+ include Machine::Clocking
48
+ include Computer::Common
42
49
  include Computer::Attachments
43
50
  include Computer::KernalTraps
44
51
  include Keys
45
52
  include Modes
46
53
  include Drives
47
54
  include FastSerial
55
+ include Z80Turns
48
56
 
49
57
  # The C64's 8x8 matrix and the three rows K0-K2 select, in port B
50
58
  # column order.
@@ -65,10 +73,6 @@ module Badline
65
73
 
66
74
  attr_reader :cpu, :cycles, :drive1541, :model, :init_threshold
67
75
 
68
- # The path of the disk or directory device 8 serves through the traps
69
- # (Computer::Attachments), or an empty one.
70
- def mounted_path = @drive.nil? ? "" : @drive.path
71
-
72
76
  def family = :c128
73
77
 
74
78
  def address_bus = @bus
@@ -116,6 +120,7 @@ module Badline
116
120
  @holding_commodore = false
117
121
  @bus = Bus.new(@model, sid_model: sid_model || @model.sid_model, mode:)
118
122
  @cpu = CPU.new(@bus, debug:)
123
+ build_z80
119
124
  @vic = @bus.vic
120
125
  @vic.open_bus = -> { @bus.ram.peek(@cpu.program_counter) }
121
126
  @cia1 = @bus.cia1
@@ -141,6 +146,8 @@ module Badline
141
146
  # mode the CPU runs in both halves of the cycle (#clock_fast), and the
142
147
  # TEST bit steps the raster counter in every cycle.
143
148
  def cycle!
149
+ return z80_cycle! if @z80_turn
150
+
144
151
  handle_init if @cycles == @init_threshold
145
152
  feed_keyboard if @pending_keys
146
153
 
@@ -164,36 +171,6 @@ module Badline
164
171
  @cycles += 1
165
172
  end
166
173
 
167
- # Runs `count` cycles, one #cycle! after another.
168
- def run_cycles(count)
169
- i = 0
170
- while i < count
171
- cycle!
172
- i += 1
173
- end
174
- end
175
-
176
- # Runs cycles until the block returns true or the cycle count passes
177
- # `limit`, checking before each cycle.
178
- def run_until(limit)
179
- cycle! until yield || @cycles > limit
180
- end
181
-
182
- def on_init(&block)
183
- if @cycles < init_threshold
184
- @init_handlers << block
185
- else
186
- block.call
187
- end
188
- end
189
-
190
- # The clock, the raster and the crop of the VIC-IIe's region.
191
- def timing
192
- region = @bus.region
193
- Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
194
- lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1)
195
- end
196
-
197
174
  # The chip whose #display a front end shows: the VIC-IIe, or with
198
175
  # :vdc the VDC, whose display paints only while its #render is on.
199
176
  def video(chip = :vic) = chip == :vdc ? @vdc : @vic
@@ -208,12 +185,6 @@ module Badline
208
185
  # The chip a front end records the machine's sound from.
209
186
  def sound_source = @sid
210
187
 
211
- def load_prg(data)
212
- uint16(data[0], data[1]).tap do |load_addr|
213
- ram.write(load_addr, data[2..])
214
- end
215
- end
216
-
217
188
  # A cartridge goes in with the power off.
218
189
  def attach_cartridge(cartridge)
219
190
  connect_cartridge(cartridge)
@@ -241,23 +212,16 @@ module Badline
241
212
  @drive&.reset!
242
213
  @drive1541&.reset!
243
214
  @drive1571&.reset!
215
+ @drive1581&.reset!
244
216
  @nmi_asserted = false
245
- @cpu.reset!
246
- end
247
-
248
- # CHROUT is at $FFD2 in both KERNALs, and the trap follows the mode.
249
- def capture_output
250
- @capture_output ||= ChroutTrap.new(cpu:, bus: @bus, layout: trap_layout).tap do |trap|
251
- cpu.install_trap(ChroutTrap::ADDRESS) { trap.call }
252
- end
253
- end
254
-
255
- def inspect
256
- "#<#{self.class.name} cycles=#{@cycles} cpu=(#{@cpu.inspect})>"
217
+ reset_z80
257
218
  end
258
219
 
259
220
  private
260
221
 
222
+ # The bus the KERNAL traps read and write through.
223
+ def trap_bus = @bus
224
+
261
225
  # The C128 KERNAL leaves $D02F at $FF, every extra keyboard row
262
226
  # deselected, before it jumps to the C64's reset.
263
227
  def enter_c64_mode
@@ -265,15 +229,6 @@ module Badline
265
229
  @bus.control_ports.extra_rows = 0xff
266
230
  end
267
231
 
268
- # The NMI line is wired-OR between CIA 2, the cartridge and RESTORE,
269
- # and the CPU takes an interrupt on its falling edge.
270
- def drive_nmi
271
- nmi = @cia2.interrupted? || @cartridge_nmi || @restore_pulse
272
- @restore_pulse = false
273
- @cpu.nmi = true if nmi && !@nmi_asserted
274
- @nmi_asserted = nmi
275
- end
276
-
277
232
  # BA halts the CPU on a read cycle.
278
233
  def clock_cpu
279
234
  @cpu.pending_write? || !@vic.ba_low? ? @cpu.cycle! : @cpu.stall!
@@ -301,22 +256,5 @@ module Badline
301
256
  @cpu.cycle!
302
257
  @vic.take_cpu_bus(phi1, @bus.data, @bus.vic_access?)
303
258
  end
304
-
305
- def handle_init
306
- @init_handlers.each(&:call)
307
- end
308
-
309
- # The keyboard buffer of the KERNAL the machine runs: the C64's, or in
310
- # C128 mode BASIC 7.0's.
311
- def feed_keyboard
312
- c64 = mode == :c64
313
- count = c64 ? KeyboardBuffer::COUNT : C128_KEYBOARD_COUNT
314
- return unless ram.peek(count).zero?
315
-
316
- chunk = @pending_keys.shift(KeyboardBuffer::CAPACITY)
317
- ram.write(c64 ? KeyboardBuffer::ADDRESS : C128_KEYBOARD_BUFFER, chunk)
318
- ram.poke(count, chunk.length)
319
- @pending_keys = nil if @pending_keys.empty?
320
- end
321
259
  end
322
260
  end
@@ -65,12 +65,12 @@ module Badline
65
65
  private
66
66
 
67
67
  def save_mapper(out)
68
- save_windows(out, windows)
68
+ super
69
69
  out.int(window_index(@rom, windows)).boolean(@active).boolean(@io2_ram).blob(@ram_data)
70
70
  end
71
71
 
72
72
  def load_mapper(input)
73
- load_windows(input, windows)
73
+ super
74
74
  @rom = window_at(input.int, windows)
75
75
  @active = input.boolean?
76
76
  @io2_ram = input.boolean?
@@ -33,13 +33,7 @@ module Badline
33
33
 
34
34
  private
35
35
 
36
- def save_mapper(out)
37
- save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
38
- end
39
-
40
- def load_mapper(input)
41
- load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
42
- end
36
+ def windows = [*@roml_banks, *@romh_banks, EMPTY_BANK]
43
37
 
44
38
  def select(number, mode)
45
39
  @roml = bank(@roml_banks, number)
@@ -24,14 +24,6 @@ module Badline
24
24
 
25
25
  private
26
26
 
27
- def save_mapper(out)
28
- save_windows(out, [*@banks, EMPTY_BANK])
29
- end
30
-
31
- def load_mapper(input)
32
- load_windows(input, [*@banks, EMPTY_BANK])
33
- end
34
-
35
27
  def install_chips(chips)
36
28
  @banks = banks_from(chips).first
37
29
  self.mode = :rom8k
@@ -49,31 +49,31 @@ module Badline
49
49
  @phi1_ultimax = false
50
50
  self.nmi = false
51
51
  select(0)
52
- self.mode = :rom16k
53
- changed!
52
+ select_mode(:rom16k)
54
53
  end
55
54
 
56
55
  def freeze!
57
56
  @register_visible = true
58
57
  @phi1_ultimax = false
59
- self.mode = :ultimax
60
- changed!
58
+ select_mode(:ultimax)
61
59
  end
62
60
 
63
61
  private
64
62
 
65
63
  def save_mapper(out)
66
- save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
64
+ super
67
65
  out.boolean(@register_visible).boolean(@phi1_ultimax)
68
66
  end
69
67
 
70
68
  def load_mapper(input)
71
- load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
69
+ super
72
70
  @io_bank = @roml
73
71
  @register_visible = input.boolean?
74
72
  @phi1_ultimax = input.boolean?
75
73
  end
76
74
 
75
+ def windows = [*@roml_banks, *@romh_banks, EMPTY_BANK]
76
+
77
77
  def select(number)
78
78
  @io_bank = @roml = bank(@roml_banks, number)
79
79
  @romh = bank(@romh_banks, number)
@@ -28,14 +28,6 @@ module Badline
28
28
 
29
29
  private
30
30
 
31
- def save_mapper(out)
32
- save_windows(out, [*@banks, EMPTY_BANK])
33
- end
34
-
35
- def load_mapper(input)
36
- load_windows(input, [*@banks, EMPTY_BANK])
37
- end
38
-
39
31
  def install_chips(chips)
40
32
  @banks = []
41
33
  chips.each { |chip| @banks[self.class.bank_number(chip.bank)] = rom_bank(chip.data) }
@@ -25,14 +25,6 @@ module Badline
25
25
 
26
26
  private
27
27
 
28
- def save_mapper(out)
29
- save_windows(out, [*@banks, EMPTY_BANK])
30
- end
31
-
32
- def load_mapper(input)
33
- load_windows(input, [*@banks, EMPTY_BANK])
34
- end
35
-
36
28
  def select(addr)
37
29
  @roml = bank(@banks, addr & 0x3f)
38
30
  changed!
@@ -26,7 +26,7 @@ module Badline
26
26
 
27
27
  def peek(addr)
28
28
  if addr < 0xdf00
29
- switch(addr.anybits?(0x02) ? :off : :rom8k)
29
+ select_mode(addr.anybits?(0x02) ? :off : :rom8k)
30
30
  @roml.peek(IO1_ROM_PAGE | (addr & 0xff))
31
31
  elsif addr.anybits?(0x80)
32
32
  (@exrom << 7) | (@game << 6) | (open_bus(addr) & 0x3f)
@@ -37,7 +37,7 @@ module Badline
37
37
 
38
38
  def poke(addr, value)
39
39
  if addr < 0xdf00
40
- switch(addr.anybits?(0x02) ? :ultimax : :rom16k)
40
+ select_mode(addr.anybits?(0x02) ? :ultimax : :rom16k)
41
41
  elsif addr.nobits?(0x80)
42
42
  @io_ram[addr & 0x7f] = value
43
43
  end
@@ -45,19 +45,22 @@ module Badline
45
45
 
46
46
  def reset
47
47
  self.nmi = false
48
- switch(:rom16k)
48
+ select_mode(:rom16k)
49
49
  end
50
50
 
51
51
  def freeze!
52
52
  self.nmi = false
53
- switch(:ultimax)
53
+ select_mode(:ultimax)
54
54
  end
55
55
 
56
56
  private
57
57
 
58
- def switch(mode)
59
- self.mode = mode
60
- changed!
58
+ def save_mapper(out)
59
+ out.blob(@io_ram)
60
+ end
61
+
62
+ def load_mapper(input)
63
+ input.blob_into(@io_ram)
61
64
  end
62
65
 
63
66
  def install_chips(chips)
@@ -14,13 +14,11 @@ module Badline
14
14
  end
15
15
 
16
16
  def poke(addr, _value)
17
- self.mode = addr < 0xdf00 ? :rom8k : :off
18
- changed!
17
+ select_mode(addr < 0xdf00 ? :rom8k : :off)
19
18
  end
20
19
 
21
20
  def reset
22
- self.mode = :rom8k
23
- changed!
21
+ select_mode(:rom8k)
24
22
  end
25
23
 
26
24
  private
@@ -20,14 +20,6 @@ module Badline
20
20
 
21
21
  private
22
22
 
23
- def save_mapper(out)
24
- save_windows(out, [*@banks, EMPTY_BANK])
25
- end
26
-
27
- def load_mapper(input)
28
- load_windows(input, [*@banks, EMPTY_BANK])
29
- end
30
-
31
23
  def install_chips(chips)
32
24
  @banks = banks_from(chips).first
33
25
  @bank_mask = (bank_mask(@banks) | 0x03) & 0x7f
@@ -19,14 +19,6 @@ module Badline
19
19
 
20
20
  private
21
21
 
22
- def save_mapper(out)
23
- save_windows(out, [*@banks, EMPTY_BANK])
24
- end
25
-
26
- def load_mapper(input)
27
- load_windows(input, [*@banks, EMPTY_BANK])
28
- end
29
-
30
22
  # In 16K mode the selected bank shows through ROMH as well.
31
23
  def select_bank(number)
32
24
  @roml = bank(@banks, number)
@@ -30,12 +30,12 @@ module Badline
30
30
  private
31
31
 
32
32
  def save_mapper(out)
33
- save_windows(out, windows)
33
+ super
34
34
  @ram_banks.each { |ram| out.blob(ram.data) }
35
35
  end
36
36
 
37
37
  def load_mapper(input)
38
- load_windows(input, windows)
38
+ super
39
39
  @ram_banks.each { |ram| input.blob_into(ram.data) }
40
40
  end
41
41
 
@@ -90,6 +90,11 @@ module Badline
90
90
 
91
91
  attr_reader :flash
92
92
 
93
+ def self.from_jumpers(crt, input)
94
+ input.boolean?
95
+ new(crt, flash_jumper: input.boolean?, bank_jumper: input.boolean?)
96
+ end
97
+
93
98
  def initialize(crt, flash_jumper: false, bank_jumper: false)
94
99
  @nordic = crt.subtype == 1
95
100
  @flash_jumper = flash_jumper
@@ -131,9 +136,7 @@ module Badline
131
136
  end
132
137
  end
133
138
 
134
- def phi1_ultimax?
135
- @phi1_ultimax
136
- end
139
+ def phi1_ultimax? = @phi1_ultimax
137
140
 
138
141
  def ultimax_a000
139
142
  @isolated[ram_view_bank] if @nordic && @ram_at_a000 && @frozen
@@ -157,9 +160,7 @@ module Badline
157
160
 
158
161
  private
159
162
 
160
- def freeze_allowed?
161
- !@no_freeze && !@flash_jumper
162
- end
163
+ def freeze_allowed? = !@no_freeze && !@flash_jumper
163
164
 
164
165
  def status
165
166
  ((@bank & 0x03) << 3) | ((@bank & 0x04) << 5) | ((@bank & 0x08) << 2) | (@flash_jumper ? 0x01 : 0) |
@@ -10,16 +10,12 @@ module Badline
10
10
  end
11
11
 
12
12
  def peek(addr)
13
- if addr >= 0xdf00
14
- self.mode = (addr & 0xff) < 0xc0 ? :off : :rom8k
15
- changed!
16
- end
13
+ select_mode((addr & 0xff) < 0xc0 ? :off : :rom8k) if addr >= 0xdf00
17
14
  open_bus(addr)
18
15
  end
19
16
 
20
17
  def reset
21
- self.mode = :rom8k
22
- changed!
18
+ select_mode(:rom8k)
23
19
  end
24
20
 
25
21
  private
@@ -29,12 +29,12 @@ module Badline
29
29
  private
30
30
 
31
31
  def save_mapper(out)
32
- save_windows(out, [*@banks, EMPTY_BANK])
32
+ super
33
33
  out.boolean(@disabled)
34
34
  end
35
35
 
36
36
  def load_mapper(input)
37
- load_windows(input, [*@banks, EMPTY_BANK])
37
+ super
38
38
  @disabled = input.boolean?
39
39
  end
40
40
 
@@ -24,11 +24,6 @@ module Badline
24
24
 
25
25
  private
26
26
 
27
- def select_mode(mode)
28
- self.mode = mode
29
- changed!
30
- end
31
-
32
27
  def install_chips(chips)
33
28
  roml, romh = banks_from(chips)
34
29
  @roml = bank(roml, 0)
@@ -21,15 +21,17 @@ module Badline
21
21
  private
22
22
 
23
23
  def save_mapper(out)
24
- save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
24
+ super
25
25
  out.boolean(@locked)
26
26
  end
27
27
 
28
28
  def load_mapper(input)
29
- load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
29
+ super
30
30
  @locked = input.boolean?
31
31
  end
32
32
 
33
+ def windows = [*@roml_banks, *@romh_banks, EMPTY_BANK]
34
+
33
35
  def select(value)
34
36
  @roml = bank(@roml_banks, value & 0x03)
35
37
  @romh = bank(@romh_banks, value & 0x03)
@@ -9,16 +9,12 @@ module Badline
9
9
  end
10
10
 
11
11
  def peek(addr)
12
- if addr >= 0xdf00
13
- self.mode = :rom8k
14
- changed!
15
- end
12
+ select_mode(:rom8k) if addr >= 0xdf00
16
13
  open_bus(addr)
17
14
  end
18
15
 
19
16
  def reset
20
- self.mode = :rom16k
21
- changed!
17
+ select_mode(:rom16k)
22
18
  end
23
19
 
24
20
  private
@@ -63,11 +63,7 @@ module Badline
63
63
  end
64
64
 
65
65
  def from_crt(crt, **)
66
- mapper = HARDWARE_TYPES.fetch(crt.hardware_type) do
67
- raise UnsupportedTypeError,
68
- "Unsupported cartridge hardware type #{crt.hardware_type}"
69
- end
70
- mapper.new(crt, **)
66
+ mapper_for(crt).new(crt, **)
71
67
  end
72
68
 
73
69
  # A cartridge built afresh from what save_setup wrote, before
@@ -76,9 +72,21 @@ module Badline
76
72
  return GeoRAM.new(size: input.int) if input.int == GEO_RAM_SETUP
77
73
 
78
74
  crt = Storage::CRTFile.new(bytes: input.string)
79
- return RetroReplay.new(crt, flash_jumper: input.boolean?, bank_jumper: input.boolean?) if input.boolean?
75
+ mapper_for(crt).from_jumpers(crt, input)
76
+ end
80
77
 
81
- from_crt(crt)
78
+ # A cartridge from its image and what save_jumpers wrote: whether
79
+ # there are jumpers, then their settings on a mapper that has them.
80
+ def from_jumpers(crt, input)
81
+ input.boolean?
82
+ new(crt)
83
+ end
84
+
85
+ def mapper_for(crt)
86
+ HARDWARE_TYPES.fetch(crt.hardware_type) do
87
+ raise UnsupportedTypeError,
88
+ "Unsupported cartridge hardware type #{crt.hardware_type}"
89
+ end
82
90
  end
83
91
  end
84
92
 
@@ -95,6 +103,7 @@ module Badline
95
103
  @exrom = crt.exrom
96
104
  @game = crt.game
97
105
  @roml = @romh = nil
106
+ @banks = nil
98
107
  @on_change = nil
99
108
  @on_nmi_change = nil
100
109
  @nmi = false
@@ -193,9 +202,22 @@ module Badline
193
202
  out.boolean(false)
194
203
  end
195
204
 
196
- def save_mapper(_out); end
205
+ # The mapper's own state: where its windows point, for a mapper that
206
+ # moves them, and whatever a mapper adds after calling super.
207
+ def save_mapper(out)
208
+ save_windows(out, windows) if windows
209
+ end
197
210
 
198
- def load_mapper(_input); end
211
+ def load_mapper(input)
212
+ load_windows(input, windows) if windows
213
+ end
214
+
215
+ # The banks and views ROML and ROMH can point at, which a snapshot
216
+ # names them by: the mapper's banks, or nothing for a mapper whose
217
+ # windows stay where install_chips put them.
218
+ def windows
219
+ [*@banks, EMPTY_BANK] if @banks
220
+ end
199
221
 
200
222
  # Where the ROML and ROMH windows point, as their places in `windows`,
201
223
  # the banks and views the mapper picks them from, or -1 for none.
@@ -232,6 +254,12 @@ module Badline
232
254
  @exrom, @game = MODES.fetch(mode)
233
255
  end
234
256
 
257
+ # Sets the lines and tells the machine to map the cartridge again.
258
+ def select_mode(mode)
259
+ self.mode = mode
260
+ changed!
261
+ end
262
+
235
263
  def open_bus(addr)
236
264
  @open_bus ? @open_bus.peek(addr) : 0xff
237
265
  end
@@ -4,6 +4,9 @@ module Badline
4
4
  class CIA
5
5
  # Running a CIA a stretch of cycles at once, for Drive::Idle.
6
6
  module FastForward
7
+ # quiet_cycles for a CIA whose counters will never set a flag.
8
+ QUIET = 1 << 40
9
+
7
10
  # Whether fast_forward can run the CIA: timer B stopped with nothing
8
11
  # in its pipeline, no interrupt or acknowledge on its way, CNT where it
9
12
  # was, the TOD clock stopped, and timer A either stopped, or free
@@ -16,11 +19,24 @@ module Badline
16
19
  @ta.idle? ? @serial.idle : timer_a_free_running?
17
20
  end
18
21
 
19
- # Runs +cycles+ cycles at once while quiet?, as cycle! would: timer A's
20
- # underflows flag in the ICR, and reach the serial port's underflow
21
- # line.
22
+ # How many cycles the CIA can run with nothing but its counters
23
+ # moving: as quiet? allows, but with timer B free running too, up to
24
+ # the cycle before it underflows. The 1581's DOS leaves timer B
25
+ # interrupting every 10 ms.
26
+ def quiet_cycles
27
+ return 0 unless @icr.quiet && @tod.quiet? && cnt_steady? && timer_a_quiet?
28
+ return QUIET if @tb.idle?
29
+ return 0 unless @tb.free_running? && @control_b.value.nobits?(0x60)
30
+
31
+ @tb.counter - 1
32
+ end
33
+
34
+ # Runs +cycles+ cycles at once while quiet? or for quiet_cycles, as
35
+ # cycle! would: timer A's underflows flag in the ICR, and reach the
36
+ # serial port's underflow line.
22
37
  def fast_forward(cycles)
23
38
  @tod.fast_forward(cycles)
39
+ @tb.fast_forward(cycles) unless @tb.idle?
24
40
  return if @ta.idle?
25
41
 
26
42
  @icr.flag(:timer_a) if @ta.fast_forward(cycles).positive?
@@ -29,6 +45,8 @@ module Badline
29
45
 
30
46
  private
31
47
 
48
+ def timer_a_quiet? = @ta.idle? ? @serial.idle : timer_a_free_running?
49
+
32
50
  # Timer A free running with its interrupt masked, and the serial port
33
51
  # an idle input it only clocks.
34
52
  def timer_a_free_running?