badline 0.15.1 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/CLAUDE.md +8 -7
  4. data/README.md +128 -9
  5. data/doc/media.md +9 -0
  6. data/doc/pinned-behaviour.md +67 -0
  7. data/doc/snapshot-format.md +215 -18
  8. data/doc/snapshots.md +55 -0
  9. data/lib/badline/c64/common.rb +1 -1
  10. data/lib/badline/chips/crtc/keyed_state.rb +46 -0
  11. data/lib/badline/chips/crtc.rb +163 -0
  12. data/lib/badline/chips/pia/control_lines.rb +138 -0
  13. data/lib/badline/chips/pia/keyed_parts.rb +56 -0
  14. data/lib/badline/chips/pia.rb +138 -0
  15. data/lib/badline/chips/vic/register_log.rb +1 -4
  16. data/lib/badline/core.rb +3 -0
  17. data/lib/badline/drives/ieee_bus.rb +74 -0
  18. data/lib/badline/drives/model1551/cable.rb +5 -2
  19. data/lib/badline/drives/model1551/tia.rb +13 -34
  20. data/lib/badline/drives/model1551.rb +26 -4
  21. data/lib/badline/drives/model2031/ieee_port.rb +133 -0
  22. data/lib/badline/drives/model2031/keyed_state.rb +66 -0
  23. data/lib/badline/drives/model2031/parts.rb +25 -0
  24. data/lib/badline/drives/model2031.rb +92 -0
  25. data/lib/badline/drives.rb +1 -0
  26. data/lib/badline/frontend/app.rb +1 -1
  27. data/lib/badline/frontend/controls.rb +23 -3
  28. data/lib/badline/frontend/gamepads.rb +10 -3
  29. data/lib/badline/frontend/keys.rb +88 -0
  30. data/lib/badline/frontend/media_slots.rb +1 -0
  31. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -1
  32. data/lib/badline/frontend/menu/menu_media.rb +27 -28
  33. data/lib/badline/frontend/menu/menu_pages.rb +24 -2
  34. data/lib/badline/frontend/menu/menu_ports.rb +22 -1
  35. data/lib/badline/frontend/menu/pause_menu.rb +1 -0
  36. data/lib/badline/frontend/pet_keyboard.rb +124 -0
  37. data/lib/badline/frontend/screens.rb +2 -1
  38. data/lib/badline/frontend/timeline.rb +16 -9
  39. data/lib/badline/frontend.rb +1 -0
  40. data/lib/badline/kernal_trap/dos/writes.rb +3 -1
  41. data/lib/badline/kernal_trap/dos.rb +6 -1
  42. data/lib/badline/kernal_trap/frames.rb +117 -0
  43. data/lib/badline/kernal_trap/ieee.rb +151 -0
  44. data/lib/badline/kernal_trap/ieee_layout.rb +113 -0
  45. data/lib/badline/kernal_trap/keyed_state.rb +4 -4
  46. data/lib/badline/kernal_trap/routine.rb +3 -53
  47. data/lib/badline/kernal_trap/serial.rb +4 -102
  48. data/lib/badline/kernal_trap/steps.rb +64 -0
  49. data/lib/badline/kernal_trap.rb +4 -0
  50. data/lib/badline/machine/keyboard_buffer.rb +8 -0
  51. data/lib/badline/machine/settling.rb +1 -1
  52. data/lib/badline/machine.rb +29 -6
  53. data/lib/badline/media/extensions.rb +5 -2
  54. data/lib/badline/media/pet_media.rb +40 -0
  55. data/lib/badline/media/program.rb +14 -2
  56. data/lib/badline/media/true_drive.rb +19 -6
  57. data/lib/badline/media/vic20_media.rb +2 -2
  58. data/lib/badline/media.rb +41 -17
  59. data/lib/badline/options/family.rb +17 -6
  60. data/lib/badline/options/help.rb +23 -3
  61. data/lib/badline/options/table.rb +22 -4
  62. data/lib/badline/options/validation.rb +84 -5
  63. data/lib/badline/options.rb +47 -16
  64. data/lib/badline/pet/bus/expansion.rb +200 -0
  65. data/lib/badline/pet/bus.rb +222 -0
  66. data/lib/badline/pet/canvas.rb +97 -0
  67. data/lib/badline/pet/cassettes.rb +63 -0
  68. data/lib/badline/pet/characters.rb +32 -0
  69. data/lib/badline/pet/cpu.rb +10 -0
  70. data/lib/badline/pet/crtc_video.rb +155 -0
  71. data/lib/badline/pet/discrete_video.rb +83 -0
  72. data/lib/badline/pet/ieee_drive.rb +76 -0
  73. data/lib/badline/pet/ieee_ports.rb +77 -0
  74. data/lib/badline/pet/ieee_traps.rb +80 -0
  75. data/lib/badline/pet/keyboard_ports.rb +74 -0
  76. data/lib/badline/pet/keyed_modules.rb +88 -0
  77. data/lib/badline/pet/keyed_parts.rb +127 -0
  78. data/lib/badline/pet/keyed_state.rb +42 -0
  79. data/lib/badline/pet/model.rb +163 -0
  80. data/lib/badline/pet/rom_sets.rb +89 -0
  81. data/lib/badline/pet/sound.rb +128 -0
  82. data/lib/badline/pet/via.rb +10 -0
  83. data/lib/badline/pet/via_ports.rb +19 -0
  84. data/lib/badline/pet.rb +244 -0
  85. data/lib/badline/plus4/keyed_state.rb +5 -3
  86. data/lib/badline/plus4/tcbm_drive.rb +14 -0
  87. data/lib/badline/plus4.rb +1 -1
  88. data/lib/badline/roms/README +169 -0
  89. data/lib/badline/roms/dos2031.rom +0 -0
  90. data/lib/badline/roms/pet/basic1.rom +0 -0
  91. data/lib/badline/roms/pet/basic2.rom +0 -0
  92. data/lib/badline/roms/pet/basic4.rom +0 -0
  93. data/lib/badline/roms/pet/characters-din.rom +0 -0
  94. data/lib/badline/roms/pet/characters1.rom +0 -0
  95. data/lib/badline/roms/pet/characters2.rom +0 -0
  96. data/lib/badline/roms/pet/editor1.rom +0 -0
  97. data/lib/badline/roms/pet/editor2-b.rom +0 -0
  98. data/lib/badline/roms/pet/editor2-n.rom +0 -0
  99. data/lib/badline/roms/pet/editor4-40-n-50hz.rom +0 -0
  100. data/lib/badline/roms/pet/editor4-40-n-60hz.rom +0 -0
  101. data/lib/badline/roms/pet/editor4-80-b-50hz-0283.rom +0 -0
  102. data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
  103. data/lib/badline/roms/pet/editor4-80-b-50hz.rom +0 -0
  104. data/lib/badline/roms/pet/editor4-80-b-60hz.rom +0 -0
  105. data/lib/badline/roms/pet/editor4-b.rom +0 -0
  106. data/lib/badline/roms/pet/editor4-n.rom +0 -0
  107. data/lib/badline/roms/pet/kernal1.rom +0 -0
  108. data/lib/badline/roms/pet/kernal2.rom +0 -0
  109. data/lib/badline/roms/pet/kernal4.rom +0 -0
  110. data/lib/badline/snapshot/container.rb +6 -1
  111. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  112. data/lib/badline/snapshot/field_doc.rb +17 -3
  113. data/lib/badline/snapshot/image.rb +28 -10
  114. data/lib/badline/snapshot/pet_setup.rb +88 -0
  115. data/lib/badline/snapshot/storage_setup.rb +7 -1
  116. data/lib/badline/snapshot/vice/pet.rb +117 -0
  117. data/lib/badline/snapshot/vice/pet_cpu.rb +67 -0
  118. data/lib/badline/snapshot/vice/pet_crtc.rb +246 -0
  119. data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
  120. data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
  121. data/lib/badline/snapshot/vice/pet_restore.rb +150 -0
  122. data/lib/badline/snapshot/vice/pet_via.rb +164 -0
  123. data/lib/badline/snapshot/vice.rb +1 -0
  124. data/lib/badline/snapshot.rb +9 -5
  125. data/lib/badline/storage/d80_image.rb +48 -0
  126. data/lib/badline/storage/d82_image.rb +21 -0
  127. data/lib/badline/storage/tap.rb +16 -3
  128. data/lib/badline/storage.rb +2 -0
  129. data/lib/badline/support/pet_checkpoint.rb +38 -0
  130. data/lib/badline/timing.rb +5 -4
  131. data/lib/badline/version.rb +1 -1
  132. data/lib/badline/vic20/attachments.rb +7 -2
  133. data/lib/badline/vic20/bus/keyed_state.rb +5 -4
  134. data/lib/badline/vic20/bus.rb +5 -0
  135. data/lib/badline/vic20.rb +1 -1
  136. metadata +70 -1
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/pet/rom_sets"
4
+ require "badline/pet/model"
5
+ require "badline/pet/cpu"
6
+ require "badline/pet/via"
7
+ require "badline/pet/via_ports"
8
+ require "badline/pet/keyboard_ports"
9
+ require "badline/pet/ieee_ports"
10
+ require "badline/pet/ieee_traps"
11
+ require "badline/pet/ieee_drive"
12
+ require "badline/pet/cassettes"
13
+ require "badline/pet/characters"
14
+ require "badline/pet/canvas"
15
+ require "badline/pet/discrete_video"
16
+ require "badline/pet/crtc_video"
17
+ require "badline/pet/bus"
18
+ require "badline/pet/sound"
19
+ require "badline/snapshot/pet_setup"
20
+ require "badline/pet/keyed_parts"
21
+ require "badline/pet/keyed_state"
22
+ require "badline/pet/keyed_modules"
23
+
24
+ module Badline
25
+ # The Commodore PET and CBM: a 6502 at 1 MHz, two 6520 PIAs at $E810 and
26
+ # $E820, a 6522 VIA at $E840 and, from the 4032 on, a 6545 CRTC at
27
+ # $E880. Every chip's IRQ is wired to the CPU's, and nothing pulls NMI.
28
+ # Nothing halts the CPU either: the video reads the screen RAM between
29
+ # its accesses.
30
+ #
31
+ # The video's vertical drive line, low in the retrace, reaches PIA 1's
32
+ # CB1, whose falling edge is the editor's 50 or 60 Hz interrupt, and the
33
+ # VIA's PB5. The discrete boards count it out themselves
34
+ # (DiscreteVideo), and the CRTC boards take it from the CRTC's vertical
35
+ # sync, inverted.
36
+ #
37
+ # The key matrix hangs off PIA 1 (KeyboardPorts), the graphics one or
38
+ # the business one as the model has, and type_text types through the
39
+ # editor's keyboard buffer.
40
+ #
41
+ # The discrete boards paint their picture (DiscreteVideo) on a Canvas.
42
+ # The CRTC boards paint theirs from the CRTC's outputs (CRTCVideo).
43
+ # The VIA's CB2 is the sound (Sound).
44
+ #
45
+ # Device 8 answers through traps on the KERNAL's IEEE-488 primitives
46
+ # (IEEETraps), on the IEEE-488 bus (IEEEPorts), or a true 2031 on the
47
+ # bus answers (IEEEDrive), and each cassette port has a datasette
48
+ # (Cassettes). It runs headless.
49
+ class PET
50
+ include Machine::KeyboardBuffer
51
+ include Machine::Clocking
52
+ include Machine::Settling
53
+ include IEEETraps
54
+ include IEEEDrive
55
+
56
+ attr_reader :model, :bus, :cpu, :pia1, :pia2, :via, :crtc, :keyboard, :ieee, :cassettes, :cycles, :init_threshold
57
+
58
+ # The editor's frame rate: 50 or 60 on a CRTC board, 60 on the others.
59
+ attr_reader :refresh_hz
60
+
61
+ # The RAM below $8000, in K.
62
+ attr_reader :ram_kb
63
+
64
+ # The 8296's board jumpers that are fitted, by name.
65
+ attr_reader :jumpers
66
+
67
+ def ram = @bus.ram
68
+
69
+ # Reads and writes memory as the CPU sees it, through the bus.
70
+ def peek(addr) = @bus.peek(addr)
71
+
72
+ def poke(addr, value) = @bus.poke(addr, value)
73
+
74
+ def install_debug_register(&) = @bus.install_debug_register(&)
75
+
76
+ def family = :pet
77
+
78
+ # The datasette on cassette port #1, where a tape goes in (Cassettes).
79
+ def datasette = @cassettes.datasette1
80
+
81
+ # +model+ names one of Model::ALL, with +ram_kb+ of RAM below $8000
82
+ # (Model::RAM_SIZES) in place of the model's own when not nil. A CRTC
83
+ # model runs its 50 Hz editor, or its 60 Hz one for +refresh_hz+ 60.
84
+ # +basic+, +editor+ and +characters+ name ROMs the board takes in place
85
+ # of its own (Model.upgraded, Model.fitted), and +jumpers+ the 8296's
86
+ # board jumpers (ROMSets::JUMPERS).
87
+ def initialize(model: Model::DEFAULT, ram_kb: nil, refresh_hz: 50, debug: false, basic: nil, editor: nil,
88
+ characters: nil, jumpers: [])
89
+ @model = Model.fitted(Model.upgraded(Model.named(model), basic, editor), characters)
90
+ ram_kb ||= @model.ram_kb
91
+ raise ArgumentError, "the #{@model.name} has no #{ram_kb}K RAM board" unless Model.ram_fits?(@model, ram_kb)
92
+
93
+ @ram_kb = ram_kb
94
+ @jumpers = Model.checked_jumpers(@model, jumpers)
95
+
96
+ @refresh_hz = @model.crtc? ? refresh_hz : 60
97
+ @crtc_video = @model.crtc?
98
+ @vertical_drive = true
99
+ matrix = @model.keyboard == :business ? KeyboardPorts::BUSINESS_MATRIX : KeyboardPorts::GRAPHICS_MATRIX
100
+ @keyboard = Input::Keyboard.new(matrix:)
101
+ keyboard_ports = KeyboardPorts.new(@keyboard)
102
+ registers = Array.new(Chips::CRTC::MASKS.length, 0)
103
+ @pia1 = Chips::PIA.new(peripheral: keyboard_ports)
104
+ @ieee = IEEEPorts.new(Drives::IEEEBus.new)
105
+ @pia2 = Chips::PIA.new(peripheral: @ieee)
106
+ @via = VIA.new(start: 0xe800, peripheral: VIAPorts.new(self))
107
+ @sound = Sound.new(@via)
108
+ @cassettes = Cassettes.new(@pia1, @via)
109
+ keyboard_ports.connect(@pia1, @ieee, @cassettes)
110
+ characters = Characters.new(@model.characters)
111
+ @crtc_output = CRTCVideo.new(characters, @via, registers, columns80: @model.video == :crtc80,
112
+ board8296: @model.cbm8296?)
113
+ @crtc = Chips::CRTC.new(@crtc_output, registers:)
114
+ @ieee.connect(@pia1, @pia2, @via)
115
+ @bus = Bus.new(model: @model, ram_kb:, editor: @model.editor(@refresh_hz), pia1: @pia1, pia2: @pia2,
116
+ via: @via, crtc: @crtc, jumpers: @jumpers)
117
+ @crtc_output.cells = @bus.screen.cells
118
+ @discrete = DiscreteVideo.new(@bus.screen.cells, characters, @via)
119
+ @canvas = @crtc_video ? @crtc_output.canvas : @discrete.canvas
120
+ cassettes = @cassettes
121
+ ieee = @ieee
122
+ @bus.on_io_write do
123
+ cassettes.lines_written
124
+ ieee.lines_written
125
+ end
126
+ @cpu = CPU.new(@bus, debug:)
127
+ @cycles = 0
128
+ @init_handlers = []
129
+ @pending_keys = nil
130
+ @dos = nil
131
+ @ieee_trap = nil
132
+ @capture_output = nil
133
+ @drive2031 = nil
134
+ @init_threshold = boot_cycles(ram_kb)
135
+ end
136
+
137
+ # The vertical drive line, high but in the retrace.
138
+ def vertical_drive? = @vertical_drive
139
+
140
+ # The model, its RAM and its editor's frame rate as the pause menu
141
+ # names them, such as "8032, 32K RAM, 50 HZ", all 128K on an 8296.
142
+ def model_label = "#{@model.name.upcase}, #{@model.cbm8296? ? 128 : @ram_kb}K RAM, #{@refresh_hz} HZ"
143
+
144
+ # The Canvas a front end shows.
145
+ def video = @canvas
146
+
147
+ # The 1 MHz clock and the frame the editor sets up, and all of the
148
+ # Canvas. The CRTC editors' lines and frames are their ROM's
149
+ # (ROMSets.crtc_frame), which a program can change. 80 columns show
150
+ # two dots to a window pixel.
151
+ def timing
152
+ crop = [0, 0, @canvas.width, @canvas.height]
153
+ if @crtc_video
154
+ cycles_per_line, lines_per_frame = ROMSets.crtc_frame(@model.editor(@refresh_hz), @refresh_hz)
155
+ Timing.new(clock_hz: 1_000_000, cycles_per_line:, lines_per_frame:,
156
+ crop:, pixel_width: 1, dots: @model.video == :crtc80 ? 2 : 1)
157
+ else
158
+ Timing.new(clock_hz: 1_000_000, cycles_per_line: DiscreteVideo::CYCLES_PER_LINE,
159
+ lines_per_frame: DiscreteVideo::LINES_PER_FRAME, crop:, pixel_width: 1, dots: 1)
160
+ end
161
+ end
162
+
163
+ # The video counts and moves the vertical drive line, then the PIAs
164
+ # and the VIA clock, ahead of the CPU, and the drive after it.
165
+ def cycle!
166
+ handle_init if @cycles == @init_threshold
167
+ feed_keyboard if @pending_keys
168
+
169
+ if @crtc_video
170
+ @crtc.cycle!
171
+ drive_retrace(!@crtc.vsync?)
172
+ else
173
+ @discrete.cycle!
174
+ drive_retrace(@discrete.vertical_drive?)
175
+ end
176
+ @pia1.cycle!
177
+ @pia2.cycle!
178
+ @via.cycle!
179
+ @sound.cycle! if @sound.recording?
180
+ @cassettes.cycle!
181
+ @cpu.irq = @pia1.irq? || @pia2.irq? || @via.irq?
182
+ @cpu.cycle!
183
+ @drive2031&.host_cycle!
184
+
185
+ @cycles += 1
186
+ end
187
+
188
+ # What a front end plays: the VIA's CB2 (Sound), which answers #record
189
+ # and #drain_samples.
190
+ def sound_source = @sound
191
+
192
+ # The RES line reaches the CPU, the PIAs, the VIA, the CRTC, the
193
+ # 8096's control register and, as IFC, the drive.
194
+ def reset!
195
+ @pia1.reset!
196
+ @pia2.reset!
197
+ @via.reset!
198
+ @crtc.reset!
199
+ @bus.reset!
200
+ @drive2031&.reset!
201
+ @ieee.lines_written
202
+ @cpu.reset!
203
+ end
204
+
205
+ # Switches the machine off and on: RAM starts cleared, and the RES
206
+ # line resets everything else.
207
+ def power_cycle!
208
+ @bus.power_on!
209
+ reset!
210
+ end
211
+
212
+ # Puts a program file's bytes where its first two say, through the bus,
213
+ # and returns that address.
214
+ def load_prg(data)
215
+ load_addr = data[0] | (data[1] << 8)
216
+ bytes = data[2..] || []
217
+ bytes = bytes[0, 0x10000 - load_addr] if load_addr + bytes.length > 0x10000
218
+ bytes.each_with_index { |byte, i| @bus.poke(load_addr + i, byte) }
219
+ load_addr
220
+ end
221
+
222
+ # Where the editor keeps its keyboard buffer: in page 2 under BASIC 1,
223
+ # in the zero page and page 2 under BASIC 2 and 4.
224
+ def keyboard_buffer
225
+ @model.basic1? ? Machine::KeyboardBuffer::PET_BASIC1_LAYOUT : Machine::KeyboardBuffer::PET_LAYOUT
226
+ end
227
+
228
+ private
229
+
230
+ # The cycle by which the KERNAL has booted and BASIC printed READY,
231
+ # with a margin. The RAM test takes most of it, about 41,000 to
232
+ # 44,000 cycles a K, and the CRTC editors take another 600,000 or so
233
+ # before it: READY is up by about 440,000 on a 2001 with 8K, 1,450,000
234
+ # on a 3032 and 1,980,000 on an 8032.
235
+ def boot_cycles(ram_kb) = (@model.basic == "pet/basic4.rom" ? 700_000 : 150_000) + (46_000 * ram_kb)
236
+
237
+ def drive_retrace(high)
238
+ return if high == @vertical_drive
239
+
240
+ @vertical_drive = high
241
+ @pia1.cb1 = high
242
+ end
243
+ end
244
+ end
@@ -4,9 +4,9 @@ module Badline
4
4
  class Plus4
5
5
  # The machine's own fields in a keyed snapshot module (Snapshot::Keyed,
6
6
  # BL-MACHINE): the family, its setup by name (Snapshot::Plus4Setup), the
7
- # cycle count, and the host's typing still to feed and on_init handlers
8
- # still to run under host.*. The badline version that wrote it is
9
- # written for reports and never read.
7
+ # cycle count, and the host's typing still to feed, on_init handlers
8
+ # still to run and the model plug_true_drive plugs in under host.*. The
9
+ # badline version that wrote it is written for reports and never read.
10
10
  module KeyedState
11
11
  # How many more on_init handlers the machine a snapshot was taken of had
12
12
  # yet to run than this one has. Nil until the machine is restored.
@@ -29,6 +29,7 @@ module Badline
29
29
  pending = @pending_keys
30
30
  out.ints("host.pending_keys", pending) if pending
31
31
  out.int("host.init_pending", booting? ? @init_handlers.length : 0)
32
+ out.text("host.true_drive_model", @true_drive_model)
32
33
  end
33
34
 
34
35
  def load_machine_fields(input)
@@ -36,6 +37,7 @@ module Badline
36
37
  pending = input.ints("host.pending_keys", [])
37
38
  @pending_keys = pending.empty? ? nil : pending.dup
38
39
  @init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
40
+ @true_drive_model = input.choice("host.true_drive_model", TCBMDrive::TRUE_DRIVE_MODELS, @true_drive_model)
39
41
  end
40
42
  end
41
43
 
@@ -32,6 +32,7 @@ module Badline
32
32
  drive.host_clock_hz = timing.clock_hz
33
33
  @drive1551 = drive
34
34
  @bus.plug_tia(drive.device, drive.host_tia)
35
+ drive.on_renumber { |old| drive1551_renumbered(old) }
35
36
  @serial_trap&.device = serial_trap_device
36
37
  @init_threshold = [@init_threshold, Badline::Drives::Model1551::BOOT_CYCLES].max if @cycles < @init_threshold
37
38
  end
@@ -43,6 +44,7 @@ module Badline
43
44
  return unless drive
44
45
 
45
46
  @bus.plug_tia(drive.device, tcbm_tia(drive.device))
47
+ drive.on_renumber
46
48
  @drive1551 = nil
47
49
  @serial_trap&.device = serial_trap_device
48
50
  end
@@ -93,6 +95,18 @@ module Badline
93
95
  @drive1551.load_modules(modules, input, "BL-D#{device}")
94
96
  end
95
97
 
98
+ # The 1551's DEV line moved it from device +old+ to the other: its
99
+ # interface's TIA moves with it, and the TCBM interface's own takes
100
+ # its old place.
101
+ def drive1551_renumbered(old)
102
+ drive = @drive1551
103
+ return unless drive
104
+
105
+ @bus.plug_tia(old, tcbm_tia(old))
106
+ @bus.plug_tia(drive.device, drive.host_tia)
107
+ @serial_trap&.device = serial_trap_device
108
+ end
109
+
96
110
  # The TCBM interface's own TIA for +device+, or nil without one.
97
111
  def tcbm_tia(device)
98
112
  tias = @tias
data/lib/badline/plus4.rb CHANGED
@@ -89,7 +89,7 @@ module Badline
89
89
  ntsc = profile == Chips::TED::NTSC
90
90
  Timing.new(clock_hz: profile.clock_hz, cycles_per_line: Chips::TED::Beam::CYCLES_PER_LINE,
91
91
  lines_per_frame: ntsc ? 262 : 312, crop: ntsc ? [0, 248, 384, 242] : [0, 276, 384, 288],
92
- pixel_width: 1)
92
+ pixel_width: 1, dots: 1)
93
93
  end
94
94
 
95
95
  # Reads and writes memory as the CPU sees it, through the bus.
@@ -88,6 +88,18 @@ Downloaded from
88
88
  https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1551/1551.318008-01.bin
89
89
  SHA-1 fae3c788ad9a6cc2dbdfbcf6c0264b2ca921d55e.
90
90
 
91
+ dos2031.rom
92
+ -----------
93
+ The 2031 disk drive's DOS 2.6, 16 KB for $C000-$FFFF: 901484-03
94
+ ($C000-$DFFF, SHA-1 54d608f7f07860f24186749f21c96724dd48bc50)
95
+ followed by 901484-05 ($E000-$FFFF, SHA-1
96
+ ec6b75ecfdd4744e5d57979ef6af990444c11ae1). Drives::Model2031 loads it.
97
+
98
+ Both halves downloaded from
99
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/old/2031/
100
+ and concatenated.
101
+ SHA-1 c53e180a96649ceb3f421739e8dc66faba7cba44.
102
+
91
103
  Commodore VIC-20 ROMs
92
104
  =====================
93
105
 
@@ -179,3 +191,160 @@ plus4/function-high.rom
179
191
  The Plus/4's 3-plus-1 function ROM, high half, 317054-01
180
192
  (3-plus-1.317054-01.bin), for $C000-$FFFF in ROM bank 1.
181
193
  SHA-1 0ad7ac2db7da692d972e586ca0dfd747d82c7693.
194
+
195
+ Commodore PET/CBM ROMs
196
+ ======================
197
+
198
+ Downloaded from
199
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/pet/
200
+ Each BASIC and KERNAL pair is one ROM set, and the editors and the
201
+ character sets are files of their own, so a model's stock set and the
202
+ upgrade ROMs Commodore sold can be picked apart. PET::ROMSets
203
+ (pet/rom_sets.rb) names them for --basic and --editor and says which
204
+ fit each model, and PET::Bus maps the ones fitted.
205
+
206
+ pet/basic1.rom
207
+ --------------
208
+ BASIC 1 for $C000-$DFFF, four 2K chips concatenated:
209
+ 901447-09 (rom-2-c000.901447-09.bin, the revision that replaced
210
+ 901447-01), 901447-02, 901447-03 and 901447-04 (rom-1-c800, rom-1-d000,
211
+ rom-1-d800).
212
+ SHA-1 3be9cd41b646fefa00d80f1c3433a06146e97c59.
213
+
214
+ pet/editor1.rom
215
+ ---------------
216
+ The BASIC 1 screen editor, 901447-05 (rom-1-e000.901447-05.bin), for
217
+ $E000-$E7FF.
218
+ SHA-1 f02f5fb492ba93dbbd390f24c10f7a832dec432a.
219
+
220
+ pet/kernal1.rom
221
+ ---------------
222
+ The BASIC 1 KERNAL for $F000-$FFFF, 901447-06 and 901447-07
223
+ (rom-1-f000.901447-06.bin, rom-1-f800.901447-07.bin) concatenated.
224
+ SHA-1 32dc08828e88c74d6433156e64d402b564556308.
225
+
226
+ pet/characters1.rom
227
+ -------------------
228
+ The 2001's character generator, 901447-08 (characters-1.901447-08.bin),
229
+ with upper and lower case swapped against 901447-10.
230
+ SHA-1 3e067cc621e4beafca2b90cb8f6dba975df2855b.
231
+
232
+ pet/basic2.rom
233
+ --------------
234
+ BASIC 2 for $C000-$DFFF, 901465-01 and 901465-02
235
+ (basic-2-c000.901465-01.bin, basic-2-d000.901465-02.bin) concatenated.
236
+ The 2K chips of the 6540 boards (rom-3-*) hold the same bytes.
237
+ SHA-1 9322a9f0354b9049847d98d3a045fbec860a6264.
238
+
239
+ pet/kernal2.rom
240
+ ---------------
241
+ The BASIC 2 KERNAL, 901465-03 (kernal-2.901465-03.bin), for $F000-$FFFF.
242
+ SHA-1 38742bdf449f629bcba6276ef24d3daeb7da6e84.
243
+
244
+ pet/editor2-n.rom
245
+ -----------------
246
+ The BASIC 2 editor for the graphics keyboard, 901447-24
247
+ (edit-2-n.901447-24.bin), for $E000-$E7FF.
248
+ SHA-1 5e5502ce32f5a7e387d65efe058916282041e54b.
249
+
250
+ pet/editor2-b.rom
251
+ -----------------
252
+ The BASIC 2 editor for the business keyboard, 901474-01
253
+ (edit-2-b.901474-01.bin), for $E000-$E7FF.
254
+ SHA-1 174ace3a8c0348cd21d39cc864e2adc58b0101a9.
255
+
256
+ pet/characters2.rom
257
+ -------------------
258
+ The character generator of BASIC 2 and 4 machines, 901447-10
259
+ (characters-2.901447-10.bin).
260
+ SHA-1 0157a2d55b7ac4eaeb38475889ebeea52e2593db.
261
+
262
+ pet/basic4.rom
263
+ --------------
264
+ BASIC 4 for $B000-$DFFF, 901465-23, 901465-20 and 901465-21
265
+ (basic-4-b000.901465-23.bin, basic-4-c000.901465-20.bin,
266
+ basic-4-d000.901465-21.bin) concatenated. 901465-23 is the patched
267
+ $B000 chip that replaced 901465-19. The 8296's 324746-01 holds the same
268
+ four chips (8296/kernal-basic-324746-01.bin, SHA-1
269
+ e3e1431969bf317c885e47f3790e0bcbdf61fe77, $C000, $D000, $B000 and
270
+ $F000 in that order), so the 8296 runs this file and kernal4.rom.
271
+ SHA-1 d70bec479e1e3a0d0908a4897066e9959bbc11c8.
272
+
273
+ pet/kernal4.rom
274
+ ---------------
275
+ The BASIC 4 KERNAL, 901465-22 (kernal-4.901465-22.bin), for
276
+ $F000-$FFFF.
277
+ SHA-1 96a0fa56e0c937da92971d9c99d504e44e898806.
278
+
279
+ pet/editor4-n.rom
280
+ -----------------
281
+ The BASIC 4 editor for the graphics keyboard without a CRTC, 901447-29
282
+ (edit-4-n.901447-29.bin), for $E000-$E7FF.
283
+ SHA-1 e88f56e5c54b0e8d8d4e8cb39a4647c803c1f51c.
284
+
285
+ pet/editor4-b.rom
286
+ -----------------
287
+ The BASIC 4 editor for the business keyboard without a CRTC, 901474-02
288
+ (edit-4-b.901474-02.bin), for $E000-$E7FF.
289
+ SHA-1 0ca5c4e8f532f914cb0bf86ea9900f20f0a655ce.
290
+
291
+ pet/editor4-40-n-50hz.rom
292
+ -------------------------
293
+ The 4032's 50 Hz editor, 40 columns on a CRTC, graphics keyboard,
294
+ 901498-01 (edit-4-40-n-50Hz.901498-01.bin), for $E000-$E7FF.
295
+ SHA-1 05af284c914d53a52987b5f602466de75765f650.
296
+
297
+ pet/editor4-40-n-60hz.rom
298
+ -------------------------
299
+ The 4032's 60 Hz editor, 901499-01 (edit-4-40-n-60Hz.901499-01.bin),
300
+ for $E000-$E7FF.
301
+ SHA-1 8cbf086c1ce4dfb2a2fe24c47476dfb878493dee.
302
+
303
+ pet/editor4-80-b-50hz.rom
304
+ -------------------------
305
+ The 8032's 50 Hz editor, 80 columns on a CRTC, business keyboard,
306
+ 901474-04, the chip dated 3681
307
+ (edit-4-80-b-50Hz.901474-04-3681.bin), for $E000-$E7FF.
308
+ SHA-1 7040b283ba39e9630e3d147f7d076b7abc39bc70.
309
+
310
+ zimmers.net has three dumps of 901474-04 that differ. This one is the
311
+ 60 Hz 901474-03 below with the 50 Hz jiffy clock patch at $E431-$E441
312
+ and $E455, the CRTC's vertical registers (R4, R5 and R7) changed in both
313
+ of its register tables at $E72A, and the byte at $E787 changed to keep
314
+ the ROM's byte sum at $8A, the same as 901474-03's. The line length (R0)
315
+ stays the 60 Hz editor's. A second chip, dated 0384, reads the same
316
+ (8296/edit-50hz-324243-02b.txt). The -0283 dump changes R0 and sums to
317
+ $AE, and the _alt dump changes R0 and $E787.
318
+
319
+ pet/editor4-80-b-50hz-0283.rom
320
+ ------------------------------
321
+ The 8032's other 50 Hz editor, 901474-04, the chip dated 0283
322
+ (edit-4-80-b-50Hz.901474-04-0283.bin), for $E000-$E7FF, which
323
+ --editor 4-80-b-50hz-0283 fits. It differs from the 3681 dump only in
324
+ the CRTC's register tables at $E72A and $E73C: lines of 64 cycles (R0
325
+ 63) and frames of 313 lines, with nine scanlines a character row.
326
+ SHA-1 81975eab31a8f4f51ae2a20d099a567c7b3f2dd1.
327
+
328
+ pet/editor4-80-b-60hz.rom
329
+ -------------------------
330
+ The 8032's 60 Hz editor, 901474-03 (edit-4-80-b-60Hz.901474-03.bin),
331
+ for $E000-$E7FF.
332
+ SHA-1 c605fa343fd77c73cbe1e0e9567e2f014f6e7e30.
333
+
334
+ pet/editor4-80-b-50hz-8296.rom
335
+ ------------------------------
336
+ The 8296's 50 Hz editor, 324243-02B, the EPROM marked "8296 UE8" in
337
+ Andre Fachat's working 8296 (8296/edit-50hz-324243-02b.bin), for
338
+ $E000-$E7FF. The 8296 parts list names it the ASCII board's. It is
339
+ 901474-04 with the CRTC's register tables at $E72A changed to lines of
340
+ 59 cycles and frames of 339 lines (8296/edit-50hz-324243-02b.txt).
341
+ SHA-1 b715db83fd26458dfd254bef5c4aae636753f7f5.
342
+
343
+ pet/characters-din.rom
344
+ ----------------------
345
+ The 8296's 4K character generator for the DIN board, 324242-01
346
+ (8296/characters-324242-01.bin), which --characters din fits. Its
347
+ second half is 901447-10 byte for byte, and its first a DIN set, with
348
+ the German letters and the section sign in place of @ [ \ ] and in
349
+ some of the graphics. The CRTC's MA13, bit 5 of R12, picks the half.
350
+ SHA-1 9616f7f18757cccefb702a945f954b644d5b17d1.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -36,6 +36,11 @@ module Badline
36
36
  C128 = "C128"
37
37
  # xplus4 names its machine PLUS4, for the C16 too.
38
38
  PLUS4 = "PLUS4"
39
+ # xpet names its machine PET, for every model.
40
+ PET = "PET"
41
+ # The file format version each machine's VICE emulator reads, where
42
+ # it isn't FORMAT_MAJOR.0: xpet refuses any other than 1.0.
43
+ FORMAT_MAJORS = { PET => 1 }.freeze
39
44
  GZIP_MAGIC = "\x1f\x8b".b
40
45
 
41
46
  # The VICE version block, when VICE wrote the file: four version
@@ -65,7 +70,7 @@ module Badline
65
70
  def [](name) = @sections.find { |section| section.name == name }
66
71
 
67
72
  def to_s
68
- header = MAGIC + [FORMAT_MAJOR, FORMAT_MINOR].pack("CC") + padded(@machine)
73
+ header = MAGIC + [FORMAT_MAJORS.fetch(@machine, FORMAT_MAJOR), FORMAT_MINOR].pack("CC") + padded(@machine)
69
74
  header += VERSION_MAGIC + @vice_version if @vice_version
70
75
  header + @sections.map { |section| encode(section) }.join
71
76
  end
@@ -7,7 +7,7 @@ module Badline
7
7
  # its default, with keys that differ only in a unit's number, such as
8
8
  # voice1 to voice3, on one row.
9
9
  class Markdown
10
- NAMES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4" }.freeze
10
+ NAMES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4", "pet" => "PET" }.freeze
11
11
  TAG_TYPES = { Keyed::INT => "int", Keyed::BOOL => "bool", Keyed::BYTES => "bytes", Keyed::INTS => "ints",
12
12
  Keyed::TEXT => "text" }.freeze
13
13
 
@@ -36,8 +36,9 @@ module Badline
36
36
 
37
37
  # The machines the doc is made from: each family at power-on, the C64
38
38
  # with each kind of expansion, cartridge and media, and a second SID,
39
- # and the 264 series' models, its TCBM interface, cartridge, tape and
40
- # true drives, the 1541 and the 1551.
39
+ # the 264 series' models, its TCBM interface, cartridge, tape and
40
+ # true drives, the 1541 and the 1551, and a CRTC PET, a 2001, an 8096,
41
+ # an 8296 with its user port jumpered and a PET with a disk and tapes.
41
42
  def self.machines
42
43
  {
43
44
  "c64" => [C64.new, C64.new(ram_expansion: :plus256k), C64.new(reu: 512),
@@ -48,10 +49,23 @@ module Badline
48
49
  "plus4" => [Plus4.new, Plus4.new(model: "c16"), Plus4.new(tcbm: true), Plus4.new(sid_card: :mos6581),
49
50
  Plus4.new(sid_card: :mos8580, sid_card_address: 0xfe80),
50
51
  Plus4.new(ram_expansion: :csory256),
51
- plus4_with_cartridge, plus4_with_tape, plus4_with_drive, plus4_with_drive("1551")]
52
+ plus4_with_cartridge, plus4_with_tape, plus4_with_drive, plus4_with_drive("1551")],
53
+ "pet" => [PET.new, PET.new(model: "2001"), PET.new(model: "8096"),
54
+ PET.new(model: "8296", characters: "din", jumpers: %w[userport]), pet_with_media]
52
55
  }
53
56
  end
54
57
 
58
+ # A PET with a disk in device 8 through the traps and a tape in each
59
+ # datasette.
60
+ def self.pet_with_media
61
+ bytes = "C64-TAPE-RAW".bytes + [1, 3, 0, 0, 4, 0, 0, 0, 0x30, 0x30, 0x30, 0x30]
62
+ PET.new.tap do |machine|
63
+ machine.mount(Storage::D64Image.new("", read_only: true, bytes: blank(174_848)))
64
+ machine.cassettes.datasette1.insert(Storage::TAP.new("", bytes:))
65
+ machine.cassettes.datasette2.insert(Storage::TAP.new("", bytes:))
66
+ end
67
+ end
68
+
55
69
  def self.blank(length) = Array.new(length, 0)
56
70
 
57
71
  def self.with_cartridge(type)