badline 0.13.0 → 0.14.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 (254) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -1
  3. data/CLAUDE.md +27 -7
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +8 -5
  6. data/doc/media.md +2 -1
  7. data/doc/pinned-behaviour.md +29 -0
  8. data/doc/snapshot-format.md +1047 -0
  9. data/doc/snapshots.md +72 -39
  10. data/lib/badline/audio/bare_player.rb +50 -17
  11. data/lib/badline/audio/machine_player.rb +15 -9
  12. data/lib/badline/audio/renderer.rb +32 -21
  13. data/lib/badline/c128/banks.rb +6 -3
  14. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  15. data/lib/badline/c128/bus.rb +17 -3
  16. data/lib/badline/c128/drives.rb +6 -25
  17. data/lib/badline/c128/keyed_modules.rb +117 -0
  18. data/lib/badline/c128/keyed_state.rb +91 -0
  19. data/lib/badline/c128/keys.rb +9 -0
  20. data/lib/badline/c128/mmu.rb +9 -8
  21. data/lib/badline/c128/mmu_pages.rb +20 -0
  22. data/lib/badline/c128/modes.rb +19 -9
  23. data/lib/badline/c128/z80_turns.rb +1 -1
  24. data/lib/badline/c128.rb +8 -8
  25. data/lib/badline/c64/attachments.rb +0 -44
  26. data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
  27. data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
  28. data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
  29. data/lib/badline/c64/bus/debug_register.rb +32 -0
  30. data/lib/badline/c64/bus/keyed_state.rb +41 -0
  31. data/lib/badline/c64/bus.rb +2 -1
  32. data/lib/badline/c64/cartridge/action_replay.rb +0 -13
  33. data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
  34. data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
  35. data/lib/badline/c64/cartridge/final_cartridge3.rb +0 -12
  36. data/lib/badline/c64/cartridge/flash.rb +3 -3
  37. data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
  38. data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
  39. data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
  40. data/lib/badline/c64/cartridge/keyed_state.rb +393 -0
  41. data/lib/badline/c64/cartridge/pagefox.rb +0 -10
  42. data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
  43. data/lib/badline/c64/cartridge/rgcd.rb +0 -10
  44. data/lib/badline/c64/cartridge/super_games.rb +0 -10
  45. data/lib/badline/c64/cartridge/zaxxon.rb +0 -16
  46. data/lib/badline/c64/cartridge.rb +1 -27
  47. data/lib/badline/c64/kernal_traps.rb +28 -25
  48. data/lib/badline/c64/keyed_modules.rb +111 -0
  49. data/lib/badline/c64/keyed_state.rb +68 -0
  50. data/lib/badline/c64/reu/dma.rb +0 -4
  51. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  52. data/lib/badline/c64/reu/ram.rb +0 -15
  53. data/lib/badline/c64/reu.rb +1 -3
  54. data/lib/badline/c64/true_drives.rb +56 -5
  55. data/lib/badline/c64.rb +7 -3
  56. data/lib/badline/chips/cia/interrupt_register.rb +0 -17
  57. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  58. data/lib/badline/chips/cia/serial.rb +0 -4
  59. data/lib/badline/chips/cia/time_of_day.rb +0 -22
  60. data/lib/badline/chips/cia/timer.rb +0 -20
  61. data/lib/badline/chips/cia.rb +2 -2
  62. data/lib/badline/chips/cpu/core.rb +18 -2
  63. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  64. data/lib/badline/chips/cpu/microcode.rb +16 -0
  65. data/lib/badline/chips/cpu/port.rb +20 -3
  66. data/lib/badline/chips/cpu.rb +5 -0
  67. data/lib/badline/chips/sid/decimator.rb +10 -8
  68. data/lib/badline/chips/sid/envelope.rb +1 -6
  69. data/lib/badline/chips/sid/filter.rb +0 -14
  70. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  71. data/lib/badline/chips/sid/voice.rb +0 -10
  72. data/lib/badline/chips/sid/waveform.rb +0 -2
  73. data/lib/badline/chips/sid.rb +1 -2
  74. data/lib/badline/chips/ted/beam.rb +119 -0
  75. data/lib/badline/chips/ted/timers.rb +66 -0
  76. data/lib/badline/chips/ted.rb +143 -0
  77. data/lib/badline/chips/tia.rb +49 -0
  78. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  79. data/lib/badline/chips/vdc/painter.rb +0 -12
  80. data/lib/badline/chips/vdc/raster.rb +0 -16
  81. data/lib/badline/chips/vdc/window.rb +0 -9
  82. data/lib/badline/chips/vdc.rb +1 -2
  83. data/lib/badline/chips/via/control_lines.rb +0 -15
  84. data/lib/badline/chips/via/interrupt_register.rb +0 -9
  85. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  86. data/lib/badline/chips/via/shift_register.rb +0 -14
  87. data/lib/badline/chips/via/timer1.rb +0 -13
  88. data/lib/badline/chips/via/timer2.rb +0 -18
  89. data/lib/badline/chips/via.rb +1 -2
  90. data/lib/badline/chips/vic/border_mask.rb +0 -10
  91. data/lib/badline/chips/vic/collisions.rb +0 -11
  92. data/lib/badline/chips/vic/color_patches.rb +0 -11
  93. data/lib/badline/chips/vic/display_state.rb +0 -19
  94. data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
  95. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  96. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  97. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  98. data/lib/badline/chips/vic/register_log.rb +0 -18
  99. data/lib/badline/chips/vic/registers.rb +0 -9
  100. data/lib/badline/chips/vic/sequencer.rb +0 -2
  101. data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
  102. data/lib/badline/chips/vic/sprite.rb +0 -2
  103. data/lib/badline/chips/vic/sprites.rb +0 -3
  104. data/lib/badline/chips/vic.rb +6 -2
  105. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  106. data/lib/badline/chips/vic_i/painter.rb +0 -4
  107. data/lib/badline/chips/vic_i/sound.rb +38 -46
  108. data/lib/badline/chips/vic_i.rb +2 -2
  109. data/lib/badline/chips/z80/core.rb +2 -2
  110. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  111. data/lib/badline/chips.rb +10 -0
  112. data/lib/badline/core.rb +4 -21
  113. data/lib/badline/drives/datasette/keyed_state.rb +38 -0
  114. data/lib/badline/drives/datasette.rb +40 -7
  115. data/lib/badline/drives/engine/core.rb +3 -0
  116. data/lib/badline/drives/engine/serial_lines.rb +4 -7
  117. data/lib/badline/drives/model1541/bus.rb +0 -13
  118. data/lib/badline/drives/model1541/disk.rb +9 -8
  119. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  120. data/lib/badline/drives/model1541/mechanism.rb +0 -2
  121. data/lib/badline/drives/model1541.rb +1 -2
  122. data/lib/badline/drives/model1571/bus.rb +0 -13
  123. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  124. data/lib/badline/drives/model1571/wd1770.rb +0 -14
  125. data/lib/badline/drives/model1571.rb +1 -2
  126. data/lib/badline/drives/model1581/bus.rb +0 -13
  127. data/lib/badline/drives/model1581/disk.rb +0 -4
  128. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  129. data/lib/badline/drives/model1581/mechanism.rb +0 -21
  130. data/lib/badline/drives/model1581/slot.rb +0 -18
  131. data/lib/badline/drives/model1581/wd1772.rb +0 -2
  132. data/lib/badline/drives/model1581.rb +2 -2
  133. data/lib/badline/drives.rb +8 -0
  134. data/lib/badline/frontend/app.rb +3 -3
  135. data/lib/badline/frontend/boot.rb +1 -1
  136. data/lib/badline/frontend/controls.rb +19 -3
  137. data/lib/badline/frontend/drive_led.rb +44 -21
  138. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -2
  139. data/lib/badline/frontend/snapshots.rb +12 -1
  140. data/lib/badline/frontend/timeline.rb +28 -10
  141. data/lib/badline/input/matrix_typist.rb +98 -0
  142. data/lib/badline/input.rb +1 -0
  143. data/lib/badline/kernal_trap/chrout.rb +45 -0
  144. data/lib/badline/kernal_trap/dos/channel.rb +0 -27
  145. data/lib/badline/kernal_trap/dos/channels.rb +0 -25
  146. data/lib/badline/kernal_trap/dos/memory.rb +0 -9
  147. data/lib/badline/kernal_trap/dos/status.rb +0 -8
  148. data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
  149. data/lib/badline/kernal_trap/dos.rb +0 -19
  150. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  151. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  152. data/lib/badline/kernal_trap/layout.rb +75 -11
  153. data/lib/badline/kernal_trap/load.rb +14 -16
  154. data/lib/badline/kernal_trap/routine.rb +12 -0
  155. data/lib/badline/kernal_trap/save.rb +8 -18
  156. data/lib/badline/kernal_trap/serial.rb +1 -17
  157. data/lib/badline/kernal_trap.rb +2 -0
  158. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  159. data/lib/badline/machine/settling.rb +29 -0
  160. data/lib/badline/machine.rb +5 -2
  161. data/lib/badline/media/boot_disk.rb +14 -2
  162. data/lib/badline/media/extensions.rb +10 -0
  163. data/lib/badline/memory.rb +3 -6
  164. data/lib/badline/options/family.rb +5 -2
  165. data/lib/badline/options/help.rb +42 -16
  166. data/lib/badline/options/table.rb +2 -0
  167. data/lib/badline/options/validation.rb +2 -0
  168. data/lib/badline/options.rb +6 -1
  169. data/lib/badline/plus4/acia.rb +45 -0
  170. data/lib/badline/plus4/bus.rb +312 -0
  171. data/lib/badline/plus4/cpu.rb +10 -0
  172. data/lib/badline/plus4/cpu_port.rb +93 -0
  173. data/lib/badline/plus4/keyboard_port.rb +51 -0
  174. data/lib/badline/plus4/model.rb +38 -0
  175. data/lib/badline/plus4/user_port.rb +31 -0
  176. data/lib/badline/plus4.rb +164 -0
  177. data/lib/badline/roms/README +38 -0
  178. data/lib/badline/roms/plus4/basic.rom +0 -0
  179. data/lib/badline/roms/plus4/function-high.rom +0 -0
  180. data/lib/badline/roms/plus4/function-low.rom +0 -0
  181. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  182. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  183. data/lib/badline/snapshot/c128_setup.rb +9 -20
  184. data/lib/badline/snapshot/c64_setup.rb +28 -24
  185. data/lib/badline/snapshot/container.rb +4 -0
  186. data/lib/badline/snapshot/field_doc/markdown.rb +112 -0
  187. data/lib/badline/snapshot/field_doc.rb +109 -0
  188. data/lib/badline/snapshot/image.rb +134 -28
  189. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  190. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  191. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  192. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  193. data/lib/badline/snapshot/keyed/report.rb +26 -0
  194. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  195. data/lib/badline/snapshot/keyed.rb +64 -0
  196. data/lib/badline/snapshot/machine_state.rb +17 -64
  197. data/lib/badline/snapshot/storage_setup.rb +29 -34
  198. data/lib/badline/snapshot/vic20_setup.rb +6 -15
  199. data/lib/badline/snapshot/vice/reu1764.rb +2 -2
  200. data/lib/badline/snapshot/vice.rb +7 -31
  201. data/lib/badline/snapshot.rb +43 -20
  202. data/lib/badline/storage/crt_file.rb +3 -1
  203. data/lib/badline/storage/g64_image.rb +8 -3
  204. data/lib/badline/storage/tap.rb +43 -15
  205. data/lib/badline/version.rb +1 -1
  206. data/lib/badline/vic20/attachments.rb +3 -0
  207. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  208. data/lib/badline/vic20/bus.rb +1 -2
  209. data/lib/badline/vic20/keyed_modules.rb +73 -0
  210. data/lib/badline/vic20/keyed_state.rb +45 -0
  211. data/lib/badline/vic20.rb +5 -5
  212. data/lib/badline.rb +1 -0
  213. metadata +61 -42
  214. data/lib/badline/c128/bus/saved_state.rb +0 -53
  215. data/lib/badline/c128/saved_state.rb +0 -117
  216. data/lib/badline/c64/bus/saved_state.rb +0 -68
  217. data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
  218. data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
  219. data/lib/badline/c64/cartridge/saved_state.rb +0 -63
  220. data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
  221. data/lib/badline/c64/reu/saved_state.rb +0 -55
  222. data/lib/badline/c64/saved_state.rb +0 -112
  223. data/lib/badline/chips/cia/saved_state.rb +0 -49
  224. data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
  225. data/lib/badline/chips/cpu/saved_state.rb +0 -117
  226. data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
  227. data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
  228. data/lib/badline/chips/sid/saved_state.rb +0 -52
  229. data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
  230. data/lib/badline/chips/vdc/saved_state.rb +0 -36
  231. data/lib/badline/chips/via/saved_state.rb +0 -44
  232. data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
  233. data/lib/badline/chips/vic/saved_state.rb +0 -82
  234. data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
  235. data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
  236. data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
  237. data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
  238. data/lib/badline/chips/vic_i/saved_state.rb +0 -55
  239. data/lib/badline/chips/z80/saved_state.rb +0 -71
  240. data/lib/badline/chrout_trap.rb +0 -43
  241. data/lib/badline/debug_register.rb +0 -28
  242. data/lib/badline/drives/datasette/saved_state.rb +0 -37
  243. data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
  244. data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
  245. data/lib/badline/drives/model1541/saved_state.rb +0 -44
  246. data/lib/badline/drives/model1571/saved_state.rb +0 -47
  247. data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
  248. data/lib/badline/drives/model1581/saved_state.rb +0 -42
  249. data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
  250. data/lib/badline/keyboard_buffer.rb +0 -33
  251. data/lib/badline/snapshot/setup.rb +0 -31
  252. data/lib/badline/snapshot/state.rb +0 -179
  253. data/lib/badline/vic20/bus/saved_state.rb +0 -35
  254. data/lib/badline/vic20/saved_state.rb +0 -110
@@ -5,7 +5,6 @@ require "badline/chips/vic/color_patches"
5
5
  require "badline/chips/vic/graphics_mode"
6
6
  require "badline/chips/vic/graphics_shifter"
7
7
  require "badline/chips/vic/sequencer_output"
8
- require "badline/chips/vic/sequencer/saved_state"
9
8
 
10
9
  module Badline
11
10
  module Chips
@@ -15,7 +14,6 @@ module Badline
15
14
  # Turns fetched graphics data into output pixels.
16
15
  class Sequencer
17
16
  include Output
18
- include SavedState
19
17
 
20
18
  BORDER_Y_BOUNDS = [
21
19
  [55, 247].freeze,
@@ -26,17 +26,6 @@ module Badline
26
26
  @line = Array.new(columns, -1)
27
27
  end
28
28
 
29
- def save_state(out)
30
- out.int(@lines).int(@ghost).ints(@value).ints(@line)
31
- end
32
-
33
- def load_state(input)
34
- @lines = input.int
35
- @ghost = input.int
36
- input.ints_into(@value)
37
- input.ints_into(@line)
38
- end
39
-
40
29
  # Sprites 3-7 fetch in Bauer cycles 1-10, so the idle byte is taken
41
30
  # as the line starts.
42
31
  def start_line(sprites_enabled)
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "badline/chips/vic/sprite/internal_bus"
4
4
  require "badline/chips/vic/sprite/shifter"
5
- require "badline/chips/vic/sprite/saved_state"
6
5
  require "badline/chips/vic/sprite/timing"
7
6
 
8
7
  module Badline
@@ -54,7 +53,6 @@ module Badline
54
53
  DISPLAY_OFF_X = 460
55
54
 
56
55
  include Shifter
57
- include SavedState
58
56
 
59
57
  attr_reader :index, :leftmost, :span, :codes,
60
58
  :reload_leftmost, :reload_span, :reload_codes
@@ -3,14 +3,11 @@
3
3
  require "badline/chips/vic/collisions"
4
4
  require "badline/chips/vic/register_log"
5
5
  require "badline/chips/vic/sprite"
6
- require "badline/chips/vic/sprites/saved_state"
7
6
 
8
7
  module Badline
9
8
  module Chips
10
9
  class VIC
11
10
  class Sprites
12
- include SavedState
13
-
14
11
  # Pixels between the start of the cycle following a write and the
15
12
  # point the new value shows. Each signal reaches the output on its own
16
13
  # path: the colors feed the final mux, the sequencer inputs — X
@@ -5,14 +5,13 @@ require "badline/chips/vic/registers"
5
5
  require "badline/chips/vic/display_state"
6
6
  require "badline/chips/vic/sequencer"
7
7
  require "badline/chips/vic/sprites"
8
- require "badline/chips/vic/saved_state"
8
+ require "badline/chips/vic/keyed_parts"
9
9
 
10
10
  module Badline
11
11
  module Chips
12
12
  class VIC
13
13
  include Addressable
14
14
  include IntegerHelper
15
- include SavedState
16
15
 
17
16
  # The 6569 of the breadbin C64, the HMOS 8565 of the C64C, and the
18
17
  # C128's VIC-IIe, the 8566 (PAL) and 8564 (NTSC). The VIC-IIe draws as
@@ -229,6 +228,11 @@ module Badline
229
228
  @sequencer.start_vertical_border(@rasterline == @last_line ? 0 : @rasterline + 1) if hooks.anybits?(HOOK_LAST)
230
229
  end
231
230
 
231
+ # Latches the sprite collisions up to the beam, as a read of $D01E or
232
+ # $D01F would, so the registers stand as the hardware has them when a
233
+ # snapshot is saved.
234
+ def fold_collisions! = @sprites.collide_upto(@column * 8, @sequencer.fg)
235
+
232
236
  # The IRQ line is held asserted while any enabled latch bit is set in
233
237
  # $D019/$D01A, until the program acknowledges it by writing to $D019.
234
238
  def interrupted?
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class VICI
6
+ class Sound
7
+ # The sound's fields in a keyed snapshot module, under `prefix`:
8
+ # the voices' shift registers, the noise LFSR, the level, and when
9
+ # each voice next shifts, in cycles from the machine's clock, with
10
+ # how far behind the clock the voices last ran. The volume and the
11
+ # voices' frequencies come from the VIC's registers on load.
12
+ module KeyedState
13
+ def save_fields(out, prefix)
14
+ now = @clock.cycles
15
+ run_to(now)
16
+ out.ints("#{prefix}shift_registers", @shift_registers).int("#{prefix}lfsr", @lfsr)
17
+ out.int("#{prefix}level", @level).int("#{prefix}ran_ago", now - @time)
18
+ out.ints("#{prefix}next_shifts", @next_shifts.map { |due| due - now })
19
+ end
20
+
21
+ def load_fields(input, prefix)
22
+ now = @clock.cycles
23
+ input.ints_into("#{prefix}shift_registers", @shift_registers)
24
+ @lfsr = input.masked("#{prefix}lfsr", @lfsr, 0xffff)
25
+ @level = input.int("#{prefix}level", @level)
26
+ @time = now - input.int("#{prefix}ran_ago", now - @time)
27
+ relative = input.ints("#{prefix}next_shifts", @next_shifts.map { |due| due - now })
28
+ VOICES.times { |voice| @next_shifts[voice] = now + relative.fetch(voice, 0) }
29
+ end
30
+
31
+ # $900A to $900E as written.
32
+ def restore_registers(registers)
33
+ @volume = registers[0x0e] & 0x0f
34
+ VOICES.times do |voice|
35
+ value = registers[0x0a + voice]
36
+ @frequencies[voice] = value & 0x7f
37
+ @enabled[voice] = value >= 0x80
38
+ end
39
+ end
40
+ end
41
+
42
+ include KeyedState
43
+ end
44
+
45
+ class Painter
46
+ # The lines' fetches and the line going out, as this painter lays
47
+ # them out (VICI::LINE_LAYOUT), and the picture.
48
+ module KeyedState
49
+ def save_fields(out)
50
+ out.int("line.row", @row).int("line.painted", @painted).bool("line.written", @written)
51
+ out.bytes("line.patterns", @patterns).bytes("line.colors", @colors)
52
+ out.ints("line.counts", @counts).ints("line.starts", @starts).bytes("display", @display)
53
+ end
54
+
55
+ def load_fields(input, layout)
56
+ input.bytes_into("display", @display)
57
+ return unless layout
58
+
59
+ @row = input.int("line.row", @row)
60
+ @painted = input.int("line.painted", @painted)
61
+ @written = input.bool("line.written", @written)
62
+ input.bytes_into("line.patterns", @patterns)
63
+ input.bytes_into("line.colors", @colors)
64
+ input.ints_into("line.counts", @counts)
65
+ input.ints_into("line.starts", @starts)
66
+ end
67
+
68
+ # The colours as $900E and $900F hold them, every line to be
69
+ # painted afresh, and the whole display changed.
70
+ def restored!(registers)
71
+ @border = registers[0x0f] & 0x07
72
+ @background = registers[0x0f] >> 4
73
+ @reverse = registers[0x0f].nobits?(0x08)
74
+ @aux = registers[0x0e] >> 4
75
+ @stale.fill(true)
76
+ @memo_keys.fill(-1)
77
+ @dirty_lines.fill(true)
78
+ end
79
+ end
80
+
81
+ include KeyedState
82
+ end
83
+
84
+ # The shape of the painter's per-line fields. A snapshot whose
85
+ # line.layout differs leaves them at power-on.
86
+ LINE_LAYOUT = 1
87
+
88
+ # The VIC-I's fields in a keyed snapshot module (Snapshot::Keyed): the
89
+ # registers, the beam, the text window's counters and the fetch in
90
+ # flight, the painter's lines under line.*, the picture, and the sound
91
+ # under sound.* What the registers decode to, where the line's fetches
92
+ # end and the next event are worked out on load.
93
+ module KeyedState
94
+ def save_fields(out)
95
+ out.bytes("registers", @registers).int("raster", @rasterline).int("column", @column)
96
+ out.bool("window_open", @window_open).bool("window_done", @window_done).int("row_start", @row_start)
97
+ out.int("matrix", @matrix).int("char_line", @char_line).int("text_rows", @text_rows)
98
+ out.int("fetch_from", @fetch_from).int("fetched", @fetched).int("code", @code)
99
+ out.int("line.layout", LINE_LAYOUT)
100
+ @painter.save_fields(out)
101
+ @sound&.save_fields(out, "sound.")
102
+ out
103
+ end
104
+
105
+ def load_fields(input)
106
+ input.bytes_into("registers", @registers)
107
+ @rasterline = input.int("raster", @rasterline)
108
+ @column = input.int("column", @column)
109
+ @window_open = input.bool("window_open", @window_open)
110
+ @window_done = input.bool("window_done", @window_done)
111
+ @row_start = input.int("row_start", @row_start)
112
+ @matrix = input.int("matrix", @matrix)
113
+ @char_line = input.int("char_line", @char_line)
114
+ @text_rows = input.int("text_rows", @text_rows)
115
+ @fetch_from = input.int("fetch_from", @fetch_from)
116
+ @fetched = input.int("fetched", @fetched)
117
+ @code = input.byte("code", @code)
118
+ line = input.int("line.layout", 0) == LINE_LAYOUT
119
+ input.adjust("line in progress from another renderer, restarted") unless line || input.missing?
120
+ @painter.load_fields(input, line)
121
+ @sound&.load_fields(input, "sound.")
122
+ end
123
+
124
+ def restored!
125
+ @origin_x = @registers[0] & 0x7f
126
+ @columns = @registers[2] & 0x7f
127
+ @origin_y = @registers[1]
128
+ rows_written(@registers[3])
129
+ bases_written
130
+ @slot = @rasterline * Painter::SLOTS
131
+ @fetch_to = 0
132
+ fetches_end if @window_open
133
+ schedule
134
+ @painter.restored!(@registers)
135
+ @sound&.restore_registers(@registers)
136
+ end
137
+ end
138
+
139
+ include KeyedState
140
+ end
141
+ end
142
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "badline/chips/vic_i/painter/saved_state"
4
-
5
3
  module Badline
6
4
  module Chips
7
5
  class VICI
@@ -28,8 +26,6 @@ module Badline
28
26
  # nibble's low three bits) and 11 the auxiliary colour, whatever
29
27
  # $900F bit 3 says (MOS 6560/6561 datasheet, "Color operating modes").
30
28
  class Painter
31
- include SavedState
32
-
33
29
  # The characters a line holds room for. A line fetches at most 34.
34
30
  SLOTS = 40
35
31
 
@@ -30,10 +30,11 @@ module Badline
30
30
  # The output is the voices that are high, plus a little more, times the
31
31
  # volume, so a volume write steps the output even with every voice off.
32
32
  #
33
- # The voices only run while #record collects the output: nothing the
34
- # CPU reads depends on them. Until then a write only sets the register.
35
- # While recording, the voices catch up to the current cycle whenever a
36
- # write lands or the samples are drained, one shift at a time.
33
+ # The voices catch up to the current cycle whenever a write lands, the
34
+ # samples are drained or #catch_up! asks, one shift at a time, whether
35
+ # or not #record collects the output, so their state doesn't depend on
36
+ # whether the host plays it. Unrecorded, while every voice is off and
37
+ # shifted out to silence, they skip to the cycle at once.
37
38
  class Sound
38
39
  VOICES = 4
39
40
  NOISE = 3
@@ -86,14 +87,15 @@ module Badline
86
87
  # second, for #drain_samples. `clock_hz` is the rate the cycles are
87
88
  # taken to run at.
88
89
  def record(rate:, clock_hz: @clock_hz)
89
- now = @clock.cycles
90
- run_to(now)
91
- resume(now)
90
+ run_to(@clock.cycles)
92
91
  @output = Output.new(clock_hz:, rate:, level: @level)
93
92
  end
94
93
 
95
94
  def recording? = !@output.nil?
96
95
 
96
+ # Runs the voices up to the current cycle.
97
+ def catch_up! = run_to(@clock.cycles)
98
+
97
99
  # The samples recorded since the last drain.
98
100
  def drain_samples
99
101
  output = @output
@@ -116,29 +118,7 @@ module Badline
116
118
  @level = level_now
117
119
  end
118
120
 
119
- # The registers and the voices as they last ran, for a snapshot.
120
- # Recording is the host's, and starts the voices again from the
121
- # cycle it starts at.
122
- def save_state(out)
123
- run_to(@clock.cycles)
124
- out.marker("VIC20 SOUND").int(@volume).ints(@frequencies).booleans(@enabled)
125
- out.ints(@shift_registers).ints(@next_shifts).int(@lfsr).int(@level).int(@time)
126
- end
127
-
128
- def load_state(input)
129
- input.marker("VIC20 SOUND")
130
- @volume = input.int
131
- input.ints_into(@frequencies)
132
- input.booleans_into(@enabled)
133
- input.ints_into(@shift_registers)
134
- input.ints_into(@next_shifts)
135
- @lfsr = input.int
136
- @level = input.int
137
- @time = input.int
138
- resume(@clock.cycles) if recording?
139
- end
140
-
141
- # A voice's shift register as of the current cycle, while recording.
121
+ # A voice's shift register as of the current cycle.
142
122
  def shift_register(voice)
143
123
  run_to(@clock.cycles)
144
124
  @shift_registers[voice]
@@ -151,35 +131,47 @@ module Badline
151
131
 
152
132
  private
153
133
 
154
- # Moves each shift that the cycles since the voices last ran have
155
- # passed to its next turn from `now`, keeping its phase.
156
- def resume(now)
157
- @time = now
158
- VOICES.times do |voice|
159
- due = @next_shifts[voice]
160
- next if due >= now
161
-
162
- interval = period(voice) * DIVIDERS[voice]
163
- @next_shifts[voice] = due + ((now - due + interval - 1) / interval * interval)
164
- end
165
- end
166
-
167
134
  # Runs the voices up to the start of cycle `target`, the output holding
168
135
  # each level for the cycles it lasts.
169
136
  def run_to(target)
170
137
  time = @time
138
+ return if target <= time
139
+
171
140
  output = @output
172
- return if output.nil? || target <= time
141
+ return skip_to(target) if output.nil? && silent?
173
142
 
174
143
  following = next_shift
175
144
  while following < target
176
- output.hold(@level, following - time)
145
+ output&.hold(@level, following - time)
177
146
  time = following
178
147
  shift_due(time)
179
148
  @level = level_now
180
149
  following = next_shift
181
150
  end
182
- output.hold(@level, target - time)
151
+ output&.hold(@level, target - time)
152
+ @time = target
153
+ end
154
+
155
+ # Whether every voice is off and shifted out to where its shifts
156
+ # change nothing: the tone voices' registers empty and the noise
157
+ # voice's LFSR full of ones.
158
+ def silent?
159
+ enabled = @enabled
160
+ registers = @shift_registers
161
+ !(enabled[0] || enabled[1] || enabled[2] || enabled[NOISE]) &&
162
+ registers[0].zero? && registers[1].zero? && registers[2].zero? && @lfsr == 0xffff
163
+ end
164
+
165
+ # Moves each shift before `target` to its next turn from there,
166
+ # keeping its phase.
167
+ def skip_to(target)
168
+ VOICES.times do |voice|
169
+ due = @next_shifts[voice]
170
+ next if due >= target
171
+
172
+ interval = period(voice) * DIVIDERS[voice]
173
+ @next_shifts[voice] = due + ((target - due + interval - 1) / interval * interval)
174
+ end
183
175
  @time = target
184
176
  end
185
177
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  require "badline/chips/vic_i/painter"
4
4
  require "badline/chips/vic_i/text_window"
5
- require "badline/chips/vic_i/saved_state"
5
+ require "badline/chips/vic_i/sound"
6
+ require "badline/chips/vic_i/keyed_parts"
6
7
 
7
8
  module Badline
8
9
  module Chips
@@ -41,7 +42,6 @@ module Badline
41
42
  # into the line lands 48 pixels in. Lines 0-27 are blanked.
42
43
  class VICI
43
44
  include TextWindow
44
- include SavedState
45
45
 
46
46
  # What a VIC-I sets for the machine: the clock, the raster's cycles
47
47
  # per line and lines per frame, and how many lines at the top of the
@@ -14,7 +14,7 @@ require "badline/chips/z80/extended_page"
14
14
  require "badline/chips/z80/block_transfers"
15
15
  require "badline/chips/z80/block_io"
16
16
  require "badline/chips/z80/interrupt_acceptance"
17
- require "badline/chips/z80/saved_state"
17
+ require "badline/chips/z80/keyed_state"
18
18
 
19
19
  module Badline
20
20
  module Chips
@@ -44,7 +44,7 @@ module Badline
44
44
  include BlockTransfers
45
45
  include BlockIO
46
46
  include InterruptAcceptance
47
- include SavedState
47
+ include KeyedState
48
48
 
49
49
  attr_reader :bus, :cycles, :nmi
50
50
  attr_accessor :int
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class Z80
6
+ # The Z80's fields in a keyed snapshot module (Snapshot::Keyed),
7
+ # between instructions, where it always is after the C128's cycle:
8
+ # the registers, the T-state count and the interrupt latches.
9
+ module KeyedState
10
+ def save_fields(out)
11
+ out.int("a", @a).int("f", @f).int("b", @b).int("c", @c).int("d", @d).int("e", @e).int("h", @h).int("l", @l)
12
+ out.int("af_alt", @af_alt).int("bc_alt", @bc_alt).int("de_alt", @de_alt).int("hl_alt", @hl_alt)
13
+ out.int("i", @i).int("r", @r).int("pc", @pc).int("sp", @sp).int("ix", @ix).int("iy", @iy).int("wz", @wz)
14
+ out.int("q", @q).int("last_q", @last_q).int("im", @im).int("cycles", @cycles)
15
+ out.bool("iff1", @iff1).bool("iff2", @iff2).bool("halted", @halted).bool("after_ei", @after_ei)
16
+ out.bool("after_ld_a_ir", @after_ld_a_ir).bool("int", @int).bool("nmi", @nmi)
17
+ out.bool("nmi_pending", @nmi_pending)
18
+ end
19
+
20
+ def load_fields(input)
21
+ load_main_fields(input)
22
+ load_special_fields(input)
23
+ @cycles = input.int("cycles", @cycles)
24
+ load_latch_fields(input)
25
+ @prefix = 0
26
+ end
27
+
28
+ def restored! = nil
29
+
30
+ private
31
+
32
+ def load_main_fields(input)
33
+ @a = input.byte("a", @a)
34
+ @f = input.byte("f", @f)
35
+ @b = input.byte("b", @b)
36
+ @c = input.byte("c", @c)
37
+ @d = input.byte("d", @d)
38
+ @e = input.byte("e", @e)
39
+ @h = input.byte("h", @h)
40
+ @l = input.byte("l", @l)
41
+ @af_alt = input.word("af_alt", @af_alt)
42
+ @bc_alt = input.word("bc_alt", @bc_alt)
43
+ @de_alt = input.word("de_alt", @de_alt)
44
+ @hl_alt = input.word("hl_alt", @hl_alt)
45
+ end
46
+
47
+ def load_special_fields(input)
48
+ @i = input.byte("i", @i)
49
+ @r = input.byte("r", @r)
50
+ @pc = input.word("pc", @pc)
51
+ @sp = input.word("sp", @sp)
52
+ @ix = input.word("ix", @ix)
53
+ @iy = input.word("iy", @iy)
54
+ @wz = input.word("wz", @wz)
55
+ @q = input.byte("q", @q)
56
+ @last_q = input.byte("last_q", @last_q)
57
+ @im = input.int("im", @im)
58
+ end
59
+
60
+ def load_latch_fields(input)
61
+ @iff1 = input.bool("iff1", @iff1)
62
+ @iff2 = input.bool("iff2", @iff2)
63
+ @halted = input.bool("halted", @halted)
64
+ @after_ei = input.bool("after_ei", @after_ei)
65
+ @after_ld_a_ir = input.bool("after_ld_a_ir", @after_ld_a_ir)
66
+ @int = input.bool("int", @int)
67
+ @nmi = input.bool("nmi", @nmi)
68
+ @nmi_pending = input.bool("nmi_pending", @nmi_pending)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The chips badline/core's machines share. The C128's own, the Z80 and the
4
+ # VDC, stay out of the core: badline/c128 requires them.
5
+ require "badline/chips/cpu"
6
+ require "badline/chips/cia"
7
+ require "badline/chips/via"
8
+ require "badline/chips/sid"
9
+ require "badline/chips/vic"
10
+ require "badline/chips/vic_i"
data/lib/badline/core.rb CHANGED
@@ -15,29 +15,13 @@ require "badline/memory"
15
15
  require "badline/c64/color_memory"
16
16
  require "badline/rom"
17
17
  require "badline/c64/banked_ram"
18
- require "badline/chips/cpu/port"
19
- require "badline/c64/bus"
20
- require "badline/chips/cpu/instruction"
21
- require "badline/chips/cpu/instruction_set"
22
18
  require "badline/status"
23
19
  require "badline/input"
24
- require "badline/drives/datasette"
25
- require "badline/chips/cia/time_of_day"
26
- require "badline/chips/cia"
27
- require "badline/chips/via"
28
- require "badline/chips/sid"
29
- require "badline/debug_register"
20
+ require "badline/chips"
21
+ require "badline/drives"
22
+ require "badline/c64/bus"
30
23
  require "badline/c64/reu"
31
- require "badline/chips/cpu/interrupts"
32
- require "badline/chips/cpu/traps"
33
- require "badline/chips/cpu"
34
- require "badline/chips/vic"
35
- require "badline/keyboard_buffer"
36
- require "badline/drives/serial_bus"
37
- require "badline/drives/model1541"
38
- require "badline/drives/model1581"
39
- require "badline/drives/model1581/slot"
40
- require "badline/snapshot/state"
24
+ require "badline/snapshot/keyed"
41
25
  require "badline/snapshot/c64_setup"
42
26
  require "badline/snapshot/storage_setup"
43
27
  require "badline/machine"
@@ -47,5 +31,4 @@ require "badline/storage"
47
31
  require "badline/sid_tune"
48
32
  require "badline/c64/cartridge"
49
33
  require "badline/kernal_trap"
50
- require "badline/chrout_trap"
51
34
  require "badline/media"
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Datasette
6
+ # The datasette's fields in a keyed snapshot module (Snapshot::Keyed,
7
+ # BL-TAPE): the keys, the motor, the countdown to the next pulse, and
8
+ # a tape in it under tape.*: its path, its bytes and how far it has
9
+ # played. A tape that isn't in leaves tape.* out. The sense and flag
10
+ # handlers don't fire on load.
11
+ module KeyedState
12
+ def save_fields(out)
13
+ out.bool("playing", @playing).bool("motor", @motor).int("countdown", @countdown)
14
+ tape = @tape
15
+ return out unless tape
16
+
17
+ out.text("tape.path", tape.path.to_s).bytes("tape.bytes", tape.bytes).int("tape.position", tape.position)
18
+ end
19
+
20
+ # The tape goes back in from the bytes the module holds, without its
21
+ # host file, unless the tape in is the same one.
22
+ def load_fields(input)
23
+ @playing = input.bool("playing", @playing)
24
+ @motor = input.bool("motor", @motor)
25
+ @countdown = input.int("countdown", @countdown)
26
+ bytes = input.bytes("tape.bytes", "".b).bytes
27
+ return @tape = nil if bytes.empty?
28
+
29
+ path = input.text("tape.path", "")
30
+ @tape = Storage::TAP.new(path, bytes:) unless @tape&.path.to_s == path && @tape&.bytes == bytes
31
+ @tape&.position = input.int("tape.position", 0)
32
+ end
33
+ end
34
+
35
+ include KeyedState
36
+ end
37
+ end
38
+ end