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/sdl.rb CHANGED
@@ -59,23 +59,6 @@ module Badline
59
59
  ffi_func :SDL_PushEvent, [:ptr], :int
60
60
  ffi_func :SDL_GetKeyName, [:int], :str
61
61
 
62
- ffi_buffer :event, 56
63
- ffi_read_u32 :event_type, 0
64
- ffi_read_u8 :event_repeat, 13
65
- ffi_read_i32 :event_scancode, 16
66
- ffi_read_i32 :event_sym, 20
67
- ffi_read_u16 :event_mod, 24
68
- # SDL_MouseMotionEvent's xrel and yrel, and SDL_MouseButtonEvent's
69
- # button.
70
- ffi_read_i32 :event_xrel, 28
71
- ffi_read_i32 :event_yrel, 32
72
- ffi_read_u8 :event_button, 16
73
- # Where the pointer is, in a motion or button event.
74
- ffi_read_i32 :event_x, 20
75
- ffi_read_i32 :event_y, 24
76
- # SDL_DropEvent's file, which the receiver frees.
77
- ffi_read_ptr :event_file, 8
78
-
79
62
  ffi_buffer :rect, 16
80
63
  ffi_write_i32 :rect_x, 0
81
64
  ffi_write_i32 :rect_y, 4
@@ -115,19 +98,53 @@ module Badline
115
98
  ffi_const :PIXELFORMAT_ARGB8888, 0x16362004
116
99
  ffi_const :BLENDMODE_BLEND, 0x01
117
100
  ffi_const :TEXTUREACCESS_STREAMING, 1
101
+ ffi_const :AUDIO_S16LSB, 0x8010
102
+ ffi_const :ALLOW_FREQUENCY_CHANGE, 0x01
103
+ end
104
+
105
+ # The events: the SDL_Event that SDL_PollEvent fills, the fields the
106
+ # front end reads from it, the event types, the key modifier and
107
+ # keycodes, and the dropped file's path.
108
+ module SDL
109
+ ffi_buffer :event, 56
110
+ ffi_read_u32 :event_type, 0
111
+ ffi_read_u8 :event_repeat, 13
112
+ ffi_read_i32 :event_scancode, 16
113
+ ffi_read_i32 :event_sym, 20
114
+ ffi_read_u16 :event_mod, 24
115
+ # SDL_MouseMotionEvent's xrel and yrel, and SDL_MouseButtonEvent's
116
+ # button.
117
+ ffi_read_i32 :event_xrel, 28
118
+ ffi_read_i32 :event_yrel, 32
119
+ ffi_read_u8 :event_button, 16
120
+ # Where the pointer is, in a motion or button event.
121
+ ffi_read_i32 :event_x, 20
122
+ ffi_read_i32 :event_y, 24
123
+ # SDL_DropEvent's file, which the receiver frees.
124
+ ffi_read_ptr :event_file, 8
125
+
118
126
  ffi_const :QUIT, 0x100
119
127
  ffi_const :KEYDOWN, 0x300
120
128
  ffi_const :KEYUP, 0x301
121
129
  ffi_const :MOUSEMOTION, 0x400
122
130
  ffi_const :MOUSEBUTTONDOWN, 0x401
123
131
  ffi_const :MOUSEBUTTONUP, 0x402
124
- ffi_const :KMOD_SHIFT, 0x0003
132
+ ffi_const :MOUSEWHEEL, 0x403
125
133
  ffi_const :CONTROLLERDEVICEADDED, 0x653
126
134
  ffi_const :CONTROLLERDEVICEREMOVED, 0x654
127
- ffi_const :AUDIO_S16LSB, 0x8010
128
- ffi_const :ALLOW_FREQUENCY_CHANGE, 0x01
135
+ ffi_const :DROPFILE, 0x1000
136
+ ffi_const :KMOD_SHIFT, 0x0003
129
137
  ffi_const :KEY_TAB, 0x09
130
138
  ffi_const :KEY_F10, 0x4000_0043
139
+
140
+ # The path of the file the DROPFILE event in SDL.event carries, as a
141
+ # String, freeing SDL's copy.
142
+ def self.dropped_file
143
+ file = SDL.event_file(SDL.event)
144
+ path = LibC.strstr(file, "")
145
+ LibC.free(file)
146
+ path
147
+ end
131
148
  end
132
149
 
133
150
  # The few C library calls the native front end makes.
data/lib/badline/sid.rb CHANGED
@@ -119,7 +119,7 @@ module Badline
119
119
  # second, for #drain_samples. `filter_chunk` overrides the one the SID
120
120
  # was built with, for a machine that built its own. `clock_hz` is the
121
121
  # rate the SID's cycles are taken to run at.
122
- def record(rate:, filter_chunk: @filter_chunk, clock_hz: TimeOfDay::CLOCK_HZ)
122
+ def record(rate:, filter_chunk: @filter_chunk, clock_hz: Region::PAL.clock_hz)
123
123
  synthesize!
124
124
  catch_up
125
125
  @filter_chunk = filter_chunk
@@ -19,6 +19,10 @@ module Badline
19
19
  MAJOR = 2
20
20
  MINOR = 0
21
21
 
22
+ # The values a 64-bit integer holds once encode doubles them, which a
23
+ # native build's integers can't exceed.
24
+ VALUES = (-(1 << 62))..((1 << 62) - 1)
25
+
22
26
  module_function
23
27
 
24
28
  def section(state)
@@ -41,7 +45,11 @@ module Badline
41
45
  strings = state.strings
42
46
  numbers = [values.length, strings.length]
43
47
  strings.each { |string| numbers << string.bytesize }
44
- values.each { |value| numbers << (value.negative? ? (-2 * value) - 1 : 2 * value) }
48
+ values.each do |value|
49
+ raise RangeError, "#{value} is too large for a snapshot" unless VALUES.cover?(value)
50
+
51
+ numbers << (value.negative? ? (-2 * value) - 1 : 2 * value)
52
+ end
45
53
  index = numbers.pack("w*")
46
54
  [index.bytesize].pack("V") + index + strings.join
47
55
  end
@@ -15,10 +15,12 @@ module Badline
15
15
  # save_state writes something else, so a State in another layout
16
16
  # fails before anything is read into a machine. Layout 3, without the
17
17
  # KERNAL and datasette in the setup, layout 4, without the VIA shift
18
- # register's clock delay, and layout 5, without the board in the
19
- # setup, still read.
20
- SCHEMA = 6
21
- READABLE = [3, 4, 5, SCHEMA].freeze
18
+ # register's clock delay, layout 5, without the board in the setup,
19
+ # layout 6, without the C128's Z80, layout 7, without a 1581 on the
20
+ # serial bus, and layout 8, with the VIC's unused cycle count and
21
+ # pending-write flag, still read.
22
+ SCHEMA = 9
23
+ READABLE = [3, 4, 5, 6, 7, 8, SCHEMA].freeze
22
24
 
23
25
  attr_reader :values, :strings
24
26
 
@@ -47,23 +47,25 @@ module Badline
47
47
  # applied and left out, yielding a line for each thing left out. A
48
48
  # snapshot that fails leaves the machine as it was.
49
49
  def restore(computer, path, &) = read(path).restore(computer, &)
50
+
51
+ # A machine's snapshots as .vsf files. Computer, Vic20 and C128 each
52
+ # include it.
53
+ module Files
54
+ def save_snapshot(path) = Snapshot.save(self, path)
55
+
56
+ def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
57
+ end
50
58
  end
51
59
 
52
60
  class Computer
53
- def save_snapshot(path) = Snapshot.save(self, path)
54
-
55
- def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
61
+ include Snapshot::Files
56
62
  end
57
63
 
58
64
  class Vic20
59
- def save_snapshot(path) = Snapshot.save(self, path)
60
-
61
- def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
65
+ include Snapshot::Files
62
66
  end
63
67
 
64
68
  class C128
65
- def save_snapshot(path) = Snapshot.save(self, path)
66
-
67
- def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
69
+ include Snapshot::Files
68
70
  end
69
71
  end
@@ -20,15 +20,23 @@ module Badline
20
20
  # The sides of a disk the image's tracks fill.
21
21
  def sides = 1
22
22
 
23
- def sectors_in(track)
24
- case track
25
- when 1..17 then 21
26
- when 18..24 then 19
27
- when 25..30 then 18
28
- else 17
29
- end
23
+ # The last track of each of the 1541's speed zones but the innermost,
24
+ # from the outermost, zone 3, in: tracks 1-17, 18-24 and 25-30, and
25
+ # zone 0 from 31 on.
26
+ ZONE_LAST_TRACKS = [17, 24, 30].freeze
27
+
28
+ # The sectors a track holds in each zone, by its number.
29
+ ZONE_SECTORS = [17, 18, 19, 21].freeze
30
+
31
+ # The speed zone a track is written in, 3 for the outermost.
32
+ def self.zone(track)
33
+ zone = 3
34
+ zone -= 1 while zone.positive? && track > ZONE_LAST_TRACKS[3 - zone]
35
+ zone
30
36
  end
31
37
 
38
+ def sectors_in(track) = ZONE_SECTORS[D64Image.zone(track)]
39
+
32
40
  private
33
41
 
34
42
  def error_tables = ERROR_TABLES
@@ -11,7 +11,7 @@ module Badline
11
11
  module Writing
12
12
  # Whether the disk takes writes: the image wasn't opened read-only
13
13
  # and the host file takes them.
14
- def writable? = !read_only? && ::File.writable?(@path)
14
+ def writable? = !read_only? && File.writable?(@path)
15
15
 
16
16
  # Writes a new file, or with `replace` writes over the one of the
17
17
  # same name in its directory entry. A name already on the disk
@@ -87,7 +87,7 @@ module Badline
87
87
  saved = [@bytes.dup, @errors&.dup]
88
88
  begin
89
89
  yield
90
- ::File.binwrite(@path, (@bytes + @errors.to_a).pack("C*"))
90
+ File.binwrite(@path, (@bytes + @errors.to_a).pack("C*"))
91
91
  rescue WriteError, SystemCallError => e
92
92
  @bytes, @errors = saved
93
93
  raise e.is_a?(WriteError) ? e : WriteError.new(WriteError::WRITE_PROTECT_ON)
@@ -46,7 +46,7 @@ module Badline
46
46
  # path, whether it is write-protected, and its bytes with the error
47
47
  # table after them.
48
48
  def save_setup(out)
49
- out.int(storage_kind).string(::File.expand_path(@path)).boolean(@read_only).blob(@bytes + @errors.to_a)
49
+ out.int(storage_kind).string(File.expand_path(@path)).boolean(@read_only).blob(@bytes + @errors.to_a)
50
50
  end
51
51
 
52
52
  # A LOAD reads only PRG files. An OPEN names the type it wants, or
@@ -19,7 +19,7 @@ module Badline
19
19
 
20
20
  # What Storage.reopen needs to open the directory again.
21
21
  def save_setup(out)
22
- out.int(HOST_DIRECTORY).string(::File.expand_path(@path))
22
+ out.int(HOST_DIRECTORY).string(File.expand_path(@path))
23
23
  end
24
24
 
25
25
  def read_file(name, **)
@@ -18,6 +18,14 @@ module Badline
18
18
  def data(bytes)
19
19
  bytes[HEADER_SIZE..]
20
20
  end
21
+
22
+ # The load address of the program in a .prg file, or wrapped in a
23
+ # .p00 one, or -1 when the file is too short to have one.
24
+ def load_address(path)
25
+ bytes = File.binread(path).bytes
26
+ offset = wraps?(bytes) ? HEADER_SIZE : 0
27
+ bytes.size >= offset + 2 ? bytes[offset] | (bytes[offset + 1] << 8) : -1
28
+ end
21
29
  end
22
30
  end
23
31
  end
@@ -26,7 +26,7 @@ module Badline
26
26
  # What Storage.reopen needs to open the archive again: its path and
27
27
  # its bytes.
28
28
  def save_setup(out)
29
- out.int(T64_ARCHIVE).string(::File.expand_path(@path)).blob(@bytes)
29
+ out.int(T64_ARCHIVE).string(File.expand_path(@path)).blob(@bytes)
30
30
  end
31
31
 
32
32
  def read_file(name, **)
@@ -52,9 +52,9 @@ module Badline
52
52
  @latch = input.boolean? ? FIELDS.to_h { |field| [field, input.int] } : nil
53
53
  end
54
54
 
55
- # Whether the clock is stopped with no alarm to raise, so its cycles
56
- # move only the divider's phase.
57
- def quiet? = @stopped && !@alarm_pending
55
+ # Whether the clock is stopped, or gets no pulses on its pin, with no
56
+ # alarm to raise, so its cycles move only the divider's phase.
57
+ def quiet? = (@stopped || @mains_hz.zero?) && !@alarm_pending
58
58
 
59
59
  # Runs +cycles+ quiet cycles at once.
60
60
  def fast_forward(cycles)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- VERSION = "0.11.1"
4
+ VERSION = "0.12.1"
5
5
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  # The 16K window the VIC sees into RAM, selected by CIA2 $DD00, in the
6
6
  # bank a +256K picks for it. Character
7
7
  # ROM shadows $1000-$1FFF in banks 0 and 2.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  # Border coverage per 8-pixel group, tracked as the sequencer outputs
6
6
  # a line.
7
7
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  # = Sprite collisions
6
6
  #
7
7
  # The $D01E/$D01F latches and the per-pixel sprite coverage that feeds
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  # A mid-line color register write becomes visible one pixel into the
6
6
  # column emitted the cycle after the write. That emit paints the whole
7
7
  # column with the new color, so the boundary pixel is patched back to
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  # = VIC-II display state
6
6
  #
7
7
  # The bad line condition, the video counters it drives, and the DMA it
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  module GraphicsMode
6
6
  # Foreground masks depend only on the data byte, so each byte maps to a
7
7
  # precomputed frozen pattern shared by reference instead of being
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  # The graphics shift register, pixel by pixel, for the groups where a
6
6
  # mode or XSCROLL change lands (VICE x64sc `draw_graphics`). The
7
7
  # sequencer paints whole bytes everywhere else, which comes to the same
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  # Mid-line writes to the sprite registers, keyed by the pixel at which
6
6
  # they become visible. Compositing replays the line through the log, so
7
7
  # each pixel is drawn with the register values that were in effect when
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  # = VIC-II Registers
6
6
  #
7
7
  # == Sprite positions
@@ -8,7 +8,7 @@ require "badline/vic/sequencer_output"
8
8
  require "badline/vic/sequencer_state"
9
9
 
10
10
  module Badline
11
- class VIC < Cycleable
11
+ class VIC
12
12
  # = VIC-II Sequencer
13
13
  #
14
14
  # Turns fetched graphics data into output pixels.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  class Sequencer
6
6
  # Writes a column's 8-pixel group into the line buffers, through the
7
7
  # main border flip-flop and XSCROLL.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  class Sequencer
6
6
  # The sequencer's save_state and load_state.
7
7
  module SavedState
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  class Sprite
6
6
  # = InternalBus
7
7
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  class Sprite
6
6
  # The sprite's save_state and load_state.
7
7
  module SavedState
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  class Sprite
6
6
  # The X comparator and the 24-bit shift register behind it: where a
7
7
  # line fires the sprite, and the pixels each firing shifts out.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  class Sprite
6
6
  # = Timing
7
7
  #
@@ -6,7 +6,7 @@ require "badline/vic/sprite/saved_state"
6
6
  require "badline/vic/sprite/timing"
7
7
 
8
8
  module Badline
9
- class VIC < Cycleable
9
+ class VIC
10
10
  # = Sprite
11
11
  #
12
12
  # The DMA/display state machine and the pixel sequencer for one sprite.
@@ -6,7 +6,7 @@ require "badline/vic/sprite"
6
6
  require "badline/vic/sprites_state"
7
7
 
8
8
  module Badline
9
- class VIC < Cycleable
9
+ class VIC
10
10
  class Sprites
11
11
  include SavedState
12
12
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class VIC < Cycleable
4
+ class VIC
5
5
  class Sprites
6
6
  # The sprites' save_state and load_state.
7
7
  module SavedState
data/lib/badline/vic.rb CHANGED
@@ -7,7 +7,7 @@ require "badline/vic/sequencer"
7
7
  require "badline/vic/sprites"
8
8
 
9
9
  module Badline
10
- class VIC < Cycleable
10
+ class VIC
11
11
  include Addressable
12
12
  include IntegerHelper
13
13
 
@@ -145,7 +145,6 @@ module Badline
145
145
  @render = true
146
146
 
147
147
  power_on!
148
- super()
149
148
  end
150
149
 
151
150
  # The VIC has no reset pin, so only a power cycle brings back the
@@ -192,7 +191,7 @@ module Badline
192
191
  # a front end repaints.
193
192
  def save_state(out)
194
193
  out.marker("VIC")
195
- out.int(@column).int(@rasterline).int(@cycles).boolean(@pending_write)
194
+ out.int(@column).int(@rasterline)
196
195
  out.int(@g_tick).ints(@g_kind).int(@g_kept_char).int(@g_kept_color).boolean(@g_display)
197
196
  out.int(@fetch_d011).boolean(@lp_triggered).boolean(@lp_low).boolean(@raster_match)
198
197
  out.int(@vic_bank.lines)
@@ -469,8 +468,10 @@ module Badline
469
468
  def load_beam(input)
470
469
  @column = input.int
471
470
  @rasterline = @output_line = input.int
472
- @cycles = input.int
473
- @pending_write = input.boolean?
471
+ if input.schema < 9
472
+ input.int
473
+ input.boolean?
474
+ end
474
475
  @g_tick = input.int
475
476
  input.ints_into(@g_kind)
476
477
  @g_kept_char = input.int
@@ -3,9 +3,13 @@
3
3
  module Badline
4
4
  class Vic20
5
5
  # What plugs into the VIC-20 besides its chips: cartridge ROM in the
6
- # expansion blocks, the disk device 8 serves through the KERNAL traps,
7
- # and a true 1541 on the serial bus.
6
+ # expansion blocks, the disk device 8 serves through the KERNAL traps
7
+ # (Computer::KernalTraps), and a true 1541 or 1581 on the serial bus
8
+ # (Computer::TrueDrives).
8
9
  module Attachments
10
+ include Computer::TrueDrives
11
+ include Computer::KernalTraps
12
+
9
13
  # The cycle by which a 1541 switched on with the machine has run its
10
14
  # DOS's reset, the RAM test and the ROM checksum, and waits on the
11
15
  # bus, with a margin: about 1.1 million of the VIC-20's cycles.
@@ -25,84 +29,24 @@ module Badline
25
29
  power_cycle!
26
30
  end
27
31
 
28
- # Puts the storage in device 8. Mounting again swaps the disk at any
29
- # point while the machine runs: the drive keeps its RAM and its
30
- # status.
31
- def mount(storage)
32
- return @drive.insert(storage) if @drive
33
-
34
- @drive = KernalTrap::Drive.new(storage)
35
- layout = KernalTrap::VIC20_LAYOUT
36
- load_trap = KernalTrap::Load.new(cpu:, bus:, layout:, drive: @drive)
37
- cpu.install_trap(layout.load) { load_trap.call }
38
- @serial_trap = KernalTrap::Serial.new(cpu:, bus:, layout:, drive: @drive, device: serial_trap_device).install
39
- save_trap = @save_trap = KernalTrap::Save.new(cpu:, bus:, layout:, drive: @drive)
40
- cpu.install_trap(layout.save) { save_trap.call }
41
- end
42
-
43
- # Takes device 8's mounted storage out, and with it the LOAD, SAVE and
44
- # serial traps, so the KERNAL's routines go out over the serial bus.
45
- # Mounting again starts a new drive, with its RAM cleared.
46
- def unmount
47
- return unless @drive
32
+ private
48
33
 
49
- cpu.remove_trap(KernalTrap::VIC20_LAYOUT.load)
50
- cpu.remove_trap(KernalTrap::VIC20_LAYOUT.save)
51
- @serial_trap.device = nil
52
- @serial_trap = nil
53
- @save_trap = nil
54
- @drive = nil
55
- end
34
+ # The VIC-20's KERNAL, which the traps stand in for.
35
+ def trap_layout = KernalTrap::VIC20_LAYOUT
56
36
 
57
- # Whether device 8 serves a disk or directory through the traps.
58
- def mounted? = !@drive.nil?
37
+ def trap_bus = @bus
59
38
 
60
- # Plugs in a Drive1541, which then runs alongside the VIC-20 on its own
61
- # clock and talks to it over the serial bus. The serial traps stop
62
- # answering the drive's device number, so the KERNAL's TALK, LISTEN
63
- # and byte transfers reach the drive. The LOAD and SAVE traps stay,
64
- # and still serve a mounted image.
65
- #
66
39
  # A drive that powers on with the machine takes longer to boot than
67
40
  # the KERNAL, and doesn't answer the bus until it has, so the
68
41
  # machine's on_init handlers wait for it too.
69
- def attach_drive1541(drive)
42
+ def drive1541_attached
70
43
  @init_threshold = [@init_threshold, DRIVE_BOOT_CYCLES].max if @cycles < @init_threshold
71
- @iec_bus.detach(@drive1541) if @drive1541
72
- drive.host_clock_hz = timing.clock_hz
73
- @drive1541 = drive
74
- drive.connect(@iec_bus)
75
- @serial_trap&.device = serial_trap_device
76
44
  end
77
45
 
78
- # The true drive on the serial bus: the Drive1541, or nil.
79
- def true_drive = @drive1541
80
-
81
- # Plugs a Drive1541 in as device 8, and returns it.
82
- def plug_true_drive = Drive1541.new.tap { |drive| attach_drive1541(drive) }
83
-
84
- # Unplugs the Drive1541, leaving the serial bus with nothing on it.
85
- def detach_drive1541
86
- return unless @drive1541
87
-
88
- @iec_bus.detach(@drive1541)
89
- @drive1541 = nil
90
- @serial_trap&.device = serial_trap_device
91
- end
92
-
93
- # Records what the KERNAL prints through CHROUT (ChroutTrap).
94
- def capture_output
95
- @capture_output ||= ChroutTrap.new(cpu:, bus:, layout: KernalTrap::VIC20_LAYOUT).tap do |trap|
96
- cpu.install_trap(ChroutTrap::ADDRESS) { trap.call }
97
- end
98
- end
99
-
100
- private
101
-
102
- # The device number the serial traps answer: device 8, unless a true
103
- # drive is on the bus as device 8.
104
- def serial_trap_device
105
- @drive1541&.device == KernalTrap::Routine::DEVICE ? nil : KernalTrap::Routine::DEVICE
46
+ # A 1581 plugged in, as a 1541 does, holds the machine's on_init
47
+ # handlers back until its DOS has booted.
48
+ def drive1581_attached
49
+ @init_threshold = [@init_threshold, Drive1581::BOOT_CYCLES].max if @cycles < @init_threshold
106
50
  end
107
51
  end
108
52
  end