badline 0.10.1 → 0.11.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/CLAUDE.md +5 -3
  4. data/README.md +59 -4
  5. data/doc/pinned-behaviour.md +75 -0
  6. data/doc/snapshots.md +15 -0
  7. data/lib/badline/address_bus/pla.rb +3 -16
  8. data/lib/badline/address_bus.rb +14 -0
  9. data/lib/badline/c128/banks.rb +35 -0
  10. data/lib/badline/c128/bus.rb +347 -0
  11. data/lib/badline/c128/bus_state.rb +53 -0
  12. data/lib/badline/c128/color_lines.rb +39 -0
  13. data/lib/badline/c128/cpu.rb +10 -0
  14. data/lib/badline/c128/keys.rb +35 -0
  15. data/lib/badline/c128/mmu.rb +182 -0
  16. data/lib/badline/c128/mmu_pages.rb +105 -0
  17. data/lib/badline/c128/model.rb +39 -0
  18. data/lib/badline/c128/modes.rb +48 -0
  19. data/lib/badline/c128/saved_state.rb +120 -0
  20. data/lib/badline/c128/vdc/memory.rb +54 -0
  21. data/lib/badline/c128/vdc/painter.rb +143 -0
  22. data/lib/badline/c128/vdc/raster.rb +97 -0
  23. data/lib/badline/c128/vdc/window.rb +215 -0
  24. data/lib/badline/c128/vdc.rb +268 -0
  25. data/lib/badline/c128.rb +320 -0
  26. data/lib/badline/chrout_trap.rb +3 -0
  27. data/lib/badline/computer/attachments.rb +4 -13
  28. data/lib/badline/computer/kernal_traps.rb +29 -0
  29. data/lib/badline/computer.rb +5 -0
  30. data/lib/badline/frontend/app.rb +34 -21
  31. data/lib/badline/frontend/boot.rb +31 -1
  32. data/lib/badline/frontend/controls.rb +11 -0
  33. data/lib/badline/frontend/keys.rb +28 -1
  34. data/lib/badline/frontend/machine_window.rb +16 -13
  35. data/lib/badline/frontend/menu_pages.rb +6 -1
  36. data/lib/badline/frontend/menu_ports.rb +1 -1
  37. data/lib/badline/frontend/pause_menu.rb +1 -0
  38. data/lib/badline/frontend/screen.rb +33 -8
  39. data/lib/badline/frontend/screens.rb +90 -0
  40. data/lib/badline/frontend/timeline.rb +15 -4
  41. data/lib/badline/frontend.rb +1 -0
  42. data/lib/badline/kernal_trap/file.rb +1 -1
  43. data/lib/badline/kernal_trap/layout.rb +58 -8
  44. data/lib/badline/kernal_trap/load.rb +1 -1
  45. data/lib/badline/kernal_trap/save.rb +1 -1
  46. data/lib/badline/kernal_trap/serial.rb +5 -0
  47. data/lib/badline/machine.rb +15 -6
  48. data/lib/badline/media.rb +8 -6
  49. data/lib/badline/options/event.rb +5 -1
  50. data/lib/badline/options/family.rb +52 -0
  51. data/lib/badline/options/help.rb +5 -1
  52. data/lib/badline/options/table.rb +7 -1
  53. data/lib/badline/options/validation.rb +14 -1
  54. data/lib/badline/options.rb +20 -19
  55. data/lib/badline/roms/README +27 -0
  56. data/lib/badline/roms/c128/basic.rom +0 -0
  57. data/lib/badline/roms/c128/character.rom +0 -0
  58. data/lib/badline/roms/c128/kernal.rom +0 -0
  59. data/lib/badline/snapshot/container.rb +2 -0
  60. data/lib/badline/snapshot/image.rb +14 -0
  61. data/lib/badline/snapshot.rb +9 -2
  62. data/lib/badline/version.rb +1 -1
  63. data/lib/badline/vic/display_state.rb +12 -0
  64. data/lib/badline/vic/registers.rb +3 -0
  65. data/lib/badline/vic.rb +108 -7
  66. data/lib/badline.rb +1 -0
  67. metadata +24 -1
@@ -39,6 +39,7 @@ module Badline
39
39
  <<~BANNER
40
40
  Usage: #{@program} [c64] [options] [media]
41
41
  #{@program} vic20 [options] [media]
42
+ #{@program} c128 [options] [media]
42
43
  #{@program} --headless [options] tune.sid
43
44
  #{@program} [options] tune.sid --audio-out FILE
44
45
  #{@program} sid [options] FILE|DIR...
@@ -46,6 +47,7 @@ module Badline
46
47
  Commands:
47
48
  c64 Run a C64, the one --model names (the default)
48
49
  vic20 Run a PAL VIC-20, with the RAM --ram names
50
+ c128 Run a C128 in C128 mode, the one --model names
49
51
  sid Play .sid tunes and directories of them
50
52
 
51
53
  Media can be a .prg/.p00 program, a .d64/.d71/.d81 disk image, a
@@ -89,7 +91,9 @@ module Badline
89
91
  the frame as a .bmp, with the frame's number in place of %d. menu
90
92
  opens the pause menu, and menu=PAGE opens it on snapshots, drive,
91
93
  datasette, expansion, ports, sound or power. Its frames still count,
92
- and resume closes it. reset, freeze and quit take no argument.
94
+ and resume closes it. display=vdc or display=vic shows the C128's
95
+ 80 or 40 column screen, as F8 switches them. reset, freeze and quit
96
+ take no argument.
93
97
  BANNER
94
98
  end
95
99
 
@@ -11,6 +11,9 @@ module Badline
11
11
  # emulated.
12
12
  VIC20_MODELS = %w[pal ntsc].freeze
13
13
 
14
+ # The C128's models, as C128::Model names them.
15
+ C128_MODELS = %w[c128 c128ntsc c128dcr c128dcrntsc].freeze
16
+
14
17
  # The VIC-20 RAM expansions --ram takes, in the order the help lists
15
18
  # them.
16
19
  RAM_NAMES = %w[unexpanded 3k 8k 16k 24k 32k all].freeze
@@ -36,9 +39,12 @@ module Badline
36
39
  section: "Window options", needs: :window),
37
40
  Option.new("--reu SIZE", "Plug in an REU of SIZE K: 128, 256, 512 (a 1750) or up to 16384",
38
41
  section: "Window options", needs: :window),
39
- Option.new("--model NAME", "C64 to run: #{MODELS.join(', ')} (default: #{MODELS.first}); VIC-20: pal",
42
+ Option.new("--model NAME", "C64 to run: #{MODELS.join(', ')} (default: #{MODELS.first}); VIC-20: pal; " \
43
+ "C128: #{C128_MODELS.join(', ')}",
40
44
  section: "Window options", needs: :window),
41
45
  Option.new("--ntsc", "Run an NTSC C64, as --model ntsc does", section: "Window options", needs: :window),
46
+ Option.new("--c64", "Start the C128 in C64 mode, holding C= at power-on",
47
+ section: "Window options", needs: :window),
42
48
  Option.new("--ram SIZE", "VIC-20 RAM expansion: #{RAM_NAMES.join(', ')} (default: unexpanded)",
43
49
  section: "Window options", needs: :window),
44
50
  Option.new("--no-vsync", "Pace frames by the timer or the sound instead of the display",
@@ -64,6 +64,19 @@ module Badline
64
64
  elsif !@ram.nil?
65
65
  raise Error, "--ram needs vic20"
66
66
  end
67
+ if @family == :c128
68
+ validate_c128
69
+ elsif @c64_mode
70
+ raise Error, "--c64 needs c128"
71
+ end
72
+ end
73
+
74
+ # The C128 runs in the window, without an REU, and its NTSC models
75
+ # have names of their own.
76
+ def validate_c128
77
+ raise Error, "--ntsc needs the C64; the NTSC C128 is --model c128ntsc" if @models.include?("ntsc")
78
+ raise Error, "--reu needs the C64" unless @reu.nil?
79
+ raise Error, "the C128 needs the window" if headless?
67
80
  end
68
81
 
69
82
  # The VIC-20 is a PAL one, in the window, without the C64's SID and
@@ -87,7 +100,7 @@ module Badline
87
100
  # --model and --ntsc may be given more than once, but only for one
88
101
  # model.
89
102
  def validate_models
90
- names = @family == :vic20 ? VIC20_MODELS : MODELS
103
+ names = family_models
91
104
  unknown = @models.find { |name| !names.include?(name) }
92
105
  raise Error, "invalid argument: --model #{unknown}" unless unknown.nil?
93
106
  raise Error, "conflicting models: #{@models.uniq.join(' and ')}" unless @models.uniq.size <= 1
@@ -5,16 +5,17 @@ require "badline/options/event"
5
5
  require "badline/options/table"
6
6
  require "badline/options/help"
7
7
  require "badline/options/validation"
8
+ require "badline/options/family"
8
9
 
9
10
  module Badline
10
11
  # The command line of both builds, badline-ruby and the native badline.
11
12
  # Either opens the window for any media, or with --headless or
12
13
  # --audio-out plays or renders a .sid tune without one. `sid`, given
13
14
  # first, plays .sid tunes and directories of them in the terminal.
14
- # `c64` or `vic20`, given first, names the machine family, and without
15
- # either the family is the C64. The
16
- # native build adds --no-sound and --version, and badline-ruby adds
17
- # --disable-jit.
15
+ # `c64`, `vic20` or `c128`, given first, names the machine family, and
16
+ # without one the family is the C64, or the C128 for a program that loads
17
+ # where C128 BASIC starts. The native build adds --no-sound and
18
+ # --version, and badline-ruby adds --disable-jit.
18
19
  #
19
20
  # TABLE lists the options, and both the parser and the help read it. It
20
21
  # parses by hand, inside the subset of Ruby Spinel compiles. Values come
@@ -23,6 +24,7 @@ module Badline
23
24
  # arguments before paying for the emulator.
24
25
  class Options
25
26
  include Validation
27
+ include Family
26
28
 
27
29
  class Error < StandardError; end
28
30
 
@@ -55,7 +57,8 @@ module Badline
55
57
  attr_reader :program, :media_path, :tune_paths, :subtune, :sid_model, :reu, :frames, :screenshot, :save_snapshot,
56
58
  :audio_out, :seconds, :songlengths, :filter_chunk, :timeline
57
59
 
58
- # The machine family to build (Badline::Machine), :c64 or :vic20.
60
+ # The machine family to build (Badline::Machine), :c64, :vic20 or
61
+ # :c128.
59
62
  attr_reader :family
60
63
 
61
64
  # The VIC-20's RAM expansion --ram names, a key of
@@ -72,6 +75,8 @@ module Badline
72
75
  @program = native ? "badline" : "badline-ruby"
73
76
  @media_path = nil
74
77
  @family = :c64
78
+ @family_named = false
79
+ @c64_mode = false
75
80
  @sid_command = false
76
81
  @tune_paths = []
77
82
  @subtune = nil
@@ -110,6 +115,7 @@ module Badline
110
115
  args = argv.dup
111
116
  subcommand(args)
112
117
  argument(args.shift, args) until args.empty?
118
+ pick_family
113
119
  play_lone_tune
114
120
  validate unless help? || version?
115
121
  self
@@ -167,9 +173,14 @@ module Badline
167
173
 
168
174
  def jit? = @jit
169
175
 
170
- # The name of the model to build: a C64 Badline::Model names, or a
171
- # VIC-20 of VIC20_MODELS. --ntsc is short for --model ntsc.
172
- def model = @models.first || (@family == :vic20 ? VIC20_MODELS.first : MODELS.first)
176
+ # The name of the model to build: a C64 Badline::Model names, a VIC-20
177
+ # of VIC20_MODELS or a C128 of C128_MODELS. --ntsc is short for --model
178
+ # ntsc.
179
+ def model = @models.first || family_models.first
180
+
181
+ # Whether --c64 asks the C128 to start in C64 mode, holding C= at
182
+ # power-on.
183
+ def c64_mode? = @c64_mode
173
184
 
174
185
  def help? = @help
175
186
 
@@ -183,17 +194,6 @@ module Badline
183
194
  [:both, @native ? :native : :ruby].include?(option.build)
184
195
  end
185
196
 
186
- # Takes `sid`, `c64` or `vic20` when given first.
187
- def subcommand(args)
188
- case args.first
189
- when "sid" then @sid_command = true
190
- when "c64" then @family = :c64
191
- when "vic20" then @family = :vic20
192
- else return
193
- end
194
- args.shift
195
- end
196
-
197
197
  # A .sid on its own plays in the SID player, as `sid` would, unless an
198
198
  # option of the emulator's window asks for the machine.
199
199
  def play_lone_tune
@@ -273,6 +273,7 @@ module Badline
273
273
  when "--verbose" then @verbose = true
274
274
  when "--true-drive" then @true_drive = true
275
275
  when "--ntsc" then @models << "ntsc"
276
+ when "--c64" then @c64_mode = true
276
277
  when "--unpaced" then @paced = false
277
278
  when "--disable-jit" then @jit = false
278
279
  when "--help" then @help = true
@@ -87,3 +87,30 @@ vic20/character.rom
87
87
  -------------------
88
88
  The character generator ROM, 901460-03, for $8000-$8FFF.
89
89
  SHA-1 4fd85ab6647ee2ac7ba40f729323f2472d35b9b4.
90
+
91
+ Commodore 128 ROMs
92
+ ==================
93
+
94
+ Downloaded from
95
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/c128/
96
+ C128::Bus maps them in C128 mode. C64 mode runs basic.rom, kernal.rom
97
+ and character.rom above.
98
+
99
+ c128/basic.rom
100
+ --------------
101
+ BASIC 7.0, 318022-02 (basic.318022-02.bin), 32 KB: the low half for
102
+ $4000-$7FFF followed by the high half for $8000-$BFFF.
103
+ SHA-1 953dcdf5784a6b39ef84dd6fd968c7a03d8d6816.
104
+
105
+ c128/kernal.rom
106
+ ---------------
107
+ 318020-05 (kernal.318020-05.bin), 16 KB: the screen editor for
108
+ $C000-$CFFF, the Z80 BIOS for $D000-$DFFF and the KERNAL for
109
+ $E000-$FFFF.
110
+ SHA-1 ceb6e1a1bf7e08eb9cbc651afa29e26adccf38ab.
111
+
112
+ c128/character.rom
113
+ ------------------
114
+ The character generator ROM, 390059-01 (characters.390059-01.bin),
115
+ 8 KB: the C64's set, the same as character.rom, followed by the C128's.
116
+ SHA-1 29ed066d513f2d5c09ff26d9166ba23c2afb2b3f.
Binary file
Binary file
Binary file
@@ -28,6 +28,8 @@ module Badline
28
28
  MACHINE = "C64SC"
29
29
  # xvic names its machine VIC20.
30
30
  VIC20 = "VIC20"
31
+ # x128 names its machine C128.
32
+ C128 = "C128"
31
33
  GZIP_MAGIC = "\x1f\x8b".b
32
34
 
33
35
  # The VICE version block, when VICE wrote the file: four version
@@ -29,6 +29,7 @@ module Badline
29
29
  # warns it without a block.
30
30
  def load(&)
31
31
  return load_vic20(&) if vic20?
32
+ return load_c128(&) if c128?
32
33
 
33
34
  computer = badline? ? opening { Computer.restored(state) } : Vice.setup(@container).build
34
35
  tell(badline? ? state_report(computer) : Vice::Restore.apply(@container, computer), &)
@@ -38,6 +39,9 @@ module Badline
38
39
  # Whether the machine is a VIC-20, as the container names it.
39
40
  def vic20? = @container.machine == Container::VIC20
40
41
 
42
+ # Whether the machine is a C128, as the container names it.
43
+ def c128? = @container.machine == Container::C128
44
+
41
45
  # Restores `computer` from the snapshot, and returns the Report. A
42
46
  # snapshot that fails leaves the machine as it was.
43
47
  def restore(computer, &)
@@ -60,6 +64,16 @@ module Badline
60
64
  machine
61
65
  end
62
66
 
67
+ # A C128 restores from its BADLINE module alone. x128's own modules
68
+ # aren't read.
69
+ def load_c128(&)
70
+ raise FormatError, "badline reads only its own C128 snapshots, not x128's" unless badline?
71
+
72
+ machine = C128.restored(state)
73
+ tell(state_report(machine), &)
74
+ machine
75
+ end
76
+
63
77
  def tell(report)
64
78
  report.ignored.each { |line| block_given? ? yield(line) : warn("badline: #{line}") }
65
79
  end
@@ -27,11 +27,12 @@ module Badline
27
27
  path
28
28
  end
29
29
 
30
- # A VIC-20's snapshot holds the BADLINE module alone, in a container
31
- # named as xvic names its machine.
30
+ # A VIC-20's or a C128's snapshot holds the BADLINE module alone, in a
31
+ # container named as xvic or x128 names its machine.
32
32
  def dump(computer)
33
33
  state = computer.snapshot
34
34
  return Container.new([MachineState.section(state)], machine: Container::VIC20).to_s if computer.family == :vic20
35
+ return Container.new([MachineState.section(state)], machine: Container::C128).to_s if computer.family == :c128
35
36
 
36
37
  Container.new(Vice.export(state) + [MachineState.section(state)]).to_s
37
38
  end
@@ -59,4 +60,10 @@ module Badline
59
60
 
60
61
  def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
61
62
  end
63
+
64
+ class C128
65
+ def save_snapshot(path) = Snapshot.save(self, path)
66
+
67
+ def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
68
+ end
62
69
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- VERSION = "0.10.1"
4
+ VERSION = "0.11.0"
5
5
  end
@@ -68,6 +68,12 @@ module Badline
68
68
  # True once AEC has followed BA down and the VIC owns the bus.
69
69
  def bus_taken?(column) = @ba ? column >= @ba + BA_DELAY : false
70
70
 
71
+ # The CPU kept the bus through the cycle before +column+, so on a bad
72
+ # line AEC falls BA_DELAY cycles after it at the earliest.
73
+ def keep_bus(column)
74
+ @ba &&= column
75
+ end
76
+
71
77
  def fetching?(column)
72
78
  @matched && column >= FETCH_FIRST && column <= DMA_LAST
73
79
  end
@@ -98,6 +104,12 @@ module Badline
98
104
 
99
105
  def new_line(line)
100
106
  @ba = nil
107
+ raster_step(line)
108
+ end
109
+
110
+ # The raster counter moved on to +line+, at the line's start or, under
111
+ # the VIC-IIe's TEST bit, within it.
112
+ def raster_step(line)
101
113
  @line_bits = line & 0b111
102
114
  @den_line = line == FIRST_LINE
103
115
  @in_window = line.between?(FIRST_LINE, LAST_LINE)
@@ -150,6 +150,9 @@ module Badline
150
150
  def fast? = @iie && @bytes[0x30].anybits?(0x01)
151
151
  def test? = @iie && @bytes[0x30].anybits?(0x02)
152
152
 
153
+ # FAST and TEST as bits 0 and 1.
154
+ def clock_bits = @iie ? @bytes[0x30] & 0x03 : 0
155
+
153
156
  def raster_target = uint16(@bytes[0x12], (@bytes[0x11] & 0x80) >> 7)
154
157
 
155
158
  def latch_irq!(bit)
data/lib/badline/vic.rb CHANGED
@@ -42,6 +42,11 @@ module Badline
42
42
  # GREEN_PALETTE on a monochrome monitor.
43
43
  attr_accessor :palette
44
44
 
45
+ # The line of #display the beam draws, which is the raster line unless
46
+ # the VIC-IIe's TEST bit has moved the raster counter on since the
47
+ # last vertical sync (#test_step!).
48
+ attr_reader :output_line
49
+
45
50
  # The parts a VICE snapshot reads and sets.
46
51
  attr_reader :registers, :display_state, :sequencer, :sprites, :character_buffer, :color_buffer,
47
52
  :fetch_d011
@@ -86,6 +91,11 @@ module Badline
86
91
  HOOK_DISPLAY = 16
87
92
  HOOK_LAST = 32
88
93
 
94
+ # The vertical sync starts this many lines into the vertical blank,
95
+ # and lasts VSYNC_LINES.
96
+ VSYNC_DELAY = 3
97
+ VSYNC_LINES = 3
98
+
89
99
  # On the 6569 the DMA compares fall in columns 53 and 54 and the
90
100
  # display compare in column 57. A region whose sprite fetches run later
91
101
  # moves them with the fetches (Region::Profile#sprite_cycle).
@@ -125,6 +135,7 @@ module Badline
125
135
  @height = region.lines_per_frame
126
136
  @last_column = @columns_per_line - 1
127
137
  @last_line = @height - 1
138
+ @vsync_line = region.vblank[0] + VSYNC_DELAY
128
139
  layout_columns
129
140
  @blank_columns = Array.new(@columns_per_line) { |column| Region.blanked?(column, region.hblank) }.freeze
130
141
  @blank_lines = Array.new(@height) { |line| Region.blanked?(line, region.vblank) }.freeze
@@ -153,6 +164,7 @@ module Badline
153
164
 
154
165
  @column = 0
155
166
  @rasterline = 0
167
+ @output_line = 0
156
168
 
157
169
  @character_buffer = Array.new(40, 0)
158
170
  @color_buffer = Array.new(40, 0)
@@ -169,11 +181,13 @@ module Badline
169
181
  @lp_triggered = false
170
182
  @lp_low = false
171
183
  @raster_match = false
184
+ @test_wrap = false
172
185
  end
173
186
 
174
187
  # Everything the VIC holds between cycles: the beam, the fetch and
175
188
  # g-access pipeline, the light pen and raster latches, the finished
176
- # lines, the registers and the display, sequencer and sprite logic.
189
+ # lines, on the VIC-IIe the display line and the TEST bit's wrap, the
190
+ # registers and the display, sequencer and sprite logic.
177
191
  # Whether it renders is the host's, and every line comes back dirty so
178
192
  # a front end repaints.
179
193
  def save_state(out)
@@ -184,6 +198,7 @@ module Badline
184
198
  out.int(@vic_bank.lines)
185
199
  out.blob(@character_buffer).blob(@color_buffer).booleans(@sprite_ba)
186
200
  @lines.each { |line| out.blob(line) }
201
+ out.int(@output_line).boolean(@test_wrap) if @iie
187
202
  @registers.save_state(out)
188
203
  @display_state.save_state(out)
189
204
  @sequencer.save_state(out)
@@ -195,6 +210,7 @@ module Badline
195
210
  load_beam(input)
196
211
  load_buffers(input)
197
212
  load_lines(input)
213
+ load_test_state(input) if @iie
198
214
  @registers.load_state(input)
199
215
  @display_state.load_state(input)
200
216
  @sequencer.load_state(input)
@@ -218,6 +234,7 @@ module Badline
218
234
  if @column == @columns_per_line
219
235
  finish_line!
220
236
  @column = 0
237
+ step_output_line
221
238
  @rasterline = @rasterline == @last_line ? 0 : @rasterline + 1
222
239
  check_raster_irq!(@rasterline.zero? ? @last_line : @rasterline)
223
240
  end
@@ -255,6 +272,57 @@ module Badline
255
272
  def fast? = @registers.fast?
256
273
  def test? = @registers.test?
257
274
 
275
+ # $D030's FAST and TEST bits as bits 0 and 1, which the C128 reads once
276
+ # a cycle.
277
+ def clock_bits = @registers.clock_bits
278
+
279
+ # Whether the cycle just run was one of the five refresh cycles, Bauer's
280
+ # 11-15, whose phi1 half the VIC keeps in the VIC-IIe's FAST mode.
281
+ # @column has already advanced, so it is the Bauer cycle less one.
282
+ def refresh_cycle? = @column.between?(10, 14)
283
+
284
+ # In the VIC-IIe's FAST mode the 8502 drives the bus in both halves of
285
+ # the cycle, and the VIC's accesses in the cycle just run latched what
286
+ # it put there. The g-access, or the idle access, took the byte of
287
+ # phi1. A c-access took the byte of phi2 as one made before AEC does
288
+ # (#fetch_character_data!): $ff for the video matrix, unless the CPU
289
+ # was reading or writing the VIC's own registers, and the byte's low
290
+ # nibble for colour. AEC can't follow BA down while the CPU runs at
291
+ # 2 MHz, so on a bad line it falls three cycles after FAST mode ends.
292
+ def take_cpu_bus(phi1, phi2, register_access)
293
+ slot = @g_tick
294
+ @g_data[slot] = phi1 unless @g_kind[slot] == G_BLANK
295
+ display_state = @display_state
296
+ display_state.keep_bus(@column)
297
+ return unless display_state.fetching?(@column.zero? ? @last_column : @column - 1)
298
+
299
+ vmli = display_state.vmli
300
+ @character_buffer[vmli] = register_access ? phi2 : 0xff
301
+ @color_buffer[vmli] = phi2 & 0x0f
302
+ end
303
+
304
+ # The VIC-IIe's TEST bit clocks the raster counter in every cycle, and
305
+ # the C128 calls this ahead of each cycle the bit is set in. The line's
306
+ # last cycle, whose own step it falls in with, adds nothing more. The
307
+ # counter takes two steps from the frame's last line to line 0, as line
308
+ # 0 reads the last line for its first cycle.
309
+ def test_step!
310
+ return if @column == @last_column
311
+
312
+ if @rasterline == @last_line
313
+ @test_wrap = !@test_wrap
314
+ return if @test_wrap
315
+
316
+ @rasterline = 0
317
+ @display_state.new_frame
318
+ else
319
+ @test_wrap = false
320
+ @rasterline += 1
321
+ end
322
+ @display_state.raster_step(@rasterline)
323
+ check_raster_irq!
324
+ end
325
+
258
326
  def peek(addr)
259
327
  i = offset_of(addr) % (2**6)
260
328
  case i
@@ -363,7 +431,7 @@ module Badline
363
431
 
364
432
  # Puts the beam at the start of `line`.
365
433
  def restore_line(line)
366
- @rasterline = line
434
+ @rasterline = @output_line = line
367
435
  @column = 0
368
436
  end
369
437
 
@@ -400,7 +468,7 @@ module Badline
400
468
 
401
469
  def load_beam(input)
402
470
  @column = input.int
403
- @rasterline = input.int
471
+ @rasterline = @output_line = input.int
404
472
  @cycles = input.int
405
473
  @pending_write = input.boolean?
406
474
  @g_tick = input.int
@@ -421,6 +489,13 @@ module Badline
421
489
  input.booleans_into(@sprite_ba)
422
490
  end
423
491
 
492
+ # The VIC-IIe's display line and TEST wrap, which only its TEST bit
493
+ # moves off the raster line.
494
+ def load_test_state(input)
495
+ @output_line = input.int
496
+ @test_wrap = input.boolean?
497
+ end
498
+
424
499
  # The display is the finished lines one after another.
425
500
  def load_lines(input)
426
501
  @lines.each_with_index do |line, number|
@@ -663,7 +738,7 @@ module Badline
663
738
  # or not there is a line to draw — then composite the active sprites
664
739
  # over the finished background and copy the line into the frame display.
665
740
  def finish_line!
666
- return @sprites.finish_line(nil, @sequencer.fg) if vblank? || !@render
741
+ return finish_blank_line if vblank? || !@render
667
742
 
668
743
  composite = @sprites.active?
669
744
  @sequencer.apply_color_patches
@@ -672,12 +747,38 @@ module Badline
672
747
  @sequencer.apply_border if composite
673
748
 
674
749
  colors = @sequencer.colors
675
- line = @lines[@rasterline]
750
+ output_line = @output_line
751
+ line = @lines[output_line]
676
752
  return if line == colors
677
753
 
678
754
  line[0, @width] = colors
679
- @display[@rasterline * @width, @width] = colors
680
- @dirty_lines[@rasterline] = true
755
+ @display[output_line * @width, @width] = colors
756
+ @dirty_lines[output_line] = true
757
+ end
758
+
759
+ # A blanked line paints nothing, and is never shown unless the TEST
760
+ # bit has moved the beam off the raster line, where it shows black.
761
+ def finish_blank_line
762
+ @sprites.finish_line(nil, @sequencer.fg)
763
+ return if @output_line == @rasterline || !@render
764
+
765
+ line = @output_line
766
+ @lines[line].fill(0)
767
+ @display.fill(0, line * @width, @width)
768
+ @dirty_lines[line] = true
769
+ end
770
+
771
+ # The display line the beam draws next follows the one before it, and
772
+ # a line that ends in the vertical sync puts it back on the raster
773
+ # line. Only the VIC-IIe's TEST bit moves the raster counter on between
774
+ # the two, so the lines it skips shift the picture up until the next
775
+ # sync.
776
+ def step_output_line
777
+ @output_line = if @rasterline.between?(@vsync_line, @vsync_line + VSYNC_LINES - 1)
778
+ @rasterline + 1
779
+ else
780
+ @output_line == @last_line ? 0 : @output_line + 1
781
+ end
681
782
  end
682
783
 
683
784
  def video_matrix(index)
data/lib/badline.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "badline/core"
4
+ require "badline/c128"
4
5
  require "badline/options"
5
6
  require "badline/data_path"
6
7
  require "badline/snapshot"
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.10.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inge Jørgensen
@@ -71,6 +71,23 @@ files:
71
71
  - lib/badline/audio/stereo.rb
72
72
  - lib/badline/audio/terminal.rb
73
73
  - lib/badline/audio/wav.rb
74
+ - lib/badline/c128.rb
75
+ - lib/badline/c128/banks.rb
76
+ - lib/badline/c128/bus.rb
77
+ - lib/badline/c128/bus_state.rb
78
+ - lib/badline/c128/color_lines.rb
79
+ - lib/badline/c128/cpu.rb
80
+ - lib/badline/c128/keys.rb
81
+ - lib/badline/c128/mmu.rb
82
+ - lib/badline/c128/mmu_pages.rb
83
+ - lib/badline/c128/model.rb
84
+ - lib/badline/c128/modes.rb
85
+ - lib/badline/c128/saved_state.rb
86
+ - lib/badline/c128/vdc.rb
87
+ - lib/badline/c128/vdc/memory.rb
88
+ - lib/badline/c128/vdc/painter.rb
89
+ - lib/badline/c128/vdc/raster.rb
90
+ - lib/badline/c128/vdc/window.rb
74
91
  - lib/badline/cartridge.rb
75
92
  - lib/badline/cartridge/action_replay.rb
76
93
  - lib/badline/cartridge/atomic_power.rb
@@ -109,6 +126,7 @@ files:
109
126
  - lib/badline/color_memory.rb
110
127
  - lib/badline/computer.rb
111
128
  - lib/badline/computer/attachments.rb
129
+ - lib/badline/computer/kernal_traps.rb
112
130
  - lib/badline/computer/saved_state.rb
113
131
  - lib/badline/control_ports.rb
114
132
  - lib/badline/core.rb
@@ -174,6 +192,7 @@ files:
174
192
  - lib/badline/frontend/player_window.rb
175
193
  - lib/badline/frontend/scope.rb
176
194
  - lib/badline/frontend/screen.rb
195
+ - lib/badline/frontend/screens.rb
177
196
  - lib/badline/frontend/screenshot.rb
178
197
  - lib/badline/frontend/sid_history.rb
179
198
  - lib/badline/frontend/sid_view.rb
@@ -229,6 +248,7 @@ files:
229
248
  - lib/badline/model.rb
230
249
  - lib/badline/options.rb
231
250
  - lib/badline/options/event.rb
251
+ - lib/badline/options/family.rb
232
252
  - lib/badline/options/help.rb
233
253
  - lib/badline/options/option.rb
234
254
  - lib/badline/options/table.rb
@@ -248,6 +268,9 @@ files:
248
268
  - lib/badline/rom.rb
249
269
  - lib/badline/roms/README
250
270
  - lib/badline/roms/basic.rom
271
+ - lib/badline/roms/c128/basic.rom
272
+ - lib/badline/roms/c128/character.rom
273
+ - lib/badline/roms/c128/kernal.rom
251
274
  - lib/badline/roms/c64gs.rom
252
275
  - lib/badline/roms/character.rom
253
276
  - lib/badline/roms/dos1541.rom