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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/hooks/session-start.sh +57 -0
  3. data/.claude/settings.json +14 -0
  4. data/CHANGELOG.md +107 -0
  5. data/CLAUDE.md +51 -16
  6. data/CONTRIBUTING.md +95 -3
  7. data/README.md +119 -51
  8. data/SECURITY.md +9 -0
  9. data/doc/pinned-behaviour.md +293 -39
  10. data/exe/badline-ruby +59 -0
  11. data/lib/badline/address_bus.rb +28 -14
  12. data/lib/badline/addressable.rb +1 -1
  13. data/lib/badline/audio/aiff.rb +8 -3
  14. data/lib/badline/audio/bare_player.rb +20 -3
  15. data/lib/badline/audio/cli.rb +17 -12
  16. data/lib/badline/audio/console.rb +12 -58
  17. data/lib/badline/audio/machine_player.rb +9 -6
  18. data/lib/badline/audio/playback.rb +3 -0
  19. data/lib/badline/audio/renderer.rb +3 -3
  20. data/lib/badline/audio/sdl_sink.rb +20 -52
  21. data/lib/badline/audio/stream.rb +117 -0
  22. data/lib/badline/audio/terminal.rb +90 -0
  23. data/lib/badline/audio.rb +3 -2
  24. data/lib/badline/cartridge/easy_flash.rb +6 -2
  25. data/lib/badline/cartridge/epyx_fastload.rb +1 -1
  26. data/lib/badline/cartridge/geo_ram.rb +63 -0
  27. data/lib/badline/cartridge/mach5.rb +1 -1
  28. data/lib/badline/cartridge/magic_desk.rb +5 -1
  29. data/lib/badline/cartridge/ocean.rb +5 -2
  30. data/lib/badline/cartridge/rex_utility.rb +1 -1
  31. data/lib/badline/cartridge/rgcd.rb +1 -1
  32. data/lib/badline/cartridge/simons_basic.rb +2 -2
  33. data/lib/badline/cartridge/westermann.rb +2 -2
  34. data/lib/badline/cartridge.rb +9 -0
  35. data/lib/badline/checkpoint.rb +98 -0
  36. data/lib/badline/cia/interrupt_register.rb +34 -10
  37. data/lib/badline/cia.rb +19 -10
  38. data/lib/badline/color_memory.rb +2 -2
  39. data/lib/badline/computer.rb +34 -14
  40. data/lib/badline/cpu.rb +2 -2
  41. data/lib/badline/gui/application.rb +70 -26
  42. data/lib/badline/gui/gamepads.rb +22 -22
  43. data/lib/badline/gui/joy_map.rb +1 -1
  44. data/lib/badline/gui/key_map.rb +6 -1
  45. data/lib/badline/gui/pane.rb +7 -6
  46. data/lib/badline/gui/screen_pane.rb +9 -13
  47. data/lib/badline/gui/window.rb +35 -20
  48. data/lib/badline/gui.rb +1 -1
  49. data/lib/badline/input/mouse1351.rb +21 -6
  50. data/lib/badline/input/paddles.rb +11 -6
  51. data/lib/badline/kernal_trap/channel.rb +102 -7
  52. data/lib/badline/kernal_trap/drive/block_commands.rb +108 -0
  53. data/lib/badline/kernal_trap/drive/channels.rb +53 -0
  54. data/lib/badline/kernal_trap/drive/memory.rb +109 -0
  55. data/lib/badline/kernal_trap/drive/parameters.rb +28 -0
  56. data/lib/badline/kernal_trap/drive/status.rb +3 -0
  57. data/lib/badline/kernal_trap/drive/write_file.rb +41 -0
  58. data/lib/badline/kernal_trap/drive/writes.rb +92 -0
  59. data/lib/badline/kernal_trap/drive.rb +109 -131
  60. data/lib/badline/kernal_trap/file.rb +6 -14
  61. data/lib/badline/kernal_trap/load.rb +6 -0
  62. data/lib/badline/kernal_trap/save.rb +13 -7
  63. data/lib/badline/keyboard.rb +19 -3
  64. data/lib/badline/media.rb +44 -13
  65. data/lib/badline/memory.rb +2 -2
  66. data/lib/badline/options.rb +209 -0
  67. data/lib/badline/ram_expansion/banking.rb +30 -0
  68. data/lib/badline/ram_expansion/plus256k.rb +38 -0
  69. data/lib/badline/ram_expansion/plus60k.rb +31 -0
  70. data/lib/badline/ram_expansion/unexpanded.rb +22 -0
  71. data/lib/badline/ram_expansion.rb +21 -0
  72. data/lib/badline/region.rb +51 -0
  73. data/lib/badline/rom.rb +14 -5
  74. data/lib/badline/sdl/events.rb +47 -0
  75. data/lib/badline/sdl/functions.rb +73 -0
  76. data/lib/badline/sdl.rb +67 -0
  77. data/lib/badline/sid/catch_up.rb +110 -0
  78. data/lib/badline/sid/decimator.rb +6 -1
  79. data/lib/badline/sid/filter.rb +5 -5
  80. data/lib/badline/sid/waveform/fast_forward.rb +10 -10
  81. data/lib/badline/sid/waveform/noise_writeback.rb +32 -14
  82. data/lib/badline/sid/waveform.rb +8 -8
  83. data/lib/badline/sid.rb +19 -93
  84. data/lib/badline/storage/d64_image.rb +10 -0
  85. data/lib/badline/storage/d71_image.rb +12 -0
  86. data/lib/badline/storage/d81_image.rb +9 -0
  87. data/lib/badline/storage/disk_image/bam.rb +111 -0
  88. data/lib/badline/storage/disk_image/directory.rb +79 -0
  89. data/lib/badline/storage/disk_image/writing.rb +110 -0
  90. data/lib/badline/storage/disk_image.rb +49 -4
  91. data/lib/badline/storage/host_directory.rb +17 -4
  92. data/lib/badline/storage/sid_file.rb +36 -2
  93. data/lib/badline/storage.rb +18 -1
  94. data/lib/badline/time_of_day.rb +11 -2
  95. data/lib/badline/version.rb +1 -1
  96. data/lib/badline/via/control_lines.rb +104 -0
  97. data/lib/badline/via/interrupt_register.rb +58 -0
  98. data/lib/badline/via/shift_register.rb +135 -0
  99. data/lib/badline/via/timer1.rb +82 -0
  100. data/lib/badline/via/timer2.rb +84 -0
  101. data/lib/badline/via.rb +262 -0
  102. data/lib/badline/vic/bank.rb +4 -3
  103. data/lib/badline/vic/color_patches.rb +6 -3
  104. data/lib/badline/vic/display_state.rb +4 -0
  105. data/lib/badline/vic/graphics_shifter.rb +49 -8
  106. data/lib/badline/vic/registers.rb +19 -27
  107. data/lib/badline/vic/sequencer.rb +18 -18
  108. data/lib/badline/vic/sequencer_output.rb +4 -4
  109. data/lib/badline/vic/sprite/internal_bus.rb +4 -0
  110. data/lib/badline/vic/sprite/shifter.rb +25 -3
  111. data/lib/badline/vic/sprite.rb +5 -4
  112. data/lib/badline/vic/sprites.rb +8 -3
  113. data/lib/badline/vic.rb +98 -46
  114. data/lib/badline.rb +5 -0
  115. data/packaging/homebrew/badline.rb +34 -0
  116. metadata +37 -20
  117. data/exe/badline +0 -75
  118. data/exe/badline-sid +0 -34
  119. data/lib/badline/audio/options.rb +0 -137
data/lib/badline/vic.rb CHANGED
@@ -11,14 +11,22 @@ module Badline
11
11
  include Addressable
12
12
  include IntegerHelper
13
13
 
14
+ # The 6569 of the breadbin C64, and the HMOS 8565 of the C64C.
15
+ MODELS = %i[mos6569 mos8565].freeze
16
+
14
17
  attr_reader :address_bus, :display, :width, :height, :vic_bank, :column,
15
- :rasterline, :dirty_lines
18
+ :rasterline, :dirty_lines, :model, :region
16
19
 
17
20
  # Returns the byte the CPU's halted read would see, for the c-accesses
18
21
  # that run before AEC. Without it they read colour RAM.
19
22
  attr_writer :open_bus
20
23
 
21
24
  LIGHTPEN_IRQ = 0x08 # $D019 latch bit
25
+ COLOR_REGISTERS = 0x20..0x2e
26
+
27
+ # What the 8565 shows on the pixel before a color register write takes
28
+ # hold, where the 6569 shows the old color: the grey dot.
29
+ GREY_DOT = 0x0f
22
30
 
23
31
  # A g-access reaches the pixel output this many columns after it runs.
24
32
  GRAPHICS_DELAY = 2
@@ -28,17 +36,19 @@ module Badline
28
36
  G_IDLE = 1
29
37
  G_DISPLAY = 2
30
38
 
39
+ # The idle g-access in the column a mid-row bad line opens display
40
+ # state reads here on a 6569.
41
+ DMA_DELAY_IDLE_ADDRESS = 0x38ff
42
+
31
43
  # The $d011 mode bits a g-access still sees for a cycle after they fall:
32
44
  # BMM always, and ECM too when the access it leaves read the character
33
45
  # ROM.
34
46
  FETCH_HOLD = 0x20
35
47
  FETCH_HOLD_ROM = 0x60
36
48
 
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
49
+ # The columns carrying a hook, besides the line's last column, which
50
+ # starts the vertical border for the line after it.
51
+ HOOKS = [14, 15, 53, 54, 57].freeze
42
52
 
43
53
  # BA falls three columns ahead of each sprite's pair of s-accesses, and
44
54
  # the five-column windows step two columns apart from sprite 0 at column
@@ -46,19 +56,29 @@ module Badline
46
56
  # split: the tail columns fall on the line whose cycle 55/56 compare
47
57
  # started the fetch, the head columns on the line after it.
48
58
  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
51
59
 
52
- def initialize(address_bus = nil, debug: false)
60
+ def initialize(address_bus = nil, debug: false, model: :mos6569, region: Region::PAL)
61
+ raise ArgumentError, "unknown VIC-II model #{model}" unless MODELS.include?(model)
62
+
53
63
  addressable_at(0xd000, length: 2**10)
64
+ @model = model
65
+ @region = region
66
+ @lightpen_extra = model == :mos8565 ? 1 : 2
67
+ @grey_dots = model == :mos8565
68
+ @delayed_fetch = model == :mos8565
69
+ @dma_delay_idle = model == :mos6569
54
70
  @address_bus = address_bus || AddressBus.new
55
71
  @vic_bank = VIC::Bank.new(@address_bus)
56
72
  @debug = debug
57
73
 
58
- @width = 504
59
- @height = 312
60
- @columns_per_line = @width / 8
74
+ @columns_per_line = region.cycles_per_line
75
+ @width = @columns_per_line * 8
76
+ @height = region.lines_per_frame
77
+ @last_column = @columns_per_line - 1
61
78
  @last_line = @height - 1
79
+ layout_columns
80
+ @blank_columns = Array.new(@columns_per_line) { |column| Region.blanked?(column, region.hblank) }.freeze
81
+ @blank_lines = Array.new(@height) { |line| Region.blanked?(line, region.vblank) }.freeze
62
82
  @display = Array.new(@width * @height, 0)
63
83
  @lines = Array.new(@height) { Array.new(@width, 0) }
64
84
  @dirty_lines = Array.new(@height, true)
@@ -75,9 +95,9 @@ module Badline
75
95
  @registers = VIC::Registers.new
76
96
  @register_bytes = @registers.bytes
77
97
  @display_state = VIC::DisplayState.new(@registers)
78
- @sequencer = VIC::Sequencer.new(@width, @registers, @vic_bank)
98
+ @sequencer = VIC::Sequencer.new(@width, @registers, @vic_bank, model: @model, region: @region)
79
99
  @sequencer.render = @render
80
- @sprites = VIC::Sprites.new(@registers, @vic_bank, @width)
100
+ @sprites = VIC::Sprites.new(@registers, @vic_bank, @width, model: @model)
81
101
  @display.fill(0)
82
102
  @lines.each { |line| line.fill(0) }
83
103
  @dirty_lines.fill(true)
@@ -108,11 +128,11 @@ module Badline
108
128
  # The g-access runs in the first half of the cycle, ahead of the bad
109
129
  # line compare, and the c-access in the second half, after it.
110
130
  draw!
111
- @display_state.cycle(@rasterline, @column)
131
+ dma_delay_idle_access if @display_state.cycle(@rasterline, @column)
112
132
 
113
133
  fetch_character_data! if dma_active?
114
134
 
115
- column_hooks if HOOK_COLUMNS[@column]
135
+ column_hooks if @hook_columns[@column]
116
136
 
117
137
  @column += 1
118
138
  if @column == @columns_per_line
@@ -140,7 +160,7 @@ module Badline
140
160
  when 53 then check_sprite_dma
141
161
  when 54 then check_dma_and_toggle_expansion
142
162
  when 57 then @sprites.check_display(@rasterline)
143
- when 62 then @sequencer.start_vertical_border(@rasterline == @last_line ? 0 : @rasterline + 1)
163
+ when @last_column then @sequencer.start_vertical_border(@rasterline == @last_line ? 0 : @rasterline + 1)
144
164
  end
145
165
  end
146
166
 
@@ -151,7 +171,7 @@ module Badline
151
171
  end
152
172
 
153
173
  def peek(addr)
154
- i = index(addr) % (2**6)
174
+ i = offset_of(addr) % (2**6)
155
175
  case i
156
176
  when 0x11 then (@registers[0x11] & 0x7f) | ((raster_counter & 0x100) >> 1)
157
177
  when 0x12 then raster_counter & 0xff
@@ -161,8 +181,11 @@ module Badline
161
181
  end.tap { |value| @sprites.bus_data(@column, value) }
162
182
  end
163
183
 
184
+ # The register file as stored, read without a bus access's side effects.
185
+ def register_file = Array.new(0x40) { |reg| @registers[reg] }
186
+
164
187
  def poke(addr, value)
165
- reg = index(addr) % (2**6)
188
+ reg = offset_of(addr) % (2**6)
166
189
  @sprites.bus_data(@column, value)
167
190
  log_register_change(reg, value)
168
191
  @registers.write(reg, value)
@@ -194,9 +217,10 @@ module Badline
194
217
  end
195
218
 
196
219
  # 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.
220
+ # raises the LP IRQ. The latch happens one cycle after the edge, with
221
+ # the model's extra half-pixels, two on the 6569 and one on the 8565; a
222
+ # trigger on the last line is consumed without latching unless it lands
223
+ # on the line's first cycle.
200
224
  def trigger_lightpen
201
225
  return if @lp_triggered
202
226
 
@@ -210,7 +234,7 @@ module Badline
210
234
  return if line == @last_line && column.positive?
211
235
 
212
236
  vic_x = ((column * 8) - Sprite::X_OFFSET) % @width
213
- latch_lightpen((vic_x >> 1) + 2, line)
237
+ latch_lightpen((vic_x >> 1) + @lightpen_extra, line)
214
238
  end
215
239
 
216
240
  # The byte the VIC fetched in the phi1 half of the cycle the CPU is
@@ -221,17 +245,9 @@ module Badline
221
245
  vic_bank.peek(phi1_address)
222
246
  end
223
247
 
224
- def hblank?
225
- @column < 10 || @column > 60
226
- end
227
-
228
- def vblank?
229
- @rasterline < 16 || @rasterline > 299
230
- end
231
-
232
- def blanking?
233
- @rasterline < 16 || @rasterline > 299 || @column < 10 || @column > 60
234
- end
248
+ def hblank? = @blank_columns[@column]
249
+ def vblank? = @blank_lines[@rasterline]
250
+ def blanking? = @blank_lines[@rasterline] || @blank_columns[@column]
235
251
 
236
252
  def render? = @render
237
253
 
@@ -262,19 +278,30 @@ module Badline
262
278
  # Mid-line writes to the color and sprite registers are logged against
263
279
  # the cycle after the write (the CPU runs after the VIC within a machine
264
280
  # cycle, so @column already points there); each register adds its own
265
- # pixel delay on top.
281
+ # pixel delay on top. On the 8565 a color register write that leaves
282
+ # the value as it was still shows its grey dot.
266
283
  def log_register_change(reg, value)
267
284
  old = @registers[reg]
268
- return if old == value
285
+ return if old == value && !grey_dot?(reg)
269
286
 
270
287
  if (0x20..0x24).cover?(reg)
271
288
  @sequencer.colors_changed! unless reg == 0x20
272
289
  @sequencer.color_patches.log(reg, old, value, @column * 8) if @render && !blanking?
273
290
  else
274
- @sprites.log_change(reg, old, value, @column * 8)
291
+ log_sprite_change(reg, old, value)
275
292
  end
276
293
  end
277
294
 
295
+ def grey_dot?(reg) = @grey_dots && COLOR_REGISTERS.cover?(reg)
296
+
297
+ # A sprite color shows its grey dot as a write of light grey on the
298
+ # pixel before the new color.
299
+ def log_sprite_change(reg, old, value)
300
+ beam_x = @column * 8
301
+ @sprites.log_change(reg, old, GREY_DOT, beam_x - 1) if grey_dot?(reg)
302
+ @sprites.log_change(reg, old, value, beam_x)
303
+ end
304
+
278
305
  # Bauer cycles 1-10 and 58-63 are sprite p- and s-accesses, 11-15
279
306
  # refresh, 16-55 g-accesses and 56-57 idle. @column has already
280
307
  # advanced, so it is the Bauer cycle less one.
@@ -308,11 +335,12 @@ module Badline
308
335
  graphics_address(@registers[0x11], @display_state.vmli - 1, (@display_state.vc - 1) & 0x3ff)
309
336
  end
310
337
 
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.
338
+ # A $d011/$d012 write is compared in the next column. A write in the
339
+ # line's second last column is left to its last, which compares the
340
+ # next line.
313
341
  def compare_raster_writes(reg)
314
342
  return unless (0x11..0x12).cover?(reg)
315
- return if @column == @columns_per_line - 1
343
+ return if @column == @last_column
316
344
 
317
345
  check_raster_irq!
318
346
  @sequencer.compare_vertical_border(@rasterline) if reg == 0x11
@@ -355,11 +383,21 @@ module Badline
355
383
  8.times do |n|
356
384
  next unless @sprites[n].displaying?
357
385
 
358
- SPRITE_BA_TAIL[n].each { |c| @sprite_ba[c] = true }
359
- SPRITE_BA_HEAD[n].each { |c| @sprite_ba[c] = true }
386
+ @sprite_ba_tail[n].each { |c| @sprite_ba[c] = true }
387
+ @sprite_ba_head[n].each { |c| @sprite_ba[c] = true }
360
388
  end
361
389
  end
362
390
 
391
+ # 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. The marks let an ordinary
393
+ # column cost one array read instead of the dispatch.
394
+ def layout_columns
395
+ last = @last_column
396
+ @sprite_ba_tail = SPRITE_BA_WINDOWS.map { |w| w.select { |c| c <= last } }.freeze
397
+ @sprite_ba_head = SPRITE_BA_WINDOWS.map { |w| w.filter_map { |c| c - last - 1 if c > last } }.freeze
398
+ @hook_columns = Array.new(@columns_per_line) { |c| c == last || HOOKS.include?(c) }.freeze
399
+ end
400
+
363
401
  # Runs this column's g-access and draws the one from GRAPHICS_DELAY
364
402
  # columns earlier. The access reads its byte now, through the mode,
365
403
  # $d018 and bank of this cycle. A column with no g-access, or one made
@@ -400,15 +438,29 @@ module Badline
400
438
  @g_color[slot] = @g_kept_color = @color_buffer[vmli] || 1
401
439
  end
402
440
 
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
441
+ # On the 6569 a bad line that opens display state mid-row (DMA delay)
442
+ # moves the idle g-access of the column that triggers it from $3fff (or
443
+ # $39ff) to DMA_DELAY_IDLE_ADDRESS, unless YSCROLL is 0 (vsp-tester,
444
+ # colorfetchbug/main).
445
+ def dma_delay_idle_access
446
+ slot = @g_tick
447
+ return unless @dma_delay_idle && @g_kind[slot] == G_IDLE
448
+ return if @registers.yscroll.zero?
449
+
450
+ @g_data[slot] = vic_bank.peek(DMA_DELAY_IDLE_ADDRESS)
451
+ end
452
+
453
+ # The 6569's g-access sees a mode bit that falls a cycle late: it
454
+ # addresses with $d011 as this column has it, OR-ed with the held bits
455
+ # the column before had. When BMM changes and the access moves from RAM
456
+ # onto the character ROM, the low address byte still comes from the old
457
+ # mode. The 8565's addresses with $d011 as the column before had it
407
458
  # (VICE x64sc `vicii_fetch_graphics`).
408
459
  def fetch_graphics(vmli, counter)
409
460
  d011 = @register_bytes[0x11]
410
461
  last = @fetch_d011
411
462
  return vic_bank.peek(graphics_address(d011, vmli, counter)) if d011 == last
463
+ return vic_bank.peek(graphics_address(last, vmli, counter)) if @delayed_fetch
412
464
 
413
465
  from = graphics_address(last, vmli, counter)
414
466
  from_rom = vic_bank.character_rom?(from)
data/lib/badline.rb CHANGED
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "badline/version"
4
+ require "badline/options"
4
5
  require "badline/integer_helper"
6
+ require "badline/region"
5
7
  require "badline/addressable"
6
8
  require "badline/memory"
7
9
  require "badline/color_memory"
8
10
  require "badline/rom"
11
+ require "badline/ram_expansion"
9
12
  require "badline/address_bus"
10
13
  require "badline/instruction"
11
14
  require "badline/instruction_set"
@@ -18,6 +21,7 @@ require "badline/cycleable"
18
21
  require "badline/datasette"
19
22
  require "badline/time_of_day"
20
23
  require "badline/cia"
24
+ require "badline/via"
21
25
  require "badline/sid"
22
26
  require "badline/debug_register"
23
27
  require "badline/interrupts"
@@ -26,6 +30,7 @@ require "badline/cpu"
26
30
  require "badline/vic"
27
31
  require "badline/keyboard_buffer"
28
32
  require "badline/computer"
33
+ require "badline/checkpoint"
29
34
  require "badline/storage"
30
35
  require "badline/cartridge"
31
36
  require "badline/kernal_trap"
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The native badline, built from the `rake native:pack` tarball attached to
4
+ # each release. The release workflow copies this file into the tap with the
5
+ # release's url and sha256 filled in.
6
+ class Badline < Formula
7
+ desc "Cycle-accurate Commodore 64 emulator"
8
+ homepage "https://github.com/elektronaut/badline"
9
+ url "https://github.com/elektronaut/badline/releases/download/v0.3.0/badline-0.3.0-spinel-15f037af.tar.gz"
10
+ sha256 "0000000000000000000000000000000000000000000000000000000000000000"
11
+ license "MIT"
12
+
13
+ depends_on "sdl2"
14
+
15
+ uses_from_macos "libxcrypt"
16
+
17
+ def install
18
+ system "make"
19
+ libexec.install "badline"
20
+ pkgshare.install "roms"
21
+ (bin / "badline").write_env_script libexec / "badline",
22
+ BADLINE_ROM_PATH: "${BADLINE_ROM_PATH:-#{pkgshare}/roms}"
23
+ end
24
+
25
+ test do
26
+ assert_match "badline #{version}", shell_output("#{bin}/badline --version")
27
+
28
+ ENV["SDL_VIDEODRIVER"] = "dummy"
29
+ ENV["SDL_RENDER_DRIVER"] = "software"
30
+ ENV["SDL_AUDIODRIVER"] = "dummy"
31
+ system bin / "badline", "--unpaced", "--no-sound", "--frames", "10", "--screenshot", testpath / "frame.bmp"
32
+ assert_path_exists testpath / "frame.bmp"
33
+ end
34
+ end
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.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inge Jørgensen
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '1.1'
26
- - !ruby/object:Gem::Dependency
27
- name: ruby-sdl2
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '0.3'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '0.3'
40
26
  description: Badline is a Commodore 64 emulator written in Ruby, implementing cycle-accurate
41
27
  timing and hardware behavior. Supports PRG/P00 programs, D64/D71/D81 disk images,
42
28
  T64 archives, TAP tapes, CRT cartridges, SID tunes and host directories mounted
@@ -44,20 +30,21 @@ description: Badline is a Commodore 64 emulator written in Ruby, implementing cy
44
30
  email:
45
31
  - inge@elektronaut.no
46
32
  executables:
47
- - badline
48
- - badline-sid
33
+ - badline-ruby
49
34
  extensions: []
50
35
  extra_rdoc_files: []
51
36
  files:
37
+ - ".claude/hooks/session-start.sh"
38
+ - ".claude/settings.json"
52
39
  - CHANGELOG.md
53
40
  - CLAUDE.md
54
41
  - CODE_OF_CONDUCT.md
55
42
  - CONTRIBUTING.md
56
43
  - MIT-LICENSE
57
44
  - README.md
45
+ - SECURITY.md
58
46
  - doc/pinned-behaviour.md
59
- - exe/badline
60
- - exe/badline-sid
47
+ - exe/badline-ruby
61
48
  - lib/badline.rb
62
49
  - lib/badline/address_bus.rb
63
50
  - lib/badline/addressable.rb
@@ -68,11 +55,12 @@ files:
68
55
  - lib/badline/audio/console.rb
69
56
  - lib/badline/audio/jukebox.rb
70
57
  - lib/badline/audio/machine_player.rb
71
- - lib/badline/audio/options.rb
72
58
  - lib/badline/audio/pcm_writer.rb
73
59
  - lib/badline/audio/playback.rb
74
60
  - lib/badline/audio/renderer.rb
75
61
  - lib/badline/audio/sdl_sink.rb
62
+ - lib/badline/audio/stream.rb
63
+ - lib/badline/audio/terminal.rb
76
64
  - lib/badline/audio/wav.rb
77
65
  - lib/badline/cartridge.rb
78
66
  - lib/badline/cartridge/action_replay.rb
@@ -88,6 +76,7 @@ files:
88
76
  - lib/badline/cartridge/fun_play.rb
89
77
  - lib/badline/cartridge/g_mod2.rb
90
78
  - lib/badline/cartridge/game_system.rb
79
+ - lib/badline/cartridge/geo_ram.rb
91
80
  - lib/badline/cartridge/kcs_power.rb
92
81
  - lib/badline/cartridge/mach5.rb
93
82
  - lib/badline/cartridge/magic_desk.rb
@@ -101,6 +90,7 @@ files:
101
90
  - lib/badline/cartridge/super_games.rb
102
91
  - lib/badline/cartridge/westermann.rb
103
92
  - lib/badline/cartridge/zaxxon.rb
93
+ - lib/badline/checkpoint.rb
104
94
  - lib/badline/chrout_trap.rb
105
95
  - lib/badline/cia.rb
106
96
  - lib/badline/cia/interrupt_register.rb
@@ -145,7 +135,13 @@ files:
145
135
  - lib/badline/kernal_trap.rb
146
136
  - lib/badline/kernal_trap/channel.rb
147
137
  - lib/badline/kernal_trap/drive.rb
138
+ - lib/badline/kernal_trap/drive/block_commands.rb
139
+ - lib/badline/kernal_trap/drive/channels.rb
140
+ - lib/badline/kernal_trap/drive/memory.rb
141
+ - lib/badline/kernal_trap/drive/parameters.rb
148
142
  - lib/badline/kernal_trap/drive/status.rb
143
+ - lib/badline/kernal_trap/drive/write_file.rb
144
+ - lib/badline/kernal_trap/drive/writes.rb
149
145
  - lib/badline/kernal_trap/file.rb
150
146
  - lib/badline/kernal_trap/load.rb
151
147
  - lib/badline/kernal_trap/routine.rb
@@ -155,6 +151,13 @@ files:
155
151
  - lib/badline/keyboard_buffer.rb
156
152
  - lib/badline/media.rb
157
153
  - lib/badline/memory.rb
154
+ - lib/badline/options.rb
155
+ - lib/badline/ram_expansion.rb
156
+ - lib/badline/ram_expansion/banking.rb
157
+ - lib/badline/ram_expansion/plus256k.rb
158
+ - lib/badline/ram_expansion/plus60k.rb
159
+ - lib/badline/ram_expansion/unexpanded.rb
160
+ - lib/badline/region.rb
158
161
  - lib/badline/rom.rb
159
162
  - lib/badline/roms/README
160
163
  - lib/badline/roms/basic.rom
@@ -163,7 +166,11 @@ files:
163
166
  - lib/badline/roms/eapi/README
164
167
  - lib/badline/roms/eapi/eapi-am29f040-14
165
168
  - lib/badline/roms/kernal.rom
169
+ - lib/badline/sdl.rb
170
+ - lib/badline/sdl/events.rb
171
+ - lib/badline/sdl/functions.rb
166
172
  - lib/badline/sid.rb
173
+ - lib/badline/sid/catch_up.rb
167
174
  - lib/badline/sid/decimator.rb
168
175
  - lib/badline/sid/envelope.rb
169
176
  - lib/badline/sid/filter.rb
@@ -179,6 +186,9 @@ files:
179
186
  - lib/badline/storage/d71_image.rb
180
187
  - lib/badline/storage/d81_image.rb
181
188
  - lib/badline/storage/disk_image.rb
189
+ - lib/badline/storage/disk_image/bam.rb
190
+ - lib/badline/storage/disk_image/directory.rb
191
+ - lib/badline/storage/disk_image/writing.rb
182
192
  - lib/badline/storage/host_directory.rb
183
193
  - lib/badline/storage/p00.rb
184
194
  - lib/badline/storage/sid_file.rb
@@ -188,6 +198,12 @@ files:
188
198
  - lib/badline/time_of_day.rb
189
199
  - lib/badline/traps.rb
190
200
  - lib/badline/version.rb
201
+ - lib/badline/via.rb
202
+ - lib/badline/via/control_lines.rb
203
+ - lib/badline/via/interrupt_register.rb
204
+ - lib/badline/via/shift_register.rb
205
+ - lib/badline/via/timer1.rb
206
+ - lib/badline/via/timer2.rb
191
207
  - lib/badline/vic.rb
192
208
  - lib/badline/vic/bank.rb
193
209
  - lib/badline/vic/border_mask.rb
@@ -204,6 +220,7 @@ files:
204
220
  - lib/badline/vic/sprite/internal_bus.rb
205
221
  - lib/badline/vic/sprite/shifter.rb
206
222
  - lib/badline/vic/sprites.rb
223
+ - packaging/homebrew/badline.rb
207
224
  - vendor/.gitkeep
208
225
  homepage: https://github.com/elektronaut/badline
209
226
  licenses:
data/exe/badline DELETED
@@ -1,75 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- lib = File.expand_path("../lib", __dir__)
5
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
-
7
- require "optparse"
8
-
9
- options = { autostart: true, jit: true }
10
-
11
- parser = OptionParser.new do |opts|
12
- opts.banner = <<~BANNER
13
- Usage: badline [options] [media]
14
-
15
- Media can be a .prg/.p00 program, a .d64/.d71/.d81 disk image,
16
- a .t64 tape archive, a .tap tape, a .crt cartridge, a .sid tune, or
17
- a directory to mount as device 8.
18
-
19
- Options:
20
- BANNER
21
-
22
- opts.on("--no-autostart", "Boot to READY. instead of running the program") do
23
- options[:autostart] = false
24
- end
25
-
26
- opts.on("--song N", Integer, "Subtune of a .sid to play (default: the tune's own)") do |n|
27
- options[:song] = n
28
- end
29
-
30
- opts.on("--sid MODEL", { "6581" => :mos6581, "8580" => :mos8580 },
31
- "SID to fit: 6581 or 8580 (default: a .sid tune's own, else 6581)") do |model|
32
- options[:sid_model] = model
33
- end
34
-
35
- opts.on("--disable-jit", "Run without enabling YJIT") do
36
- options[:jit] = false
37
- end
38
-
39
- opts.on("-h", "--help", "Show this help") do
40
- puts opts
41
- exit
42
- end
43
- end
44
-
45
- begin
46
- parser.parse!
47
- rescue OptionParser::ParseError => e
48
- warn "badline: #{e.message}"
49
- warn "Try 'badline --help' for more information."
50
- exit 1
51
- end
52
-
53
- media_path = ARGV[0]
54
- if media_path && !File.exist?(media_path)
55
- warn "badline: no such file or directory: #{media_path}"
56
- exit 1
57
- end
58
-
59
- jit_available = defined?(RubyVM::YJIT) && !RubyVM::YJIT.enabled?
60
- RubyVM::YJIT.enable if options[:jit] && jit_available
61
-
62
- require "badline"
63
- require "badline/gui"
64
-
65
- begin
66
- application = Badline::GUI::Application.new(media_path:, autostart: options[:autostart],
67
- song: options[:song], sid_model: options[:sid_model])
68
- rescue Badline::Storage::SIDFile::FormatError, Badline::Storage::T64::FormatError,
69
- Badline::Storage::TAP::FormatError, Badline::Storage::CRTFile::FormatError,
70
- Badline::Cartridge::UnsupportedTypeError => e
71
- warn "badline: #{media_path}: #{e.message}"
72
- exit 1
73
- end
74
-
75
- application.run
data/exe/badline-sid DELETED
@@ -1,34 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- lib = File.expand_path("../lib", __dir__)
5
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
-
7
- require "badline/audio/options"
8
-
9
- begin
10
- options = Badline::Audio::Options.parse(ARGV)
11
- rescue Badline::Audio::Options::Error => e
12
- warn "badline-sid: #{e.message}"
13
- warn "Try 'badline-sid --help' for more information."
14
- exit 1
15
- end
16
-
17
- if options.help?
18
- puts options.help
19
- exit
20
- end
21
-
22
- jit_available = defined?(RubyVM::YJIT) && !RubyVM::YJIT.enabled?
23
- RubyVM::YJIT.enable if options.jit? && jit_available
24
-
25
- require "badline"
26
-
27
- begin
28
- Badline::Audio::CLI.new(options).run
29
- rescue Badline::Audio::CLI::Error => e
30
- warn "badline-sid: #{e.message}"
31
- exit 1
32
- rescue Interrupt
33
- exit 130
34
- end