badline 0.5.0 → 0.6.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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/CLAUDE.md +23 -23
  4. data/CONTRIBUTING.md +5 -0
  5. data/README.md +59 -1
  6. data/doc/pinned-behaviour.md +4 -2
  7. data/exe/badline-ruby +2 -1
  8. data/lib/badline/address_bus/saved_state.rb +72 -0
  9. data/lib/badline/address_bus.rb +1 -0
  10. data/lib/badline/audio/stream.rb +6 -0
  11. data/lib/badline/cartridge/action_replay.rb +15 -0
  12. data/lib/badline/cartridge/comal80.rb +8 -0
  13. data/lib/badline/cartridge/dinamic.rb +8 -0
  14. data/lib/badline/cartridge/easy_flash.rb +20 -1
  15. data/lib/badline/cartridge/epyx_fastload.rb +8 -0
  16. data/lib/badline/cartridge/final_cartridge3.rb +12 -0
  17. data/lib/badline/cartridge/flash.rb +41 -0
  18. data/lib/badline/cartridge/fun_play.rb +8 -0
  19. data/lib/badline/cartridge/g_mod2.rb +12 -0
  20. data/lib/badline/cartridge/game_system.rb +8 -0
  21. data/lib/badline/cartridge/geo_ram.rb +16 -0
  22. data/lib/badline/cartridge/magic_desk.rb +8 -0
  23. data/lib/badline/cartridge/ocean.rb +8 -0
  24. data/lib/badline/cartridge/pagefox.rb +12 -0
  25. data/lib/badline/cartridge/retro_replay.rb +44 -1
  26. data/lib/badline/cartridge/rgcd.rb +10 -0
  27. data/lib/badline/cartridge/super_games.rb +10 -0
  28. data/lib/badline/cartridge/zaxxon.rb +16 -0
  29. data/lib/badline/cartridge.rb +72 -0
  30. data/lib/badline/cia/interrupt_register.rb +17 -0
  31. data/lib/badline/cia/serial.rb +44 -0
  32. data/lib/badline/cia/state.rb +47 -0
  33. data/lib/badline/cia/timer.rb +33 -0
  34. data/lib/badline/cia.rb +24 -2
  35. data/lib/badline/computer/attachments.rb +132 -0
  36. data/lib/badline/computer/saved_state.rb +116 -0
  37. data/lib/badline/computer.rb +8 -31
  38. data/lib/badline/cpu/microcode.rb +8 -0
  39. data/lib/badline/cpu/saved_state.rb +115 -0
  40. data/lib/badline/cpu.rb +2 -0
  41. data/lib/badline/datasette.rb +27 -0
  42. data/lib/badline/drive1541/bus.rb +89 -13
  43. data/lib/badline/drive1541/disk.rb +7 -2
  44. data/lib/badline/drive1541/disk_state.rb +80 -0
  45. data/lib/badline/drive1541/idle.rb +9 -57
  46. data/lib/badline/drive1541/mechanism.rb +2 -0
  47. data/lib/badline/drive1541/mechanism_state.rb +67 -0
  48. data/lib/badline/drive1541/orbit.rb +81 -0
  49. data/lib/badline/drive1541/rotation.rb +10 -0
  50. data/lib/badline/drive1541/serial_port.rb +10 -0
  51. data/lib/badline/drive1541/sleep.rb +114 -0
  52. data/lib/badline/drive1541.rb +52 -2
  53. data/lib/badline/gui/application.rb +41 -14
  54. data/lib/badline/gui/gamepads.rb +7 -0
  55. data/lib/badline/gui/snapshots.rb +67 -0
  56. data/lib/badline/gui.rb +1 -0
  57. data/lib/badline/iec_bus.rb +16 -0
  58. data/lib/badline/kernal_trap/channel.rb +27 -0
  59. data/lib/badline/kernal_trap/drive/channels.rb +25 -0
  60. data/lib/badline/kernal_trap/drive/memory.rb +9 -0
  61. data/lib/badline/kernal_trap/drive/status.rb +8 -0
  62. data/lib/badline/kernal_trap/drive/write_file.rb +21 -0
  63. data/lib/badline/kernal_trap/drive.rb +19 -0
  64. data/lib/badline/kernal_trap/save.rb +8 -0
  65. data/lib/badline/kernal_trap/serial.rb +16 -0
  66. data/lib/badline/memory.rb +11 -0
  67. data/lib/badline/options.rb +3 -2
  68. data/lib/badline/ram_expansion/plus256k.rb +16 -0
  69. data/lib/badline/ram_expansion/plus60k.rb +12 -0
  70. data/lib/badline/ram_expansion/unexpanded.rb +6 -0
  71. data/lib/badline/reu/dma.rb +36 -0
  72. data/lib/badline/reu/ram.rb +15 -0
  73. data/lib/badline/reu/saved_state.rb +52 -0
  74. data/lib/badline/reu.rb +4 -0
  75. data/lib/badline/sdl/events.rb +2 -0
  76. data/lib/badline/sid/envelope/state.rb +68 -0
  77. data/lib/badline/sid/envelope.rb +7 -0
  78. data/lib/badline/sid/filter.rb +38 -0
  79. data/lib/badline/sid/voice.rb +10 -0
  80. data/lib/badline/sid/waveform/state.rb +66 -0
  81. data/lib/badline/sid/waveform.rb +2 -0
  82. data/lib/badline/sid.rb +34 -0
  83. data/lib/badline/snapshot/container.rb +117 -0
  84. data/lib/badline/snapshot/fields.rb +54 -0
  85. data/lib/badline/snapshot/image.rb +70 -0
  86. data/lib/badline/snapshot/machine_state.rb +77 -0
  87. data/lib/badline/snapshot/setup.rb +58 -0
  88. data/lib/badline/snapshot/state.rb +178 -0
  89. data/lib/badline/snapshot/vice/c64_mem.rb +53 -0
  90. data/lib/badline/snapshot/vice/cias.rb +89 -0
  91. data/lib/badline/snapshot/vice/cias_import.rb +70 -0
  92. data/lib/badline/snapshot/vice/main_cpu.rb +76 -0
  93. data/lib/badline/snapshot/vice/peripherals.rb +47 -0
  94. data/lib/badline/snapshot/vice/restore.rb +125 -0
  95. data/lib/badline/snapshot/vice/sid_extended.rb +96 -0
  96. data/lib/badline/snapshot/vice/sid_registers.rb +46 -0
  97. data/lib/badline/snapshot/vice/vicii.rb +119 -0
  98. data/lib/badline/snapshot/vice/vicii_import.rb +131 -0
  99. data/lib/badline/snapshot/vice.rb +74 -0
  100. data/lib/badline/snapshot.rb +52 -0
  101. data/lib/badline/storage/crt_file.rb +9 -3
  102. data/lib/badline/storage/d64_image.rb +2 -0
  103. data/lib/badline/storage/d71_image.rb +2 -0
  104. data/lib/badline/storage/d81_image.rb +2 -0
  105. data/lib/badline/storage/disk_image.rb +11 -2
  106. data/lib/badline/storage/host_directory.rb +5 -0
  107. data/lib/badline/storage/t64.rb +6 -0
  108. data/lib/badline/storage/tap.rb +10 -1
  109. data/lib/badline/storage/unavailable.rb +20 -0
  110. data/lib/badline/storage.rb +34 -0
  111. data/lib/badline/time_of_day.rb +56 -0
  112. data/lib/badline/version.rb +1 -1
  113. data/lib/badline/via/control_lines.rb +15 -0
  114. data/lib/badline/via/fast_forward.rb +15 -0
  115. data/lib/badline/via/interrupt_register.rb +9 -0
  116. data/lib/badline/via/shift_register.rb +13 -0
  117. data/lib/badline/via/state.rb +42 -0
  118. data/lib/badline/via/timer1.rb +19 -0
  119. data/lib/badline/via/timer2.rb +24 -0
  120. data/lib/badline/via.rb +2 -0
  121. data/lib/badline/vic/border_mask.rb +10 -0
  122. data/lib/badline/vic/collisions.rb +11 -0
  123. data/lib/badline/vic/color_patches.rb +11 -0
  124. data/lib/badline/vic/display_state.rb +29 -1
  125. data/lib/badline/vic/graphics_shifter.rb +26 -0
  126. data/lib/badline/vic/register_log.rb +18 -0
  127. data/lib/badline/vic/registers.rb +13 -2
  128. data/lib/badline/vic/sequencer.rb +12 -0
  129. data/lib/badline/vic/sequencer_state.rb +89 -0
  130. data/lib/badline/vic/sprite/internal_bus.rb +11 -0
  131. data/lib/badline/vic/sprite/saved_state.rb +64 -0
  132. data/lib/badline/vic/sprite.rb +2 -0
  133. data/lib/badline/vic/sprites.rb +3 -0
  134. data/lib/badline/vic/sprites_state.rb +57 -0
  135. data/lib/badline/vic.rb +94 -10
  136. data/lib/badline.rb +1 -0
  137. metadata +37 -1
@@ -55,6 +55,21 @@ module Badline
55
55
 
56
56
  def active_c1_edge?(high) = high == @control.anybits?(0x01)
57
57
 
58
+ # The lines' levels and mode. Which flags they set, and whether reads
59
+ # handshake, are the port's wiring.
60
+ def save_state(out)
61
+ out.int(@control).boolean(@c1_high).boolean(@c2_high).boolean(@handshake_low).int(@pulse).boolean(@c2_output)
62
+ end
63
+
64
+ def load_state(input)
65
+ @control = input.int
66
+ @c1_high = input.boolean?
67
+ @c2_high = input.boolean?
68
+ @handshake_low = input.boolean?
69
+ @pulse = input.int
70
+ @c2_output = input.boolean?
71
+ end
72
+
58
73
  # Everything the lines hold, for comparing them at two points.
59
74
  def state = [@control, @c1_high, @c2_high, @handshake_low, @pulse, @c2_output]
60
75
 
@@ -26,6 +26,21 @@ module Badline
26
26
  @t2.fast_forward(cycles)
27
27
  end
28
28
 
29
+ # The counters an orbit (see Drive1541::Idle) has to bring back: those
30
+ # of the armed timers, and of the timers +touched+ names (bit 0 for
31
+ # timer 1, bit 1 for timer 2). The others only count.
32
+ def counter_state(touched)
33
+ [(@t1.count_state if @t1.armed? || touched.anybits?(1)),
34
+ (@t2.count_state if @t2.armed? || touched.anybits?(2))]
35
+ end
36
+
37
+ # Runs +cycles+ cycles of whole orbits at once: the counters
38
+ # counter_state leaves out move on, and the rest come back round.
39
+ def skip_orbits(cycles, touched)
40
+ @t1.fast_forward(cycles) unless @t1.armed? || touched.anybits?(1)
41
+ @t2.fast_forward(cycles) unless @t2.armed? || touched.anybits?(2)
42
+ end
43
+
29
44
  # Everything the VIA holds but its counters, for comparing it at two
30
45
  # points. Timer 1's PB7 level counts only while ACR bit 7 puts it on
31
46
  # the pin: nothing reads it otherwise.
@@ -24,6 +24,15 @@ module Badline
24
24
  @enable = 0x00
25
25
  end
26
26
 
27
+ def save_state(out)
28
+ out.int(@flags).int(@enable)
29
+ end
30
+
31
+ def load_state(input)
32
+ @flags = input.int
33
+ @enable = input.int
34
+ end
35
+
27
36
  def set(bits)
28
37
  @flags |= bits
29
38
  end
@@ -67,6 +67,19 @@ module Badline
67
67
  # changes it from cycle to cycle.
68
68
  def clocking? = @bits.nonzero? && internal_clock?
69
69
 
70
+ def save_state(out)
71
+ out.int(@mode).int(@data).int(@bits).boolean(@clock).boolean(@cb2).boolean(@cb2_input)
72
+ end
73
+
74
+ def load_state(input)
75
+ @mode = input.int
76
+ @data = input.int
77
+ @bits = input.int
78
+ @clock = input.boolean?
79
+ @cb2 = input.boolean?
80
+ @cb2_input = input.boolean?
81
+ end
82
+
70
83
  # Everything the register holds, for comparing it at two points.
71
84
  def state = [@mode, @data, @bits, @clock, @cb2, @cb2_input]
72
85
 
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class VIA
5
+ # Saving and restoring a VIA for a snapshot.
6
+ module State
7
+ # The registers, the timers, the shift register, the interrupt flags
8
+ # and the control lines. The peripheral is the drive's wiring.
9
+ def save_state(out)
10
+ out.int(@ora).int(@orb).int(@ddra).int(@ddrb).int(@latch_a).int(@latch_b).int(@acr).int(@pcr)
11
+ out.boolean(@pb6_high).boolean(@sr_uses_t2)
12
+ @t1.save_state(out)
13
+ @t2.save_state(out)
14
+ @shift_register.save_state(out)
15
+ @ifr.save_state(out)
16
+ @ca.save_state(out)
17
+ @cb.save_state(out)
18
+ end
19
+
20
+ # Puts the state back without telling the peripheral, which restores
21
+ # its own.
22
+ def load_state(input)
23
+ @ora = input.int
24
+ @orb = input.int
25
+ @ddra = input.int
26
+ @ddrb = input.int
27
+ @latch_a = input.int
28
+ @latch_b = input.int
29
+ @acr = input.int
30
+ @pcr = input.int
31
+ @pb6_high = input.boolean?
32
+ @sr_uses_t2 = input.boolean?
33
+ @t1.load_state(input)
34
+ @t2.load_state(input)
35
+ @shift_register.load_state(input)
36
+ @ifr.load_state(input)
37
+ @ca.load_state(input)
38
+ @cb.load_state(input)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -92,10 +92,29 @@ module Badline
92
92
  @counter = @reload ? 0xffff : @latch - phase + 1
93
93
  end
94
94
 
95
+ def save_state(out)
96
+ out.int(@counter).int(@latch).boolean(@hold).boolean(@reload).boolean(@armed).boolean(@pb7)
97
+ end
98
+
99
+ def load_state(input)
100
+ @counter = input.int
101
+ @latch = input.int
102
+ @hold = input.boolean?
103
+ @reload = input.boolean?
104
+ @armed = input.boolean?
105
+ @pb7 = input.boolean?
106
+ end
107
+
95
108
  # What the counter and PB7 don't hold, for comparing the timer at two
96
109
  # points.
97
110
  def state = [@latch, @armed]
98
111
 
112
+ # Whether a timeout sets the flag.
113
+ def armed? = @armed
114
+
115
+ # The count, for comparing the timer at two points where it matters.
116
+ def count_state = [@counter, @hold, @reload]
117
+
99
118
  private
100
119
 
101
120
  # The counter reloads either way. Armed, PB7 inverts and the flag
@@ -84,10 +84,34 @@ module Badline
84
84
  @low_underflowed = @counter.allbits?(0xff)
85
85
  end
86
86
 
87
+ def save_state(out)
88
+ out.int(@counter).int(@latch_low)
89
+ [@hold, @low_reload, @armed, @low_underflowed, @count_pulses, @counting_pulses].each do |flag|
90
+ out.boolean(flag)
91
+ end
92
+ end
93
+
94
+ def load_state(input)
95
+ @counter = input.int
96
+ @latch_low = input.int
97
+ @hold = input.boolean?
98
+ @low_reload = input.boolean?
99
+ @armed = input.boolean?
100
+ @low_underflowed = input.boolean?
101
+ @count_pulses = input.boolean?
102
+ @counting_pulses = input.boolean?
103
+ end
104
+
87
105
  # What the counter and the low byte's underflow don't hold, for
88
106
  # comparing the timer at two points.
89
107
  def state = [@latch_low, @armed, @hold, @low_reload, @count_pulses, @counting_pulses]
90
108
 
109
+ # Whether the next underflow sets the flag.
110
+ def armed? = @armed
111
+
112
+ # The count, for comparing the timer at two points where it matters.
113
+ def count_state = [@counter, @low_underflowed]
114
+
91
115
  private
92
116
 
93
117
  # A cycle while counting pulses, or held after a load or a low byte
data/lib/badline/via.rb CHANGED
@@ -4,6 +4,7 @@ require "badline/via/control_lines"
4
4
  require "badline/via/fast_forward"
5
5
  require "badline/via/interrupt_register"
6
6
  require "badline/via/shift_register"
7
+ require "badline/via/state"
7
8
  require "badline/via/timer1"
8
9
  require "badline/via/timer2"
9
10
 
@@ -18,6 +19,7 @@ module Badline
18
19
  class VIA
19
20
  include Addressable
20
21
  include FastForward
22
+ include State
21
23
 
22
24
  attr_reader :start, :peripheral, :shift_register, :acr, :pcr
23
25
 
@@ -27,6 +27,16 @@ module Badline
27
27
  @groups.fill(FULL)
28
28
  end
29
29
 
30
+ def save_state(out)
31
+ out.ints(@groups).booleans(@mask).blob(@snapshot)
32
+ end
33
+
34
+ def load_state(input)
35
+ input.ints_into(@groups)
36
+ input.booleans_into(@mask)
37
+ input.blob_into(@snapshot)
38
+ end
39
+
30
40
  def at?(pixel_x)
31
41
  case @groups[pixel_x >> 3]
32
42
  when FULL then true
@@ -36,6 +36,17 @@ module Badline
36
36
  @cleared_data = 0
37
37
  end
38
38
 
39
+ def save_state(out)
40
+ out.blob(@hits).int(@folded).int(@cleared_sprite).int(@cleared_data)
41
+ end
42
+
43
+ def load_state(input)
44
+ input.blob_into(@hits)
45
+ @folded = input.int
46
+ @cleared_sprite = input.int
47
+ @cleared_data = input.int
48
+ end
49
+
39
50
  def start_line
40
51
  @folded = 0
41
52
  @cleared_sprite = carry(@cleared_sprite)
@@ -17,6 +17,17 @@ module Badline
17
17
 
18
18
  def clear = @patches.clear
19
19
 
20
+ # Each patch as its four numbers: the boundary pixel, the register,
21
+ # the old colour and the new.
22
+ def save_state(out)
23
+ out.int(@patches.length)
24
+ @patches.each { |patch| out.ints(patch) }
25
+ end
26
+
27
+ def load_state(input)
28
+ @patches.replace(Array.new(input.int) { input.ints })
29
+ end
30
+
20
31
  def log(reg, old, value, boundary_x)
21
32
  @patches << [boundary_x, reg, @grey_dots ? GREY_DOT : old & 0x0f, value & 0x0f]
22
33
  end
@@ -30,7 +30,7 @@ module Badline
30
30
 
31
31
  GRAPHICS_COLUMNS = Array.new(65) { |column| column.between?(GRAPHICS_FIRST, GRAPHICS_LAST) }.freeze
32
32
 
33
- attr_reader :vc_base, :vc, :vmli, :rc
33
+ attr_reader :vc_base, :vc, :vmli, :rc, :bad_lines_enabled
34
34
 
35
35
  # The wrap column is the last column of a rasterline, 62 on the 6569,
36
36
  # which already compares against the line about to start, the same
@@ -50,6 +50,15 @@ module Badline
50
50
  end
51
51
 
52
52
  def display? = @display
53
+
54
+ # Sets VCBASE, VC, RC and VMLI, in that order, and the display and
55
+ # bad-line flags, as a VICE snapshot gives them.
56
+ def restore_counters(counters, display, bad_lines_enabled)
57
+ @vc_base, @vc, @rc, @vmli = counters
58
+ @display = display
59
+ @bad_lines_enabled = bad_lines_enabled
60
+ end
61
+
53
62
  def idle? = !@display
54
63
  def bad_line? = !@ba.nil?
55
64
 
@@ -63,6 +72,25 @@ module Badline
63
72
  @matched && column >= FETCH_FIRST && column <= DMA_LAST
64
73
  end
65
74
 
75
+ def save_state(out)
76
+ out.int(@vc_base).int(@vc).int(@vmli).int(@rc).boolean(@display).boolean(@bad_lines_enabled)
77
+ out.boolean(@matched).optional_int(@ba).int(@line_bits).boolean(@den_line).boolean(@in_window)
78
+ end
79
+
80
+ def load_state(input)
81
+ @vc_base = input.int
82
+ @vc = input.int
83
+ @vmli = input.int
84
+ @rc = input.int
85
+ @display = input.boolean?
86
+ @bad_lines_enabled = input.boolean?
87
+ @matched = input.boolean?
88
+ @ba = input.optional_int
89
+ @line_bits = input.int
90
+ @den_line = input.boolean?
91
+ @in_window = input.boolean?
92
+ end
93
+
66
94
  def new_frame
67
95
  @vc_base = 0
68
96
  @bad_lines_enabled = false
@@ -50,6 +50,24 @@ module Badline
50
50
  @late_read = false
51
51
  end
52
52
 
53
+ def save_state(out)
54
+ out.int(@next_ecm_bmm).int(@black_below).ints(@colors).booleans(@fg)
55
+ out.int(@gbuf).int(@vbuf).int(@cbuf).int(@flop).int(@pixel)
56
+ out.int(@mode_lookup).int(@mode_read).boolean(@late_mcm).boolean(@late_read)
57
+ end
58
+
59
+ def load_state(input)
60
+ @next_ecm_bmm = input.int
61
+ @black_below = input.int
62
+ input.ints_into(@colors)
63
+ input.booleans_into(@fg)
64
+ load_buffers(input)
65
+ @mode_lookup = input.int
66
+ @mode_read = input.int
67
+ @late_mcm = input.boolean?
68
+ @late_read = input.boolean?
69
+ end
70
+
53
71
  # Rebuilds the state at the end of a group that painted whole bytes:
54
72
  # the byte loaded at pixel shift under an unchanging mode.
55
73
  def prime(data, screencode, color, shift, mode)
@@ -78,6 +96,14 @@ module Badline
78
96
 
79
97
  private
80
98
 
99
+ def load_buffers(input)
100
+ @gbuf = input.int
101
+ @vbuf = input.int
102
+ @cbuf = input.int
103
+ @flop = input.int
104
+ @pixel = input.int
105
+ end
106
+
81
107
  def step_mode(pixel, mode)
82
108
  case pixel
83
109
  when 0 then start_group
@@ -19,6 +19,24 @@ module Badline
19
19
  # The pixel at which the next logged write takes effect.
20
20
  attr_reader :next_x
21
21
 
22
+ # The entries as pixel, register and value in turn, the seed with -1
23
+ # for a register the line hasn't logged, and a next pixel past the
24
+ # last entry as -1.
25
+ def save_state(out)
26
+ out.ints(@entries.flatten)
27
+ out.ints(@seed.map { |value| value.nil? ? -1 : value })
28
+ out.ints(@values).int(@cursor).int(@next_x.infinite? ? -1 : @next_x)
29
+ end
30
+
31
+ def load_state(input)
32
+ @entries.replace(input.ints.each_slice(3).to_a)
33
+ @seed.replace(input.ints.map { |value| value.negative? ? nil : value })
34
+ input.ints_into(@values)
35
+ @cursor = input.int
36
+ next_x = input.int
37
+ @next_x = next_x.negative? ? Float::INFINITY : next_x
38
+ end
39
+
22
40
  def empty? = @entries.empty?
23
41
  def length = @entries.length
24
42
 
@@ -143,6 +143,15 @@ module Badline
143
143
 
144
144
  def latch_raster_irq! = latch_irq!(0x01)
145
145
 
146
+ def save_state(out)
147
+ out.blob(@bytes).boolean(@irq_line)
148
+ end
149
+
150
+ def load_state(input)
151
+ input.blob_into(@bytes)
152
+ @irq_line = input.boolean?
153
+ end
154
+
146
155
  def collide!(reg, bits)
147
156
  return false if bits.zero?
148
157
 
@@ -151,12 +160,14 @@ module Badline
151
160
  was_zero
152
161
  end
153
162
 
154
- private
155
-
163
+ # Works the IRQ line out from $D019 and $D01A again, after the bytes
164
+ # were set directly.
156
165
  def update_irq_line
157
166
  @irq_line = (@bytes[0x19] & @bytes[0x1a]).anybits?(0x0f)
158
167
  end
159
168
 
169
+ private
170
+
160
171
  def read_clear(reg)
161
172
  @bytes[reg].tap { @bytes[reg] = 0 }
162
173
  end
@@ -5,6 +5,7 @@ require "badline/vic/color_patches"
5
5
  require "badline/vic/graphics_mode"
6
6
  require "badline/vic/graphics_shifter"
7
7
  require "badline/vic/sequencer_output"
8
+ require "badline/vic/sequencer_state"
8
9
 
9
10
  module Badline
10
11
  class VIC < Cycleable
@@ -13,6 +14,7 @@ module Badline
13
14
  # Turns fetched graphics data into output pixels.
14
15
  class Sequencer
15
16
  include Output
17
+ include SavedState
16
18
 
17
19
  BORDER_Y_BOUNDS = [
18
20
  [55, 247].freeze,
@@ -149,6 +151,16 @@ module Badline
149
151
  # slot before is the byte the previous group loaded.
150
152
  attr_reader :ring_data, :ring_char, :ring_color
151
153
 
154
+ # The border flip-flops: vertical, the vertical one armed to set, and
155
+ # the main one.
156
+ def borders = [@vertical_border, @vertical_armed, @main_border]
157
+
158
+ def restore_borders(vertical, armed, main)
159
+ @vertical_border = vertical
160
+ @vertical_armed = armed
161
+ @main_border = main
162
+ end
163
+
152
164
  # Paints the byte in the slot through the current mode. An idle access
153
165
  # latches 0/0 and one that made no access passes on zero data with the
154
166
  # kept values; under a closed border neither has foreground. A group
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class VIC < Cycleable
5
+ class Sequencer
6
+ # The sequencer's save_state and load_state.
7
+ module SavedState
8
+ FG_SHIFTER = -1
9
+ PAIR_CODES = 256
10
+
11
+ # The line buffers, the border flip-flops, the g-access ring and the
12
+ # pixel pipeline. The foreground masks the groups point at go as
13
+ # FG_SHIFTER for the shift register's, or their data byte in
14
+ # GraphicsMode's HIRES_FG, or that plus 256 in its PAIR_FG. The left
15
+ # vertical border compare goes as -1 before it has run.
16
+ def save_state(out)
17
+ out.int(@line).boolean(@first_csel).int(@left_vertical_border.nil? ? -1 : bit(@left_vertical_border))
18
+ out.blob(@colors).booleans(@fg)
19
+ @border_mask.save_state(out)
20
+ out.ints(@cur_colors).int(fg_code(@cur_fg)).ints(@prev_colors).int(fg_code(@prev_fg))
21
+ out.boolean(@vertical_border).boolean(@vertical_armed).boolean(@main_border)
22
+ @color_patches.save_state(out)
23
+ @shifter.save_state(out)
24
+ out.int(@xscroll).int(@last_shift).int(@last_mode).boolean(@settling).boolean(@prev_fresh).boolean(@check)
25
+ out.ints(@ring_data).ints(@ring_char).ints(@ring_color)
26
+ end
27
+
28
+ def load_state(input)
29
+ load_line(input)
30
+ @border_mask.load_state(input)
31
+ input.ints_into(@cur_colors)
32
+ @cur_fg = fg_pattern(input.int)
33
+ input.ints_into(@prev_colors)
34
+ @prev_fg = fg_pattern(input.int)
35
+ @vertical_border = input.boolean?
36
+ @vertical_armed = input.boolean?
37
+ @main_border = input.boolean?
38
+ @color_patches.load_state(input)
39
+ @shifter.load_state(input)
40
+ load_pipeline(input)
41
+ end
42
+
43
+ private
44
+
45
+ def bit(flag) = flag ? 1 : 0
46
+
47
+ def load_line(input)
48
+ @line = input.int
49
+ @first_csel = input.boolean?
50
+ left = input.int
51
+ @left_vertical_border = left.negative? ? nil : left == 1
52
+ input.blob_into(@colors)
53
+ input.booleans_into(@fg)
54
+ end
55
+
56
+ def load_pipeline(input)
57
+ @xscroll = input.int
58
+ @last_shift = input.int
59
+ @last_mode = input.int
60
+ @settling = input.boolean?
61
+ @prev_fresh = input.boolean?
62
+ @check = input.boolean?
63
+ input.ints_into(@ring_data)
64
+ input.ints_into(@ring_char)
65
+ input.ints_into(@ring_color)
66
+ end
67
+
68
+ def fg_code(pattern)
69
+ return FG_SHIFTER if pattern.equal?(@shifter.fg)
70
+
71
+ hires = GraphicsMode::HIRES_FG.index { |fg| fg.equal?(pattern) }
72
+ return hires if hires
73
+
74
+ pair = GraphicsMode::PAIR_FG.index { |fg| fg.equal?(pattern) }
75
+ raise ArgumentError, "a foreground mask the sequencer doesn't know" unless pair
76
+
77
+ PAIR_CODES + pair
78
+ end
79
+
80
+ def fg_pattern(code)
81
+ return @shifter.fg if code == FG_SHIFTER
82
+ return GraphicsMode::HIRES_FG.fetch(code) if code < PAIR_CODES
83
+
84
+ GraphicsMode::PAIR_FG.fetch(code - PAIR_CODES)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -25,6 +25,17 @@ module Badline
25
25
  @line = Array.new(columns, -1)
26
26
  end
27
27
 
28
+ def save_state(out)
29
+ out.int(@lines).int(@ghost).ints(@value).ints(@line)
30
+ end
31
+
32
+ def load_state(input)
33
+ @lines = input.int
34
+ @ghost = input.int
35
+ input.ints_into(@value)
36
+ input.ints_into(@line)
37
+ end
38
+
28
39
  # Sprites 3-7 fetch in Bauer cycles 1-10, so the idle byte is taken
29
40
  # as the line starts.
30
41
  def start_line(sprites_enabled)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class VIC < Cycleable
5
+ class Sprite
6
+ # The sprite's save_state and load_state.
7
+ module SavedState
8
+ # The shift register, MC, MCBASE, the expansion flip-flop and whether
9
+ # the sprite shows, for a VICE snapshot.
10
+ attr_reader :sr, :mc, :mcbase, :exp_ff, :display_on
11
+
12
+ # Sets MC, MCBASE, the expansion flip-flop, whether the sprite's DMA
13
+ # runs and whether it shows, as a VICE snapshot gives them.
14
+ def restore_counters(counter, base, expanded, dma, shows)
15
+ @mc = counter
16
+ @mcbase = base
17
+ @exp_ff = expanded
18
+ @dma = dma
19
+ @display_on = shows
20
+ end
21
+
22
+ def save_state(out)
23
+ out.boolean(@dma).boolean(@display_on).int(@mcbase).int(@mc).boolean(@exp_ff).optional_int(@crunched_mc)
24
+ out.int(@bits).boolean(@row_ready).optional_int(@prev_bits).boolean(@first_byte_lost).boolean(@blind)
25
+ out.int(@show_from).optional_int(@stop_x)
26
+ out.ints(@codes).int(@leftmost).int(@span).ints(@reload_codes).int(@reload_leftmost).int(@reload_span)
27
+ out.int(@sr).int(@latch).boolean(@mc_flop).boolean(@xe_flop)
28
+ end
29
+
30
+ def load_state(input)
31
+ load_counters(input)
32
+ @show_from = input.int
33
+ @stop_x = input.optional_int
34
+ input.ints_into(@codes)
35
+ @leftmost = input.int
36
+ @span = input.int
37
+ input.ints_into(@reload_codes)
38
+ @reload_leftmost = input.int
39
+ @reload_span = input.int
40
+ @sr = input.int
41
+ @latch = input.int
42
+ @mc_flop = input.boolean?
43
+ @xe_flop = input.boolean?
44
+ end
45
+
46
+ private
47
+
48
+ def load_counters(input)
49
+ @dma = input.boolean?
50
+ @display_on = input.boolean?
51
+ @mcbase = input.int
52
+ @mc = input.int
53
+ @exp_ff = input.boolean?
54
+ @crunched_mc = input.optional_int
55
+ @bits = input.int
56
+ @row_ready = input.boolean?
57
+ @prev_bits = input.optional_int
58
+ @first_byte_lost = input.boolean?
59
+ @blind = input.boolean?
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "badline/vic/sprite/internal_bus"
4
4
  require "badline/vic/sprite/shifter"
5
+ require "badline/vic/sprite/saved_state"
5
6
  require "badline/vic/sprite/timing"
6
7
 
7
8
  module Badline
@@ -51,6 +52,7 @@ module Badline
51
52
  DISPLAY_OFF_X = 460
52
53
 
53
54
  include Shifter
55
+ include SavedState
54
56
 
55
57
  attr_reader :index, :leftmost, :span, :codes,
56
58
  :reload_leftmost, :reload_span, :reload_codes
@@ -3,10 +3,13 @@
3
3
  require "badline/vic/collisions"
4
4
  require "badline/vic/register_log"
5
5
  require "badline/vic/sprite"
6
+ require "badline/vic/sprites_state"
6
7
 
7
8
  module Badline
8
9
  class VIC < Cycleable
9
10
  class Sprites
11
+ include SavedState
12
+
10
13
  # Pixels between the start of the cycle following a write and the
11
14
  # point the new value shows. Each signal reaches the output on its own
12
15
  # path: the colors feed the final mux, the sequencer inputs — X