badline 0.18.0 → 0.19.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CLAUDE.md +8 -0
  4. data/CONTRIBUTING.md +4 -0
  5. data/README.md +10 -7
  6. data/doc/library.md +5 -1
  7. data/doc/media.md +3 -2
  8. data/doc/pinned-behaviour.md +41 -1
  9. data/doc/snapshot-format.md +56 -24
  10. data/doc/snapshots.md +45 -8
  11. data/doc/vice.md +344 -0
  12. data/lib/badline/audio/renderer.rb +4 -8
  13. data/lib/badline/c128.rb +4 -0
  14. data/lib/badline/c64/bus/fittings.rb +6 -0
  15. data/lib/badline/c64.rb +6 -0
  16. data/lib/badline/cbm2/p500.rb +4 -0
  17. data/lib/badline/cbm2.rb +4 -0
  18. data/lib/badline/chips/cpu/core.rb +29 -4
  19. data/lib/badline/chips/cpu/interrupts.rb +9 -0
  20. data/lib/badline/chips/cpu/keyed_state.rb +15 -9
  21. data/lib/badline/chips/cpu/microcode.rb +10 -0
  22. data/lib/badline/chips/rriot.rb +4 -1
  23. data/lib/badline/chips/sid/decimator.rb +1 -1
  24. data/lib/badline/chips/sid/keyed_parts.rb +51 -18
  25. data/lib/badline/chips/sid.rb +6 -2
  26. data/lib/badline/chips/vic_i/keyed_parts.rb +6 -1
  27. data/lib/badline/chips/vic_i/light_pen.rb +89 -0
  28. data/lib/badline/chips/vic_i.rb +12 -7
  29. data/lib/badline/drives/model1541/sector_reader.rb +20 -3
  30. data/lib/badline/drives/model1541.rb +1 -0
  31. data/lib/badline/drives/model1551.rb +1 -0
  32. data/lib/badline/drives/model1571.rb +1 -0
  33. data/lib/badline/drives/model1581.rb +1 -0
  34. data/lib/badline/drives/model2031.rb +1 -0
  35. data/lib/badline/drives/model4040/mechanism.rb +6 -3
  36. data/lib/badline/drives/model4040.rb +17 -1
  37. data/lib/badline/drives/model8050/board.rb +2 -0
  38. data/lib/badline/drives/model8050/dos_board.rb +49 -6
  39. data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
  40. data/lib/badline/drives/model8050/idle.rb +137 -24
  41. data/lib/badline/drives/model8050.rb +14 -0
  42. data/lib/badline/drives/model9060/idle.rb +30 -10
  43. data/lib/badline/drives/model9060.rb +2 -0
  44. data/lib/badline/input/joystick.rb +15 -2
  45. data/lib/badline/kim1/tape.rb +3 -1
  46. data/lib/badline/kim1.rb +4 -0
  47. data/lib/badline/machine.rb +3 -0
  48. data/lib/badline/media/true_drive.rb +10 -0
  49. data/lib/badline/media/vic20_media.rb +4 -2
  50. data/lib/badline/media.rb +5 -1
  51. data/lib/badline/memory.rb +2 -4
  52. data/lib/badline/options/table.rb +1 -1
  53. data/lib/badline/pet/superpet/dongle.rb +13 -2
  54. data/lib/badline/pet/superpet.rb +5 -0
  55. data/lib/badline/pet.rb +4 -0
  56. data/lib/badline/plus4.rb +4 -0
  57. data/lib/badline/roms/README +11 -1
  58. data/lib/badline/snapshot/container.rb +6 -4
  59. data/lib/badline/snapshot/field_doc.rb +8 -4
  60. data/lib/badline/snapshot/image.rb +15 -6
  61. data/lib/badline/snapshot/vice/pet.rb +1 -0
  62. data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
  63. data/lib/badline/snapshot/vice/pet_mem.rb +11 -5
  64. data/lib/badline/snapshot/vice/vic20.rb +70 -0
  65. data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
  66. data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
  67. data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
  68. data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
  69. data/lib/badline/snapshot/vice.rb +1 -0
  70. data/lib/badline/snapshot.rb +4 -4
  71. data/lib/badline/storage/disk_image/writing.rb +2 -2
  72. data/lib/badline/version.rb +1 -1
  73. data/lib/badline/vic20/port_wiring.rb +11 -0
  74. data/lib/badline/vic20.rb +4 -0
  75. metadata +9 -1
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module Vic20
7
+ # The VIC-I as xvic 3.10 saves it, VIC-I 0.4. The layout was inferred
8
+ # from xvic's snapshots of known states, set from its monitor and
9
+ # stepped an instruction at a time, against what its monitor's io
10
+ # command and its registers showed:
11
+ #
12
+ # - $00 dword: 1 while the frame is interlaced, $04 dword: the field,
13
+ # 0 the long one and 1 the short one; $08 qword: the clock at the
14
+ # frame's first cycle
15
+ # - $10 byte: the cycle in the line; $11 word: the raster line; $13
16
+ # byte: the text window, 0 before it, 1 on its first line until
17
+ # the column in $9000, 2 on its lines after that and 3 after it;
18
+ # $15 byte: the line's fetches, 0 off the window or before the
19
+ # column in $9000, 1 from there, 2 from the cycle before the first
20
+ # fetch, then 3 with a pattern fetch next and 2 with a code fetch
21
+ # next, 4 once done; $17 word: the line again
22
+ # - $1B dword: the columns; $1F dword: the rows; $23 dword: the
23
+ # columns again; $27 dword: 0 on a line after one of the
24
+ # window's, 1 on the others; $2B dword: the video matrix
25
+ # offset the text row starts at; $33 dword: the text rows done;
26
+ # $37 dword: while the fetches wait, the cycles left until the
27
+ # cycle before the first, then the characters fetched, and the
28
+ # columns while $15 is 0
29
+ # - $3D and $41 dwords: the light pen's latch, $9006 and $9007;
30
+ # $45 dword: 1 in every snapshot xvic wrote; $49: 8 bytes of $FF;
31
+ # $51 byte: the last character code fetched
32
+ # - $52: the colour RAM, 1,024 bytes; $452: the registers, 16
33
+ # bytes, with $9003 bit 7 and $9004 as written; $462 dword: the
34
+ # raster line again
35
+ # - $466 dword: the width of xvic's picture, $46A dword: its lines,
36
+ # 312 on the 6561 and 262 on the 6560; $46E dword: its pitch,
37
+ # then the picture itself, twice its lines and 8 more of pitch
38
+ # bytes, and 4 bytes after it, 0 in every snapshot
39
+ #
40
+ # Bytes the layout leaves out, $14, $16, $19-$1A, $2F-$32 and
41
+ # $3B-$3C, were 0 in every snapshot.
42
+ #
43
+ # Written: all of it from badline's VIC, the picture as zeros. The
44
+ # cycle is badline's column, which is where xvic's monitor puts
45
+ # the cycle at an instruction boundary. Read: the registers, the
46
+ # beam, the frame and field, the text window's counters and the
47
+ # line's fetches, the light pen and the colour RAM; the character
48
+ # line is worked out from the line and $9001, as xvic's monitor
49
+ # works it out. The picture is painted afresh.
50
+ module VICI
51
+ NAME = "VIC-I"
52
+ MAJOR = 0
53
+ MINOR = 4
54
+ HEADER = 0x472
55
+ COLOR_RAM = 0x52
56
+ REGISTERS = 0x452
57
+ # xvic's picture: width, lines and pitch, by model.
58
+ PICTURES = { "pal" => [1648, 312, 1648], "ntsc" => [1536, 262, 1536] }.freeze
59
+ AREA_BEFORE = 0
60
+ AREA_FIRST = 1
61
+ AREA_IN = 2
62
+ AREA_AFTER = 3
63
+ FETCH_WAITING = 1
64
+ FETCH_CODE = 2
65
+ FETCH_PATTERN = 3
66
+ FETCH_DONE = 4
67
+ LIGHT_PEN_FLAG = 1
68
+ FILLER = 0xff
69
+
70
+ module_function
71
+
72
+ def reads?(section) = section.major == MAJOR && section.minor == MINOR && section.data.bytesize >= HEADER
73
+
74
+ # The model whose picture the module's size says, or nil.
75
+ def model(section)
76
+ lines = FieldReader.new(section).skip(0x46a).dword
77
+ PICTURES.find { |_, picture| picture[1] == lines }&.first
78
+ end
79
+
80
+ # The module for `machine` from its BL-VICI and BL-COLORRAM fields.
81
+ def export(machine, vic, color_ram)
82
+ profile = machine.vic.profile
83
+ registers = vic.bytes("registers", "\0".b * 16).bytes
84
+ line = vic.int("raster", 0)
85
+ column = vic.int("column", 0)
86
+ frame_lines = vic.int("frame_lines", profile.lines_per_frame)
87
+ fields = FieldWriter.new
88
+ interlaced = frame_lines != profile.lines_per_frame
89
+ fields.dword(interlaced ? 1 : 0).dword(interlaced && frame_lines != profile.interlaced_lines ? 1 : 0)
90
+ fields.qword(machine.cycles - (line * profile.cycles_per_line) - column)
91
+ window(fields, vic, registers, line, column)
92
+ fields.dword(vic.int("pen.x", 0)).dword(vic.int("pen.y", 0)).dword(LIGHT_PEN_FLAG)
93
+ fields.bytes(Array.new(8, FILLER)).byte(vic.int("code", 0))
94
+ colors = color_ram.bytes("ram", "".b).bytes
95
+ fields.bytes(Array.new(0x400) { |cell| colors.fetch(cell, 0) & 0x0f })
96
+ fields.bytes(registers).dword(line)
97
+ picture(fields, PICTURES.fetch(machine.model))
98
+ fields.section(NAME, MAJOR, MINOR)
99
+ end
100
+
101
+ def window(fields, vic, registers, line, column)
102
+ open = vic.bool("window_open", false)
103
+ done = vic.bool("window_done", false)
104
+ columns = registers[2] & 0x7f
105
+ rows = (registers[3] >> 1) & 0x3f
106
+ first = open && vic.int("text_rows", 0).zero? && vic.int("char_line", 0).zero?
107
+ origin = registers[0] & 0x7f
108
+ area = if open then first && column < origin ? AREA_FIRST : AREA_IN
109
+ else done ? AREA_AFTER : AREA_BEFORE
110
+ end
111
+ state, count = fetches(vic, open && column >= origin, columns, column)
112
+ fields.byte(column).word(line).byte(area).byte(0).byte(state).byte(0).word(line).word(0)
113
+ fields.dword(columns).dword(rows).dword(columns)
114
+ fields.dword(border?(open, first, done, registers, line) ? 1 : 0)
115
+ fields.dword(vic.int("row_start", 0)).dword(0).dword(vic.int("text_rows", 0)).dword(count).word(0)
116
+ end
117
+
118
+ # The line's fetches as xvic counts them, from the column in
119
+ # $9000 on: waiting, with the cycles left until the cycle before
120
+ # the first fetch, then fetching, with the characters fetched.
121
+ # Before that column, and off the window's lines, the count is
122
+ # the columns.
123
+ def fetches(vic, started, columns, column)
124
+ return [0, columns] unless started
125
+
126
+ from = vic.int("fetch_from", 0)
127
+ return [FETCH_WAITING, from - 1 - column] if column < from - 1
128
+ return [FETCH_CODE, 0] if column < from
129
+
130
+ fetched = vic.int("fetched", 0)
131
+ return [FETCH_DONE, columns] if fetched >= columns
132
+
133
+ [(column - from).even? ? FETCH_PATTERN : FETCH_CODE, fetched]
134
+ end
135
+
136
+ # Whether xvic's flag at $27 is set: unless the window was open
137
+ # on the line before.
138
+ def border?(open, first, done, registers, line)
139
+ return first if open
140
+ return true unless done
141
+
142
+ height = registers[3].anybits?(0x01) ? 16 : 8
143
+ line != (registers[1] * 2) + (((registers[3] >> 1) & 0x3f) * height)
144
+ end
145
+
146
+ def picture(fields, (width, lines, pitch))
147
+ fields.dword(width).dword(lines).dword(pitch).zeros((((2 * lines) + 8) * pitch) + 4)
148
+ end
149
+
150
+ # The colour RAM's cells, low nibbles.
151
+ def colors(section) = FieldReader.new(section).skip(COLOR_RAM).bytes(0x400).map { |cell| cell & 0x0f }
152
+
153
+ # BL-VICI's fields for `machine`'s profile, in a keyed module's
154
+ # writer.
155
+ def import(section, out, profile)
156
+ fields = FieldReader.new(section)
157
+ interlaced = fields.dword == 1
158
+ short = fields.dword == 1
159
+ fields.skip(8)
160
+ column = fields.byte
161
+ line = fields.word
162
+ area = fields.byte
163
+ fields.skip(1)
164
+ state = fields.byte
165
+ fields.skip(5 + 16)
166
+ row_start = fields.dword
167
+ fields.skip(4)
168
+ text_rows = fields.dword
169
+ count = fields.dword
170
+ fields.skip(2)
171
+ pen_x = fields.dword
172
+ pen_y = fields.dword
173
+ fields.skip(4 + 8)
174
+ code = fields.byte
175
+ registers = FieldReader.new(section).skip(REGISTERS).bytes(16)
176
+ out.bytes("registers", registers).int("raster", line).int("column", column)
177
+ frame_lines = if interlaced then short ? profile.interlaced_lines - 1 : profile.interlaced_lines
178
+ else profile.lines_per_frame
179
+ end
180
+ out.int("frame_lines", frame_lines).bool("long_field", !interlaced || short)
181
+ open = [AREA_FIRST, AREA_IN].include?(area)
182
+ out.bool("window_open", open).bool("window_done", area == AREA_AFTER)
183
+ fetching = open && state != FETCH_WAITING && state != 0
184
+ out.int("row_start", row_start).int("matrix", row_start + (fetching ? count : 0))
185
+ out.int("char_line", open ? char_line(registers, line) : 0).int("text_rows", text_rows)
186
+ out.int("fetch_from", open ? (registers[0] & 0x7f) + Chips::VICI::FETCH_DELAY : 0)
187
+ out.int("fetched", fetching ? count : 0).int("code", code)
188
+ out.int("pen.x", pen_x & 0xff).int("pen.y", pen_y & 0xff)
189
+ end
190
+
191
+ # The line of the character row the window is on.
192
+ def char_line(registers, line)
193
+ height = registers[3].anybits?(0x01) ? 16 : 8
194
+ (line - (registers[1] * 2)) % height
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -14,6 +14,7 @@ require "badline/snapshot/vice/vicii"
14
14
  require "badline/snapshot/vice/vicii_import"
15
15
  require "badline/snapshot/vice/restore"
16
16
  require "badline/snapshot/vice/pet"
17
+ require "badline/snapshot/vice/vic20"
17
18
  require "badline/snapshot/vice/cbm2"
18
19
  require "badline/snapshot/vice/p500"
19
20
  require "badline/snapshot/vice/p500_restore"
@@ -25,8 +25,8 @@ module Badline
25
25
  path
26
26
  end
27
27
 
28
- # Saves the machine's snapshot with VICE's modules ahead of its own for
29
- # a C64, so x64sc opens it too.
28
+ # Saves the machine's snapshot with VICE's modules ahead of its own, so
29
+ # x64sc, xvic, xpet, xcbm2 or xcbm5x0 opens it too (Image.exported).
30
30
  def export(computer, path)
31
31
  File.binwrite(path, Image.exported(computer).to_s)
32
32
  path
@@ -56,8 +56,8 @@ module Badline
56
56
  module Files
57
57
  def save_snapshot(path) = Snapshot.save(self, path)
58
58
 
59
- # Saves the snapshot with VICE's modules too, for a C64, so x64sc
60
- # opens it as well as badline.
59
+ # Saves the snapshot with VICE's modules too, so VICE's emulator for
60
+ # the machine opens it as well as badline (Image.exported).
61
61
  def export_snapshot(path) = Snapshot.export(self, path)
62
62
 
63
63
  def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
@@ -151,8 +151,8 @@ module Badline
151
151
  end
152
152
 
153
153
  def put_sector(track, sector, data)
154
- offset = sector_offset(track, sector)
155
- SECTOR_SIZE.times { |i| @bytes[offset + i] = data[i] || 0 }
154
+ block = data.first(SECTOR_SIZE)
155
+ @bytes[sector_offset(track, sector), SECTOR_SIZE] = block + Array.new(SECTOR_SIZE - block.length, 0)
156
156
  @errors[track_offset(track) + sector] = 1 if @errors
157
157
  end
158
158
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- VERSION = "0.18.0"
4
+ VERSION = "0.19.0"
5
5
  end
@@ -15,6 +15,11 @@ module Badline
15
15
  # The datasette's read line pulses VIA 2's CA1, its sense switch reads
16
16
  # on VIA 1's PA6, its motor runs while VIA 1's CA2 is low, and VIA 2's
17
17
  # PB3 drives its write line, the same pin as a keyboard column.
18
+ #
19
+ # VIA 1's PA5 is the VIC's light pen input too, low while the VIA
20
+ # drives it low or the joystick's fire button is held. The machine
21
+ # passes it on after each write to a VIA, as it does the serial lines,
22
+ # and as the fire button moves.
18
23
  module PortWiring
19
24
  # The lines the machine pulls, as Drives::SerialBus host lines.
20
25
  def serial_lines
@@ -30,6 +35,7 @@ module Badline
30
35
  def wire_ports
31
36
  @datasette.on_flag { tape_pulse }
32
37
  @bus.on_via_write { via_written }
38
+ @joystick1.on_change { pass_lightpen }
33
39
  via_written
34
40
  end
35
41
 
@@ -38,6 +44,11 @@ module Badline
38
44
  @iec_bus.host_lines = lines unless lines == @iec_bus.host_lines
39
45
  @datasette.motor = !@via1.ca2_output
40
46
  @datasette.write_line = @via2.port_b_output.anybits?(0x08)
47
+ pass_lightpen
48
+ end
49
+
50
+ def pass_lightpen
51
+ @vic.lightpen_level(@via1.port_a_output.anybits?(0x20) && @joystick1.port_bits.anybits?(0x10))
41
52
  end
42
53
 
43
54
  # A falling edge on the read line, and the line back up.
data/lib/badline/vic20.rb CHANGED
@@ -51,6 +51,8 @@ module Badline
51
51
 
52
52
  def family = :vic20
53
53
 
54
+ def portless_board = nil
55
+
54
56
  # The one control port's joystick, which the front end's second
55
57
  # joystick drives too.
56
58
  def joystick2 = @joystick1
@@ -79,6 +81,7 @@ module Badline
79
81
  kernal: "vic20/kernal-#{model}.rom")
80
82
  @vic.connect(@bus)
81
83
  @cpu = CPU.new(@bus, debug:)
84
+ @cpu.power_on!
82
85
  @cycles = 0
83
86
  @sound = Chips::VICI::Sound.new(self, @vic.profile.clock_hz)
84
87
  @vic.sound = @sound
@@ -181,6 +184,7 @@ module Badline
181
184
  @vic.power_on!
182
185
  @sound.power_on!
183
186
  reset!
187
+ @cpu.power_on!
184
188
  end
185
189
 
186
190
  private
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: badline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inge Jørgensen
@@ -47,6 +47,7 @@ files:
47
47
  - doc/sid-player.md
48
48
  - doc/snapshot-format.md
49
49
  - doc/snapshots.md
50
+ - doc/vice.md
50
51
  - exe/badline-ruby
51
52
  - lib/badline.rb
52
53
  - lib/badline/addressable.rb
@@ -275,6 +276,7 @@ files:
275
276
  - lib/badline/chips/vic/sprites.rb
276
277
  - lib/badline/chips/vic_i.rb
277
278
  - lib/badline/chips/vic_i/keyed_parts.rb
279
+ - lib/badline/chips/vic_i/light_pen.rb
278
280
  - lib/badline/chips/vic_i/painter.rb
279
281
  - lib/badline/chips/vic_i/sound.rb
280
282
  - lib/badline/chips/vic_i/sound/output.rb
@@ -681,6 +683,7 @@ files:
681
683
  - lib/badline/snapshot/vice/pet.rb
682
684
  - lib/badline/snapshot/vice/pet_cpu.rb
683
685
  - lib/badline/snapshot/vice/pet_crtc.rb
686
+ - lib/badline/snapshot/vice/pet_dongle.rb
684
687
  - lib/badline/snapshot/vice/pet_drive.rb
685
688
  - lib/badline/snapshot/vice/pet_drive8050.rb
686
689
  - lib/badline/snapshot/vice/pet_mem.rb
@@ -692,6 +695,11 @@ files:
692
695
  - lib/badline/snapshot/vice/reu_fields.rb
693
696
  - lib/badline/snapshot/vice/sid_extended.rb
694
697
  - lib/badline/snapshot/vice/sid_registers.rb
698
+ - lib/badline/snapshot/vice/vic20.rb
699
+ - lib/badline/snapshot/vice/vic20_cpu.rb
700
+ - lib/badline/snapshot/vice/vic20_mem.rb
701
+ - lib/badline/snapshot/vice/vic20_restore.rb
702
+ - lib/badline/snapshot/vice/vic20_vici.rb
695
703
  - lib/badline/snapshot/vice/vicii.rb
696
704
  - lib/badline/snapshot/vice/vicii_import.rb
697
705
  - lib/badline/status.rb