badline 0.6.0 → 0.6.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CLAUDE.md +10 -9
  4. data/README.md +24 -10
  5. data/doc/pinned-behaviour.md +131 -30
  6. data/exe/badline-ruby +20 -24
  7. data/lib/badline/address_bus.rb +10 -1
  8. data/lib/badline/audio/cli.rb +18 -5
  9. data/lib/badline/audio/console.rb +2 -1
  10. data/lib/badline/audio.rb +0 -3
  11. data/lib/badline/computer.rb +6 -3
  12. data/lib/badline/cpu/boundary.rb +54 -0
  13. data/lib/badline/cpu/core.rb +165 -0
  14. data/lib/badline/cpu.rb +3 -214
  15. data/lib/badline/drive1541/cpu.rb +29 -0
  16. data/lib/badline/drive1541.rb +1 -0
  17. data/lib/badline/ffi.rb +157 -0
  18. data/lib/badline/frontend/app.rb +225 -0
  19. data/lib/badline/frontend/audio_sink.rb +72 -0
  20. data/lib/badline/frontend/boot.rb +52 -0
  21. data/lib/badline/frontend/controls.rb +163 -0
  22. data/lib/badline/frontend/drive_led.rb +51 -0
  23. data/lib/badline/frontend/frame_rate.rb +83 -0
  24. data/lib/badline/frontend/gamepads.rb +76 -0
  25. data/lib/badline/frontend/keys.rb +48 -0
  26. data/lib/badline/frontend/pacer.rb +100 -0
  27. data/lib/badline/frontend/pad_port.rb +61 -0
  28. data/lib/badline/frontend/screen.rb +65 -0
  29. data/lib/badline/frontend/screenshot.rb +22 -0
  30. data/lib/badline/frontend/snapshots.rb +60 -0
  31. data/lib/badline/frontend/sound.rb +153 -0
  32. data/lib/badline/frontend/timeline.rb +171 -0
  33. data/lib/badline/frontend.rb +18 -0
  34. data/lib/badline/options/event.rb +94 -0
  35. data/lib/badline/options/help.rb +78 -0
  36. data/lib/badline/options/option.rb +29 -0
  37. data/lib/badline/options/table.rb +62 -0
  38. data/lib/badline/options.rb +181 -145
  39. data/lib/badline/reu/dma/saved_state.rb +50 -0
  40. data/lib/badline/reu/dma.rb +56 -47
  41. data/lib/badline/reu/saved_state.rb +2 -1
  42. data/lib/badline/reu.rb +19 -10
  43. data/lib/badline/sdl.rb +123 -51
  44. data/lib/badline/sid/waveform/noise_writeback.rb +33 -11
  45. data/lib/badline/sid/waveform.rb +2 -3
  46. data/lib/badline/snapshot/state.rb +1 -1
  47. data/lib/badline/version.rb +1 -1
  48. data/lib/badline/vic/bank.rb +21 -0
  49. data/lib/badline/vic.rb +15 -6
  50. metadata +26 -19
  51. data/.claude/hooks/session-start.sh +0 -57
  52. data/.claude/settings.json +0 -14
  53. data/lib/badline/audio/sdl_sink.rb +0 -70
  54. data/lib/badline/audio/stream.rb +0 -123
  55. data/lib/badline/gui/application.rb +0 -243
  56. data/lib/badline/gui/drive_led_pane.rb +0 -29
  57. data/lib/badline/gui/gamepads.rb +0 -104
  58. data/lib/badline/gui/joy_map.rb +0 -27
  59. data/lib/badline/gui/key_map.rb +0 -39
  60. data/lib/badline/gui/palette.rb +0 -35
  61. data/lib/badline/gui/pane.rb +0 -36
  62. data/lib/badline/gui/screen_pane.rb +0 -46
  63. data/lib/badline/gui/snapshots.rb +0 -67
  64. data/lib/badline/gui/window.rb +0 -61
  65. data/lib/badline/gui.rb +0 -14
  66. data/lib/badline/sdl/events.rb +0 -49
  67. data/lib/badline/sdl/functions.rb +0 -74
  68. data/vendor/.gitkeep +0 -0
data/lib/badline/reu.rb CHANGED
@@ -106,9 +106,8 @@ module Badline
106
106
  # From the moment the REC asks for the bus until it hands it back.
107
107
  def dma? = @requested || @running
108
108
 
109
- # Whether the REC held the bus on the last cycle clocked. The CPU keeps
110
- # it while the REC waits to start, and has it back from the cycle the
111
- # REC lets go.
109
+ # Whether the REC ran a transfer on the last cycle clocked, which it
110
+ # goes on doing until the cycle it lets go of the bus.
112
111
  def holds_bus? = @running
113
112
 
114
113
  def reset!
@@ -120,7 +119,7 @@ module Badline
120
119
  @length = @length_start = 0xffff
121
120
  @interrupt_mask = 0x1f
122
121
  @address_control = 0x3f
123
- @armed = @requested = @running = false
122
+ @armed = @requested = @running = @ba_was_low = false
124
123
  interrupt(false)
125
124
  end
126
125
 
@@ -158,16 +157,20 @@ module Badline
158
157
  request_bus
159
158
  end
160
159
 
161
- # Clocks the REC for a cycle, given the VIC's BA line and whether the
162
- # CPU writes on it. A requested transfer starts on the first cycle the
163
- # CPU reads with BA high.
164
- def dma_cycle!(ba_low, cpu_writing)
160
+ # Clocks the REC for a cycle, given the VIC's BA line. A requested
161
+ # transfer starts on the first cycle with BA high as the REC sees it,
162
+ # even one the CPU writes on. Pinned by REU/rmw-trigger. A fetch
163
+ # requested on the cycle before BA falls writes its first byte on the
164
+ # first BA-low cycle as well, and again when it starts. Pinned by
165
+ # REU/badoublewrite.
166
+ def dma_cycle!(ba_low)
167
+ late = ba_low && @vic.reu_ba_late?
165
168
  if @requested
166
- return if cpu_writing || ba_low
169
+ return hold_off if ba_low && !late
167
170
 
168
171
  start_transfer
169
172
  end
170
- @dma.cycle!(ba_low)
173
+ @dma.cycle!(ba_low, late, ba_low && @vic.reu_ba_handed_on?)
171
174
  end_transfer unless @dma.holds_bus?
172
175
  end
173
176
 
@@ -239,9 +242,15 @@ module Badline
239
242
 
240
243
  def request_bus
241
244
  @requested = true
245
+ @ba_was_low = @vic.ba_low?
242
246
  @on_dma&.call
243
247
  end
244
248
 
249
+ def hold_off
250
+ @dma.write_ahead(@command & TRANSFER_TYPE, @c64, @expansion | (@bank << 16)) unless @ba_was_low
251
+ @ba_was_low = true
252
+ end
253
+
245
254
  def start_transfer
246
255
  @requested = false
247
256
  @running = true
data/lib/badline/sdl.rb CHANGED
@@ -1,67 +1,139 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fiddle"
4
-
5
3
  module Badline
6
- # The libSDL2 calls the window, the gamepads and the audio sink make,
7
- # bound through Fiddle, so nothing has to compile against SDL at install
8
- # time. Only the library itself has to be present.
9
- #
10
- # Each function is a constant named after its C name without the prefix:
11
- # SDL::RenderClear.call(renderer) calls SDL_RenderClear. They're built
12
- # with need_gvl, which skips releasing Ruby's lock around the call: none
13
- # of these block for long, and releasing it costs more than most of them.
4
+ # libSDL2 through Spinel's FFI DSL, the one binding both builds use. The
5
+ # native build compiles the declarations, and finds the library's
6
+ # directory (see NativeBuild.sdl2_flags). badline-ruby loads badline/ffi
7
+ # first, which implements them over Fiddle, so only the library itself
8
+ # has to be present.
14
9
  module SDL
15
- class Error < StandardError; end
10
+ ffi_lib "SDL2"
16
11
 
17
- # Where package managers put the shared library. dlopen searches the
18
- # system paths for the bare names, but not Homebrew's.
19
- LIBRARIES = %w[
20
- libSDL2-2.0.so.0 libSDL2.so
21
- libSDL2-2.0.0.dylib /opt/homebrew/lib/libSDL2-2.0.0.dylib /usr/local/lib/libSDL2-2.0.0.dylib
22
- SDL2.dll
23
- ].freeze
12
+ ffi_func :SDL_Init, [:uint32], :int
13
+ ffi_func :SDL_Quit, [], :void
14
+ ffi_func :SDL_GetError, [], :str
15
+ ffi_func :SDL_SetHint, %i[str str], :int
16
+ ffi_func :SDL_CreateWindow, %i[str int int int int uint32], :ptr
17
+ ffi_func :SDL_SetWindowTitle, %i[ptr str], :void
18
+ ffi_func :SDL_DestroyWindow, [:ptr], :void
19
+ ffi_func :SDL_CreateRenderer, %i[ptr int uint32], :ptr
20
+ ffi_func :SDL_GetWindowDisplayMode, %i[ptr ptr], :int
21
+ ffi_func :SDL_DestroyRenderer, [:ptr], :void
22
+ ffi_func :SDL_RenderSetLogicalSize, %i[ptr int int], :int
23
+ ffi_func :SDL_CreateTexture, %i[ptr uint32 int int int], :ptr
24
+ ffi_func :SDL_DestroyTexture, [:ptr], :void
25
+ ffi_func :SDL_UpdateTexture, %i[ptr ptr int_array int], :int
26
+ ffi_func :SDL_RenderClear, [:ptr], :int
27
+ ffi_func :SDL_SetRenderDrawColor, %i[ptr int int int int], :int
28
+ ffi_func :SDL_RenderFillRect, %i[ptr ptr], :int
29
+ ffi_func :SDL_RenderCopy, %i[ptr ptr ptr ptr], :int
30
+ ffi_func :SDL_RenderPresent, [:ptr], :void
31
+ ffi_func :SDL_PollEvent, [:ptr], :int
32
+ ffi_func :SDL_SetRelativeMouseMode, [:int], :int
33
+ ffi_func :SDL_Delay, [:uint32], :void
34
+ ffi_func :SDL_GetRendererOutputSize, %i[ptr ptr ptr], :int
35
+ ffi_func :SDL_RenderReadPixels, %i[ptr ptr uint32 ptr int], :int
36
+ ffi_func :SDL_CreateRGBSurfaceWithFormatFrom, %i[ptr int int int int uint32], :ptr
37
+ ffi_func :SDL_FreeSurface, [:ptr], :void
38
+ ffi_func :SDL_RWFromFile, %i[str str], :ptr
39
+ ffi_func :SDL_SaveBMP_RW, %i[ptr ptr int], :int
40
+ ffi_func :SDL_InitSubSystem, [:uint32], :int
41
+ ffi_func :SDL_QuitSubSystem, [:uint32], :void
42
+ ffi_func :SDL_OpenAudioDevice, %i[ptr int ptr ptr int], :uint32
43
+ ffi_func :SDL_CloseAudioDevice, [:uint32], :void
44
+ ffi_func :SDL_PauseAudioDevice, %i[uint32 int], :void
45
+ ffi_func :SDL_QueueAudio, %i[uint32 buffer_in uint32], :int
46
+ ffi_func :SDL_GetQueuedAudioSize, [:uint32], :uint32
47
+ ffi_func :SDL_NumJoysticks, [], :int
48
+ ffi_func :SDL_IsGameController, [:int], :int
49
+ ffi_func :SDL_GameControllerOpen, [:int], :ptr
50
+ ffi_func :SDL_GameControllerClose, [:ptr], :void
51
+ ffi_func :SDL_GameControllerName, [:ptr], :str
52
+ ffi_func :SDL_GameControllerGetButton, %i[ptr int], :uint8
53
+ ffi_func :SDL_GameControllerGetAxis, %i[ptr int], :int16
54
+ ffi_func :SDL_ClearQueuedAudio, [:uint32], :void
55
+ ffi_func :SDL_GetCurrentDisplayMode, %i[int ptr], :int
56
+ ffi_func :SDL_CreateRGBSurfaceFrom, %i[ptr int int int int uint32 uint32 uint32 uint32], :ptr
57
+ ffi_func :SDL_CreateTextureFromSurface, %i[ptr ptr], :ptr
58
+ ffi_func :SDL_PushEvent, [:ptr], :int
59
+ ffi_func :SDL_GetKeyName, [:int], :str
24
60
 
25
- def self.open_library
26
- LIBRARIES.each do |name|
27
- return Fiddle.dlopen(name)
28
- rescue Fiddle::DLError
29
- next
30
- end
31
- raise Error, "can't find libSDL2 (tried #{LIBRARIES.join(', ')})"
32
- end
61
+ ffi_buffer :event, 56
62
+ ffi_read_u32 :event_type, 0
63
+ ffi_read_u8 :event_repeat, 13
64
+ ffi_read_i32 :event_scancode, 16
65
+ ffi_read_i32 :event_sym, 20
66
+ ffi_read_u16 :event_mod, 24
67
+ # SDL_MouseMotionEvent's xrel and yrel, and SDL_MouseButtonEvent's
68
+ # button.
69
+ ffi_read_i32 :event_xrel, 28
70
+ ffi_read_i32 :event_yrel, 32
71
+ ffi_read_u8 :event_button, 16
33
72
 
34
- LIBRARY = open_library
73
+ ffi_buffer :rect, 16
74
+ ffi_write_i32 :rect_x, 0
75
+ ffi_write_i32 :rect_y, 4
76
+ ffi_write_i32 :rect_w, 8
77
+ ffi_write_i32 :rect_h, 12
78
+ # Where the drive LED goes.
79
+ ffi_buffer :led_rect, 16
35
80
 
36
- VOID = Fiddle::TYPE_VOID
37
- INT = Fiddle::TYPE_INT
38
- UINT8 = Fiddle::TYPE_UINT8_T
39
- INT16 = Fiddle::TYPE_INT16_T
40
- UINT32 = Fiddle::TYPE_UINT32_T
41
- POINTER = Fiddle::TYPE_VOIDP
42
- STRING = Fiddle::TYPE_CONST_STRING
81
+ # SDL_DisplayMode: Uint32 format; int w, h, refresh_rate; then a pointer.
82
+ ffi_buffer :display_mode, 24
83
+ ffi_read_i32 :mode_refresh, 12
43
84
 
44
- def self.function(name, arguments, result)
45
- Fiddle::Function.new(LIBRARY[name], arguments, result, name:, need_gvl: true)
46
- end
85
+ ffi_buffer :output_w, 4
86
+ ffi_buffer :output_h, 4
87
+ ffi_read_i32 :read_i32, 0
47
88
 
48
- def self.error = GetError.call
89
+ # SDL_AudioSpec: int freq; Uint16 format; Uint8 channels, silence;
90
+ # Uint16 samples, padding; Uint32 size; then two pointers.
91
+ ffi_buffer :wanted, 32
92
+ ffi_buffer :obtained, 32
93
+ ffi_write_i32 :spec_freq, 0
94
+ ffi_write_u16 :spec_format, 4
95
+ ffi_write_u8 :spec_channels, 6
96
+ ffi_write_u16 :spec_samples, 8
49
97
 
50
- # A negative result from SDL is an error, and SDL_GetError says which.
51
- def self.check(result)
52
- raise Error, error if result.negative?
98
+ ffi_const :INIT_AUDIO, 0x10
99
+ ffi_const :INIT_VIDEO, 0x20
100
+ ffi_const :INIT_GAMECONTROLLER, 0x2000
101
+ ffi_const :INIT_EVENTS, 0x4000
102
+ ffi_const :WINDOWPOS_CENTERED, 0x2fff0000
103
+ ffi_const :WINDOW_RESIZABLE, 0x20
104
+ ffi_const :RENDERER_ACCELERATED, 0x02
105
+ ffi_const :RENDERER_PRESENTVSYNC, 0x04
106
+ ffi_const :PIXELFORMAT_RGB888, 0x16161804
107
+ ffi_const :TEXTUREACCESS_STREAMING, 1
108
+ ffi_const :QUIT, 0x100
109
+ ffi_const :KEYDOWN, 0x300
110
+ ffi_const :KEYUP, 0x301
111
+ ffi_const :MOUSEMOTION, 0x400
112
+ ffi_const :MOUSEBUTTONDOWN, 0x401
113
+ ffi_const :MOUSEBUTTONUP, 0x402
114
+ ffi_const :KMOD_SHIFT, 0x0003
115
+ ffi_const :CONTROLLERDEVICEADDED, 0x653
116
+ ffi_const :CONTROLLERDEVICEREMOVED, 0x654
117
+ ffi_const :CONTROLLERDEVICEREMAPPED, 0x655
118
+ ffi_const :AUDIO_S16LSB, 0x8010
119
+ ffi_const :ALLOW_FREQUENCY_CHANGE, 0x01
120
+ ffi_const :KEY_TAB, 0x09
121
+ ffi_const :KEY_F10, 0x4000_0043
122
+ ffi_const :KEY_F11, 0x4000_0044
123
+ ffi_const :KEY_F12, 0x4000_0045
124
+ end
53
125
 
54
- result
55
- end
126
+ # The few C library calls the native front end makes.
127
+ module LibC
128
+ ffi_func :malloc, [:size_t], :ptr
129
+ ffi_func :free, [:ptr], :void
130
+ ffi_func :poll, %i[ptr size_t int], :int
56
131
 
57
- # So is a null handle.
58
- def self.check_pointer(pointer)
59
- raise Error, error if pointer.null?
132
+ ffi_const :POLLIN, 0x01
60
133
 
61
- pointer
62
- end
134
+ # struct pollfd: int fd; short events, revents.
135
+ ffi_buffer :pollfd, 8
136
+ ffi_write_i32 :pollfd_fd, 0
137
+ ffi_write_i16 :pollfd_events, 4
63
138
  end
64
139
  end
65
-
66
- require "badline/sdl/functions"
67
- require "badline/sdl/events"
@@ -20,11 +20,12 @@ module Badline
20
20
  # so they lose a different number of bits. The 8580's pair is the one
21
21
  # that reproduces SID/wf12nsr's pulse+noise row (it reads $f8, and the
22
22
  # writeback leaves $fc behind), and SID/wb_testsuite's C->9 and C->E
23
- # rows need that same $fc written at release. On the 6581 the read is
24
- # the $fc of SID/wf12nsr's readme (VICE bug #1037), and the writeback
25
- # pulls nothing: wb_testsuite's 8/9/A/B->C rows run pulse+noise and
26
- # leave the register alone.
27
- PULSE_NOISE_READ = { mos6581: 0xfc0, mos8580: 0xf80 }.freeze
23
+ # rows need that same $fc written at release. The 6581 reads $00 of a
24
+ # full register (SID/wf12nsr's pulse+noise row) and pulls nothing
25
+ # while the register holds: wb_testsuite's 8->C and C->C rows run
26
+ # pulse+noise and leave the register alone. Its lines only land
27
+ # part way through a shift (#write_back_shift).
28
+ PULSE_NOISE_READ = { mos6581: 0x000, mos8580: 0xf80 }.freeze
28
29
  PULSE_NOISE_WRITE = { mos6581: 0xff0, mos8580: 0xfc0 }.freeze
29
30
 
30
31
  # What a 6581 writes back when the test bit falls with only pulse+noise
@@ -40,24 +41,45 @@ module Badline
40
41
  # (SID/wf12nsr's noise+triangle and noise+sawtooth rows).
41
42
  def write_back(selected, value)
42
43
  return value & ((value << 1) | (value >> 1)) unless selected == 0xc
43
- return value if value.zero?
44
+ return 0x000 if @pulse.zero?
44
45
 
45
46
  noise & @pulse_noise_write
46
47
  end
47
48
 
49
+ # What the lines write while the register is part way through a
50
+ # shift, where the 6581's pulse+noise writes its output as read.
51
+ def write_back_mid_shift(selected, value)
52
+ return value if pulse_noise_mid_shift?(selected)
53
+
54
+ write_back(selected, value)
55
+ end
56
+
57
+ def pulse_noise_mid_shift?(selected) = @topbit_feedback && selected == 0xc
58
+
48
59
  def write_shift_register(value)
49
60
  NOISE_TAPS.each { |bit, line| @shift_register &= ~bit if value.nobits?(line) }
50
61
  end
51
62
 
52
- # Setting the test bit starts the bleed, and writes the output of a
53
- # noise combination back first, from the accumulator it had. Only
54
- # SID/noiselfsrinit sets the bit onto such a combination from outside
55
- # it: its $f8/$80 pairs go from noise alone to all four.
63
+ # The second phase of a shift writes back by the test bit release
64
+ # rule with the waveform left alone. The 6581's pulse+noise, which
65
+ # that rule leaves out, writes its lines too (SID/wf12nsr's
66
+ # pulse+noise row).
67
+ def write_back_shift
68
+ return unless pulse_noise_mid_shift?(@selected) || release_writes_back?(@selected, @selected)
69
+
70
+ write_shift_register(write_back_mid_shift(@selected, @output))
71
+ end
72
+
73
+ # Setting the test bit starts the bleed, and stalls the register part
74
+ # way through a shift, where a noise combination writes its output
75
+ # back first, from the accumulator it had. SID/noiselfsrinit's
76
+ # $f8/$80 pairs set the bit onto all four from noise alone, and
77
+ # SID/wf12nsr's pulse+noise row sets it onto pulse+noise.
56
78
  def raise_test
57
79
  @shift_register_reset = @shift_register_reset_delay
58
80
  return unless @selected.anybits?(0x8) && combined?(@selected) && @shift_pipeline != 1
59
81
 
60
- write_shift_register(write_back(@selected, shape(@selected)))
82
+ write_shift_register(write_back_mid_shift(@selected, shape(@selected)))
61
83
  end
62
84
 
63
85
  # Releasing the test bit finishes the shift it interrupted: the old
@@ -256,8 +256,7 @@ module Badline
256
256
  # A rise of bit 19 starts a shift that takes two more cycles
257
257
  # (SID/noisewriteback's noise_writeback_test2, after libresidfp's shift
258
258
  # pipeline). The first phase latches the output, and the second writes
259
- # it over the taps, by the test bit release rule with the waveform left
260
- # alone, before the register shifts.
259
+ # it over the taps before the register shifts.
261
260
  def advance
262
261
  previous = @accumulator
263
262
  @accumulator = (previous + @frequency) & 0xffffff
@@ -265,7 +264,7 @@ module Badline
265
264
  if @accumulator & ~previous & 0x080000 != 0
266
265
  @shift_pipeline = 2
267
266
  elsif @shift_pipeline != 0 && (@shift_pipeline -= 1).zero?
268
- write_shift_register(write_back(@selected, @output)) if release_writes_back?(@selected, @selected)
267
+ write_back_shift
269
268
  shift_noise
270
269
  end
271
270
  end
@@ -14,7 +14,7 @@ module Badline
14
14
  # The layout the chips write their fields in. It goes up whenever a
15
15
  # save_state writes something else, so a State in another layout
16
16
  # fails before anything is read into a machine.
17
- SCHEMA = 1
17
+ SCHEMA = 2
18
18
 
19
19
  attr_reader :values, :strings
20
20
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- VERSION = "0.6.0"
4
+ VERSION = "0.6.1"
5
5
  end
@@ -13,9 +13,26 @@ module Badline
13
13
 
14
14
  attr_reader :address_bus
15
15
 
16
+ # The bank lines as #sample_lines last saw them.
17
+ attr_accessor :lines
18
+
16
19
  def initialize(address_bus = nil)
17
20
  addressable_at(0x0000, length: 2**14)
18
21
  @address_bus = address_bus || AddressBus.new
22
+ @lines = 0b11
23
+ @swapped = false
24
+ end
25
+
26
+ # On the 8565, a port A write that swaps the two bank lines, driving
27
+ # one high as the other goes low, shows the VIC both lines low for the
28
+ # cycle after it: every access in that cycle reads bank 3. A swap that
29
+ # the DDR makes, releasing a line to float high, shows nothing.
30
+ # Called once a cycle, ahead of the cycle's accesses.
31
+ def sample_lines
32
+ cia2 = @address_bus.cia2
33
+ lines = cia2.port_a_lines & 0b11
34
+ @swapped = swap?(@lines, lines) && cia2.port_registers[2].allbits?(0b11)
35
+ @lines = lines
19
36
  end
20
37
 
21
38
  def peek(offset)
@@ -66,7 +83,11 @@ module Badline
66
83
  end
67
84
  end
68
85
 
86
+ def swap?(from, to) = (from ^ to) == 0b11 && from.anybits?(0b11) && to.anybits?(0b11)
87
+
69
88
  def bank_switch_register
89
+ return 0 if @swapped
90
+
70
91
  @address_bus.cia2.port_a_lines & 0b11
71
92
  end
72
93
  end
data/lib/badline/vic.rb CHANGED
@@ -86,6 +86,7 @@ module Badline
86
86
  @grey_dots = model == :mos8565
87
87
  @delayed_fetch = model == :mos8565
88
88
  @dma_delay_idle = model == :mos6569
89
+ @bank_swaps = model == :mos8565
89
90
  @address_bus = address_bus || AddressBus.new
90
91
  @vic_bank = VIC::Bank.new(@address_bus)
91
92
  @debug = debug
@@ -153,6 +154,7 @@ module Badline
153
154
  out.int(@column).int(@rasterline).int(@cycles).boolean(@pending_write)
154
155
  out.int(@g_tick).ints(@g_kind).int(@g_kept_char).int(@g_kept_color).boolean(@g_display)
155
156
  out.int(@fetch_d011).boolean(@lp_triggered).boolean(@lp_low).boolean(@raster_match)
157
+ out.int(@vic_bank.lines)
156
158
  out.blob(@character_buffer).blob(@color_buffer).booleans(@sprite_ba)
157
159
  @lines.each { |line| out.blob(line) }
158
160
  @registers.save_state(out)
@@ -260,14 +262,19 @@ module Badline
260
262
  @column > DisplayState::DMA_FIRST && @column <= DisplayState::DMA_LAST + 1
261
263
  end
262
264
 
263
- # BA as an REU's DMA sees it. On the line whose raster matches sprite
264
- # 0's Y, where the sprite's DMA starts, the REU doesn't see BA fall on
265
- # the first cycle of the sprite's window. Pinned by
265
+ # Whether this is the first cycle of sprite 0's BA window on the line
266
+ # whose raster matches its Y, where the sprite's DMA starts. An REU
267
+ # that moved a byte on the cycle before doesn't see BA fall here. Pinned by
266
268
  # REU/bonzai/spritetiming.
267
- def reu_ba_low?
268
- return false if @column == SPRITE_BA_FIRST + @sprite_shift && sprite_zero_starting?
269
+ def reu_ba_late?
270
+ @column == SPRITE_BA_FIRST + @sprite_shift && sprite_zero_starting?
271
+ end
269
272
 
270
- ba_low?
273
+ # Whether BA passes straight from a bad line's window to sprite 0's on
274
+ # this cycle, the first of sprite 0's. An REU sees the bad line's DMA
275
+ # end here. Pinned by REU/reutiming2/e4-m2 and e6-m2.
276
+ def reu_ba_handed_on?
277
+ @column == DisplayState::DMA_LAST + 2 && @sprite_ba[@column] && @display_state.bad_line_condition?
271
278
  end
272
279
 
273
280
  # Light pen input level (CIA1 PB4). A falling edge triggers the latch.
@@ -372,6 +379,7 @@ module Badline
372
379
  @lp_triggered = input.boolean?
373
380
  @lp_low = input.boolean?
374
381
  @raster_match = input.boolean?
382
+ @vic_bank.lines = input.int
375
383
  end
376
384
 
377
385
  def load_buffers(input)
@@ -536,6 +544,7 @@ module Badline
536
544
  # on zero data with the screen byte and colour the last g-access
537
545
  # latched, which an idle g-access clears.
538
546
  def draw!
547
+ @vic_bank.sample_lines if @bank_swaps
539
548
  slot = @g_tick = (@g_tick + 1) & 3
540
549
  display_state = @display_state
541
550
  access = display_state.graphics_column?(@column) && !@sequencer.vertical_closed?
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.6.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inge Jørgensen
@@ -34,8 +34,6 @@ executables:
34
34
  extensions: []
35
35
  extra_rdoc_files: []
36
36
  files:
37
- - ".claude/hooks/session-start.sh"
38
- - ".claude/settings.json"
39
37
  - CHANGELOG.md
40
38
  - CLAUDE.md
41
39
  - CODE_OF_CONDUCT.md
@@ -60,8 +58,6 @@ files:
60
58
  - lib/badline/audio/pcm_writer.rb
61
59
  - lib/badline/audio/playback.rb
62
60
  - lib/badline/audio/renderer.rb
63
- - lib/badline/audio/sdl_sink.rb
64
- - lib/badline/audio/stream.rb
65
61
  - lib/badline/audio/terminal.rb
66
62
  - lib/badline/audio/wav.rb
67
63
  - lib/badline/cartridge.rb
@@ -106,6 +102,8 @@ files:
106
102
  - lib/badline/control_ports.rb
107
103
  - lib/badline/cpu.rb
108
104
  - lib/badline/cpu/addressing.rb
105
+ - lib/badline/cpu/boundary.rb
106
+ - lib/badline/cpu/core.rb
109
107
  - lib/badline/cpu/microcode.rb
110
108
  - lib/badline/cpu/operations.rb
111
109
  - lib/badline/cpu/saved_state.rb
@@ -115,6 +113,7 @@ files:
115
113
  - lib/badline/debug_register.rb
116
114
  - lib/badline/drive1541.rb
117
115
  - lib/badline/drive1541/bus.rb
116
+ - lib/badline/drive1541/cpu.rb
118
117
  - lib/badline/drive1541/disk.rb
119
118
  - lib/badline/drive1541/disk_state.rb
120
119
  - lib/badline/drive1541/disk_via.rb
@@ -128,17 +127,23 @@ files:
128
127
  - lib/badline/drive1541/serial_port.rb
129
128
  - lib/badline/drive1541/sleep.rb
130
129
  - lib/badline/drive1541/track.rb
131
- - lib/badline/gui.rb
132
- - lib/badline/gui/application.rb
133
- - lib/badline/gui/drive_led_pane.rb
134
- - lib/badline/gui/gamepads.rb
135
- - lib/badline/gui/joy_map.rb
136
- - lib/badline/gui/key_map.rb
137
- - lib/badline/gui/palette.rb
138
- - lib/badline/gui/pane.rb
139
- - lib/badline/gui/screen_pane.rb
140
- - lib/badline/gui/snapshots.rb
141
- - lib/badline/gui/window.rb
130
+ - lib/badline/ffi.rb
131
+ - lib/badline/frontend.rb
132
+ - lib/badline/frontend/app.rb
133
+ - lib/badline/frontend/audio_sink.rb
134
+ - lib/badline/frontend/boot.rb
135
+ - lib/badline/frontend/controls.rb
136
+ - lib/badline/frontend/drive_led.rb
137
+ - lib/badline/frontend/frame_rate.rb
138
+ - lib/badline/frontend/gamepads.rb
139
+ - lib/badline/frontend/keys.rb
140
+ - lib/badline/frontend/pacer.rb
141
+ - lib/badline/frontend/pad_port.rb
142
+ - lib/badline/frontend/screen.rb
143
+ - lib/badline/frontend/screenshot.rb
144
+ - lib/badline/frontend/snapshots.rb
145
+ - lib/badline/frontend/sound.rb
146
+ - lib/badline/frontend/timeline.rb
142
147
  - lib/badline/iec_bus.rb
143
148
  - lib/badline/input.rb
144
149
  - lib/badline/input/mouse1351.rb
@@ -177,6 +182,10 @@ files:
177
182
  - lib/badline/media/true_drive.rb
178
183
  - lib/badline/memory.rb
179
184
  - lib/badline/options.rb
185
+ - lib/badline/options/event.rb
186
+ - lib/badline/options/help.rb
187
+ - lib/badline/options/option.rb
188
+ - lib/badline/options/table.rb
180
189
  - lib/badline/ram_expansion.rb
181
190
  - lib/badline/ram_expansion/banking.rb
182
191
  - lib/badline/ram_expansion/plus256k.rb
@@ -185,6 +194,7 @@ files:
185
194
  - lib/badline/region.rb
186
195
  - lib/badline/reu.rb
187
196
  - lib/badline/reu/dma.rb
197
+ - lib/badline/reu/dma/saved_state.rb
188
198
  - lib/badline/reu/ram.rb
189
199
  - lib/badline/reu/saved_state.rb
190
200
  - lib/badline/reu/trigger.rb
@@ -198,8 +208,6 @@ files:
198
208
  - lib/badline/roms/eapi/eapi-am29f040-14
199
209
  - lib/badline/roms/kernal.rom
200
210
  - lib/badline/sdl.rb
201
- - lib/badline/sdl/events.rb
202
- - lib/badline/sdl/functions.rb
203
211
  - lib/badline/sid.rb
204
212
  - lib/badline/sid/catch_up.rb
205
213
  - lib/badline/sid/decimator.rb
@@ -282,7 +290,6 @@ files:
282
290
  - lib/badline/vic/sprites.rb
283
291
  - lib/badline/vic/sprites_state.rb
284
292
  - packaging/homebrew/badline.rb
285
- - vendor/.gitkeep
286
293
  homepage: https://github.com/elektronaut/badline
287
294
  licenses:
288
295
  - MIT
@@ -1,57 +0,0 @@
1
- #!/bin/bash
2
- # Prepares a Claude Code on the web container to run `bundle exec rubocop`
3
- # and `bundle exec rspec`. The regression suites need vendor/ and aren't set
4
- # up here.
5
- set -euo pipefail
6
-
7
- if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
8
- exit 0
9
- fi
10
-
11
- cd "${CLAUDE_PROJECT_DIR:-$(dirname "$0")/../..}"
12
-
13
- # The SDL specs open libSDL2 through Fiddle, so only the runtime library is
14
- # needed, as in CI.
15
- if ! ldconfig -p | grep 'libSDL2-2\.0\.so\.0' >/dev/null; then
16
- export DEBIAN_FRONTEND=noninteractive
17
- apt-get update -qq
18
- apt-get install -y -qq libsdl2-2.0-0 >/dev/null
19
- fi
20
-
21
- # The image ships Ruby 3.3 and an rbenv. Install the newest Ruby 4.0.x, as
22
- # CI's ruby-version "4.0" picks, from the prebuilt binaries setup-ruby uses,
23
- # unless a 4.0 is installed already.
24
- export RBENV_ROOT=/opt/rbenv
25
- export PATH="$RBENV_ROOT/bin:$RBENV_ROOT/shims:$PATH"
26
- ruby_version=$(rbenv versions --bare | grep -E '^4\.0\.[0-9]+$' | sort -V | tail -1 || true)
27
- if [ -z "$ruby_version" ]; then
28
- ruby_version=$(curl -fsSL https://raw.githubusercontent.com/ruby/setup-ruby/master/ruby-builder-versions.json |
29
- ruby -rjson -e 'puts JSON.parse($stdin.read)["ruby"].grep(/\A4\.0\.\d+\z/).max_by { Gem::Version.new(_1) }')
30
- # The binaries only run from the prefix they were built with, so unpack
31
- # there and link it into rbenv.
32
- . /etc/os-release
33
- prefix="/opt/hostedtoolcache/Ruby/$ruby_version/x64"
34
- mkdir -p "$prefix"
35
- if curl -fsSL "https://github.com/ruby/ruby-builder/releases/download/ruby-$ruby_version/ruby-$ruby_version-ubuntu-$VERSION_ID-x64.tar.gz" |
36
- tar -xz -C "$prefix" --strip-components=1; then
37
- ln -sfn "$prefix" "$RBENV_ROOT/versions/$ruby_version"
38
- else
39
- # No binary for this Ubuntu release: build from source, which takes minutes.
40
- rm -rf "$prefix"
41
- git -C "$RBENV_ROOT/plugins/ruby-build" pull --ff-only -q || true
42
- MAKE_OPTS="-j$(nproc)" rbenv install --skip-existing "$ruby_version"
43
- fi
44
- fi
45
- rbenv global "$ruby_version"
46
- rbenv rehash
47
-
48
- # Put rbenv's shims ahead of /usr/local/bin/ruby for the session.
49
- if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
50
- {
51
- echo "export RBENV_ROOT=$RBENV_ROOT"
52
- echo "export PATH=\"$RBENV_ROOT/bin:$RBENV_ROOT/shims:\$PATH\""
53
- } >> "$CLAUDE_ENV_FILE"
54
- fi
55
-
56
- # Bundler switches to the version Gemfile.lock was bundled with.
57
- bundle install --jobs "$(nproc)"
@@ -1,14 +0,0 @@
1
- {
2
- "hooks": {
3
- "SessionStart": [
4
- {
5
- "hooks": [
6
- {
7
- "type": "command",
8
- "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh"
9
- }
10
- ]
11
- }
12
- ]
13
- }
14
- }