badline 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +185 -0
- data/CLAUDE.md +217 -0
- data/CODE_OF_CONDUCT.md +92 -0
- data/CONTRIBUTING.md +51 -0
- data/README.md +224 -31
- data/doc/pinned-behaviour.md +1117 -0
- data/exe/badline +22 -2
- data/exe/badline-sid +34 -0
- data/lib/badline/address_bus.rb +96 -25
- data/lib/badline/audio/aiff.rb +36 -0
- data/lib/badline/audio/bare_player.rb +129 -0
- data/lib/badline/audio/cli.rb +133 -0
- data/lib/badline/audio/console.rb +75 -0
- data/lib/badline/audio/jukebox.rb +82 -0
- data/lib/badline/audio/machine_player.rb +57 -0
- data/lib/badline/audio/options.rb +137 -0
- data/lib/badline/audio/pcm_writer.rb +57 -0
- data/lib/badline/audio/playback.rb +108 -0
- data/lib/badline/audio/renderer.rb +85 -0
- data/lib/badline/audio/sdl_sink.rb +102 -0
- data/lib/badline/audio/wav.rb +23 -0
- data/lib/badline/audio.rb +14 -0
- data/lib/badline/cartridge/action_replay.rb +111 -0
- data/lib/badline/cartridge/atomic_power.rb +24 -0
- data/lib/badline/cartridge/bank.rb +81 -0
- data/lib/badline/cartridge/comal80.rb +48 -0
- data/lib/badline/cartridge/dinamic.rb +34 -0
- data/lib/badline/cartridge/easy_flash.rb +122 -0
- data/lib/badline/cartridge/epyx_fastload.rb +71 -0
- data/lib/badline/cartridge/final_cartridge3.rb +77 -0
- data/lib/badline/cartridge/flash.rb +265 -0
- data/lib/badline/cartridge/freezer.rb +31 -0
- data/lib/badline/cartridge/fun_play.rb +38 -0
- data/lib/badline/cartridge/g_mod2.rb +62 -0
- data/lib/badline/cartridge/game_system.rb +40 -0
- data/lib/badline/cartridge/kcs_power.rb +72 -0
- data/lib/badline/cartridge/mach5.rb +34 -0
- data/lib/badline/cartridge/magic_desk.rb +6 -3
- data/lib/badline/cartridge/ocean.rb +11 -12
- data/lib/badline/cartridge/pagefox.rb +56 -0
- data/lib/badline/cartridge/retro_replay.rb +276 -0
- data/lib/badline/cartridge/rex_utility.rb +33 -0
- data/lib/badline/cartridge/rgcd.rb +48 -0
- data/lib/badline/cartridge/simons_basic.rb +40 -0
- data/lib/badline/cartridge/standard.rb +3 -19
- data/lib/badline/cartridge/super_games.rb +36 -0
- data/lib/badline/cartridge/westermann.rb +34 -0
- data/lib/badline/cartridge/zaxxon.rb +50 -0
- data/lib/badline/cartridge.rb +132 -16
- data/lib/badline/cia/interrupt_register.rb +102 -0
- data/lib/badline/cia/serial.rb +219 -0
- data/lib/badline/cia/timer.rb +43 -31
- data/lib/badline/cia.rb +138 -88
- data/lib/badline/color_memory.rb +14 -5
- data/lib/badline/computer.rb +97 -12
- data/lib/badline/control_ports.rb +54 -9
- data/lib/badline/cpu/addressing.rb +131 -0
- data/lib/badline/cpu/microcode.rb +169 -0
- data/lib/badline/cpu/operations.rb +88 -0
- data/lib/badline/cpu/stack_operations.rb +82 -0
- data/lib/badline/cpu.rb +109 -168
- data/lib/badline/cycleable.rb +3 -0
- data/lib/badline/datasette.rb +81 -0
- data/lib/badline/debug_register.rb +28 -0
- data/lib/badline/gui/application.rb +79 -18
- data/lib/badline/gui/gamepads.rb +97 -0
- data/lib/badline/gui/joy_map.rb +15 -7
- data/lib/badline/gui.rb +1 -0
- data/lib/badline/input/mouse1351.rb +41 -0
- data/lib/badline/input/paddles.rb +36 -0
- data/lib/badline/input.rb +4 -0
- data/lib/badline/instruction_set/arithmetic.rb +16 -21
- data/lib/badline/instruction_set/bitwise.rb +17 -22
- data/lib/badline/instruction_set/branch.rb +20 -19
- data/lib/badline/instruction_set/flag.rb +7 -7
- data/lib/badline/instruction_set/illegal.rb +14 -22
- data/lib/badline/instruction_set/inc_dec.rb +8 -10
- data/lib/badline/instruction_set/stack.rb +5 -57
- data/lib/badline/instruction_set/transfer.rb +9 -9
- data/lib/badline/instruction_set.rb +9 -31
- data/lib/badline/interrupts.rb +104 -0
- data/lib/badline/joystick.rb +9 -9
- data/lib/badline/kernal_trap/channel.rb +51 -0
- data/lib/badline/kernal_trap/drive/status.rb +49 -0
- data/lib/badline/kernal_trap/drive.rb +277 -0
- data/lib/badline/kernal_trap/file.rb +5 -25
- data/lib/badline/kernal_trap/load.rb +86 -24
- data/lib/badline/kernal_trap/routine.rb +51 -0
- data/lib/badline/kernal_trap/save.rb +46 -14
- data/lib/badline/kernal_trap/serial.rb +171 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/keyboard.rb +40 -16
- data/lib/badline/media.rb +48 -10
- data/lib/badline/memory.rb +5 -0
- data/lib/badline/roms/eapi/LICENSE.md +15 -0
- data/lib/badline/roms/eapi/README +41 -0
- data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
- data/lib/badline/sid/decimator.rb +46 -0
- data/lib/badline/sid/envelope.rb +232 -0
- data/lib/badline/sid/filter.rb +213 -0
- data/lib/badline/sid/voice.rb +61 -0
- data/lib/badline/sid/waveform/combined.rb +126 -0
- data/lib/badline/sid/waveform/fast_forward.rb +105 -0
- data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
- data/lib/badline/sid/waveform.rb +289 -0
- data/lib/badline/sid.rb +274 -7
- data/lib/badline/status.rb +251 -23
- data/lib/badline/storage/crt_file.rb +18 -2
- data/lib/badline/storage/d64_image.rb +6 -0
- data/lib/badline/storage/d71_image.rb +4 -0
- data/lib/badline/storage/d81_image.rb +4 -0
- data/lib/badline/storage/disk_image.rb +99 -13
- data/lib/badline/storage/host_directory.rb +30 -4
- data/lib/badline/storage/sid_file.rb +274 -0
- data/lib/badline/storage/song_lengths.rb +65 -0
- data/lib/badline/storage/t64.rb +67 -0
- data/lib/badline/storage/tap.rb +68 -0
- data/lib/badline/storage.rb +19 -0
- data/lib/badline/time_of_day.rb +105 -52
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +14 -6
- data/lib/badline/vic/border_mask.rb +76 -0
- data/lib/badline/vic/collisions.rb +104 -0
- data/lib/badline/vic/color_patches.rb +33 -0
- data/lib/badline/vic/display_state.rb +92 -29
- data/lib/badline/vic/graphics_mode.rb +78 -53
- data/lib/badline/vic/graphics_shifter.rb +140 -0
- data/lib/badline/vic/register_log.rb +63 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic/sequencer.rb +163 -137
- data/lib/badline/vic/sequencer_output.rb +116 -0
- data/lib/badline/vic/sprite/internal_bus.rb +49 -0
- data/lib/badline/vic/sprite/shifter.rb +129 -0
- data/lib/badline/vic/sprite.rb +275 -65
- data/lib/badline/vic/sprites.rb +220 -58
- data/lib/badline/vic.rb +389 -55
- data/lib/badline.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +104 -4
data/lib/badline/vic.rb
CHANGED
|
@@ -14,10 +14,40 @@ module Badline
|
|
|
14
14
|
attr_reader :address_bus, :display, :width, :height, :vic_bank, :column,
|
|
15
15
|
:rasterline, :dirty_lines
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
# Returns the byte the CPU's halted read would see, for the c-accesses
|
|
18
|
+
# that run before AEC. Without it they read colour RAM.
|
|
19
|
+
attr_writer :open_bus
|
|
20
|
+
|
|
21
|
+
LIGHTPEN_IRQ = 0x08 # $D019 latch bit
|
|
22
|
+
|
|
23
|
+
# A g-access reaches the pixel output this many columns after it runs.
|
|
24
|
+
GRAPHICS_DELAY = 2
|
|
25
|
+
|
|
26
|
+
# What a column's g-access passed to the sequencer.
|
|
27
|
+
G_BLANK = 0
|
|
28
|
+
G_IDLE = 1
|
|
29
|
+
G_DISPLAY = 2
|
|
30
|
+
|
|
31
|
+
# The $d011 mode bits a g-access still sees for a cycle after they fall:
|
|
32
|
+
# BMM always, and ECM too when the access it leaves read the character
|
|
33
|
+
# ROM.
|
|
34
|
+
FETCH_HOLD = 0x20
|
|
35
|
+
FETCH_HOLD_ROM = 0x60
|
|
36
|
+
|
|
37
|
+
# Columns carrying a per-cycle hook, so an ordinary column costs one
|
|
38
|
+
# array read instead of the dispatch.
|
|
39
|
+
HOOK_COLUMNS = Array.new(63) do |column|
|
|
40
|
+
[14, 15, 53, 54, 57, 62].include?(column)
|
|
41
|
+
end.freeze
|
|
42
|
+
|
|
43
|
+
# BA falls three columns ahead of each sprite's pair of s-accesses, and
|
|
44
|
+
# the five-column windows step two columns apart from sprite 0 at column
|
|
45
|
+
# 54. From sprite 3 on they reach past the end of the line, so each is
|
|
46
|
+
# split: the tail columns fall on the line whose cycle 55/56 compare
|
|
47
|
+
# started the fetch, the head columns on the line after it.
|
|
48
|
+
SPRITE_BA_WINDOWS = Array.new(8) { |n| (54 + (2 * n))..(58 + (2 * n)) }.freeze
|
|
49
|
+
SPRITE_BA_TAIL = SPRITE_BA_WINDOWS.map { |w| w.select { |c| c < 63 } }.freeze
|
|
50
|
+
SPRITE_BA_HEAD = SPRITE_BA_WINDOWS.map { |w| w.filter_map { |c| c - 63 if c >= 63 } }.freeze
|
|
21
51
|
|
|
22
52
|
def initialize(address_bus = nil, debug: false)
|
|
23
53
|
addressable_at(0xd000, length: 2**10)
|
|
@@ -27,51 +57,93 @@ module Badline
|
|
|
27
57
|
|
|
28
58
|
@width = 504
|
|
29
59
|
@height = 312
|
|
60
|
+
@columns_per_line = @width / 8
|
|
61
|
+
@last_line = @height - 1
|
|
62
|
+
@display = Array.new(@width * @height, 0)
|
|
63
|
+
@lines = Array.new(@height) { Array.new(@width, 0) }
|
|
64
|
+
@dirty_lines = Array.new(@height, true)
|
|
65
|
+
@render = true
|
|
30
66
|
|
|
67
|
+
power_on!
|
|
68
|
+
super()
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The VIC has no reset pin, so only a power cycle brings back the
|
|
72
|
+
# registers, the raster position and the fetch and sprite state it
|
|
73
|
+
# starts with. The display keeps its buffers, cleared to black.
|
|
74
|
+
def power_on!
|
|
31
75
|
@registers = VIC::Registers.new
|
|
76
|
+
@register_bytes = @registers.bytes
|
|
32
77
|
@display_state = VIC::DisplayState.new(@registers)
|
|
33
78
|
@sequencer = VIC::Sequencer.new(@width, @registers, @vic_bank)
|
|
79
|
+
@sequencer.render = @render
|
|
34
80
|
@sprites = VIC::Sprites.new(@registers, @vic_bank, @width)
|
|
35
|
-
@display
|
|
36
|
-
@
|
|
81
|
+
@display.fill(0)
|
|
82
|
+
@lines.each { |line| line.fill(0) }
|
|
83
|
+
@dirty_lines.fill(true)
|
|
37
84
|
|
|
38
85
|
@column = 0
|
|
39
86
|
@rasterline = 0
|
|
40
|
-
@columns_per_line = @width / 8
|
|
41
|
-
@last_line = @height - 1
|
|
42
87
|
|
|
43
88
|
@character_buffer = Array.new(40, 0)
|
|
44
89
|
@color_buffer = Array.new(40, 0)
|
|
45
90
|
@sprite_ba = Array.new(@width / 8, false)
|
|
46
|
-
|
|
47
|
-
|
|
91
|
+
@g_tick = 0
|
|
92
|
+
@g_kind = Array.new(4, G_BLANK)
|
|
93
|
+
@g_kept_char = 0
|
|
94
|
+
@g_kept_color = 0
|
|
95
|
+
@g_data = @sequencer.ring_data
|
|
96
|
+
@g_char = @sequencer.ring_char
|
|
97
|
+
@g_color = @sequencer.ring_color
|
|
98
|
+
@g_display = false
|
|
99
|
+
@fetch_d011 = @register_bytes[0x11]
|
|
100
|
+
@lp_triggered = false
|
|
101
|
+
@lp_low = false
|
|
102
|
+
@raster_match = false
|
|
48
103
|
end
|
|
49
104
|
|
|
50
105
|
def cycle!
|
|
51
|
-
if @column.zero?
|
|
52
|
-
@display_state.new_frame if @rasterline.zero?
|
|
53
|
-
check_raster_irq!
|
|
54
|
-
@sequencer.new_line(@rasterline)
|
|
55
|
-
@sprites.start_line(@rasterline)
|
|
56
|
-
rebuild_sprite_ba
|
|
57
|
-
@display_state.new_line
|
|
58
|
-
end
|
|
106
|
+
start_line! if @column.zero?
|
|
59
107
|
|
|
108
|
+
# The g-access runs in the first half of the cycle, ahead of the bad
|
|
109
|
+
# line compare, and the c-access in the second half, after it.
|
|
110
|
+
draw!
|
|
60
111
|
@display_state.cycle(@rasterline, @column)
|
|
61
112
|
|
|
62
113
|
fetch_character_data! if dma_active?
|
|
63
114
|
|
|
64
|
-
|
|
115
|
+
column_hooks if HOOK_COLUMNS[@column]
|
|
65
116
|
|
|
66
117
|
@column += 1
|
|
67
118
|
if @column == @columns_per_line
|
|
68
119
|
finish_line!
|
|
69
120
|
@column = 0
|
|
70
121
|
@rasterline = @rasterline == @last_line ? 0 : @rasterline + 1
|
|
122
|
+
check_raster_irq!(@rasterline.zero? ? @last_line : @rasterline)
|
|
71
123
|
end
|
|
124
|
+
watch_collisions
|
|
72
125
|
nil
|
|
73
126
|
end
|
|
74
127
|
|
|
128
|
+
# The sprite and border hooks that fall on named cycles, one column
|
|
129
|
+
# ahead of Bauer's numbering — two for MCBASE, the DMA compares and the
|
|
130
|
+
# expansion flip-flop, which the `spriteenable` and `spritecrunch`
|
|
131
|
+
# references place a column earlier still. Guarded in
|
|
132
|
+
# #cycle! so an ordinary column pays two compares rather than the
|
|
133
|
+
# dispatch.
|
|
134
|
+
def column_hooks
|
|
135
|
+
case @column
|
|
136
|
+
when 14 then @sprites.advance_mcbase
|
|
137
|
+
when 15
|
|
138
|
+
@sequencer.left_compare_vertical_border
|
|
139
|
+
finish_sprite_mcbase
|
|
140
|
+
when 53 then check_sprite_dma
|
|
141
|
+
when 54 then check_dma_and_toggle_expansion
|
|
142
|
+
when 57 then @sprites.check_display(@rasterline)
|
|
143
|
+
when 62 then @sequencer.start_vertical_border(@rasterline == @last_line ? 0 : @rasterline + 1)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
75
147
|
# The IRQ line is held asserted while any enabled latch bit is set in
|
|
76
148
|
# $D019/$D01A, until the program acknowledges it by writing to $D019.
|
|
77
149
|
def interrupted?
|
|
@@ -81,15 +153,20 @@ module Badline
|
|
|
81
153
|
def peek(addr)
|
|
82
154
|
i = index(addr) % (2**6)
|
|
83
155
|
case i
|
|
84
|
-
when 0x11 then (@registers[0x11] & 0x7f) | ((
|
|
85
|
-
when 0x12 then
|
|
156
|
+
when 0x11 then (@registers[0x11] & 0x7f) | ((raster_counter & 0x100) >> 1)
|
|
157
|
+
when 0x12 then raster_counter & 0xff
|
|
86
158
|
when 0x19 then irq_status # Latch + master IRQ bit, unused bits read 1
|
|
159
|
+
when 0x1e, 0x1f then read_collision(i)
|
|
87
160
|
else @registers.read(i)
|
|
88
|
-
end
|
|
161
|
+
end.tap { |value| @sprites.bus_data(@column, value) }
|
|
89
162
|
end
|
|
90
163
|
|
|
91
164
|
def poke(addr, value)
|
|
92
|
-
|
|
165
|
+
reg = index(addr) % (2**6)
|
|
166
|
+
@sprites.bus_data(@column, value)
|
|
167
|
+
log_register_change(reg, value)
|
|
168
|
+
@registers.write(reg, value)
|
|
169
|
+
compare_raster_writes(reg)
|
|
93
170
|
end
|
|
94
171
|
|
|
95
172
|
def position
|
|
@@ -97,13 +174,51 @@ module Badline
|
|
|
97
174
|
end
|
|
98
175
|
|
|
99
176
|
def dma_active?
|
|
100
|
-
@display_state.
|
|
177
|
+
@display_state.fetching?(@column)
|
|
101
178
|
end
|
|
102
179
|
|
|
180
|
+
# Asked once the VIC has advanced, so @column is one ahead of the cycle
|
|
181
|
+
# the CPU is about to run. A bad line holds BA on the condition as it
|
|
182
|
+
# stands rather than the latched match.
|
|
103
183
|
def ba_low?
|
|
104
184
|
return true if @sprite_ba[@column]
|
|
105
185
|
|
|
106
|
-
@display_state.
|
|
186
|
+
@display_state.bad_line_condition? &&
|
|
187
|
+
@column > DisplayState::DMA_FIRST && @column <= DisplayState::DMA_LAST + 1
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Light pen input level (CIA1 PB4). A falling edge triggers the latch.
|
|
191
|
+
def lightpen_level(high)
|
|
192
|
+
@lp_low = !high
|
|
193
|
+
trigger_lightpen if @lp_low
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# The first trigger per frame latches the beam position into LPX/LPY and
|
|
197
|
+
# raises the LP IRQ. The latch happens one cycle after the edge, with the
|
|
198
|
+
# 6569's two extra half-pixels; a trigger on the last line is consumed
|
|
199
|
+
# without latching unless it lands on the line's first cycle.
|
|
200
|
+
def trigger_lightpen
|
|
201
|
+
return if @lp_triggered
|
|
202
|
+
|
|
203
|
+
@lp_triggered = true
|
|
204
|
+
column = @column + 1
|
|
205
|
+
line = @rasterline
|
|
206
|
+
if column == @columns_per_line
|
|
207
|
+
column = 0
|
|
208
|
+
line = line == @last_line ? 0 : line + 1
|
|
209
|
+
end
|
|
210
|
+
return if line == @last_line && column.positive?
|
|
211
|
+
|
|
212
|
+
vic_x = ((column * 8) - Sprite::X_OFFSET) % @width
|
|
213
|
+
latch_lightpen((vic_x >> 1) + 2, line)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# The byte the VIC fetched in the phi1 half of the cycle the CPU is
|
|
217
|
+
# running, worked out from the VIC's state. The CPU cycle after column
|
|
218
|
+
# c is Bauer cycle c + 2, and each Bauer cycle has a fixed access (VICE
|
|
219
|
+
# `cycle_phi1_fetch`).
|
|
220
|
+
def phi1_data
|
|
221
|
+
vic_bank.peek(phi1_address)
|
|
107
222
|
end
|
|
108
223
|
|
|
109
224
|
def hblank?
|
|
@@ -118,6 +233,18 @@ module Badline
|
|
|
118
233
|
@rasterline < 16 || @rasterline > 299 || @column < 10 || @column > 60
|
|
119
234
|
end
|
|
120
235
|
|
|
236
|
+
def render? = @render
|
|
237
|
+
|
|
238
|
+
# Whether finished lines are painted into #display. A headless run that
|
|
239
|
+
# only reads the machine's state can turn it off: the sprite
|
|
240
|
+
# collisions, lightpen and every register still behave the same, but
|
|
241
|
+
# #display keeps the last lines painted. Turned back on, it is whole
|
|
242
|
+
# again from the start of the next frame.
|
|
243
|
+
def render=(on)
|
|
244
|
+
@render = on
|
|
245
|
+
@sequencer.render = on
|
|
246
|
+
end
|
|
247
|
+
|
|
121
248
|
# Reset the dirty flags once the frontend has consumed them.
|
|
122
249
|
def clear_dirty_lines!
|
|
123
250
|
@dirty_lines.fill(false)
|
|
@@ -125,68 +252,275 @@ module Badline
|
|
|
125
252
|
|
|
126
253
|
private
|
|
127
254
|
|
|
255
|
+
# The raster counter as $D011/$D012 read it. Every line advances on the
|
|
256
|
+
# CPU cycle paired with column 62 except line 0, which the counter
|
|
257
|
+
# reaches a cycle later, together with the line 0 raster compare.
|
|
258
|
+
def raster_counter
|
|
259
|
+
@column.zero? && @rasterline.zero? ? @last_line : @rasterline
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Mid-line writes to the color and sprite registers are logged against
|
|
263
|
+
# the cycle after the write (the CPU runs after the VIC within a machine
|
|
264
|
+
# cycle, so @column already points there); each register adds its own
|
|
265
|
+
# pixel delay on top.
|
|
266
|
+
def log_register_change(reg, value)
|
|
267
|
+
old = @registers[reg]
|
|
268
|
+
return if old == value
|
|
269
|
+
|
|
270
|
+
if (0x20..0x24).cover?(reg)
|
|
271
|
+
@sequencer.colors_changed! unless reg == 0x20
|
|
272
|
+
@sequencer.color_patches.log(reg, old, value, @column * 8) if @render && !blanking?
|
|
273
|
+
else
|
|
274
|
+
@sprites.log_change(reg, old, value, @column * 8)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Bauer cycles 1-10 and 58-63 are sprite p- and s-accesses, 11-15
|
|
279
|
+
# refresh, 16-55 g-accesses and 56-57 idle. @column has already
|
|
280
|
+
# advanced, so it is the Bauer cycle less one.
|
|
281
|
+
def phi1_address
|
|
282
|
+
cycle = @column + 1
|
|
283
|
+
if cycle < 11 || cycle > 57
|
|
284
|
+
sprite_phi1_address((cycle - 58) % 63)
|
|
285
|
+
elsif cycle < 16
|
|
286
|
+
0x3f00 | ((0xff - (5 * @rasterline) - (cycle - 11)) & 0xff)
|
|
287
|
+
elsif cycle < 56
|
|
288
|
+
graphics_phi1_address
|
|
289
|
+
else
|
|
290
|
+
0x3fff
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Each sprite takes two cycles from Bauer 58, the p-access and then the
|
|
295
|
+
# middle s-access.
|
|
296
|
+
def sprite_phi1_address(slot)
|
|
297
|
+
n = slot >> 1
|
|
298
|
+
return @registers.screen_base + 0x3f8 + n if slot.even?
|
|
299
|
+
|
|
300
|
+
@sprites[n].phi1_address || 0x3fff
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# The g-access of the column just run. In display state it used the
|
|
304
|
+
# VC and VMLI it then stepped past.
|
|
305
|
+
def graphics_phi1_address
|
|
306
|
+
return @registers.ecm.nonzero? ? 0x39ff : 0x3fff unless @g_display
|
|
307
|
+
|
|
308
|
+
graphics_address(@registers[0x11], @display_state.vmli - 1, (@display_state.vc - 1) & 0x3ff)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# A $d011/$d012 write is compared in the next column. A write after
|
|
312
|
+
# column 61 is left to column 62, which compares the next line.
|
|
313
|
+
def compare_raster_writes(reg)
|
|
314
|
+
return unless (0x11..0x12).cover?(reg)
|
|
315
|
+
return if @column == @columns_per_line - 1
|
|
316
|
+
|
|
317
|
+
check_raster_irq!
|
|
318
|
+
@sequencer.compare_vertical_border(@rasterline) if reg == 0x11
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def check_sprite_dma
|
|
322
|
+
rebuild_sprite_ba if @sprites.check_dma(@rasterline, @column)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# A sprite whose DMA ends here fetches nothing at the end of this line,
|
|
326
|
+
# so its BA tail goes too.
|
|
327
|
+
def finish_sprite_mcbase
|
|
328
|
+
@sprites.finish_mcbase
|
|
329
|
+
rebuild_sprite_ba if @sprites.stopped_dma?
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def check_dma_and_toggle_expansion
|
|
333
|
+
check_sprite_dma
|
|
334
|
+
@sprites.toggle_expansion
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# The trigger re-arms at the start of each frame; if the pen line is
|
|
338
|
+
# still low, the latch retriggers immediately with a fixed LPX of $d1.
|
|
339
|
+
def start_lightpen_frame
|
|
340
|
+
@lp_triggered = false
|
|
341
|
+
return unless @lp_low
|
|
342
|
+
|
|
343
|
+
@lp_triggered = true
|
|
344
|
+
latch_lightpen(0xd1, 0)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def latch_lightpen(lpx, line)
|
|
348
|
+
@registers.write(0x13, lpx & 0xff)
|
|
349
|
+
@registers.write(0x14, line & 0xff)
|
|
350
|
+
@registers.latch_irq!(LIGHTPEN_IRQ)
|
|
351
|
+
end
|
|
352
|
+
|
|
128
353
|
def rebuild_sprite_ba
|
|
129
354
|
@sprite_ba.fill(false)
|
|
130
|
-
|
|
355
|
+
8.times do |n|
|
|
131
356
|
next unless @sprites[n].displaying?
|
|
132
357
|
|
|
133
|
-
|
|
358
|
+
SPRITE_BA_TAIL[n].each { |c| @sprite_ba[c] = true }
|
|
359
|
+
SPRITE_BA_HEAD[n].each { |c| @sprite_ba[c] = true }
|
|
134
360
|
end
|
|
135
361
|
end
|
|
136
362
|
|
|
363
|
+
# Runs this column's g-access and draws the one from GRAPHICS_DELAY
|
|
364
|
+
# columns earlier. The access reads its byte now, through the mode,
|
|
365
|
+
# $d018 and bank of this cycle. A column with no g-access, or one made
|
|
366
|
+
# while the vertical border flip-flop is set, latches nothing: it passes
|
|
367
|
+
# on zero data with the screen byte and colour the last g-access
|
|
368
|
+
# latched, which an idle g-access clears.
|
|
137
369
|
def draw!
|
|
138
|
-
|
|
370
|
+
slot = @g_tick = (@g_tick + 1) & 3
|
|
371
|
+
display_state = @display_state
|
|
372
|
+
access = display_state.graphics_column?(@column) && !@sequencer.vertical_closed?
|
|
373
|
+
if access
|
|
374
|
+
latch_graphics(slot, display_state)
|
|
375
|
+
else
|
|
376
|
+
@g_kind[slot] = G_BLANK
|
|
377
|
+
@g_data[slot] = 0
|
|
378
|
+
@g_char[slot] = @g_kept_char
|
|
379
|
+
@g_color[slot] = @g_kept_color
|
|
380
|
+
end
|
|
381
|
+
@g_display = display_state.display?
|
|
382
|
+
display_state.graphics_access(@column) if @g_display
|
|
383
|
+
emit_graphics((slot - GRAPHICS_DELAY) & 3, @column - 16) unless blanking?
|
|
384
|
+
@sequencer.latch_xscroll(@register_bytes[0x16] & 0b111) if access
|
|
385
|
+
@fetch_d011 = @register_bytes[0x11]
|
|
386
|
+
end
|
|
139
387
|
|
|
140
|
-
|
|
141
|
-
unless
|
|
142
|
-
@
|
|
388
|
+
def latch_graphics(slot, display_state)
|
|
389
|
+
unless display_state.display?
|
|
390
|
+
@g_kind[slot] = G_IDLE
|
|
391
|
+
@g_data[slot] = vic_bank.peek(@registers.ecm.zero? ? 0x3fff : 0x39ff)
|
|
392
|
+
@g_char[slot] = @g_color[slot] = @g_kept_char = @g_kept_color = 0
|
|
143
393
|
return
|
|
144
394
|
end
|
|
145
395
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
@
|
|
149
|
-
|
|
396
|
+
vmli = display_state.vmli
|
|
397
|
+
@g_kind[slot] = G_DISPLAY
|
|
398
|
+
@g_data[slot] = fetch_graphics(vmli, display_state.vc)
|
|
399
|
+
@g_char[slot] = @g_kept_char = @character_buffer[vmli] || 0
|
|
400
|
+
@g_color[slot] = @g_kept_color = @color_buffer[vmli] || 1
|
|
150
401
|
end
|
|
151
402
|
|
|
152
|
-
|
|
153
|
-
|
|
403
|
+
# The g-access sees a mode bit that falls a cycle late: it addresses
|
|
404
|
+
# with $d011 as this column has it, OR-ed with the held bits the column
|
|
405
|
+
# before had. When BMM changes and the access moves from RAM onto the
|
|
406
|
+
# character ROM, the low address byte still comes from the old mode
|
|
407
|
+
# (VICE x64sc `vicii_fetch_graphics`).
|
|
408
|
+
def fetch_graphics(vmli, counter)
|
|
409
|
+
d011 = @register_bytes[0x11]
|
|
410
|
+
last = @fetch_d011
|
|
411
|
+
return vic_bank.peek(graphics_address(d011, vmli, counter)) if d011 == last
|
|
412
|
+
|
|
413
|
+
from = graphics_address(last, vmli, counter)
|
|
414
|
+
from_rom = vic_bank.character_rom?(from)
|
|
415
|
+
address = graphics_address(d011 | (last & (from_rom ? FETCH_HOLD_ROM : FETCH_HOLD)), vmli, counter)
|
|
416
|
+
if (d011 ^ last).anybits?(0x20) && !from_rom
|
|
417
|
+
to = graphics_address(d011, vmli, counter)
|
|
418
|
+
address = (from & 0xff) | (to & 0x3f00) if vic_bank.character_rom?(to)
|
|
419
|
+
end
|
|
420
|
+
vic_bank.peek(address)
|
|
421
|
+
end
|
|
154
422
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
423
|
+
def graphics_address(d011, vmli, counter)
|
|
424
|
+
rc = @display_state.rc
|
|
425
|
+
case d011 & 0x60
|
|
426
|
+
when 0x00 then @registers.char_base | ((@character_buffer[vmli] || 0) << 3) | rc
|
|
427
|
+
when 0x20 then @registers.bitmap_base | (counter << 3) | rc
|
|
428
|
+
when 0x40 then (@registers.char_base | ((@character_buffer[vmli] || 0) << 3) | rc) & 0x39ff
|
|
429
|
+
else (@registers.bitmap_base | (counter << 3) | rc) & 0x39ff
|
|
160
430
|
end
|
|
161
|
-
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def emit_graphics(slot, col)
|
|
434
|
+
@sequencer.emit(slot, col, @g_kind[slot] == G_DISPLAY)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# Fold the rest of the line's sprite collisions in — they latch whether
|
|
438
|
+
# or not there is a line to draw — then composite the active sprites
|
|
439
|
+
# over the finished background and copy the line into the frame display.
|
|
440
|
+
def finish_line!
|
|
441
|
+
return @sprites.finish_line(nil, @sequencer.fg) if vblank? || !@render
|
|
442
|
+
|
|
443
|
+
composite = @sprites.active?
|
|
444
|
+
@sequencer.apply_color_patches
|
|
445
|
+
@sequencer.snapshot_line if composite
|
|
446
|
+
@sprites.finish_line(composite ? @sequencer.colors : nil, @sequencer.fg)
|
|
447
|
+
@sequencer.apply_border if composite
|
|
448
|
+
|
|
162
449
|
colors = @sequencer.colors
|
|
163
|
-
|
|
450
|
+
line = @lines[@rasterline]
|
|
451
|
+
return if line == colors
|
|
164
452
|
|
|
165
|
-
|
|
453
|
+
line[0, @width] = colors
|
|
454
|
+
@display[@rasterline * @width, @width] = colors
|
|
166
455
|
@dirty_lines[@rasterline] = true
|
|
167
456
|
end
|
|
168
457
|
|
|
169
458
|
def video_matrix(index)
|
|
170
|
-
vic_bank.
|
|
459
|
+
vic_bank.peek_phi2(@registers.screen_base + index)
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def start_line!
|
|
463
|
+
if @rasterline.zero?
|
|
464
|
+
@display_state.new_frame
|
|
465
|
+
start_lightpen_frame
|
|
466
|
+
# The line 0 raster compare happens one cycle later than on all
|
|
467
|
+
# other lines (Bauer 3.12).
|
|
468
|
+
check_raster_irq!
|
|
469
|
+
end
|
|
470
|
+
@sequencer.new_line(@rasterline)
|
|
471
|
+
@sprites.start_line
|
|
472
|
+
rebuild_sprite_ba
|
|
473
|
+
@display_state.new_line(@rasterline)
|
|
171
474
|
end
|
|
172
475
|
|
|
173
|
-
|
|
174
|
-
|
|
476
|
+
# The raster compare latches the flag only as the line and the target
|
|
477
|
+
# come to match, so a target moved along with the raster line holds the
|
|
478
|
+
# match without latching again (VICE x64sc). It runs at each line step
|
|
479
|
+
# and after each $d011/$d012 write. The line asserts via #interrupted?
|
|
480
|
+
# when the mask bit in $D01A is set.
|
|
481
|
+
def check_raster_irq!(line = @rasterline)
|
|
482
|
+
match = line == @registers.raster_target
|
|
483
|
+
@registers.latch_raster_irq! if match && !@raster_match
|
|
484
|
+
@raster_match = match
|
|
485
|
+
end
|
|
175
486
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
487
|
+
# A collision register carries the pixels drawn up to the cycle before
|
|
488
|
+
# the read — the VIC runs ahead of the CPU within a machine cycle, so
|
|
489
|
+
# the column the read lands on has not latched yet — and the reset the
|
|
490
|
+
# read asserts outlives it, swallowing the pixels drawn under it.
|
|
491
|
+
def read_collision(reg)
|
|
492
|
+
beam_x = @column * 8
|
|
493
|
+
@sprites.collide_upto(beam_x, @sequencer.fg)
|
|
494
|
+
@registers.read(reg).tap { @sprites.clear_collision(reg, beam_x) }
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# A collision raises its $D019 bit as the beam crosses the pixel rather
|
|
498
|
+
# than when the line is folded at its end, so while an enabled
|
|
499
|
+
# collision IRQ is still unlatched the fold keeps up with the beam.
|
|
500
|
+
def watch_collisions
|
|
501
|
+
return if (@registers[0x1a] & ~@registers[0x19]).nobits?(0x06)
|
|
502
|
+
|
|
503
|
+
@sprites.collide_upto(@column * 8, @sequencer.fg)
|
|
179
504
|
end
|
|
180
505
|
|
|
181
506
|
def irq_status
|
|
507
|
+
@sprites.collide_upto(@column * 8, @sequencer.fg)
|
|
182
508
|
(@registers[0x19] & 0x0f) | 0x70 | (interrupted? ? 0x80 : 0)
|
|
183
509
|
end
|
|
184
510
|
|
|
511
|
+
# A c-access that falls between BA and AEC reads a bus the CPU still
|
|
512
|
+
# drives: the video matrix byte comes back as $ff, and the colour
|
|
513
|
+
# nibble is the low nibble of the byte at the halted CPU's PC.
|
|
185
514
|
def fetch_character_data!
|
|
186
|
-
vmli = @
|
|
187
|
-
vc =
|
|
188
|
-
@
|
|
189
|
-
|
|
515
|
+
vmli = @display_state.vmli
|
|
516
|
+
vc = @display_state.vc
|
|
517
|
+
if @display_state.bus_taken?(@column)
|
|
518
|
+
@character_buffer[vmli] = video_matrix(vc)
|
|
519
|
+
@color_buffer[vmli] = vic_bank.peek_color(vc)
|
|
520
|
+
else
|
|
521
|
+
@character_buffer[vmli] = 0xff
|
|
522
|
+
@color_buffer[vmli] = @open_bus ? @open_bus.call & 0x0f : vic_bank.peek_color(vc)
|
|
523
|
+
end
|
|
190
524
|
end
|
|
191
525
|
end
|
|
192
526
|
end
|
data/lib/badline.rb
CHANGED
|
@@ -13,10 +13,14 @@ require "badline/status"
|
|
|
13
13
|
require "badline/keyboard"
|
|
14
14
|
require "badline/joystick"
|
|
15
15
|
require "badline/control_ports"
|
|
16
|
+
require "badline/input"
|
|
16
17
|
require "badline/cycleable"
|
|
18
|
+
require "badline/datasette"
|
|
17
19
|
require "badline/time_of_day"
|
|
18
20
|
require "badline/cia"
|
|
19
21
|
require "badline/sid"
|
|
22
|
+
require "badline/debug_register"
|
|
23
|
+
require "badline/interrupts"
|
|
20
24
|
require "badline/traps"
|
|
21
25
|
require "badline/cpu"
|
|
22
26
|
require "badline/vic"
|
|
@@ -27,3 +31,4 @@ require "badline/cartridge"
|
|
|
27
31
|
require "badline/kernal_trap"
|
|
28
32
|
require "badline/chrout_trap"
|
|
29
33
|
require "badline/media"
|
|
34
|
+
require "badline/audio"
|
data/vendor/.gitkeep
ADDED
|
File without changes
|