badline 0.2.1 → 0.4.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.
- checksums.yaml +4 -4
- data/.claude/hooks/session-start.sh +57 -0
- data/.claude/settings.json +14 -0
- data/CHANGELOG.md +107 -0
- data/CLAUDE.md +51 -16
- data/CONTRIBUTING.md +95 -3
- data/README.md +119 -51
- data/SECURITY.md +9 -0
- data/doc/pinned-behaviour.md +293 -39
- data/exe/badline-ruby +59 -0
- data/lib/badline/address_bus.rb +28 -14
- data/lib/badline/addressable.rb +1 -1
- data/lib/badline/audio/aiff.rb +8 -3
- data/lib/badline/audio/bare_player.rb +20 -3
- data/lib/badline/audio/cli.rb +17 -12
- data/lib/badline/audio/console.rb +12 -58
- data/lib/badline/audio/machine_player.rb +9 -6
- data/lib/badline/audio/playback.rb +3 -0
- data/lib/badline/audio/renderer.rb +3 -3
- data/lib/badline/audio/sdl_sink.rb +20 -52
- data/lib/badline/audio/stream.rb +117 -0
- data/lib/badline/audio/terminal.rb +90 -0
- data/lib/badline/audio.rb +3 -2
- data/lib/badline/cartridge/easy_flash.rb +6 -2
- data/lib/badline/cartridge/epyx_fastload.rb +1 -1
- data/lib/badline/cartridge/geo_ram.rb +63 -0
- data/lib/badline/cartridge/mach5.rb +1 -1
- data/lib/badline/cartridge/magic_desk.rb +5 -1
- data/lib/badline/cartridge/ocean.rb +5 -2
- data/lib/badline/cartridge/rex_utility.rb +1 -1
- data/lib/badline/cartridge/rgcd.rb +1 -1
- data/lib/badline/cartridge/simons_basic.rb +2 -2
- data/lib/badline/cartridge/westermann.rb +2 -2
- data/lib/badline/cartridge.rb +9 -0
- data/lib/badline/checkpoint.rb +98 -0
- data/lib/badline/cia/interrupt_register.rb +34 -10
- data/lib/badline/cia.rb +19 -10
- data/lib/badline/color_memory.rb +2 -2
- data/lib/badline/computer.rb +34 -14
- data/lib/badline/cpu.rb +2 -2
- data/lib/badline/gui/application.rb +70 -26
- data/lib/badline/gui/gamepads.rb +22 -22
- data/lib/badline/gui/joy_map.rb +1 -1
- data/lib/badline/gui/key_map.rb +6 -1
- data/lib/badline/gui/pane.rb +7 -6
- data/lib/badline/gui/screen_pane.rb +9 -13
- data/lib/badline/gui/window.rb +35 -20
- data/lib/badline/gui.rb +1 -1
- data/lib/badline/input/mouse1351.rb +21 -6
- data/lib/badline/input/paddles.rb +11 -6
- data/lib/badline/kernal_trap/channel.rb +102 -7
- data/lib/badline/kernal_trap/drive/block_commands.rb +108 -0
- data/lib/badline/kernal_trap/drive/channels.rb +53 -0
- data/lib/badline/kernal_trap/drive/memory.rb +109 -0
- data/lib/badline/kernal_trap/drive/parameters.rb +28 -0
- data/lib/badline/kernal_trap/drive/status.rb +3 -0
- data/lib/badline/kernal_trap/drive/write_file.rb +41 -0
- data/lib/badline/kernal_trap/drive/writes.rb +92 -0
- data/lib/badline/kernal_trap/drive.rb +109 -131
- data/lib/badline/kernal_trap/file.rb +6 -14
- data/lib/badline/kernal_trap/load.rb +6 -0
- data/lib/badline/kernal_trap/save.rb +13 -7
- data/lib/badline/keyboard.rb +19 -3
- data/lib/badline/media.rb +44 -13
- data/lib/badline/memory.rb +2 -2
- data/lib/badline/options.rb +209 -0
- data/lib/badline/ram_expansion/banking.rb +30 -0
- data/lib/badline/ram_expansion/plus256k.rb +38 -0
- data/lib/badline/ram_expansion/plus60k.rb +31 -0
- data/lib/badline/ram_expansion/unexpanded.rb +22 -0
- data/lib/badline/ram_expansion.rb +21 -0
- data/lib/badline/region.rb +51 -0
- data/lib/badline/rom.rb +14 -5
- data/lib/badline/sdl/events.rb +47 -0
- data/lib/badline/sdl/functions.rb +73 -0
- data/lib/badline/sdl.rb +67 -0
- data/lib/badline/sid/catch_up.rb +110 -0
- data/lib/badline/sid/decimator.rb +6 -1
- data/lib/badline/sid/filter.rb +5 -5
- data/lib/badline/sid/waveform/fast_forward.rb +10 -10
- data/lib/badline/sid/waveform/noise_writeback.rb +32 -14
- data/lib/badline/sid/waveform.rb +8 -8
- data/lib/badline/sid.rb +19 -93
- data/lib/badline/storage/d64_image.rb +10 -0
- data/lib/badline/storage/d71_image.rb +12 -0
- data/lib/badline/storage/d81_image.rb +9 -0
- data/lib/badline/storage/disk_image/bam.rb +111 -0
- data/lib/badline/storage/disk_image/directory.rb +79 -0
- data/lib/badline/storage/disk_image/writing.rb +110 -0
- data/lib/badline/storage/disk_image.rb +49 -4
- data/lib/badline/storage/host_directory.rb +17 -4
- data/lib/badline/storage/sid_file.rb +36 -2
- data/lib/badline/storage.rb +18 -1
- data/lib/badline/time_of_day.rb +11 -2
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/control_lines.rb +104 -0
- data/lib/badline/via/interrupt_register.rb +58 -0
- data/lib/badline/via/shift_register.rb +135 -0
- data/lib/badline/via/timer1.rb +82 -0
- data/lib/badline/via/timer2.rb +84 -0
- data/lib/badline/via.rb +262 -0
- data/lib/badline/vic/bank.rb +4 -3
- data/lib/badline/vic/color_patches.rb +6 -3
- data/lib/badline/vic/display_state.rb +4 -0
- data/lib/badline/vic/graphics_shifter.rb +49 -8
- data/lib/badline/vic/registers.rb +19 -27
- data/lib/badline/vic/sequencer.rb +18 -18
- data/lib/badline/vic/sequencer_output.rb +4 -4
- data/lib/badline/vic/sprite/internal_bus.rb +4 -0
- data/lib/badline/vic/sprite/shifter.rb +25 -3
- data/lib/badline/vic/sprite.rb +5 -4
- data/lib/badline/vic/sprites.rb +8 -3
- data/lib/badline/vic.rb +98 -46
- data/lib/badline.rb +5 -0
- data/packaging/homebrew/badline.rb +34 -0
- metadata +37 -20
- data/exe/badline +0 -75
- data/exe/badline-sid +0 -34
- data/lib/badline/audio/options.rb +0 -137
|
@@ -9,22 +9,34 @@ module Badline
|
|
|
9
9
|
#
|
|
10
10
|
# The byte a group draws loads at pixel XSCROLL. ECM and BMM take hold at
|
|
11
11
|
# pixel 4 as they rise and at pixel 6 as they fall (the 6569's colour
|
|
12
|
-
# latency)
|
|
12
|
+
# latency), except that BMM falls at pixel 5 out of any mode but hi-res
|
|
13
|
+
# bitmap. MCM takes hold at pixel 4 for the colour lookup but only at
|
|
13
14
|
# pixel 7 for how the register is read, where a rising MCM also resets
|
|
14
15
|
# the multicolour flip-flop. An MCM that falls out of the invalid
|
|
15
16
|
# ECM+MCM mode is a pixel later on both counts: the lookup changes at
|
|
16
17
|
# pixel 5 and the read at the next group's pixel 0. The mode bits are
|
|
17
18
|
# the sequencer's: ECM 4, BMM 2, MCM 1.
|
|
19
|
+
#
|
|
20
|
+
# The 8565 has no colour latency: ECM and BMM take hold, rising or
|
|
21
|
+
# falling, at the next group's pixel 0, and an MCM that falls out of
|
|
22
|
+
# ECM+MCM does so on time. Where that pixel 0 takes the group out of an
|
|
23
|
+
# invalid mode, it is still black into hi-res text, and so is a
|
|
24
|
+
# background pixel into multicolour bitmap.
|
|
18
25
|
class GraphicsShifter
|
|
26
|
+
ECM = 0b100
|
|
19
27
|
ECM_BMM = 0b110
|
|
28
|
+
MULTICOLOR_BITMAP = 0b011
|
|
20
29
|
ECM_MCM = 0b101
|
|
21
30
|
BMM = 0b010
|
|
22
31
|
|
|
23
32
|
# The group #draw painted, and which of its pixels are foreground.
|
|
24
33
|
attr_reader :colors, :fg
|
|
25
34
|
|
|
26
|
-
def initialize(registers)
|
|
35
|
+
def initialize(registers, model: :mos6569)
|
|
27
36
|
@registers = registers
|
|
37
|
+
@latency = model != :mos8565
|
|
38
|
+
@next_ecm_bmm = 0
|
|
39
|
+
@black_below = 0
|
|
28
40
|
@colors = Array.new(8, 0)
|
|
29
41
|
@fg = Array.new(8, false)
|
|
30
42
|
@gbuf = 0
|
|
@@ -42,6 +54,7 @@ module Badline
|
|
|
42
54
|
# the byte loaded at pixel shift under an unchanging mode.
|
|
43
55
|
def prime(data, screencode, color, shift, mode)
|
|
44
56
|
@mode_lookup = mode
|
|
57
|
+
@next_ecm_bmm = mode & ECM_BMM
|
|
45
58
|
@mode_read = mode & 1
|
|
46
59
|
@late_read = false
|
|
47
60
|
load(data, screencode, color)
|
|
@@ -55,27 +68,55 @@ module Badline
|
|
|
55
68
|
step_mode(i, mode)
|
|
56
69
|
load(data, screencode, color) if i == shift
|
|
57
70
|
pixel = read_pixel
|
|
58
|
-
@colors[i] = lookup(pixel)
|
|
71
|
+
@colors[i] = pixel < @black_below ? 0 : lookup(pixel)
|
|
72
|
+
@black_below = 0
|
|
59
73
|
@fg[i] = pixel >= 2
|
|
60
74
|
i += 1
|
|
61
75
|
end
|
|
76
|
+
@next_ecm_bmm = mode & ECM_BMM
|
|
62
77
|
end
|
|
63
78
|
|
|
64
79
|
private
|
|
65
80
|
|
|
66
81
|
def step_mode(pixel, mode)
|
|
67
82
|
case pixel
|
|
68
|
-
when 0 then
|
|
83
|
+
when 0 then start_group
|
|
69
84
|
when 4 then step_lookup(mode)
|
|
70
|
-
when 5 then
|
|
71
|
-
when 6 then @mode_lookup &= mode | 1
|
|
85
|
+
when 5 then step_early_fall(mode)
|
|
86
|
+
when 6 then @mode_lookup &= mode | 1 if @latency
|
|
72
87
|
when 7 then step_read
|
|
73
88
|
end
|
|
74
89
|
end
|
|
75
90
|
|
|
91
|
+
def start_group
|
|
92
|
+
end_late_read
|
|
93
|
+
return if @latency
|
|
94
|
+
|
|
95
|
+
invalid = @mode_lookup > ECM
|
|
96
|
+
@mode_lookup = (@mode_lookup & ~ECM_BMM) | @next_ecm_bmm
|
|
97
|
+
@black_below = black_pixels_out_of_invalid if invalid
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The pixel values below which pixel 0 stays black as the group leaves
|
|
101
|
+
# an invalid mode: all four into hi-res text, the background ones into
|
|
102
|
+
# multicolour bitmap.
|
|
103
|
+
def black_pixels_out_of_invalid
|
|
104
|
+
case @mode_lookup
|
|
105
|
+
when 0 then 4
|
|
106
|
+
when MULTICOLOR_BITMAP then 2
|
|
107
|
+
else 0
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
76
111
|
def step_lookup(mode)
|
|
77
|
-
@late_mcm = @mode_lookup.allbits?(ECM_MCM) && mode.nobits?(1)
|
|
78
|
-
|
|
112
|
+
@late_mcm = @latency && @mode_lookup.allbits?(ECM_MCM) && mode.nobits?(1)
|
|
113
|
+
rising = @latency ? mode & ECM_BMM : 0
|
|
114
|
+
@mode_lookup = (@mode_lookup & ~1) | (mode & 1) | (@late_mcm ? 1 : 0) | rising
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def step_early_fall(mode)
|
|
118
|
+
@mode_lookup &= ~1 if @late_mcm
|
|
119
|
+
@mode_lookup &= mode | ~BMM if @latency && @mode_lookup != BMM
|
|
79
120
|
end
|
|
80
121
|
|
|
81
122
|
def step_read
|
|
@@ -53,21 +53,21 @@ module Badline
|
|
|
53
53
|
#
|
|
54
54
|
# All color registers are 4bit, bits 4-7 always read 1.
|
|
55
55
|
#
|
|
56
|
-
# $D020: Border color
|
|
57
|
-
# $D021: Background color 0
|
|
58
|
-
# $D022: Background color 1
|
|
59
|
-
# $D023: Background color 2
|
|
60
|
-
# $D024: Background color 3
|
|
61
|
-
# $D025: Sprite multicolor 0
|
|
62
|
-
# $D026: Sprite multicolor 1
|
|
63
|
-
# $D027: Sprite 0 color
|
|
64
|
-
# $D028: Sprite 1 color
|
|
65
|
-
# $D029: Sprite 2 color
|
|
66
|
-
# $D02A: Sprite 3 color
|
|
67
|
-
# $D02B: Sprite 4 color
|
|
68
|
-
# $D02C: Sprite 5 color
|
|
69
|
-
# $D02D: Sprite 6 color
|
|
70
|
-
# $D02E: Sprite 7 color
|
|
56
|
+
# $D020: Border color
|
|
57
|
+
# $D021: Background color 0
|
|
58
|
+
# $D022: Background color 1
|
|
59
|
+
# $D023: Background color 2
|
|
60
|
+
# $D024: Background color 3
|
|
61
|
+
# $D025: Sprite multicolor 0
|
|
62
|
+
# $D026: Sprite multicolor 1
|
|
63
|
+
# $D027: Sprite 0 color
|
|
64
|
+
# $D028: Sprite 1 color
|
|
65
|
+
# $D029: Sprite 2 color
|
|
66
|
+
# $D02A: Sprite 3 color
|
|
67
|
+
# $D02B: Sprite 4 color
|
|
68
|
+
# $D02C: Sprite 5 color
|
|
69
|
+
# $D02D: Sprite 6 color
|
|
70
|
+
# $D02E: Sprite 7 color
|
|
71
71
|
#
|
|
72
72
|
# $D02F-$D03F: Not in use, always reads 0xff
|
|
73
73
|
# $D040-$D3FF: Repeat $D0000 to $D03F every 64 bytes
|
|
@@ -77,10 +77,10 @@ module Badline
|
|
|
77
77
|
# The raw register bytes, for the per-column reads on the hot path.
|
|
78
78
|
attr_reader :bytes
|
|
79
79
|
|
|
80
|
+
# Every register powers on as zero, as in VICE.
|
|
80
81
|
def initialize
|
|
81
82
|
@bytes = Array.new(2**6, 0)
|
|
82
83
|
@irq_line = false
|
|
83
|
-
write_defaults
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
# True while any enabled latch bit is set in $D019/$D01A. Cached and
|
|
@@ -91,6 +91,8 @@ module Badline
|
|
|
91
91
|
|
|
92
92
|
def read(reg)
|
|
93
93
|
case reg
|
|
94
|
+
when 0x16 then @bytes[reg] | 0xc0 # bits 7-6 unused, read 1
|
|
95
|
+
when 0x18 then @bytes[reg] | 0x01 # bit 0 unused, reads 1
|
|
94
96
|
when 0x1a, 0x20..0x2e then @bytes[reg] | 0xf0
|
|
95
97
|
when 0x1e, 0x1f then read_clear(reg) # collision registers clear on read
|
|
96
98
|
when 0x2f..0x3f then 0xff
|
|
@@ -106,6 +108,7 @@ module Badline
|
|
|
106
108
|
when 0x1a # only the four latch bits
|
|
107
109
|
@bytes[reg] = value & 0x0f
|
|
108
110
|
update_irq_line
|
|
111
|
+
when 0x1e, 0x1f then nil # the collision registers are read-only
|
|
109
112
|
else @bytes[reg] = value
|
|
110
113
|
end
|
|
111
114
|
end
|
|
@@ -157,17 +160,6 @@ module Badline
|
|
|
157
160
|
def read_clear(reg)
|
|
158
161
|
@bytes[reg].tap { @bytes[reg] = 0 }
|
|
159
162
|
end
|
|
160
|
-
|
|
161
|
-
def write_defaults
|
|
162
|
-
write_each(0x20, [14, 6, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 7, 12])
|
|
163
|
-
write_each(0x11, [0x1b, 0]) # $D011: DEN=1, RSEL=1, YSCROLL=3
|
|
164
|
-
write_each(0x16, [0xc8]) # $D016: Text mode, XSCROLL=0
|
|
165
|
-
write_each(0x19, [0, 0]) # IRQ flags
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def write_each(addr, values)
|
|
169
|
-
values.each_with_index { |v, i| @bytes[addr + i] = v }
|
|
170
|
-
end
|
|
171
163
|
end
|
|
172
164
|
end
|
|
173
165
|
end
|
|
@@ -14,21 +14,6 @@ module Badline
|
|
|
14
14
|
class Sequencer
|
|
15
15
|
include Output
|
|
16
16
|
|
|
17
|
-
DISPLAY_X_BOUNDS = [
|
|
18
|
-
[135, 438].freeze,
|
|
19
|
-
[128, 447].freeze
|
|
20
|
-
].freeze
|
|
21
|
-
|
|
22
|
-
# The graphics window always spans the full 40 columns, ignoring CSEL.
|
|
23
|
-
GFX_X_START = DISPLAY_X_BOUNDS[1][0]
|
|
24
|
-
GFX_X_END = DISPLAY_X_BOUNDS[1][1] + 1
|
|
25
|
-
|
|
26
|
-
# [left compare, right compare] for the border flip-flop per CSEL state.
|
|
27
|
-
WINDOW_COMPARES = [
|
|
28
|
-
[DISPLAY_X_BOUNDS[0][0], DISPLAY_X_BOUNDS[0][1] + 1].freeze,
|
|
29
|
-
[GFX_X_START, GFX_X_END].freeze
|
|
30
|
-
].freeze
|
|
31
|
-
|
|
32
17
|
BORDER_Y_BOUNDS = [
|
|
33
18
|
[55, 247].freeze,
|
|
34
19
|
[51, 251].freeze
|
|
@@ -45,8 +30,9 @@ module Badline
|
|
|
45
30
|
# foreground mask the sprite collisions read.
|
|
46
31
|
attr_writer :render
|
|
47
32
|
|
|
48
|
-
def initialize(width, registers, bank)
|
|
33
|
+
def initialize(width, registers, bank, model: :mos6569, region: Region::PAL)
|
|
49
34
|
@width = width
|
|
35
|
+
window_bounds(region.display_x_bounds)
|
|
50
36
|
@registers = registers
|
|
51
37
|
@bank = bank
|
|
52
38
|
@colors = Array.new(width, 0)
|
|
@@ -63,8 +49,8 @@ module Badline
|
|
|
63
49
|
@vertical_border = true
|
|
64
50
|
@vertical_armed = true
|
|
65
51
|
@main_border = true
|
|
66
|
-
@color_patches = ColorPatches.new(self)
|
|
67
|
-
@shifter = GraphicsShifter.new(registers)
|
|
52
|
+
@color_patches = ColorPatches.new(self, grey_dots: model == :mos8565)
|
|
53
|
+
@shifter = GraphicsShifter.new(registers, model:)
|
|
68
54
|
@xscroll = 0
|
|
69
55
|
@last_shift = 0
|
|
70
56
|
@last_mode = 0
|
|
@@ -179,6 +165,20 @@ module Badline
|
|
|
179
165
|
|
|
180
166
|
private
|
|
181
167
|
|
|
168
|
+
# The display window's edges per CSEL state, as the region gives them.
|
|
169
|
+
# The graphics window always spans the full 40 columns, ignoring CSEL,
|
|
170
|
+
# and the border flip-flop takes [left compare, right compare] per
|
|
171
|
+
# CSEL state.
|
|
172
|
+
def window_bounds(bounds)
|
|
173
|
+
narrow, wide = bounds
|
|
174
|
+
@gfx_x_start = wide[0]
|
|
175
|
+
@gfx_x_end = wide[1] + 1
|
|
176
|
+
@window_compares = [
|
|
177
|
+
[narrow[0], narrow[1] + 1].freeze,
|
|
178
|
+
[@gfx_x_start, @gfx_x_end].freeze
|
|
179
|
+
].freeze
|
|
180
|
+
end
|
|
181
|
+
|
|
182
182
|
# After a mode, XSCROLL or colour change: groups paint whole bytes
|
|
183
183
|
# again once the change has settled, and run pixel by pixel until then.
|
|
184
184
|
def emit_checked(slot, col, display, mode)
|
|
@@ -16,9 +16,9 @@ module Badline
|
|
|
16
16
|
def output(col, shift)
|
|
17
17
|
x_pos = (col + 16) * 8
|
|
18
18
|
csel = @registers.csel?
|
|
19
|
-
first =
|
|
19
|
+
first = @window_compares[@first_csel ? 1 : 0]
|
|
20
20
|
@first_csel = csel
|
|
21
|
-
compares =
|
|
21
|
+
compares = @window_compares[csel ? 1 : 0]
|
|
22
22
|
win_lo, right_compare = compares
|
|
23
23
|
|
|
24
24
|
if !first.equal?(compares) || boundary_group?(x_pos, win_lo, right_compare)
|
|
@@ -45,7 +45,7 @@ module Badline
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def output_window(x_pos, shift)
|
|
48
|
-
in_gfx = x_pos >=
|
|
48
|
+
in_gfx = x_pos >= @gfx_x_start && x_pos < @gfx_x_end
|
|
49
49
|
@border_groups[x_pos >> 3] = BorderMask::NONE
|
|
50
50
|
|
|
51
51
|
if shift.zero?
|
|
@@ -105,7 +105,7 @@ module Badline
|
|
|
105
105
|
shown = pixel_shown?(x, win_lo, right_compare)
|
|
106
106
|
@colors[x] = shown ? pixel : border
|
|
107
107
|
@border[x] = !shown
|
|
108
|
-
@fg[x] = x >=
|
|
108
|
+
@fg[x] = x >= @gfx_x_start && x < @gfx_x_end ? mask : false
|
|
109
109
|
win_lo, right_compare = compares if i.zero?
|
|
110
110
|
i += 1
|
|
111
111
|
end
|
|
@@ -10,8 +10,12 @@ module Badline
|
|
|
10
10
|
# the CPU reads or writes a VIC register in that cycle, and the middle
|
|
11
11
|
# one is the idle fetch at $3fff (sbsprf24's readme).
|
|
12
12
|
class InternalBus
|
|
13
|
+
# The columns of a raster line.
|
|
14
|
+
attr_reader :columns
|
|
15
|
+
|
|
13
16
|
def initialize(bank, columns)
|
|
14
17
|
@bank = bank
|
|
18
|
+
@columns = columns
|
|
15
19
|
@lines = 0
|
|
16
20
|
@ghost = 0xff
|
|
17
21
|
@value = Array.new(columns, 0xff)
|
|
@@ -71,8 +71,8 @@ module Badline
|
|
|
71
71
|
while count < MAX_SPAN
|
|
72
72
|
if pos >= boundary
|
|
73
73
|
log.advance(pos)
|
|
74
|
-
mc = log[0x1c].anybits?(@bit)
|
|
75
74
|
expanded = log[0x1d].anybits?(@bit)
|
|
75
|
+
mc = logged_multicolor(log, mc, expanded, count)
|
|
76
76
|
boundary = log.next_x
|
|
77
77
|
end
|
|
78
78
|
count.zero? ? prime(mc) : step(mc, expanded, count == cut - 1)
|
|
@@ -88,11 +88,29 @@ module Badline
|
|
|
88
88
|
# The X match loads the shift register and arms both flip-flops, so
|
|
89
89
|
# the sprite's first pixel comes straight off the fetched row.
|
|
90
90
|
def prime(multicolor)
|
|
91
|
-
@mc_flop =
|
|
91
|
+
@mc_flop = @new_chip && !multicolor
|
|
92
92
|
@xe_flop = false
|
|
93
93
|
@latch = (@sr >> 22) & (multicolor ? 3 : 2)
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
+
# The multicolor bit as the log has it at the pixel. A change after
|
|
97
|
+
# the first pixel reaches the 8565's flip-flop.
|
|
98
|
+
def logged_multicolor(log, was, expanded, count)
|
|
99
|
+
multicolor = log[0x1c].anybits?(@bit)
|
|
100
|
+
switch_multicolor(multicolor, expanded) if @new_chip && multicolor != was && count.positive?
|
|
101
|
+
multicolor
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# On the 8565 a multicolor change works on the flip-flop instead of
|
|
105
|
+
# the next pixels. It stays as it is unless the change lands between
|
|
106
|
+
# the two halves of an expanded pixel, where it flips, or is set by a
|
|
107
|
+
# switch to hi-res.
|
|
108
|
+
def switch_multicolor(multicolor, expanded)
|
|
109
|
+
return unless expanded && !@xe_flop
|
|
110
|
+
|
|
111
|
+
@mc_flop = multicolor ? !@mc_flop : true
|
|
112
|
+
end
|
|
113
|
+
|
|
96
114
|
# One pixel of the sequencer. An unexpanded sprite shifts every pixel;
|
|
97
115
|
# an expanded one shifts every other, and the multicolor latch reloads
|
|
98
116
|
# every other shift, so its pairs stretch with the expansion. Both
|
|
@@ -106,7 +124,7 @@ module Badline
|
|
|
106
124
|
else
|
|
107
125
|
@xe_flop = false
|
|
108
126
|
end
|
|
109
|
-
@mc_flop = false unless multicolor
|
|
127
|
+
@mc_flop = false unless multicolor || @new_chip
|
|
110
128
|
return unless shift
|
|
111
129
|
|
|
112
130
|
@sr = (@sr << 1) & 0xffffff
|
|
@@ -115,10 +133,14 @@ module Badline
|
|
|
115
133
|
|
|
116
134
|
# A multicolor pair loaded on the last pixel before the reload keeps
|
|
117
135
|
# only its high bit, as a hi-res pixel does (spritefetchbug).
|
|
136
|
+
# On the 8565 a hi-res pixel with the multicolor flip-flop clear
|
|
137
|
+
# sets it and loads nothing, so the latch repeats its last pixel.
|
|
118
138
|
def load_latch(multicolor, last)
|
|
119
139
|
if multicolor
|
|
120
140
|
@latch = (@sr >> 22) & (last ? 2 : 3) if @mc_flop
|
|
121
141
|
@mc_flop = !@mc_flop
|
|
142
|
+
elsif @new_chip && !@mc_flop
|
|
143
|
+
@mc_flop = true
|
|
122
144
|
else
|
|
123
145
|
@latch = (@sr >> 22) & 2
|
|
124
146
|
end
|
data/lib/badline/vic/sprite.rb
CHANGED
|
@@ -56,12 +56,13 @@ module Badline
|
|
|
56
56
|
attr_reader :index, :leftmost, :span, :codes,
|
|
57
57
|
:reload_leftmost, :reload_span, :reload_codes
|
|
58
58
|
|
|
59
|
-
def initialize(index, registers, bank,
|
|
59
|
+
def initialize(index, registers, bank, bus, model: :mos6569)
|
|
60
60
|
@index = index
|
|
61
|
+
@new_chip = model == :mos8565
|
|
61
62
|
@registers = registers
|
|
62
63
|
@bank = bank
|
|
63
64
|
@bus = bus
|
|
64
|
-
@width =
|
|
65
|
+
@width = bus.columns * 8
|
|
65
66
|
@bit = 1 << index
|
|
66
67
|
@dma = false
|
|
67
68
|
@display_on = false
|
|
@@ -77,8 +78,8 @@ module Badline
|
|
|
77
78
|
@reload_codes = Array.new(MAX_SPAN + RELOAD_HOLD, 0)
|
|
78
79
|
@reload_leftmost = 0
|
|
79
80
|
@reload_span = 0
|
|
80
|
-
@reload_x = (RELOAD_X + (RELOAD_STEP * index)) % width
|
|
81
|
-
@reload_next_line = RELOAD_X + (RELOAD_STEP * index) < width
|
|
81
|
+
@reload_x = (RELOAD_X + (RELOAD_STEP * index)) % @width
|
|
82
|
+
@reload_next_line = RELOAD_X + (RELOAD_STEP * index) < @width
|
|
82
83
|
@sr = 0
|
|
83
84
|
@latch = 0
|
|
84
85
|
@mc_flop = false
|
data/lib/badline/vic/sprites.rb
CHANGED
|
@@ -27,12 +27,17 @@ module Badline
|
|
|
27
27
|
[0x25, 0x26, *0x27..0x2e].each { |reg| delays[reg] = COLOR_DELAY }
|
|
28
28
|
end.freeze
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
# The 8565's sprite sequencers take a multicolor change a pixel
|
|
31
|
+
# sooner than the 6569's.
|
|
32
|
+
WRITE_DELAY_8565 = WRITE_DELAY.dup.tap { |delays| delays[0x1c] = SEQUENCER_DELAY - 1 }.freeze
|
|
33
|
+
|
|
34
|
+
def initialize(registers, bank, width, model: :mos6569)
|
|
31
35
|
@registers = registers
|
|
36
|
+
@write_delay = model == :mos8565 ? WRITE_DELAY_8565 : WRITE_DELAY
|
|
32
37
|
@bank = bank
|
|
33
38
|
@width = width
|
|
34
39
|
@bus = Sprite::InternalBus.new(bank, width / 8)
|
|
35
|
-
@sprites = Array.new(8) { |i| Sprite.new(i, registers, bank,
|
|
40
|
+
@sprites = Array.new(8) { |i| Sprite.new(i, registers, bank, @bus, model:) }
|
|
36
41
|
@collisions = Collisions.new(registers, width)
|
|
37
42
|
@hits = @collisions.hits
|
|
38
43
|
@win_color = Array.new(width, 0)
|
|
@@ -123,7 +128,7 @@ module Badline
|
|
|
123
128
|
# Record a mid-line write at the pixel where it becomes visible.
|
|
124
129
|
def log_change(reg, old, value, beam_x)
|
|
125
130
|
clear_y_expansion(old & ~value, beam_x) if reg == 0x17
|
|
126
|
-
delay =
|
|
131
|
+
delay = @write_delay[reg]
|
|
127
132
|
return unless delay && active?
|
|
128
133
|
|
|
129
134
|
@log.log(beam_x + delay, reg, old, value)
|