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
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/ted/timers"
4
+ require "badline/chips/ted/beam"
5
+
6
+ module Badline
7
+ module Chips
8
+ # The TED of the Commodore 264 series, the 7360 and 8360: its registers
9
+ # at $FF00-$FF1F, the raster, the three timers, the interrupt, the
10
+ # keyboard latch and the clock it gives the CPU (Beam). It paints no
11
+ # picture and plays no sound yet: those registers keep what is written
12
+ # to them.
13
+ #
14
+ # The machine clocks it once a single clock cycle, 57 to a line, and
15
+ # asks it then how many cycles the CPU runs in that one (#cpu_slots)
16
+ # and whether BA is low (#ba_low?).
17
+ #
18
+ # $FF08 is the keyboard latch: a write latches the K0-K7 inputs, which
19
+ # the keyboard port answers for the byte written (KeyboardPort#latch),
20
+ # and a read returns them. $FF09 holds the interrupt flags, each
21
+ # cleared by writing a 1 to it, with bit 7 set while the interrupt
22
+ # line is pulled. $FF0A holds the mask and bit 8 of the raster compare,
23
+ # and $FF0B the compare's low byte. $FF13 bit 0 reads whether the CPU
24
+ # sees ROM at $8000-$FFFF (#rom_selected=). $FF1C and $FF1D read and
25
+ # set the raster line, and $FF1E reads the horizontal position's top 8
26
+ # bits.
27
+ class TED
28
+ include Timers
29
+ include Beam
30
+
31
+ # The clock a TED is fed, its single clock: the crystal divided by 20
32
+ # on PAL machines and by 16 on NTSC ones (the Plus/4 hardware manual's
33
+ # TED datasheet).
34
+ Profile = Data.define(:name, :clock_hz)
35
+
36
+ PAL = Profile.new(name: :pal, clock_hz: 886_724)
37
+ NTSC = Profile.new(name: :ntsc, clock_hz: 894_886)
38
+
39
+ RASTER_FLAG = 0x02
40
+
41
+ # The interrupt sources a mask bit can enable: the raster, the light
42
+ # pen and the three timers.
43
+ SOURCES = 0x5e
44
+
45
+ attr_reader :profile, :interrupt_flags, :interrupt_mask
46
+
47
+ # What answers the keyboard latch: an object with #latch(value).
48
+ attr_writer :keyboard_port
49
+
50
+ # Whether the CPU sees ROM at $8000-$FFFF, which $FF13 bit 0 reads.
51
+ attr_writer :rom_selected
52
+
53
+ def initialize(profile = PAL)
54
+ @profile = profile
55
+ @keyboard_port = nil
56
+ @rom_selected = true
57
+ power_on!
58
+ end
59
+
60
+ def power_on!
61
+ @registers = Array.new(0x20, 0)
62
+ @interrupt_flags = 0
63
+ @interrupt_mask = 0
64
+ @raster_compare = 0
65
+ @key_latch = 0xff
66
+ reset_timers
67
+ reset_beam
68
+ end
69
+
70
+ # Counts the timers and moves the beam on a cycle, unless $FF07's
71
+ # freeze bit holds them, then decides the CPU's clock for the cycle.
72
+ def cycle!
73
+ unless frozen?
74
+ count_timers
75
+ move_beam
76
+ end
77
+ decide_clock
78
+ end
79
+
80
+ # Whether the interrupt line is pulled: a flag raised whose mask bit
81
+ # is set.
82
+ def irq? = @interrupt_flags.anybits?(@interrupt_mask & SOURCES)
83
+
84
+ def peek(addr)
85
+ register = addr & 0x3f
86
+ return 0xff if register >= 0x20
87
+
88
+ case register
89
+ when 0x00..0x05 then read_timer(register)
90
+ when 0x08 then @key_latch
91
+ when 0x09 then read_interrupt_flags
92
+ when 0x0a then @interrupt_mask | 0xa0
93
+ when 0x0b then @raster_compare & 0xff
94
+ when 0x13 then (@registers[0x13] & 0xfe) | (@rom_selected ? 1 : 0)
95
+ when 0x1c then 0xfe | (@line >> 8)
96
+ when 0x1d then @line & 0xff
97
+ when 0x1e then @column << 2
98
+ else @registers[register]
99
+ end
100
+ end
101
+
102
+ def poke(addr, value)
103
+ register = addr & 0x3f
104
+ return if register >= 0x20
105
+
106
+ @registers[register] = value
107
+ case register
108
+ when 0x00..0x05 then write_timer(register, value)
109
+ when 0x08 then @key_latch = @keyboard_port ? @keyboard_port.latch(value) : 0xff
110
+ when 0x09 then @interrupt_flags &= ~value
111
+ when 0x0a then write_interrupt_mask(value)
112
+ when 0x0b then @raster_compare = (@raster_compare & 0x100) | value
113
+ when 0x1c then self.line = (@line & 0xff) | ((value & 0x01) << 8)
114
+ when 0x1d then self.line = (@line & 0x100) | value
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ # $FF07 bit 5 stops the beam and the timers.
121
+ def frozen? = @registers[0x07].anybits?(0x20)
122
+
123
+ def read_interrupt_flags
124
+ value = @interrupt_flags | 0x21
125
+ irq? ? value | 0x80 : value
126
+ end
127
+
128
+ def write_interrupt_mask(value)
129
+ @interrupt_mask = value & 0x5f
130
+ @raster_compare = (@raster_compare & 0xff) | ((value & 0x01) << 8)
131
+ end
132
+
133
+ def line=(value)
134
+ @line = value
135
+ raster_reached
136
+ end
137
+
138
+ def raster_reached
139
+ @interrupt_flags |= RASTER_FLAG if @line == @raster_compare
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ # The 6523 TIA, a tri-port interface adapter: three 8-bit ports, A, B
6
+ # and C at registers 0-2, each with its direction register at 3-5, a 1
7
+ # making the pin an output. Its three address lines repeat the
8
+ # registers every 8 bytes, and registers 6 and 7 read $FF.
9
+ #
10
+ # A pin read as an output returns what the port drives, and one read as
11
+ # an input returns what the other side drives, #inputs, per port.
12
+ class TIA
13
+ attr_reader :ports, :directions
14
+
15
+ # What the other side drives onto each port's pins.
16
+ attr_reader :inputs
17
+
18
+ def initialize
19
+ @inputs = [0, 0, 0]
20
+ reset!
21
+ end
22
+
23
+ def reset!
24
+ @ports = [0, 0, 0]
25
+ @directions = [0, 0, 0]
26
+ end
27
+
28
+ def peek(addr)
29
+ register = addr & 0x07
30
+ return 0xff if register > 5
31
+ return @directions[register - 3] if register >= 3
32
+
33
+ direction = @directions[register]
34
+ (@ports[register] & direction) | (@inputs[register] & ~direction & 0xff)
35
+ end
36
+
37
+ def poke(addr, value)
38
+ register = addr & 0x07
39
+ return if register > 5
40
+
41
+ if register >= 3
42
+ @directions[register - 3] = value
43
+ else
44
+ @ports[register] = value
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class VDC
6
+ class Raster
7
+ # The raster's fields in a keyed snapshot module, under `prefix`:
8
+ # the frame count, where the beam is in the character rows and the
9
+ # frame, and the frame's size and sync as it latched them.
10
+ module KeyedState
11
+ def save_fields(out, prefix)
12
+ out.int("#{prefix}frame", @frame).int("#{prefix}row", @row).int("#{prefix}row_line", @row_line)
13
+ out.bool("#{prefix}adjusting", @adjusting).int("#{prefix}frame_line", @frame_line)
14
+ out.int("#{prefix}frame_lines", @frame_lines).int("#{prefix}sync_start", @sync_start)
15
+ out.int("#{prefix}sync_lines", @sync_lines)
16
+ end
17
+
18
+ def load_fields(input, prefix)
19
+ @frame = input.int("#{prefix}frame", @frame)
20
+ @row = input.int("#{prefix}row", @row)
21
+ @row_line = input.int("#{prefix}row_line", @row_line)
22
+ @adjusting = input.bool("#{prefix}adjusting", @adjusting)
23
+ @frame_line = input.int("#{prefix}frame_line", @frame_line)
24
+ @frame_lines = input.int("#{prefix}frame_lines", @frame_lines)
25
+ @sync_start = input.int("#{prefix}sync_start", @sync_start)
26
+ @sync_lines = input.int("#{prefix}sync_lines", @sync_lines)
27
+ end
28
+ end
29
+
30
+ include KeyedState
31
+ end
32
+
33
+ class Window
34
+ # The start addresses the frame latched, under `prefix`.
35
+ module KeyedState
36
+ def save_fields(out, prefix)
37
+ out.int("#{prefix}display_start", @display_start).int("#{prefix}attribute_start", @attribute_start)
38
+ end
39
+
40
+ def load_fields(input, prefix)
41
+ @display_start = input.word("#{prefix}display_start", @display_start)
42
+ @attribute_start = input.word("#{prefix}attribute_start", @attribute_start)
43
+ end
44
+ end
45
+
46
+ include KeyedState
47
+ end
48
+
49
+ class Painter
50
+ # The display's size, as the frame latched it, and the window's
51
+ # start addresses. The picture is painted again.
52
+ module KeyedState
53
+ def save_fields(out)
54
+ out.int("display.width", @width).int("display.height", @height)
55
+ @window.save_fields(out, "window.")
56
+ end
57
+
58
+ def load_fields(input)
59
+ resize(input.int("display.width", @width), input.int("display.height", @height))
60
+ @window.load_fields(input, "window.")
61
+ end
62
+ end
63
+
64
+ include KeyedState
65
+ end
66
+
67
+ # The VDC's fields in a keyed snapshot module (Snapshot::Keyed): the
68
+ # registers, the RAM, the register selected, the data latches, the
69
+ # dot clock's phase and the line's length in the same units
70
+ # (dots·Hz, the line latched at its start), how long an access or
71
+ # block operation stays busy from now, the raster under raster.* and
72
+ # the display's size and window under display.* and window.*. The
73
+ # RAM's layout comes from R28 on load.
74
+ module KeyedState
75
+ def save_fields(out)
76
+ out.bytes("registers", @registers).bytes("ram", @memory.ram).int("selected", @selected)
77
+ out.int("read_latch", @read_latch).int("write_data", @write_data).int("phase", @phase)
78
+ out.int("line_units", @line_units).int("busy_for", @busy_until - @phase)
79
+ @raster.save_fields(out, "raster.")
80
+ @painter.save_fields(out)
81
+ out
82
+ end
83
+
84
+ def load_fields(input)
85
+ input.bytes_into("registers", @registers)
86
+ input.bytes_into("ram", @memory.ram)
87
+ @selected = input.masked("selected", @selected, 0x3f)
88
+ @read_latch = input.byte("read_latch", @read_latch)
89
+ @write_data = input.byte("write_data", @write_data)
90
+ @phase = input.int("phase", @phase)
91
+ @line_units = input.int("line_units", @line_units)
92
+ @busy_until = @phase + input.int("busy_for", @busy_until - @phase)
93
+ @raster.load_fields(input, "raster.")
94
+ @painter.load_fields(input)
95
+ end
96
+
97
+ def restored!
98
+ @memory.mode64 = @registers[28].anybits?(0x10)
99
+ end
100
+ end
101
+
102
+ include KeyedState
103
+ end
104
+ end
105
+ end
@@ -56,18 +56,6 @@ module Badline
56
56
  [0, 0, shown.clamp(1, @width), (frame_lines - sync_lines).clamp(1, @height)]
57
57
  end
58
58
 
59
- # The display's size and the window's start addresses. The display
60
- # starts again cleared, to be painted while the VDC renders.
61
- def save_state(out)
62
- out.int(@width).int(@height)
63
- @window.save_state(out)
64
- end
65
-
66
- def load_state(input)
67
- resize(input.int, input.int)
68
- @window.load_state(input)
69
- end
70
-
71
59
  def clear_dirty_lines!
72
60
  @dirty_lines.fill(false)
73
61
  end
@@ -30,22 +30,6 @@ module Badline
30
30
  # Outside the displayed rows, as status bit 5 reads.
31
31
  def vertical_blank? = @adjusting || @row >= @registers[6]
32
32
 
33
- def save_state(out)
34
- out.int(@frame).int(@row).int(@row_line).boolean(@adjusting).int(@frame_line).int(@frame_lines)
35
- out.int(@sync_start).int(@sync_lines)
36
- end
37
-
38
- def load_state(input)
39
- @frame = input.int
40
- @row = input.int
41
- @row_line = input.int
42
- @adjusting = input.boolean?
43
- @frame_line = input.int
44
- @frame_lines = input.int
45
- @sync_start = input.int
46
- @sync_lines = input.int
47
- end
48
-
49
33
  def end_line(render)
50
34
  paint_line if render
51
35
  @frame_line += 1
@@ -57,15 +57,6 @@ module Badline
57
57
  @attribute_start = (regs[20] << 8) | regs[21]
58
58
  end
59
59
 
60
- def save_state(out)
61
- out.int(@display_start).int(@attribute_start)
62
- end
63
-
64
- def load_state(input)
65
- @display_start = input.int
66
- @attribute_start = input.int
67
- end
68
-
69
60
  def paint(line, row, row_line, frame)
70
61
  regs = @registers
71
62
  lay_out_cell
@@ -4,7 +4,7 @@ require "badline/chips/vdc/memory"
4
4
  require "badline/chips/vdc/window"
5
5
  require "badline/chips/vdc/painter"
6
6
  require "badline/chips/vdc/raster"
7
- require "badline/chips/vdc/saved_state"
7
+ require "badline/chips/vdc/keyed_parts"
8
8
 
9
9
  module Badline
10
10
  module Chips
@@ -34,7 +34,6 @@ module Badline
34
34
  # fills or two per byte it copies.
35
35
  class VDC
36
36
  include Addressable
37
- include SavedState
38
37
 
39
38
  STATUS_READY = 0x80
40
39
  STATUS_VBLANK = 0x20
@@ -56,21 +56,6 @@ module Badline
56
56
 
57
57
  def active_c1_edge?(high) = high == @control.anybits?(0x01)
58
58
 
59
- # The lines' levels and mode. Which flags they set, and whether reads
60
- # handshake, are the port's wiring.
61
- def save_state(out)
62
- out.int(@control).boolean(@c1_high).boolean(@c2_high).boolean(@handshake_low).int(@pulse).boolean(@c2_output)
63
- end
64
-
65
- def load_state(input)
66
- @control = input.int
67
- @c1_high = input.boolean?
68
- @c2_high = input.boolean?
69
- @handshake_low = input.boolean?
70
- @pulse = input.int
71
- @c2_output = input.boolean?
72
- end
73
-
74
59
  # Everything the lines hold, for comparing them at two points.
75
60
  def state = [@control, @c1_high, @c2_high, @handshake_low, @pulse, @c2_output]
76
61
 
@@ -25,15 +25,6 @@ module Badline
25
25
  @enable = 0x00
26
26
  end
27
27
 
28
- def save_state(out)
29
- out.int(@flags).int(@enable)
30
- end
31
-
32
- def load_state(input)
33
- @flags = input.int
34
- @enable = input.int
35
- end
36
-
37
28
  def set(bits)
38
29
  @flags |= bits
39
30
  end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class VIA
6
+ class Timer1
7
+ # Timer 1's fields in a keyed snapshot module, under `prefix`.
8
+ module KeyedState
9
+ def save_fields(out, prefix)
10
+ out.int("#{prefix}counter", @counter).int("#{prefix}latch", @latch).bool("#{prefix}hold", @hold)
11
+ out.bool("#{prefix}reload", @reload).bool("#{prefix}armed", @armed).bool("#{prefix}pb7", @pb7)
12
+ end
13
+
14
+ def load_fields(input, prefix)
15
+ @counter = input.word("#{prefix}counter", @counter)
16
+ @latch = input.word("#{prefix}latch", @latch)
17
+ @hold = input.bool("#{prefix}hold", @hold)
18
+ @reload = input.bool("#{prefix}reload", @reload)
19
+ @armed = input.bool("#{prefix}armed", @armed)
20
+ @pb7 = input.bool("#{prefix}pb7", @pb7)
21
+ end
22
+ end
23
+
24
+ include KeyedState
25
+ end
26
+
27
+ class Timer2
28
+ # Timer 2's fields in a keyed snapshot module, under `prefix`.
29
+ module KeyedState
30
+ def save_fields(out, prefix)
31
+ out.int("#{prefix}counter", @counter).int("#{prefix}latch_low", @latch_low).bool("#{prefix}hold", @hold)
32
+ out.bool("#{prefix}low_reload", @low_reload).bool("#{prefix}armed", @armed)
33
+ out.bool("#{prefix}low_underflowed", @low_underflowed).bool("#{prefix}count_pulses", @count_pulses)
34
+ out.bool("#{prefix}counting_pulses", @counting_pulses)
35
+ end
36
+
37
+ def load_fields(input, prefix)
38
+ @counter = input.word("#{prefix}counter", @counter)
39
+ @latch_low = input.byte("#{prefix}latch_low", @latch_low)
40
+ @hold = input.bool("#{prefix}hold", @hold)
41
+ @low_reload = input.bool("#{prefix}low_reload", @low_reload)
42
+ @armed = input.bool("#{prefix}armed", @armed)
43
+ @low_underflowed = input.bool("#{prefix}low_underflowed", @low_underflowed)
44
+ @count_pulses = input.bool("#{prefix}count_pulses", @count_pulses)
45
+ @counting_pulses = input.bool("#{prefix}counting_pulses", @counting_pulses)
46
+ end
47
+ end
48
+
49
+ include KeyedState
50
+ end
51
+
52
+ class ShiftRegister
53
+ # The shift register's fields in a keyed snapshot module, under
54
+ # `prefix`.
55
+ module KeyedState
56
+ def save_fields(out, prefix)
57
+ out.int("#{prefix}mode", @mode).int("#{prefix}data", @data).int("#{prefix}bits", @bits)
58
+ out.int("#{prefix}delay", @delay).bool("#{prefix}clock", @clock).bool("#{prefix}cb2", @cb2)
59
+ out.bool("#{prefix}cb2_input", @cb2_input)
60
+ end
61
+
62
+ def load_fields(input, prefix)
63
+ @mode = input.masked("#{prefix}mode", @mode, 0x07)
64
+ @data = input.byte("#{prefix}data", @data)
65
+ @bits = input.int("#{prefix}bits", @bits)
66
+ @delay = input.int("#{prefix}delay", @delay)
67
+ @clock = input.bool("#{prefix}clock", @clock)
68
+ @cb2 = input.bool("#{prefix}cb2", @cb2)
69
+ @cb2_input = input.bool("#{prefix}cb2_input", @cb2_input)
70
+ end
71
+ end
72
+
73
+ include KeyedState
74
+ end
75
+
76
+ class InterruptRegister
77
+ # The flags and the enable mask, under `prefix`.
78
+ module KeyedState
79
+ def save_fields(out, prefix)
80
+ out.int("#{prefix}flags", @flags).int("#{prefix}enable", @enable)
81
+ end
82
+
83
+ def load_fields(input, prefix)
84
+ @flags = input.masked("#{prefix}flags", @flags, 0x7f)
85
+ @enable = input.masked("#{prefix}enable", @enable, 0x7f)
86
+ end
87
+ end
88
+
89
+ include KeyedState
90
+ end
91
+
92
+ class ControlLines
93
+ # A pair of control lines' fields, under `prefix`.
94
+ module KeyedState
95
+ def save_fields(out, prefix)
96
+ out.int("#{prefix}control", @control).bool("#{prefix}c1_high", @c1_high).bool("#{prefix}c2_high", @c2_high)
97
+ out.bool("#{prefix}handshake_low", @handshake_low).int("#{prefix}pulse", @pulse)
98
+ out.bool("#{prefix}c2_output", @c2_output)
99
+ end
100
+
101
+ def load_fields(input, prefix)
102
+ @control = input.masked("#{prefix}control", @control, 0x0f)
103
+ @c1_high = input.bool("#{prefix}c1_high", @c1_high)
104
+ @c2_high = input.bool("#{prefix}c2_high", @c2_high)
105
+ @handshake_low = input.bool("#{prefix}handshake_low", @handshake_low)
106
+ @pulse = input.int("#{prefix}pulse", @pulse)
107
+ @c2_output = input.bool("#{prefix}c2_output", @c2_output)
108
+ end
109
+ end
110
+
111
+ include KeyedState
112
+ end
113
+
114
+ # The VIA's fields in a keyed snapshot module (Snapshot::Keyed): the
115
+ # registers and PB6, and its parts under t1., t2., sr., ifr., ca. and
116
+ # cb. Whether the shift register clocks off timer 2 comes from the
117
+ # ACR on load. The peripheral restores its own state.
118
+ module KeyedState
119
+ def save_fields(out)
120
+ out.int("ora", @ora).int("orb", @orb).int("ddra", @ddra).int("ddrb", @ddrb).int("latch_a", @latch_a)
121
+ out.int("latch_b", @latch_b).int("acr", @acr).int("pcr", @pcr).bool("pb6_high", @pb6_high)
122
+ @t1.save_fields(out, "t1.")
123
+ @t2.save_fields(out, "t2.")
124
+ @shift_register.save_fields(out, "sr.")
125
+ @ifr.save_fields(out, "ifr.")
126
+ @ca.save_fields(out, "ca.")
127
+ @cb.save_fields(out, "cb.")
128
+ out
129
+ end
130
+
131
+ def load_fields(input)
132
+ @ora = input.byte("ora", @ora)
133
+ @orb = input.byte("orb", @orb)
134
+ @ddra = input.byte("ddra", @ddra)
135
+ @ddrb = input.byte("ddrb", @ddrb)
136
+ @latch_a = input.byte("latch_a", @latch_a)
137
+ @latch_b = input.byte("latch_b", @latch_b)
138
+ @acr = input.byte("acr", @acr)
139
+ @pcr = input.byte("pcr", @pcr)
140
+ @pb6_high = input.bool("pb6_high", @pb6_high)
141
+ load_part_fields(input)
142
+ end
143
+
144
+ def restored!
145
+ @sr_uses_t2 = [1, 4, 5].include?((@acr >> 2) & 0x07)
146
+ end
147
+
148
+ private
149
+
150
+ def load_part_fields(input)
151
+ @t1.load_fields(input, "t1.")
152
+ @t2.load_fields(input, "t2.")
153
+ @shift_register.load_fields(input, "sr.")
154
+ @ifr.load_fields(input, "ifr.")
155
+ @ca.load_fields(input, "ca.")
156
+ @cb.load_fields(input, "cb.")
157
+ end
158
+ end
159
+
160
+ include KeyedState
161
+ end
162
+ end
163
+ end
@@ -81,20 +81,6 @@ module Badline
81
81
  # changes it from cycle to cycle.
82
82
  def clocking? = @bits.nonzero? && internal_clock?
83
83
 
84
- def save_state(out)
85
- out.int(@mode).int(@data).int(@bits).int(@delay).boolean(@clock).boolean(@cb2).boolean(@cb2_input)
86
- end
87
-
88
- def load_state(input)
89
- @mode = input.int
90
- @data = input.int
91
- @bits = input.int
92
- @delay = input.int
93
- @clock = input.boolean?
94
- @cb2 = input.boolean?
95
- @cb2_input = input.boolean?
96
- end
97
-
98
84
  # Everything the register holds, for comparing it at two points.
99
85
  def state = [@mode, @data, @bits, @delay, @clock, @cb2, @cb2_input]
100
86
 
@@ -93,19 +93,6 @@ module Badline
93
93
  @counter = @reload ? 0xffff : @latch - phase + 1
94
94
  end
95
95
 
96
- def save_state(out)
97
- out.int(@counter).int(@latch).boolean(@hold).boolean(@reload).boolean(@armed).boolean(@pb7)
98
- end
99
-
100
- def load_state(input)
101
- @counter = input.int
102
- @latch = input.int
103
- @hold = input.boolean?
104
- @reload = input.boolean?
105
- @armed = input.boolean?
106
- @pb7 = input.boolean?
107
- end
108
-
109
96
  # What the counter and PB7 don't hold, for comparing the timer at two
110
97
  # points.
111
98
  def state = [@latch, @armed]
@@ -85,24 +85,6 @@ module Badline
85
85
  @low_underflowed = @counter.allbits?(0xff)
86
86
  end
87
87
 
88
- def save_state(out)
89
- out.int(@counter).int(@latch_low)
90
- [@hold, @low_reload, @armed, @low_underflowed, @count_pulses, @counting_pulses].each do |flag|
91
- out.boolean(flag)
92
- end
93
- end
94
-
95
- def load_state(input)
96
- @counter = input.int
97
- @latch_low = input.int
98
- @hold = input.boolean?
99
- @low_reload = input.boolean?
100
- @armed = input.boolean?
101
- @low_underflowed = input.boolean?
102
- @count_pulses = input.boolean?
103
- @counting_pulses = input.boolean?
104
- end
105
-
106
88
  # What the counter and the low byte's underflow don't hold, for
107
89
  # comparing the timer at two points.
108
90
  def state = [@latch_low, @armed, @hold, @low_reload, @count_pulses, @counting_pulses]
@@ -4,9 +4,9 @@ require "badline/chips/via/control_lines"
4
4
  require "badline/chips/via/fast_forward"
5
5
  require "badline/chips/via/interrupt_register"
6
6
  require "badline/chips/via/shift_register"
7
- require "badline/chips/via/saved_state"
8
7
  require "badline/chips/via/timer1"
9
8
  require "badline/chips/via/timer2"
9
+ require "badline/chips/via/keyed_parts"
10
10
 
11
11
  module Badline
12
12
  module Chips
@@ -20,7 +20,6 @@ module Badline
20
20
  class VIA
21
21
  include Addressable
22
22
  include FastForward
23
- include SavedState
24
23
 
25
24
  attr_reader :start, :peripheral, :shift_register, :acr, :pcr
26
25
 
@@ -28,16 +28,6 @@ module Badline
28
28
  @groups.fill(FULL)
29
29
  end
30
30
 
31
- def save_state(out)
32
- out.ints(@groups).booleans(@mask).blob(@snapshot)
33
- end
34
-
35
- def load_state(input)
36
- input.ints_into(@groups)
37
- input.booleans_into(@mask)
38
- input.blob_into(@snapshot)
39
- end
40
-
41
31
  def at?(pixel_x)
42
32
  case @groups[pixel_x >> 3]
43
33
  when FULL then true