badline 0.4.1 → 0.5.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/CHANGELOG.md +35 -0
- data/CLAUDE.md +10 -5
- data/README.md +59 -24
- data/doc/pinned-behaviour.md +221 -0
- data/exe/badline-ruby +8 -4
- data/lib/badline/address_bus/ultimax_pages.rb +38 -0
- data/lib/badline/address_bus.rb +19 -29
- data/lib/badline/audio/cli.rb +2 -1
- data/lib/badline/audio/jukebox.rb +6 -4
- data/lib/badline/audio/machine_player.rb +10 -6
- data/lib/badline/cartridge/kcs_power.rb +3 -3
- data/lib/badline/cia.rb +5 -1
- data/lib/badline/computer.rb +86 -7
- data/lib/badline/cpu.rb +39 -0
- data/lib/badline/drive1541/bus.rb +134 -0
- data/lib/badline/drive1541/disk.rb +256 -0
- data/lib/badline/drive1541/disk_via.rb +33 -0
- data/lib/badline/drive1541/gcr.rb +57 -0
- data/lib/badline/drive1541/idle.rb +184 -0
- data/lib/badline/drive1541/mechanism.rb +277 -0
- data/lib/badline/drive1541/rotation.rb +111 -0
- data/lib/badline/drive1541/sector_reader.rb +76 -0
- data/lib/badline/drive1541/serial_port.rb +67 -0
- data/lib/badline/drive1541/track.rb +98 -0
- data/lib/badline/drive1541.rb +218 -0
- data/lib/badline/gui/application.rb +24 -7
- data/lib/badline/gui/drive_led_pane.rb +29 -0
- data/lib/badline/gui.rb +1 -0
- data/lib/badline/iec_bus.rb +93 -0
- data/lib/badline/kernal_trap/channel.rb +9 -1
- data/lib/badline/kernal_trap/serial.rb +9 -3
- data/lib/badline/media/true_drive.rb +54 -0
- data/lib/badline/media.rb +22 -1
- data/lib/badline/options.rb +30 -4
- data/lib/badline/region.rb +35 -10
- data/lib/badline/reu/dma.rb +228 -0
- data/lib/badline/reu/ram.rb +65 -0
- data/lib/badline/reu/trigger.rb +32 -0
- data/lib/badline/reu.rb +276 -0
- data/lib/badline/roms/README +10 -0
- data/lib/badline/roms/dos1541.rom +0 -0
- data/lib/badline/sdl/functions.rb +1 -0
- data/lib/badline/storage/d64_image.rb +18 -9
- data/lib/badline/storage/d81_image.rb +1 -0
- data/lib/badline/storage/disk_image/catalog.rb +52 -0
- data/lib/badline/storage/disk_image/writing.rb +16 -0
- data/lib/badline/storage/disk_image.rb +2 -0
- data/lib/badline/storage/g64_image.rb +222 -0
- data/lib/badline/storage/host_directory.rb +21 -1
- data/lib/badline/storage/listing.rb +118 -0
- data/lib/badline/storage/t64.rb +14 -0
- data/lib/badline/storage.rb +2 -0
- data/lib/badline/traps.rb +4 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/control_lines.rb +3 -0
- data/lib/badline/via/fast_forward.rb +38 -0
- data/lib/badline/via/shift_register.rb +8 -3
- data/lib/badline/via/timer1.rb +30 -0
- data/lib/badline/via/timer2.rb +25 -0
- data/lib/badline/via.rb +2 -0
- data/lib/badline/vic/display_state.rb +7 -7
- data/lib/badline/vic/sprite/internal_bus.rb +11 -7
- data/lib/badline/vic/sprite/shifter.rb +10 -15
- data/lib/badline/vic/sprite/timing.rb +60 -0
- data/lib/badline/vic/sprite.rb +14 -12
- data/lib/badline/vic/sprites.rb +2 -2
- data/lib/badline/vic.rb +86 -39
- data/lib/badline.rb +3 -0
- metadata +26 -1
data/lib/badline/via/timer1.rb
CHANGED
|
@@ -66,6 +66,36 @@ module Badline
|
|
|
66
66
|
@counter == 0xffff && timeout(free_run)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# How many cycles the timer can run without setting its flag: until
|
|
70
|
+
# an armed timer's timeout, and forever for an unarmed one, whose
|
|
71
|
+
# timeouts only reload the counter. None while an armed timer loads
|
|
72
|
+
# or reloads.
|
|
73
|
+
def quiet_cycles
|
|
74
|
+
return FastForward::QUIET unless @armed
|
|
75
|
+
return 0 if @hold || @reload
|
|
76
|
+
|
|
77
|
+
@counter
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Runs +cycles+ cycles at once, as many as quiet_cycles allows. An
|
|
81
|
+
# unarmed timer goes round its period of latch + 2 cycles: the
|
|
82
|
+
# timeout at $FFFF, the reload, and latch down to 0.
|
|
83
|
+
def fast_forward(cycles)
|
|
84
|
+
if @hold || @reload
|
|
85
|
+
cycle!(false)
|
|
86
|
+
cycles -= 1
|
|
87
|
+
end
|
|
88
|
+
return @counter -= cycles if cycles <= @counter
|
|
89
|
+
|
|
90
|
+
phase = (cycles - @counter - 1) % (@latch + 2)
|
|
91
|
+
@reload = phase.zero?
|
|
92
|
+
@counter = @reload ? 0xffff : @latch - phase + 1
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# What the counter and PB7 don't hold, for comparing the timer at two
|
|
96
|
+
# points.
|
|
97
|
+
def state = [@latch, @armed]
|
|
98
|
+
|
|
69
99
|
private
|
|
70
100
|
|
|
71
101
|
# The counter reloads either way. Armed, PB7 inverts and the flag
|
data/lib/badline/via/timer2.rb
CHANGED
|
@@ -63,6 +63,31 @@ module Badline
|
|
|
63
63
|
# A falling edge on PB6. Returns true when it sets the flag.
|
|
64
64
|
def pulse! = @counting_pulses && decrement
|
|
65
65
|
|
|
66
|
+
# How many cycles the timer can run without setting its flag, taken
|
|
67
|
+
# without the shift register clocking off it: until an armed timer
|
|
68
|
+
# underflows, and forever for one counting pulses or unarmed, which
|
|
69
|
+
# only rolls on. None while a load or a switch of mode is under way.
|
|
70
|
+
def quiet_cycles
|
|
71
|
+
return 0 if @hold || @counting_pulses != @count_pulses
|
|
72
|
+
return FastForward::QUIET if @counting_pulses || !@armed
|
|
73
|
+
|
|
74
|
+
@counter
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Runs +cycles+ cycles at once, as many as quiet_cycles allows. The
|
|
78
|
+
# low byte's underflow is set when the last cycle took it from $00.
|
|
79
|
+
def fast_forward(cycles)
|
|
80
|
+
@low_underflowed = false
|
|
81
|
+
return if @counting_pulses
|
|
82
|
+
|
|
83
|
+
@counter = (@counter - cycles) & 0xffff
|
|
84
|
+
@low_underflowed = @counter.allbits?(0xff)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# What the counter and the low byte's underflow don't hold, for
|
|
88
|
+
# comparing the timer at two points.
|
|
89
|
+
def state = [@latch_low, @armed, @hold, @low_reload, @count_pulses, @counting_pulses]
|
|
90
|
+
|
|
66
91
|
private
|
|
67
92
|
|
|
68
93
|
# A cycle while counting pulses, or held after a load or a low byte
|
data/lib/badline/via.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "badline/via/control_lines"
|
|
4
|
+
require "badline/via/fast_forward"
|
|
4
5
|
require "badline/via/interrupt_register"
|
|
5
6
|
require "badline/via/shift_register"
|
|
6
7
|
require "badline/via/timer1"
|
|
@@ -16,6 +17,7 @@ module Badline
|
|
|
16
17
|
# feed the counter.
|
|
17
18
|
class VIA
|
|
18
19
|
include Addressable
|
|
20
|
+
include FastForward
|
|
19
21
|
|
|
20
22
|
attr_reader :start, :peripheral, :shift_register, :acr, :pcr
|
|
21
23
|
|
|
@@ -12,10 +12,6 @@ module Badline
|
|
|
12
12
|
LAST_LINE = 0xf7 # 247
|
|
13
13
|
COLUMNS_PER_ROW = 40
|
|
14
14
|
|
|
15
|
-
# The last column of a rasterline already compares against the line
|
|
16
|
-
# about to start, the same wrap the raster IRQ latch uses.
|
|
17
|
-
WRAP_COLUMN = 62
|
|
18
|
-
|
|
19
15
|
# AEC follows BA three cycles later; the c-accesses before it read
|
|
20
16
|
# the bus the CPU still drives.
|
|
21
17
|
BA_DELAY = 3
|
|
@@ -32,12 +28,16 @@ module Badline
|
|
|
32
28
|
LOAD_COLUMN = 12
|
|
33
29
|
RC_COLUMN = 56
|
|
34
30
|
|
|
35
|
-
GRAPHICS_COLUMNS = Array.new(
|
|
31
|
+
GRAPHICS_COLUMNS = Array.new(65) { |column| column.between?(GRAPHICS_FIRST, GRAPHICS_LAST) }.freeze
|
|
36
32
|
|
|
37
33
|
attr_reader :vc_base, :vc, :vmli, :rc
|
|
38
34
|
|
|
39
|
-
|
|
35
|
+
# The wrap column is the last column of a rasterline, 62 on the 6569,
|
|
36
|
+
# which already compares against the line about to start, the same
|
|
37
|
+
# wrap the raster IRQ latch uses.
|
|
38
|
+
def initialize(registers, wrap_column = 62)
|
|
40
39
|
@registers = registers
|
|
40
|
+
@wrap_column = wrap_column
|
|
41
41
|
@vc_base = 0
|
|
42
42
|
@vc = 0
|
|
43
43
|
@vmli = 0
|
|
@@ -77,7 +77,7 @@ module Badline
|
|
|
77
77
|
|
|
78
78
|
# Returns true when this compare opened display state.
|
|
79
79
|
def cycle(rasterline, column)
|
|
80
|
-
match = column ==
|
|
80
|
+
match = column == @wrap_column ? wrap_match(rasterline + 1) : line_match
|
|
81
81
|
@matched = match
|
|
82
82
|
opened = false
|
|
83
83
|
if match
|
|
@@ -10,12 +10,15 @@ 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
|
-
|
|
13
|
+
# The columns of a raster line, and where the region puts the
|
|
14
|
+
# sprites in it.
|
|
15
|
+
attr_reader :columns, :timing
|
|
15
16
|
|
|
16
|
-
def initialize(bank,
|
|
17
|
+
def initialize(bank, timing)
|
|
17
18
|
@bank = bank
|
|
18
|
-
@
|
|
19
|
+
@timing = timing
|
|
20
|
+
@columns = timing.columns
|
|
21
|
+
@sprite_cycle = timing.sprite_cycle
|
|
19
22
|
@lines = 0
|
|
20
23
|
@ghost = 0xff
|
|
21
24
|
@value = Array.new(columns, 0xff)
|
|
@@ -37,10 +40,11 @@ module Badline
|
|
|
37
40
|
@line[column] = column.zero? ? @lines + 1 : @lines
|
|
38
41
|
end
|
|
39
42
|
|
|
40
|
-
# The row sprite `index` (3-7) fetched this line, in
|
|
41
|
-
# 2n-5 and 2n-4
|
|
43
|
+
# The row sprite `index` (3-7) fetched this line, in the phi2 halves
|
|
44
|
+
# of the two cycles from its p-access: Bauer cycles 2n-5 and 2n-4 on
|
|
45
|
+
# the 6569.
|
|
42
46
|
def row(index)
|
|
43
|
-
column = (2 * index) -
|
|
47
|
+
column = (@sprite_cycle + (2 * index) - 1) % @columns
|
|
44
48
|
(phi2(column) << 16) | (@ghost << 8) | phi2(column + 1)
|
|
45
49
|
end
|
|
46
50
|
|
|
@@ -6,18 +6,13 @@ module Badline
|
|
|
6
6
|
# The X comparator and the 24-bit shift register behind it: where a
|
|
7
7
|
# line fires the sprite, and the pixels each firing shifts out.
|
|
8
8
|
module Shifter
|
|
9
|
-
EMPTY = [].freeze
|
|
10
|
-
|
|
11
9
|
private
|
|
12
10
|
|
|
13
11
|
# The first pixel of every comparator match along the line. Writes to
|
|
14
12
|
# $d000/$d010 move the compare value mid-line, so a sprite can miss
|
|
15
13
|
# its match entirely and pick up a later one, a second one, or none.
|
|
16
14
|
def comparator_hits(log)
|
|
17
|
-
unless log
|
|
18
|
-
match = compare_x(@registers)
|
|
19
|
-
return match ? [(match + 1) % @width] : EMPTY
|
|
20
|
-
end
|
|
15
|
+
return compare_x(@registers) unless log
|
|
21
16
|
|
|
22
17
|
hits = []
|
|
23
18
|
log.rewind
|
|
@@ -25,8 +20,10 @@ module Badline
|
|
|
25
20
|
from = 0
|
|
26
21
|
while from < @width
|
|
27
22
|
upto = [log.next_x, @width].min
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
compare_x(log).each do |start|
|
|
24
|
+
match = (start - 1) % @width
|
|
25
|
+
hits << start if match >= from && match < upto
|
|
26
|
+
end
|
|
30
27
|
break if upto >= @width
|
|
31
28
|
|
|
32
29
|
from = upto
|
|
@@ -35,15 +32,13 @@ module Badline
|
|
|
35
32
|
hits
|
|
36
33
|
end
|
|
37
34
|
|
|
38
|
-
# The
|
|
39
|
-
#
|
|
40
|
-
#
|
|
35
|
+
# The pixels the sprite starts at, one past each comparator match.
|
|
36
|
+
# The 6569's X counter only runs to 503, so the eight coordinates
|
|
37
|
+
# above it never match and the sprite stays dark — the gap the
|
|
38
|
+
# spritegap tests find at $1f8.
|
|
41
39
|
def compare_x(view)
|
|
42
40
|
msb = view[0x10].anybits?(@bit) ? 0x100 : 0
|
|
43
|
-
|
|
44
|
-
return if xpos >= @width
|
|
45
|
-
|
|
46
|
-
(xpos + COMPARE_OFFSET) % @width
|
|
41
|
+
@timing.x_pixels[msb | view[index * 2]]
|
|
47
42
|
end
|
|
48
43
|
|
|
49
44
|
# Pixels from the X match to the next reload.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class VIC < Cycleable
|
|
5
|
+
class Sprite
|
|
6
|
+
# = Timing
|
|
7
|
+
#
|
|
8
|
+
# Where a region puts the sprites in the line: the cycles of their
|
|
9
|
+
# fetches and of the display compare, the pixels those land on, and
|
|
10
|
+
# the raster pixels the X counter reads each coordinate at. Where the
|
|
11
|
+
# fetches or the compare run a cycle later than on the 6569, what
|
|
12
|
+
# hangs off them moves 8 pixels with them.
|
|
13
|
+
class Timing
|
|
14
|
+
# The X counter's value where it holds on a raster of more than 512
|
|
15
|
+
# pixels, and the four values it repeats from there.
|
|
16
|
+
HOLD_AT = 0x188
|
|
17
|
+
HOLD_BASE = 0x184
|
|
18
|
+
|
|
19
|
+
attr_reader :columns, :sprite_cycle, :display_off_x, :x_pixels
|
|
20
|
+
|
|
21
|
+
def initialize(region)
|
|
22
|
+
@columns = region.cycles_per_line
|
|
23
|
+
@sprite_cycle = region.sprite_cycle
|
|
24
|
+
@shift = region.sprite_cycle - Region::PAL.sprite_cycle
|
|
25
|
+
@display_off_x = DISPLAY_OFF_X + (8 * (region.sprite_display_cycle - Region::PAL.sprite_display_cycle))
|
|
26
|
+
@x_pixels = Timing.x_pixels(@columns * 8, region.x_hold)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The column BA falls in for a sprite's own fetch: 55 for sprite 0 on
|
|
30
|
+
# the 6569, and two later for each sprite after it.
|
|
31
|
+
def ba_column(index) = 55 + @shift + (2 * index)
|
|
32
|
+
|
|
33
|
+
# The raster pixel of a sprite's reload, counted from the start of
|
|
34
|
+
# the line it is fetched on, so past the end of it for sprites 3-7
|
|
35
|
+
# on the 6569.
|
|
36
|
+
def reload_x(index) = RELOAD_X + (8 * @shift) + (RELOAD_STEP * index)
|
|
37
|
+
|
|
38
|
+
# The X counter at a raster pixel: X_OFFSET behind it, wrapping at
|
|
39
|
+
# the end of the line, less the pixels it held for past HOLD_AT,
|
|
40
|
+
# where it ran over $184-$187 again instead.
|
|
41
|
+
def self.xpos(pixel, width, hold)
|
|
42
|
+
count = (pixel - X_OFFSET) % width
|
|
43
|
+
return count if count < HOLD_AT
|
|
44
|
+
return count - hold if count >= HOLD_AT + hold
|
|
45
|
+
|
|
46
|
+
HOLD_BASE + ((count - HOLD_AT) % 4)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The raster pixels at which the X counter reads each coordinate, 512
|
|
50
|
+
# lists. A sprite starts at one where its X matches. On the 6569 the
|
|
51
|
+
# coordinates past 503 have none.
|
|
52
|
+
def self.x_pixels(width, hold)
|
|
53
|
+
pixels = Array.new(512) { [] }
|
|
54
|
+
width.times { |pixel| pixels[xpos(pixel, width, hold)] << pixel }
|
|
55
|
+
pixels.each(&:freeze).freeze
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/badline/vic/sprite.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "badline/vic/sprite/internal_bus"
|
|
4
4
|
require "badline/vic/sprite/shifter"
|
|
5
|
+
require "badline/vic/sprite/timing"
|
|
5
6
|
|
|
6
7
|
module Badline
|
|
7
8
|
class VIC < Cycleable
|
|
@@ -22,9 +23,6 @@ module Badline
|
|
|
22
23
|
LAST_MCBASE = 63
|
|
23
24
|
MC_MASK = 0x3f
|
|
24
25
|
|
|
25
|
-
# The X comparator matches the pixel before the sprite's first one.
|
|
26
|
-
COMPARE_OFFSET = X_OFFSET - 1
|
|
27
|
-
|
|
28
26
|
# 24 bits at two pixels each, plus slack for a mid-sprite expansion
|
|
29
27
|
# change stretching the tail.
|
|
30
28
|
MAX_SPAN = 64
|
|
@@ -48,7 +46,8 @@ module Badline
|
|
|
48
46
|
|
|
49
47
|
# On the line of its last row, a sprite whose DMA ended at cycle 16
|
|
50
48
|
# loses its display at cycle 58, so no hit from this pixel on starts
|
|
51
|
-
# it (spritegap3). One already shifting runs on.
|
|
49
|
+
# it (spritegap3). One already shifting runs on. A region whose
|
|
50
|
+
# display compare falls later moves it with the compare.
|
|
52
51
|
DISPLAY_OFF_X = 460
|
|
53
52
|
|
|
54
53
|
include Shifter
|
|
@@ -63,6 +62,7 @@ module Badline
|
|
|
63
62
|
@bank = bank
|
|
64
63
|
@bus = bus
|
|
65
64
|
@width = bus.columns * 8
|
|
65
|
+
@timing = bus.timing
|
|
66
66
|
@bit = 1 << index
|
|
67
67
|
@dma = false
|
|
68
68
|
@display_on = false
|
|
@@ -78,8 +78,9 @@ module Badline
|
|
|
78
78
|
@reload_codes = Array.new(MAX_SPAN + RELOAD_HOLD, 0)
|
|
79
79
|
@reload_leftmost = 0
|
|
80
80
|
@reload_span = 0
|
|
81
|
-
|
|
82
|
-
@
|
|
81
|
+
reload = @timing.reload_x(index)
|
|
82
|
+
@reload_x = reload % @width
|
|
83
|
+
@reload_next_line = reload < @width
|
|
83
84
|
@sr = 0
|
|
84
85
|
@latch = 0
|
|
85
86
|
@mc_flop = false
|
|
@@ -157,9 +158,10 @@ module Badline
|
|
|
157
158
|
# MCBASE, and returns true. Display is enabled separately in cycle 58,
|
|
158
159
|
# so the rows render from the following line on.
|
|
159
160
|
#
|
|
160
|
-
# On the VIC's side, BA falls at the sprite's own column
|
|
161
|
-
#
|
|
162
|
-
#
|
|
161
|
+
# On the VIC's side, BA falls at the sprite's own column
|
|
162
|
+
# (Timing#ba_column) — 55 for sprite 0 on the 6569, two later for each
|
|
163
|
+
# sprite after it, a column behind the window the CPU sees
|
|
164
|
+
# (VIC#layout_columns) — or two columns after
|
|
163
165
|
# this compare, whichever is later. AEC follows three columns on, and
|
|
164
166
|
# the first of the three s-accesses runs in the column it arrives in:
|
|
165
167
|
# a DMA starting on the second compare therefore loses that access for
|
|
@@ -168,7 +170,7 @@ module Badline
|
|
|
168
170
|
def check_dma(line, column)
|
|
169
171
|
return false if @dma || !enabled? || !y_match?(line)
|
|
170
172
|
|
|
171
|
-
@first_byte_lost = column + 2 >
|
|
173
|
+
@first_byte_lost = column + 2 > @timing.ba_column(index)
|
|
172
174
|
@dma = true
|
|
173
175
|
@mcbase = 0
|
|
174
176
|
@exp_ff = true
|
|
@@ -229,7 +231,7 @@ module Badline
|
|
|
229
231
|
return unless @row_ready || @prev_bits || reload_bits
|
|
230
232
|
|
|
231
233
|
log = nil if log.nil? || log.empty?
|
|
232
|
-
@stop_x = @dma ? nil :
|
|
234
|
+
@stop_x = @dma ? nil : @timing.display_off_x
|
|
233
235
|
comparator_hits(log).each { |start| sequence_hit(log, start, reload_bits) }
|
|
234
236
|
end
|
|
235
237
|
|
|
@@ -261,7 +263,7 @@ module Badline
|
|
|
261
263
|
def blind_fetch
|
|
262
264
|
@bits = @bus.row(index)
|
|
263
265
|
@row_ready = true
|
|
264
|
-
@show_from =
|
|
266
|
+
@show_from = @timing.display_off_x
|
|
265
267
|
end
|
|
266
268
|
|
|
267
269
|
def code_at(raster_x, leftmost, span, codes)
|
data/lib/badline/vic/sprites.rb
CHANGED
|
@@ -31,12 +31,12 @@ module Badline
|
|
|
31
31
|
# sooner than the 6569's.
|
|
32
32
|
WRITE_DELAY_8565 = WRITE_DELAY.dup.tap { |delays| delays[0x1c] = SEQUENCER_DELAY - 1 }.freeze
|
|
33
33
|
|
|
34
|
-
def initialize(registers, bank, width, model: :mos6569)
|
|
34
|
+
def initialize(registers, bank, width, model: :mos6569, region: Region::PAL)
|
|
35
35
|
@registers = registers
|
|
36
36
|
@write_delay = model == :mos8565 ? WRITE_DELAY_8565 : WRITE_DELAY
|
|
37
37
|
@bank = bank
|
|
38
38
|
@width = width
|
|
39
|
-
@bus = Sprite::InternalBus.new(bank,
|
|
39
|
+
@bus = Sprite::InternalBus.new(bank, Sprite::Timing.new(region))
|
|
40
40
|
@sprites = Array.new(8) { |i| Sprite.new(i, registers, bank, @bus, model:) }
|
|
41
41
|
@collisions = Collisions.new(registers, width)
|
|
42
42
|
@hits = @collisions.hits
|
data/lib/badline/vic.rb
CHANGED
|
@@ -46,16 +46,31 @@ module Badline
|
|
|
46
46
|
FETCH_HOLD = 0x20
|
|
47
47
|
FETCH_HOLD_ROM = 0x60
|
|
48
48
|
|
|
49
|
-
# The
|
|
50
|
-
#
|
|
51
|
-
|
|
49
|
+
# The hooks a column can carry, as bits: MCBASE, the left border
|
|
50
|
+
# compare with the end of the sprite, the sprite DMA compare, the
|
|
51
|
+
# expansion flip-flop, the sprite display compare, and the start of the
|
|
52
|
+
# vertical border for the line after the last column.
|
|
53
|
+
HOOK_MCBASE = 1
|
|
54
|
+
HOOK_LEFT = 2
|
|
55
|
+
HOOK_DMA = 4
|
|
56
|
+
HOOK_EXPANSION = 8
|
|
57
|
+
HOOK_DISPLAY = 16
|
|
58
|
+
HOOK_LAST = 32
|
|
59
|
+
|
|
60
|
+
# On the 6569 the DMA compares fall in columns 53 and 54 and the
|
|
61
|
+
# display compare in column 57. A region whose sprite fetches run later
|
|
62
|
+
# moves them with the fetches (Region::Profile#sprite_cycle).
|
|
63
|
+
DMA_COLUMN = 53
|
|
64
|
+
EXPANSION_COLUMN = 54
|
|
52
65
|
|
|
53
66
|
# BA falls three columns ahead of each sprite's pair of s-accesses, and
|
|
54
67
|
# the five-column windows step two columns apart from sprite 0 at column
|
|
55
|
-
# 54
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
|
|
68
|
+
# 54 on the 6569, a column later where the fetches are. From sprite 3 on
|
|
69
|
+
# they reach past the end of the line, so each is split: the tail
|
|
70
|
+
# columns fall on the line whose DMA compare started the fetch, the head
|
|
71
|
+
# columns on the line after it.
|
|
72
|
+
SPRITE_BA_FIRST = 54
|
|
73
|
+
SPRITE_BA_LENGTH = 5
|
|
59
74
|
|
|
60
75
|
def initialize(address_bus = nil, debug: false, model: :mos6569, region: Region::PAL)
|
|
61
76
|
raise ArgumentError, "unknown VIC-II model #{model}" unless MODELS.include?(model)
|
|
@@ -72,6 +87,8 @@ module Badline
|
|
|
72
87
|
@debug = debug
|
|
73
88
|
|
|
74
89
|
@columns_per_line = region.cycles_per_line
|
|
90
|
+
@sprite_cycle = region.sprite_cycle
|
|
91
|
+
@sprite_shift = region.sprite_cycle - Region::PAL.sprite_cycle
|
|
75
92
|
@width = @columns_per_line * 8
|
|
76
93
|
@height = region.lines_per_frame
|
|
77
94
|
@last_column = @columns_per_line - 1
|
|
@@ -94,10 +111,10 @@ module Badline
|
|
|
94
111
|
def power_on!
|
|
95
112
|
@registers = VIC::Registers.new
|
|
96
113
|
@register_bytes = @registers.bytes
|
|
97
|
-
@display_state = VIC::DisplayState.new(@registers)
|
|
114
|
+
@display_state = VIC::DisplayState.new(@registers, @last_column)
|
|
98
115
|
@sequencer = VIC::Sequencer.new(@width, @registers, @vic_bank, model: @model, region: @region)
|
|
99
116
|
@sequencer.render = @render
|
|
100
|
-
@sprites = VIC::Sprites.new(@registers, @vic_bank, @width, model: @model)
|
|
117
|
+
@sprites = VIC::Sprites.new(@registers, @vic_bank, @width, model: @model, region: @region)
|
|
101
118
|
@display.fill(0)
|
|
102
119
|
@lines.each { |line| line.fill(0) }
|
|
103
120
|
@dirty_lines.fill(true)
|
|
@@ -132,7 +149,8 @@ module Badline
|
|
|
132
149
|
|
|
133
150
|
fetch_character_data! if dma_active?
|
|
134
151
|
|
|
135
|
-
|
|
152
|
+
hooks = @hook_columns[@column]
|
|
153
|
+
column_hooks(hooks) if hooks
|
|
136
154
|
|
|
137
155
|
@column += 1
|
|
138
156
|
if @column == @columns_per_line
|
|
@@ -149,19 +167,18 @@ module Badline
|
|
|
149
167
|
# ahead of Bauer's numbering — two for MCBASE, the DMA compares and the
|
|
150
168
|
# expansion flip-flop, which the `spriteenable` and `spritecrunch`
|
|
151
169
|
# references place a column earlier still. Guarded in
|
|
152
|
-
# #cycle! so an ordinary column pays
|
|
170
|
+
# #cycle! so an ordinary column pays an array read rather than the
|
|
153
171
|
# dispatch.
|
|
154
|
-
def column_hooks
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
when 15
|
|
172
|
+
def column_hooks(hooks)
|
|
173
|
+
@sprites.advance_mcbase if hooks.anybits?(HOOK_MCBASE)
|
|
174
|
+
if hooks.anybits?(HOOK_LEFT)
|
|
158
175
|
@sequencer.left_compare_vertical_border
|
|
159
176
|
finish_sprite_mcbase
|
|
160
|
-
when 53 then check_sprite_dma
|
|
161
|
-
when 54 then check_dma_and_toggle_expansion
|
|
162
|
-
when 57 then @sprites.check_display(@rasterline)
|
|
163
|
-
when @last_column then @sequencer.start_vertical_border(@rasterline == @last_line ? 0 : @rasterline + 1)
|
|
164
177
|
end
|
|
178
|
+
check_sprite_dma if hooks.anybits?(HOOK_DMA)
|
|
179
|
+
@sprites.toggle_expansion if hooks.anybits?(HOOK_EXPANSION)
|
|
180
|
+
@sprites.check_display(@rasterline) if hooks.anybits?(HOOK_DISPLAY)
|
|
181
|
+
@sequencer.start_vertical_border(@rasterline == @last_line ? 0 : @rasterline + 1) if hooks.anybits?(HOOK_LAST)
|
|
165
182
|
end
|
|
166
183
|
|
|
167
184
|
# The IRQ line is held asserted while any enabled latch bit is set in
|
|
@@ -210,6 +227,16 @@ module Badline
|
|
|
210
227
|
@column > DisplayState::DMA_FIRST && @column <= DisplayState::DMA_LAST + 1
|
|
211
228
|
end
|
|
212
229
|
|
|
230
|
+
# BA as an REU's DMA sees it. On the line whose raster matches sprite
|
|
231
|
+
# 0's Y, where the sprite's DMA starts, the REU doesn't see BA fall on
|
|
232
|
+
# the first cycle of the sprite's window. Pinned by
|
|
233
|
+
# REU/bonzai/spritetiming.
|
|
234
|
+
def reu_ba_low?
|
|
235
|
+
return false if @column == SPRITE_BA_FIRST + @sprite_shift && sprite_zero_starting?
|
|
236
|
+
|
|
237
|
+
ba_low?
|
|
238
|
+
end
|
|
239
|
+
|
|
213
240
|
# Light pen input level (CIA1 PB4). A falling edge triggers the latch.
|
|
214
241
|
def lightpen_level(high)
|
|
215
242
|
@lp_low = !high
|
|
@@ -233,7 +260,7 @@ module Badline
|
|
|
233
260
|
end
|
|
234
261
|
return if line == @last_line && column.positive?
|
|
235
262
|
|
|
236
|
-
vic_x = (
|
|
263
|
+
vic_x = Sprite::Timing.xpos(column * 8, @width, @region.x_hold)
|
|
237
264
|
latch_lightpen((vic_x >> 1) + @lightpen_extra, line)
|
|
238
265
|
end
|
|
239
266
|
|
|
@@ -302,24 +329,27 @@ module Badline
|
|
|
302
329
|
@sprites.log_change(reg, old, value, beam_x)
|
|
303
330
|
end
|
|
304
331
|
|
|
305
|
-
# Bauer cycles 1-10 and 58-63 are sprite p- and s-accesses,
|
|
306
|
-
# refresh, 16-55 g-accesses and 56-57 idle.
|
|
307
|
-
#
|
|
332
|
+
# On the 6569 Bauer cycles 1-10 and 58-63 are sprite p- and s-accesses,
|
|
333
|
+
# 11-15 refresh, 16-55 g-accesses and 56-57 idle. Where the sprite
|
|
334
|
+
# fetches start later, the idle cycles before them grow, and on the
|
|
335
|
+
# 6567R8 cycle 10 idles too. @column has already advanced, so it is the
|
|
336
|
+
# Bauer cycle less one.
|
|
308
337
|
def phi1_address
|
|
309
338
|
cycle = @column + 1
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
339
|
+
slot = (cycle - @sprite_cycle) % @columns_per_line
|
|
340
|
+
if slot < 16
|
|
341
|
+
sprite_phi1_address(slot)
|
|
342
|
+
elsif cycle.between?(11, 15)
|
|
313
343
|
0x3f00 | ((0xff - (5 * @rasterline) - (cycle - 11)) & 0xff)
|
|
314
|
-
elsif cycle
|
|
344
|
+
elsif cycle.between?(16, 55)
|
|
315
345
|
graphics_phi1_address
|
|
316
346
|
else
|
|
317
347
|
0x3fff
|
|
318
348
|
end
|
|
319
349
|
end
|
|
320
350
|
|
|
321
|
-
# Each sprite takes two cycles from
|
|
322
|
-
# middle s-access.
|
|
351
|
+
# Each sprite takes two cycles from sprite 0's, the p-access and then
|
|
352
|
+
# the middle s-access.
|
|
323
353
|
def sprite_phi1_address(slot)
|
|
324
354
|
n = slot >> 1
|
|
325
355
|
return @registers.screen_base + 0x3f8 + n if slot.even?
|
|
@@ -357,11 +387,6 @@ module Badline
|
|
|
357
387
|
rebuild_sprite_ba if @sprites.stopped_dma?
|
|
358
388
|
end
|
|
359
389
|
|
|
360
|
-
def check_dma_and_toggle_expansion
|
|
361
|
-
check_sprite_dma
|
|
362
|
-
@sprites.toggle_expansion
|
|
363
|
-
end
|
|
364
|
-
|
|
365
390
|
# The trigger re-arms at the start of each frame; if the pen line is
|
|
366
391
|
# still low, the latch retriggers immediately with a fixed LPX of $d1.
|
|
367
392
|
def start_lightpen_frame
|
|
@@ -378,6 +403,10 @@ module Badline
|
|
|
378
403
|
@registers.latch_irq!(LIGHTPEN_IRQ)
|
|
379
404
|
end
|
|
380
405
|
|
|
406
|
+
def sprite_zero_starting?
|
|
407
|
+
@registers[0x15].anybits?(0x01) && @registers[0x01] == (@rasterline & 0xff)
|
|
408
|
+
end
|
|
409
|
+
|
|
381
410
|
def rebuild_sprite_ba
|
|
382
411
|
@sprite_ba.fill(false)
|
|
383
412
|
8.times do |n|
|
|
@@ -389,13 +418,31 @@ module Badline
|
|
|
389
418
|
end
|
|
390
419
|
|
|
391
420
|
# Splits the sprite BA windows at the end of the line and marks the hook
|
|
392
|
-
# columns, for a line of the region's length
|
|
393
|
-
# column
|
|
421
|
+
# columns, for a line of the region's length: 14, 15, 53, 54 and 57, and
|
|
422
|
+
# the line's last column, which starts the vertical border for the line
|
|
423
|
+
# after it. The marks let an ordinary column cost one array read instead
|
|
424
|
+
# of the dispatch.
|
|
394
425
|
def layout_columns
|
|
395
426
|
last = @last_column
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
427
|
+
windows = Array.new(8) do |n|
|
|
428
|
+
first = SPRITE_BA_FIRST + @sprite_shift + (2 * n)
|
|
429
|
+
first...(first + SPRITE_BA_LENGTH)
|
|
430
|
+
end
|
|
431
|
+
@sprite_ba_tail = windows.map { |w| w.select { |c| c <= last } }.freeze
|
|
432
|
+
@sprite_ba_head = windows.map { |w| w.filter_map { |c| c - last - 1 if c > last } }.freeze
|
|
433
|
+
@hook_columns = layout_hooks.map { |hooks| hooks unless hooks.zero? }.freeze
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def layout_hooks
|
|
437
|
+
hooks = Array.new(@columns_per_line, 0)
|
|
438
|
+
hooks[14] |= HOOK_MCBASE
|
|
439
|
+
hooks[15] |= HOOK_LEFT
|
|
440
|
+
hooks[DMA_COLUMN + @sprite_shift] |= HOOK_DMA
|
|
441
|
+
hooks[DMA_COLUMN + @sprite_shift + 1] |= HOOK_DMA
|
|
442
|
+
hooks[EXPANSION_COLUMN] |= HOOK_EXPANSION
|
|
443
|
+
hooks[@region.sprite_display_cycle - 1] |= HOOK_DISPLAY
|
|
444
|
+
hooks[@last_column] |= HOOK_LAST
|
|
445
|
+
hooks
|
|
399
446
|
end
|
|
400
447
|
|
|
401
448
|
# Runs this column's g-access and draws the one from GRAPHICS_DELAY
|
data/lib/badline.rb
CHANGED
|
@@ -24,11 +24,14 @@ require "badline/cia"
|
|
|
24
24
|
require "badline/via"
|
|
25
25
|
require "badline/sid"
|
|
26
26
|
require "badline/debug_register"
|
|
27
|
+
require "badline/reu"
|
|
27
28
|
require "badline/interrupts"
|
|
28
29
|
require "badline/traps"
|
|
29
30
|
require "badline/cpu"
|
|
30
31
|
require "badline/vic"
|
|
31
32
|
require "badline/keyboard_buffer"
|
|
33
|
+
require "badline/iec_bus"
|
|
34
|
+
require "badline/drive1541"
|
|
32
35
|
require "badline/computer"
|
|
33
36
|
require "badline/checkpoint"
|
|
34
37
|
require "badline/storage"
|