badline 0.16.0 → 0.17.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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/CLAUDE.md +1 -1
  4. data/README.md +130 -17
  5. data/doc/media.md +14 -2
  6. data/doc/pinned-behaviour.md +61 -0
  7. data/doc/snapshot-format.md +379 -70
  8. data/doc/snapshots.md +185 -10
  9. data/lib/badline/cbm2/acia.rb +10 -0
  10. data/lib/badline/cbm2/bus.rb +263 -0
  11. data/lib/badline/cbm2/cpu.rb +58 -0
  12. data/lib/badline/cbm2/crtc_video.rb +133 -0
  13. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  14. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  15. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  16. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  17. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  18. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  19. data/lib/badline/cbm2/keyed_state.rb +91 -0
  20. data/lib/badline/cbm2/model.rb +44 -0
  21. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  22. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  23. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  24. data/lib/badline/cbm2/p500/model.rb +39 -0
  25. data/lib/badline/cbm2/p500/pages.rb +57 -0
  26. data/lib/badline/cbm2/p500.rb +262 -0
  27. data/lib/badline/cbm2/sid_page.rb +19 -0
  28. data/lib/badline/cbm2.rb +267 -0
  29. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  30. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  31. data/lib/badline/chips/acia.rb +307 -0
  32. data/lib/badline/chips/crtc.rb +3 -2
  33. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  34. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  35. data/lib/badline/chips/riot/timer.rb +80 -0
  36. data/lib/badline/chips/riot.rb +162 -0
  37. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  38. data/lib/badline/chips/rriot.rb +132 -0
  39. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  40. data/lib/badline/chips/tpi.rb +233 -0
  41. data/lib/badline/chips/vic/bank.rb +60 -5
  42. data/lib/badline/chips/vic/sprite.rb +4 -3
  43. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  44. data/lib/badline/drives/datasette.rb +27 -3
  45. data/lib/badline/drives/ieee_bus.rb +50 -4
  46. data/lib/badline/drives/model2031/ieee_port.rb +133 -0
  47. data/lib/badline/drives/model2031/keyed_state.rb +66 -0
  48. data/lib/badline/drives/model2031/parts.rb +25 -0
  49. data/lib/badline/drives/model2031.rb +92 -0
  50. data/lib/badline/drives/model8050/disk.rb +163 -0
  51. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  52. data/lib/badline/drives/model8050/idle.rb +177 -0
  53. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  54. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  55. data/lib/badline/drives/model8050/keyed_state.rb +160 -0
  56. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  57. data/lib/badline/drives/model8050/parts.rb +37 -0
  58. data/lib/badline/drives/model8050.rb +243 -0
  59. data/lib/badline/frontend/app.rb +10 -3
  60. data/lib/badline/frontend/boot.rb +1 -1
  61. data/lib/badline/frontend/controls.rb +51 -15
  62. data/lib/badline/frontend/gamepads.rb +3 -3
  63. data/lib/badline/frontend/keys.rb +61 -2
  64. data/lib/badline/frontend/media_slots.rb +1 -1
  65. data/lib/badline/frontend/menu/menu_media.rb +14 -11
  66. data/lib/badline/frontend/menu/menu_pages.rb +29 -6
  67. data/lib/badline/frontend/menu/menu_ports.rb +40 -4
  68. data/lib/badline/frontend/menu/pause_menu.rb +1 -1
  69. data/lib/badline/frontend/pet_keyboard.rb +23 -7
  70. data/lib/badline/frontend/timeline.rb +19 -10
  71. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  72. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  73. data/lib/badline/kernal_trap/dos.rb +18 -5
  74. data/lib/badline/kernal_trap/ieee.rb +7 -2
  75. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  76. data/lib/badline/machine/ieee_drives.rb +194 -0
  77. data/lib/badline/machine/settling.rb +4 -0
  78. data/lib/badline/machine.rb +26 -8
  79. data/lib/badline/media/cbm2_media.rb +29 -0
  80. data/lib/badline/media/pet_media.rb +5 -5
  81. data/lib/badline/media/program.rb +6 -3
  82. data/lib/badline/media/true_drive.rb +50 -8
  83. data/lib/badline/media.rb +21 -8
  84. data/lib/badline/options/family.rb +15 -3
  85. data/lib/badline/options/help.rb +24 -6
  86. data/lib/badline/options/table.rb +25 -5
  87. data/lib/badline/options/validation.rb +47 -7
  88. data/lib/badline/options.rb +48 -15
  89. data/lib/badline/pet/bus/expansion.rb +99 -5
  90. data/lib/badline/pet/bus.rb +30 -12
  91. data/lib/badline/pet/cassettes.rb +22 -1
  92. data/lib/badline/pet/characters.rb +8 -2
  93. data/lib/badline/pet/crtc_video.rb +20 -7
  94. data/lib/badline/pet/ieee_drive.rb +17 -0
  95. data/lib/badline/pet/ieee_ports.rb +19 -1
  96. data/lib/badline/pet/ieee_traps.rb +10 -5
  97. data/lib/badline/pet/keyed_modules.rb +4 -1
  98. data/lib/badline/pet/keyed_parts.rb +10 -6
  99. data/lib/badline/pet/model.rb +56 -6
  100. data/lib/badline/pet/rom_sets.rb +30 -6
  101. data/lib/badline/pet.rb +45 -16
  102. data/lib/badline/plus4/acia.rb +4 -298
  103. data/lib/badline/plus4/keyed_parts.rb +0 -44
  104. data/lib/badline/plus4.rb +1 -0
  105. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  106. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  107. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  108. data/lib/badline/roms/README +144 -1
  109. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  110. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  111. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  112. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  113. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  114. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  115. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  116. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  117. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  118. data/lib/badline/roms/dos2031.rom +0 -0
  119. data/lib/badline/roms/pet/characters-din.rom +0 -0
  120. data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
  121. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  122. data/lib/badline/snapshot/container.rb +9 -1
  123. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  124. data/lib/badline/snapshot/field_doc.rb +39 -3
  125. data/lib/badline/snapshot/image.rb +79 -15
  126. data/lib/badline/snapshot/pet_setup.rb +30 -13
  127. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  128. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  129. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  130. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  131. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  132. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  133. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  134. data/lib/badline/snapshot/vice/cias.rb +13 -9
  135. data/lib/badline/snapshot/vice/p500.rb +96 -0
  136. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  137. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  138. data/lib/badline/snapshot/vice/pet.rb +121 -0
  139. data/lib/badline/snapshot/vice/pet_cpu.rb +70 -0
  140. data/lib/badline/snapshot/vice/pet_crtc.rb +241 -0
  141. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  142. data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
  143. data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
  144. data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
  145. data/lib/badline/snapshot/vice/pet_restore.rb +214 -0
  146. data/lib/badline/snapshot/vice/pet_via.rb +165 -0
  147. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  148. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  149. data/lib/badline/snapshot/vice.rb +4 -0
  150. data/lib/badline/snapshot.rb +11 -3
  151. data/lib/badline/storage/crt_file.rb +7 -1
  152. data/lib/badline/storage/d64_image.rb +7 -0
  153. data/lib/badline/storage/d80_image.rb +17 -1
  154. data/lib/badline/storage/d81_image.rb +14 -0
  155. data/lib/badline/storage/disk_image/bam.rb +18 -3
  156. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  157. data/lib/badline/storage/disk_image/writing.rb +35 -0
  158. data/lib/badline/storage/disk_image.rb +2 -0
  159. data/lib/badline/storage.rb +1 -0
  160. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  161. data/lib/badline/support/p500_checkpoint.rb +35 -0
  162. data/lib/badline/version.rb +1 -1
  163. data/lib/badline.rb +1 -0
  164. metadata +87 -2
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module P500
7
+ # VIC-II 1.3 as xcbm5x0 writes it, which runs VICE's line-based
8
+ # VIC-II rather than x64sc's, so the module is laid out otherwise
9
+ # than x64sc's under the same name: whether bad lines may happen,
10
+ # whether this line is one, the vertical border, the colour
11
+ # buffer, colour RAM, whether the sequencer is idle, the light
12
+ # pen's trigger and position, the video matrix buffer, the sprites
13
+ # whose DMA starts next line, a word xcbm5x0 keeps at $00100000,
14
+ # the beam's cycle and line, the registers, the sprite collisions
15
+ # pending, the sprites' DMA, the VIC's bank, VC, the matrix's
16
+ # width, VCBASE, then sprite and drawing fields with the bank
17
+ # again, the line its picture draws next and its size, and the
18
+ # picture. xcbm5x0's cycle in the line is badline's column.
19
+ #
20
+ # Written: the registers, colour RAM, the buffers, the beam with
21
+ # its counters and the sprites' DMA as badline's VIC has them, the
22
+ # bank as TPI2's PC6 and PC7 pick it, and the rest as xcbm5x0
23
+ # leaves it. The picture is left blank, as xcbm5x0 paints it again.
24
+ #
25
+ # Read: the registers, colour RAM, the buffers, the beam with VC,
26
+ # VCBASE and whether the sequencer is idle, the light pen and the
27
+ # sprites' DMA, through the x64sc module's reader (VICII::Import).
28
+ # The row counter follows from the line and YSCROLL, and the
29
+ # sprites' counters and the borders' flip-flops start afresh.
30
+ # xcbm5x0's VIC isn't x64sc's, so a program badline runs on from its
31
+ # snapshot reads the raster a cycle or so off now and then.
32
+ module VICII
33
+ NAME = Vice::VICII::NAME
34
+ MAJOR = 1
35
+ MINOR = 3
36
+ ALLOW_BAD_LINES = 0x000
37
+ BAD_LINE = 0x001
38
+ BLANK = 0x002
39
+ COLOR_BUFFER = 0x003
40
+ COLOR_RAM = 0x02b
41
+ IDLE = 0x42b
42
+ LIGHT_PEN = 0x42c
43
+ MATRIX_BUFFER = 0x42f
44
+ RAM_BASE = 0x458
45
+ CYCLE = 0x45c
46
+ LINE = 0x45d
47
+ REGISTERS = 0x45f
48
+ SPRITE_DMA = 0x4a0
49
+ BANK = 0x4a2
50
+ VC = 0x4a4
51
+ VC_ADD = 0x4a6
52
+ VC_BASE = 0x4a7
53
+ VIDEO_INT = 0x4a9
54
+ DRAW_FLAGS = 0x4cd
55
+ FETCH_BANK = 0x4cf
56
+ DRAWN_LINE = 0x4d1
57
+ PICTURE = 0x4d5
58
+ HEADER = 0x4e1
59
+ # The picture's size: PAL's and NTSC's.
60
+ SIZES = { 312 => [504, 312], 263 => [520, 275] }.freeze
61
+
62
+ module_function
63
+
64
+ def reads?(section) = section.major == MAJOR && section.minor == MINOR
65
+
66
+ # The lines xcbm5x0's picture has, which name its region.
67
+ def picture_lines(section) = FieldReader.new(section).skip(PICTURE + 4).dword
68
+
69
+ def export(machine)
70
+ vic = machine.vic
71
+ width, height = SIZES.fetch(vic.region.lines_per_frame)
72
+ bytes = Array.new(HEADER + (width * (height + 4)), 0)
73
+ sequencer(vic, bytes)
74
+ bytes[COLOR_RAM, 0x400] = Array.new(0x400) { |offset| machine.color_ram.nibble(0xd400 + offset) }
75
+ beam(vic, bytes)
76
+ bank = ((machine.tpi2.port_c_output >> 6) & 0x03) << 14
77
+ [BANK, FETCH_BANK].each { |at| word(bytes, at, bank) }
78
+ [RAM_BASE + 2, DRAW_FLAGS].each { |at| bytes[at] = 0x10 }
79
+ bytes[VIDEO_INT] = 1
80
+ [vic.rasterline, width, height, width].each_with_index do |value, n|
81
+ dword(bytes, DRAWN_LINE + (4 * n), value)
82
+ end
83
+ FieldWriter.new.bytes(bytes).section(NAME, MAJOR, MINOR)
84
+ end
85
+
86
+ # The sequencer's state and the buffers it reads.
87
+ def sequencer(vic, bytes)
88
+ state = vic.display_state
89
+ bytes[ALLOW_BAD_LINES] = state.bad_lines_enabled ? 1 : 0
90
+ bytes[BAD_LINE] = state.bad_line_condition? ? 1 : 0
91
+ bytes[BLANK] = vic.sequencer.borders.first ? 1 : 0
92
+ bytes[COLOR_BUFFER, 40] = vic.color_buffer.map { |value| value || 0 }
93
+ bytes[IDLE] = state.display? ? 0 : 1
94
+ bytes[MATRIX_BUFFER, 40] = vic.character_buffer.map { |value| value || 0 }
95
+ end
96
+
97
+ # The beam, the registers and the counters.
98
+ def beam(vic, bytes)
99
+ state = vic.display_state
100
+ bytes[CYCLE] = vic.column
101
+ word(bytes, LINE, vic.rasterline)
102
+ bytes[REGISTERS, 0x40] = vic.register_file
103
+ bytes[SPRITE_DMA] = 8.times.sum { |n| vic.sprites[n].displaying? ? 1 << n : 0 }
104
+ word(bytes, VC, state.vc)
105
+ bytes[VC_ADD] = 40
106
+ word(bytes, VC_BASE, state.vc_base)
107
+ end
108
+
109
+ def word(bytes, at, value) = bytes[at, 2] = [value & 0xffff].pack("v").bytes
110
+
111
+ def dword(bytes, at, value) = bytes[at, 4] = [value].pack("V").bytes
112
+
113
+ def import(section, vic, color_ram)
114
+ Import.new(section, vic.region).apply_to(vic, color_ram, 0xd400)
115
+ end
116
+
117
+ # Reads xcbm5x0's module into the fields x64sc's reader puts into
118
+ # badline's VIC.
119
+ class Import < Vice::VICII::Import
120
+ # x64sc's cycle is badline's column plus one, its cycle 0 the
121
+ # last column of the line before (VICII.vice_cycle).
122
+ def initialize(section, region)
123
+ super(nil)
124
+ bytes = section.data.bytes
125
+ @registers = bytes[REGISTERS, 0x40]
126
+ line = bytes[LINE] | (bytes[LINE + 1] << 8)
127
+ @cycle = (bytes[CYCLE] + 1) % region.cycles_per_line
128
+ @line = @cycle.zero? ? (line + 1) % region.lines_per_frame : line
129
+ @frame_start = false
130
+ @irq_status = @registers[0x19]
131
+ @raster_match = false
132
+ @character_buffer = bytes[MATRIX_BUFFER, 40]
133
+ @color_buffer = bytes[COLOR_BUFFER, 40]
134
+ read_old_counters(bytes)
135
+ @color_ram = bytes[COLOR_RAM, 0x400]
136
+ @sprite_mc = Array.new(8, 0)
137
+ @sprite_mcbase = Array.new(8, 0)
138
+ @sprite_exp_ff = 0
139
+ end
140
+
141
+ private
142
+
143
+ def read_old_counters(bytes)
144
+ @bad_lines_enabled = !bytes[ALLOW_BAD_LINES].zero?
145
+ @collisions = [@registers[0x1e], @registers[0x1f]]
146
+ @display = bytes[IDLE].zero?
147
+ @vc_base = bytes[VC_BASE] | (bytes[VC_BASE + 1] << 8)
148
+ @vc = bytes[VC] | (bytes[VC + 1] << 8)
149
+ @rc = @display ? (@line - @registers[0x11]) & 0x07 : 7
150
+ @vmli = 0
151
+ @lp_low = false
152
+ @lp_triggered = !bytes[LIGHT_PEN].zero?
153
+ @fetch_d011 = @registers[0x11]
154
+ @dma_bits = bytes[SPRITE_DMA]
155
+ @display_bits = @dma_bits
156
+ blank = !bytes[BLANK].zero?
157
+ @vertical_border = blank
158
+ @vertical_armed = false
159
+ @main_border = blank
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/snapshot/vice/pet_cpu"
4
+ require "badline/snapshot/vice/pet_mem"
5
+ require "badline/snapshot/vice/pet_pias"
6
+ require "badline/snapshot/vice/pet_via"
7
+ require "badline/snapshot/vice/pet_crtc"
8
+ require "badline/snapshot/vice/pet_drive"
9
+ require "badline/snapshot/vice/pet_drive8050"
10
+ require "badline/snapshot/vice/pet_restore"
11
+
12
+ module Badline
13
+ module Snapshot
14
+ module Vice
15
+ # xpet's modules for the PET: the CPU, the RAM, the CRTC, both PIAs,
16
+ # the VIA and a true 2031, written from badline's machine and read
17
+ # into it. Each
18
+ # module's file says what maps and what is approximated. They go
19
+ # through the PET's own keyed fields (Keyed), which a save writes and a
20
+ # load reads, so they read no chip's insides.
21
+ module PET
22
+ # The models badline builds by the board PETMEM names (PETMem): the
23
+ # discrete board is a 2001's where xpet's settings say so, and a
24
+ # 3032 otherwise.
25
+ MODELS = { PETMem::CRTC40 => "4032", PETMem::CRTC80 => "8032", PETMem::CBM8096 => "8096",
26
+ PETMem::CBM8296 => "8296" }.freeze
27
+ # The modules xpet resets the machine without, for the parts badline
28
+ # doesn't model in VICE's terms, each as xpet leaves them with
29
+ # nothing attached: no file system drive open, both cassette ports
30
+ # on with a datasette in #1, and nothing in the user port.
31
+ PERIPHERALS = [
32
+ ["FSDRIVE", 0, 0, Array.new(258, 0)], ["TAPEPORT", 1, 0, [0x01, 0x01, 0x01]], ["USERPORT", 1, 0, [0x01, 0x00]]
33
+ ].freeze
34
+ # The cycles a frame below which a CRTC editor runs at 60 Hz.
35
+ SIXTY_HZ_FRAME = 18_333
36
+ # The 8032's 50 Hz editors by the cycles a line they set up: xpet's
37
+ # own, 901474-04 dated 0283, sets 64.
38
+ EDITOR_0283_LINE = 64
39
+
40
+ module_function
41
+
42
+ # The modules xpet 3.10 needs, in its order, describing `machine`,
43
+ # which Machine::Settling has brought between instructions, from
44
+ # its keyed modules, `sections`.
45
+ def export(machine, sections)
46
+ readers = {}
47
+ sections.each { |section| readers[section.name] = Keyed::Reader.parse(section) }
48
+ [MainCPU.export(machine), CPU6809.export(machine), PETMem.export(machine),
49
+ CRTC.export(machine, reader(readers, "BL-CRTC"), reader(readers, "BL-VIDEO")),
50
+ PIAs.export("PIA1", machine.pia1, reader(readers, "BL-PIA1")),
51
+ PIAs.export("PIA2", machine.pia2, reader(readers, "BL-PIA2")), VIA.export(reader(readers, "BL-VIA")),
52
+ *Drive2031.export(machine, readers), *Drive8050.export(machine, readers)] +
53
+ PERIPHERALS.map { |name, major, minor, bytes| FieldWriter.new.bytes(bytes).section(name, major, minor) }
54
+ end
55
+
56
+ def reader(readers, name) = readers[name] || Keyed::Reader.missing(name)
57
+
58
+ # How the machine xpet saved was built: the model from PETMEM's
59
+ # board, the RAM, and on a CRTC board the editor's frame rate, and
60
+ # for an 8032's 50 Hz editor which one, from the CRTC's registers.
61
+ # xpet doesn't save the keyboard, the ROMs or an 8296's jumpers, so
62
+ # a discrete board is a 3032 with the graphics keyboard unless it is
63
+ # a 2001, each model has its own ROMs, and an 8296 none of its
64
+ # jumpers, as xpet's own has.
65
+ def setup(container)
66
+ memory = memory(container)
67
+ ram_kb = PETMem.ram_kb(memory)
68
+ name = model_name(memory)
69
+ profile = Badline::PET::Model.named(name)
70
+ unless Badline::PET::Model.ram_fits?(profile, ram_kb)
71
+ raise FormatError, "#{memory}: a #{name} with #{ram_kb}K of RAM, which badline doesn't build"
72
+ end
73
+
74
+ crtc = container[CRTC::NAME]
75
+ crtc = nil unless crtc.nil? || CRTC.reads?(crtc)
76
+ refresh_hz = refresh_hz(crtc, profile)
77
+ profile = Badline::PET::Model.upgraded(profile, nil, editor(crtc, profile, refresh_hz))
78
+ PETSetup.new(model: name, ram_kb:, refresh_hz:, basic: profile.basic, editor: profile.editor(refresh_hz),
79
+ kernal: profile.kernal, characters: profile.characters, jumpers: "")
80
+ end
81
+
82
+ def memory(container)
83
+ section = container[PETMem::NAME]
84
+ raise FormatError, "the snapshot has no #{PETMem::NAME} module" if section.nil?
85
+ raise FormatError, "#{section}: badline doesn't read this version" unless PETMem.reads?(section)
86
+
87
+ section
88
+ end
89
+
90
+ def model_name(memory)
91
+ board = PETMem.board_of(memory)
92
+ return PETMem.pet2001?(memory) ? "2001" : "3032" if board == PETMem::DISCRETE
93
+
94
+ name = MODELS[board]
95
+ return name unless name.nil?
96
+
97
+ what = board == PETMem::SUPERPET ? "a SuperPET" : "board #{board}"
98
+ raise FormatError, "#{memory}: #{what}, which badline doesn't build"
99
+ end
100
+
101
+ # The editor's frame rate, from the cycles the CRTC's registers set
102
+ # up a frame.
103
+ def refresh_hz(crtc, profile)
104
+ return 60 unless profile.crtc?
105
+ return 50 if crtc.nil?
106
+
107
+ registers = CRTC.registers(crtc)
108
+ (registers[0] + 1) * CRTC.frame_lines(registers) > SIXTY_HZ_FRAME ? 50 : 60
109
+ end
110
+
111
+ # The editor by ROMSets' name where the model's own doesn't set up
112
+ # the CRTC's frame, or nil.
113
+ def editor(crtc, profile, refresh_hz)
114
+ return nil if crtc.nil? || refresh_hz != 50 || profile.video != :crtc80
115
+
116
+ CRTC.registers(crtc)[0] + 1 == EDITOR_0283_LINE ? "4-80-b-50hz-0283" : nil
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module PET
7
+ # The 6502 as xpet 3.10 saves it, MAINCPU 1.3: the clock, the
8
+ # registers, the last opcode and two words after it, then the IRQ
9
+ # and NMI bookkeeping: five clocks, the IRQ and NMI source counts and
10
+ # the pending interrupts. xpet's MAINCPU has no BA line to save.
11
+ #
12
+ # Written: the clock and the registers, which between instructions
13
+ # are the whole CPU, with the IRQ sources that pull the line and a
14
+ # pending IRQ. VICE's interrupt clocks are written as long past.
15
+ #
16
+ # Read: the clock and the registers. The CPU starts on an
17
+ # instruction boundary, as xpet's snapshot is taken on one.
18
+ module MainCPU
19
+ NAME = "MAINCPU"
20
+ MAJOR = 1
21
+ MINOR = 3
22
+ IK_IRQ = 0x02
23
+
24
+ module_function
25
+
26
+ def reads?(section) = section.major == MAJOR && section.minor == MINOR
27
+
28
+ def export(machine) = write(machine, [machine.pia1.irq?, machine.pia2.irq?, machine.via.irq?].count(true))
29
+
30
+ # The module for `machine`, whose CPU's IRQ line `irqs` sources
31
+ # pull.
32
+ def write(machine, irqs)
33
+ cpu = machine.cpu
34
+ fields = FieldWriter.new.qword(machine.cycles)
35
+ [cpu.a, cpu.x, cpu.y, cpu.stack_pointer].each { |register| fields.byte(register) }
36
+ fields.word(cpu.program_counter).byte(cpu.p)
37
+ fields.dword(0).dword(0).dword(0)
38
+ 5.times { fields.qword(0) }
39
+ fields.dword(irqs).dword(0).dword(irqs.positive? ? IK_IRQ : 0)
40
+ fields.section(NAME, MAJOR, MINOR)
41
+ end
42
+
43
+ # The clock, the registers by their BL-CPU keys, in a keyed
44
+ # module's writer.
45
+ def import(section, out)
46
+ fields = FieldReader.new(section)
47
+ clock = fields.qword
48
+ out.int("a", fields.byte).int("x", fields.byte).int("y", fields.byte).int("sp", fields.byte)
49
+ out.int("pc", fields.word).int("p", fields.byte).int("cycles", clock)
50
+ clock
51
+ end
52
+ end
53
+
54
+ # The SuperPET's 6809, CPU6809 1.1, which xpet saves on every model
55
+ # and won't load a snapshot without: the clock and the registers as
56
+ # an xpet with no SuperPET board leaves them. badline doesn't read
57
+ # it.
58
+ module CPU6809
59
+ NAME = "CPU6809"
60
+
61
+ module_function
62
+
63
+ def export(machine)
64
+ FieldWriter.new.qword(machine.cycles).zeros(11).byte(0x04).zeros(7).section(NAME, 1, 1)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module PET
7
+ # CRTC 1.2, which xpet saves on every model, the discrete boards'
8
+ # video too, as a 6545 with the registers fixed: the board's screen
9
+ # settings, the registers, the one selected, the beam's character,
10
+ # row and line in the row, whether the frame is in its vertical
11
+ # adjust, the row's start in screen RAM, the lines of vertical sync
12
+ # left, whether the rows show, the picture's size, the lines in the
13
+ # frame and the line in it, then xpet's picture.
14
+ #
15
+ # Written: the registers and the beam as badline's CRTC, or its
16
+ # discrete video, has them. The picture is left blank, as xpet
17
+ # paints it again.
18
+ #
19
+ # Read: the registers and the beam, with what follows from them: the
20
+ # memory address, the characters since horizontal sync and whether
21
+ # the character shows. The picture starts again with the next
22
+ # frame.
23
+ module CRTC
24
+ NAME = "CRTC"
25
+ MAJOR = 1
26
+ MINOR = 2
27
+ # The registers xpet saves, two more than badline's CRTC has.
28
+ REGISTERS = 20
29
+ # Where the selected register is in the module, and the beam after
30
+ # it.
31
+ SELECTED = 0x23
32
+ # The board's screen settings xpet saves ahead of the registers:
33
+ # the screen RAM's masks and the bits of the frame's start address
34
+ # it latches (BOARD_ATTRIBUTES), then the bytes a character reads
35
+ # and whether the board is a 2001's.
36
+ SETTINGS = [0xff, 0x03, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0xff, 0x1f].freeze
37
+ # The 8296's, whose screen RAM spans 4K.
38
+ SETTINGS_8296 = [0xff, 0x0f, 0x00, 0x20, 0x00, 0x20, 0xff, 0x0f, 0xff, 0x1f].freeze
39
+ # The 2001's and 3000 series' video as xpet's CRTC runs it: 64
40
+ # cycles a line, 32 rows of 8 lines and 4 more, the first 25 rows
41
+ # shown.
42
+ DISCRETE_REGISTERS = [0x3f, 0x28, 0x32, 0x08, 0x1f, 0x04, 0x19, 0x1d, 0x00, 0x07, 0x00, 0x00, 0x10, 0x00,
43
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].freeze
44
+ DISCRETE_HEIGHT = 216
45
+ DISCRETE_LINE = 64
46
+ DISCRETE_FRAME = DISCRETE_LINE * 260
47
+ # How many cycles badline's beam runs ahead of xpet's, where both
48
+ # drop the vertical drive line on the same cycle.
49
+ DISCRETE_LEAD = 20
50
+ CRTC_LEAD = -3
51
+ HEIGHT = 272
52
+ # A byte xpet writes after the picture's size.
53
+ AFTER_SIZE = 0x20
54
+ # The memory address's bits that pick a byte of screen RAM.
55
+ SCREEN_BITS = 0x0fff
56
+ # The bits above them, which the boards decode as attributes.
57
+ HIGH_BITS = 0x3000
58
+
59
+ # Where the beam is, in xpet's terms: the character, the row and
60
+ # its line, the lines of vertical sync left, whether the rows show,
61
+ # the row's start address and the line in the frame.
62
+ Beam = Data.define(:column, :row, :raster, :vsync, :shown, :start, :frame_line)
63
+
64
+ module_function
65
+
66
+ def reads?(section) = section.major == MAJOR && section.minor == MINOR
67
+
68
+ def width(model) = model.video == :crtc80 ? 704 : 384
69
+
70
+ def height(model) = model.video == :discrete ? DISCRETE_HEIGHT : HEIGHT
71
+
72
+ def columns(model) = model.video == :crtc80 ? 2 : 1
73
+
74
+ # The frame's start address bits xpet latches on the board: MA12,
75
+ # which inverts the 80-column boards' picture.
76
+ def attributes(model) = model.video == :crtc80 ? 0x1000 : 0
77
+
78
+ def export(machine, crtc_input, video_input)
79
+ model = machine.model
80
+ discrete = model.video == :discrete
81
+ registers = discrete ? DISCRETE_REGISTERS : crtc_registers(crtc_input)
82
+ beam = if discrete
83
+ discrete_beam(video_input)
84
+ else
85
+ crtc_beam(run_on(fresh_crtc(machine), crtc_input, -CRTC_LEAD % frame_cycles(registers)),
86
+ registers)
87
+ end
88
+ fields = FieldWriter.new.bytes(model.cbm8296? ? SETTINGS_8296 : SETTINGS).word(attributes(model))
89
+ fields.byte(0).byte(columns(model))
90
+ fields.byte(model.basic1? ? 1 : 0).bytes(registers).byte(discrete ? 0 : crtc_input.int("selected", 0))
91
+ beam_fields(fields, beam, registers, model)
92
+ picture(fields, model)
93
+ fields.section(NAME, MAJOR, MINOR)
94
+ end
95
+
96
+ def crtc_registers(input)
97
+ registers = input.bytes("registers", "".b).bytes
98
+ Array.new(REGISTERS) { |index| registers[index] || 0 }
99
+ end
100
+
101
+ def beam_fields(fields, beam, registers, model)
102
+ fields.byte(beam.column).byte(beam.row).byte(beam.raster).zeros(2).flag(beam.raster.zero?)
103
+ fields.word(beam.start & attributes(model)).word((beam.start & SCREEN_BITS) * columns(model))
104
+ fields.byte(beam.vsync).byte(0).flag(beam.shown)
105
+ fields.word(width(model)).word(height(model)).byte(AFTER_SIZE).zeros(3)
106
+ shown = registers[6] * (registers[9] + 1)
107
+ fields.byte([(height(model) - shown) / 2, 0].max).byte(0)
108
+ fields.word(frame_lines(registers)).word(beam.frame_line).byte(0)
109
+ end
110
+
111
+ # Blank, as xpet paints it again: the line it draws, its size twice
112
+ # over, and four lines more than it shows.
113
+ def picture(fields, model)
114
+ width = width(model)
115
+ height = height(model)
116
+ fields.dword(0).dword(width).dword(height).dword(width).zeros(width * (height + 4))
117
+ end
118
+
119
+ def frame_lines(registers) = ((registers[4] + 1) * (registers[9] + 1)) + registers[5]
120
+
121
+ def frame_cycles(registers) = (registers[0] + 1) * frame_lines(registers)
122
+
123
+ # The CRTC of a machine built as `machine` was, at power-on.
124
+ def fresh_crtc(machine) = PETSetup.of(machine).build.crtc
125
+
126
+ # The BL-CRTC fields of `crtc`, a CRTC of its own, loaded with those
127
+ # in `input` and run `cycles` cycles.
128
+ def run_on(crtc, input, cycles)
129
+ crtc.load_fields(input)
130
+ cycles.times { crtc.cycle! }
131
+ Keyed::Reader.parse(Keyed.section("BL-CRTC") { |out| crtc.save_fields(out) })
132
+ end
133
+
134
+ # Writes the BL-CRTC fields `input` holds to `out`.
135
+ def copy(input, out)
136
+ out.bytes("registers", input.bytes("registers", "".b)).int("selected", input.int("selected", 0))
137
+ %w[column since_hsync line row vsync_lines row_start address].each { |key| out.int(key, input.int(key, 0)) }
138
+ %w[adjusting row_display column_display].each { |key| out.bool(key, input.bool(key, false)) }
139
+ end
140
+
141
+ # Both move a row's start on at R1 of its last line.
142
+ def crtc_beam(input, registers)
143
+ column = input.int("column", 0)
144
+ row = input.int("row", 0)
145
+ line = input.int("line", 0)
146
+ adjusting = input.bool("adjusting", false)
147
+ start = input.int("row_start", 0)
148
+ rows = registers[4] + 1
149
+ lines = registers[9] + 1
150
+ Beam.new(column:, row: adjusting ? rows : row, raster: adjusting ? 0 : line,
151
+ vsync: input.int("vsync_lines", 0), shown: input.bool("row_display", true), start:,
152
+ frame_line: adjusting ? (rows * lines) + line : (row * lines) + line)
153
+ end
154
+
155
+ # Whether the row's start has moved on by `column` of `line`.
156
+ def moved?(line, column, registers)
157
+ line > registers[9] || (line == registers[9] && column > registers[1])
158
+ end
159
+
160
+ # The discrete video's line in the frame, in xpet's fixed rows: the
161
+ # rows show until line 200, vertical sync runs from row 29 for 16
162
+ # lines, and the 4 lines past row 31 are the vertical adjust.
163
+ def discrete_beam(input)
164
+ cycle = ((input.int("line", 0) * DISCRETE_LINE) + input.int("column", 0) - DISCRETE_LEAD) % DISCRETE_FRAME
165
+ line = cycle / DISCRETE_LINE
166
+ row = [line >> 3, 32].min
167
+ adjusting = row == 32
168
+ raster = adjusting ? 0 : line & 0x07
169
+ column = cycle % DISCRETE_LINE
170
+ sync = line - (DISCRETE_REGISTERS[7] * 8)
171
+ moved = !adjusting && moved?(raster, column, DISCRETE_REGISTERS)
172
+ Beam.new(column:, row:, raster:, vsync: sync.between?(0, 15) ? 16 - sync : 0, shown: line < 200,
173
+ start: 0x1000 + ((moved ? row + 1 : row) * 40), frame_line: line)
174
+ end
175
+
176
+ # The beam the module holds, with the row's start as the module
177
+ # has it: the attribute bits latched in the high word and the
178
+ # offset into screen RAM, in bytes, in the low.
179
+ def beam(section)
180
+ fields = FieldReader.new(section).skip(SELECTED + 1)
181
+ column, row, raster = fields.bytes(3)
182
+ fields.skip(3)
183
+ latched = fields.word
184
+ offset = fields.word
185
+ vsync = fields.byte
186
+ fields.skip(1)
187
+ shown = fields.flag?
188
+ fields.skip(12)
189
+ Beam.new(column:, row:, raster:, vsync:, shown:, start: (latched << 16) | offset,
190
+ frame_line: fields.word)
191
+ end
192
+
193
+ def registers(section) = FieldReader.new(section).skip(SETTINGS.length + 5).bytes(REGISTERS)
194
+
195
+ def selected(section) = FieldReader.new(section).skip(SELECTED).byte
196
+
197
+ # How many cycles into its line xpet's beam is. A beam whose line
198
+ # has moved on a cycle before it starts holds -1.
199
+ def column_of(section)
200
+ column = beam(section).column
201
+ column < 0x80 ? column : column - 0x100
202
+ end
203
+
204
+ # The CRTC's fields by their BL-CRTC keys at the start of the line
205
+ # the module's beam is in (#column_of), in a keyed module's writer,
206
+ # for a board that reads `columns` bytes a character with
207
+ # `attributes` latched from the frame's start.
208
+ def import(section, out, columns, attributes)
209
+ registers = registers(section)
210
+ beam = beam(section)
211
+ rows = registers[4] + 1
212
+ adjusting = beam.row >= rows
213
+ line = adjusting ? beam.frame_line - (rows * (registers[9] + 1)) : beam.raster
214
+ start = start_address(beam.start, registers, columns, attributes)
215
+ start = (start - registers[1]) & 0x3fff if !adjusting && moved?(line, column_of(section), registers)
216
+ out.bytes("registers", registers.first(Chips::CRTC::MASKS.length)).int("selected", selected(section) & 0x1f)
217
+ out.int("column", 0).int("line", [line, 0].max).int("row", adjusting ? rows - 1 : beam.row)
218
+ out.int("since_hsync", registers[0] + 1 - registers[2]).int("vsync_lines", beam.vsync)
219
+ out.bool("adjusting", adjusting).bool("row_display", beam.shown).bool("column_display", true)
220
+ out.int("row_start", start).int("address", start)
221
+ end
222
+
223
+ # The row's start address from the module's (#beam): the bits
224
+ # that pick screen RAM from its offset, the attribute bits the
225
+ # board latches from its high word, and those above from R12.
226
+ def start_address(start, registers, columns, attributes)
227
+ high = (registers[12] << 8) & HIGH_BITS & ~attributes
228
+ high | ((start >> 16) & attributes) | (((start & 0xffff) / columns) & SCREEN_BITS)
229
+ end
230
+
231
+ # The discrete video's fields by their BL-VIDEO keys, in a keyed
232
+ # module's writer.
233
+ def import_discrete(section, out)
234
+ cycle = ((beam(section).frame_line * DISCRETE_LINE) + column_of(section) + DISCRETE_LEAD) % DISCRETE_FRAME
235
+ out.int("column", cycle % DISCRETE_LINE).int("line", cycle / DISCRETE_LINE)
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end