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.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +185 -0
  3. data/CLAUDE.md +217 -0
  4. data/CODE_OF_CONDUCT.md +92 -0
  5. data/CONTRIBUTING.md +51 -0
  6. data/README.md +224 -31
  7. data/doc/pinned-behaviour.md +1117 -0
  8. data/exe/badline +22 -2
  9. data/exe/badline-sid +34 -0
  10. data/lib/badline/address_bus.rb +96 -25
  11. data/lib/badline/audio/aiff.rb +36 -0
  12. data/lib/badline/audio/bare_player.rb +129 -0
  13. data/lib/badline/audio/cli.rb +133 -0
  14. data/lib/badline/audio/console.rb +75 -0
  15. data/lib/badline/audio/jukebox.rb +82 -0
  16. data/lib/badline/audio/machine_player.rb +57 -0
  17. data/lib/badline/audio/options.rb +137 -0
  18. data/lib/badline/audio/pcm_writer.rb +57 -0
  19. data/lib/badline/audio/playback.rb +108 -0
  20. data/lib/badline/audio/renderer.rb +85 -0
  21. data/lib/badline/audio/sdl_sink.rb +102 -0
  22. data/lib/badline/audio/wav.rb +23 -0
  23. data/lib/badline/audio.rb +14 -0
  24. data/lib/badline/cartridge/action_replay.rb +111 -0
  25. data/lib/badline/cartridge/atomic_power.rb +24 -0
  26. data/lib/badline/cartridge/bank.rb +81 -0
  27. data/lib/badline/cartridge/comal80.rb +48 -0
  28. data/lib/badline/cartridge/dinamic.rb +34 -0
  29. data/lib/badline/cartridge/easy_flash.rb +122 -0
  30. data/lib/badline/cartridge/epyx_fastload.rb +71 -0
  31. data/lib/badline/cartridge/final_cartridge3.rb +77 -0
  32. data/lib/badline/cartridge/flash.rb +265 -0
  33. data/lib/badline/cartridge/freezer.rb +31 -0
  34. data/lib/badline/cartridge/fun_play.rb +38 -0
  35. data/lib/badline/cartridge/g_mod2.rb +62 -0
  36. data/lib/badline/cartridge/game_system.rb +40 -0
  37. data/lib/badline/cartridge/kcs_power.rb +72 -0
  38. data/lib/badline/cartridge/mach5.rb +34 -0
  39. data/lib/badline/cartridge/magic_desk.rb +6 -3
  40. data/lib/badline/cartridge/ocean.rb +11 -12
  41. data/lib/badline/cartridge/pagefox.rb +56 -0
  42. data/lib/badline/cartridge/retro_replay.rb +276 -0
  43. data/lib/badline/cartridge/rex_utility.rb +33 -0
  44. data/lib/badline/cartridge/rgcd.rb +48 -0
  45. data/lib/badline/cartridge/simons_basic.rb +40 -0
  46. data/lib/badline/cartridge/standard.rb +3 -19
  47. data/lib/badline/cartridge/super_games.rb +36 -0
  48. data/lib/badline/cartridge/westermann.rb +34 -0
  49. data/lib/badline/cartridge/zaxxon.rb +50 -0
  50. data/lib/badline/cartridge.rb +132 -16
  51. data/lib/badline/cia/interrupt_register.rb +102 -0
  52. data/lib/badline/cia/serial.rb +219 -0
  53. data/lib/badline/cia/timer.rb +43 -31
  54. data/lib/badline/cia.rb +138 -88
  55. data/lib/badline/color_memory.rb +14 -5
  56. data/lib/badline/computer.rb +97 -12
  57. data/lib/badline/control_ports.rb +54 -9
  58. data/lib/badline/cpu/addressing.rb +131 -0
  59. data/lib/badline/cpu/microcode.rb +169 -0
  60. data/lib/badline/cpu/operations.rb +88 -0
  61. data/lib/badline/cpu/stack_operations.rb +82 -0
  62. data/lib/badline/cpu.rb +109 -168
  63. data/lib/badline/cycleable.rb +3 -0
  64. data/lib/badline/datasette.rb +81 -0
  65. data/lib/badline/debug_register.rb +28 -0
  66. data/lib/badline/gui/application.rb +79 -18
  67. data/lib/badline/gui/gamepads.rb +97 -0
  68. data/lib/badline/gui/joy_map.rb +15 -7
  69. data/lib/badline/gui.rb +1 -0
  70. data/lib/badline/input/mouse1351.rb +41 -0
  71. data/lib/badline/input/paddles.rb +36 -0
  72. data/lib/badline/input.rb +4 -0
  73. data/lib/badline/instruction_set/arithmetic.rb +16 -21
  74. data/lib/badline/instruction_set/bitwise.rb +17 -22
  75. data/lib/badline/instruction_set/branch.rb +20 -19
  76. data/lib/badline/instruction_set/flag.rb +7 -7
  77. data/lib/badline/instruction_set/illegal.rb +14 -22
  78. data/lib/badline/instruction_set/inc_dec.rb +8 -10
  79. data/lib/badline/instruction_set/stack.rb +5 -57
  80. data/lib/badline/instruction_set/transfer.rb +9 -9
  81. data/lib/badline/instruction_set.rb +9 -31
  82. data/lib/badline/interrupts.rb +104 -0
  83. data/lib/badline/joystick.rb +9 -9
  84. data/lib/badline/kernal_trap/channel.rb +51 -0
  85. data/lib/badline/kernal_trap/drive/status.rb +49 -0
  86. data/lib/badline/kernal_trap/drive.rb +277 -0
  87. data/lib/badline/kernal_trap/file.rb +5 -25
  88. data/lib/badline/kernal_trap/load.rb +86 -24
  89. data/lib/badline/kernal_trap/routine.rb +51 -0
  90. data/lib/badline/kernal_trap/save.rb +46 -14
  91. data/lib/badline/kernal_trap/serial.rb +171 -0
  92. data/lib/badline/kernal_trap.rb +4 -0
  93. data/lib/badline/keyboard.rb +40 -16
  94. data/lib/badline/media.rb +48 -10
  95. data/lib/badline/memory.rb +5 -0
  96. data/lib/badline/roms/eapi/LICENSE.md +15 -0
  97. data/lib/badline/roms/eapi/README +41 -0
  98. data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
  99. data/lib/badline/sid/decimator.rb +46 -0
  100. data/lib/badline/sid/envelope.rb +232 -0
  101. data/lib/badline/sid/filter.rb +213 -0
  102. data/lib/badline/sid/voice.rb +61 -0
  103. data/lib/badline/sid/waveform/combined.rb +126 -0
  104. data/lib/badline/sid/waveform/fast_forward.rb +105 -0
  105. data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
  106. data/lib/badline/sid/waveform.rb +289 -0
  107. data/lib/badline/sid.rb +274 -7
  108. data/lib/badline/status.rb +251 -23
  109. data/lib/badline/storage/crt_file.rb +18 -2
  110. data/lib/badline/storage/d64_image.rb +6 -0
  111. data/lib/badline/storage/d71_image.rb +4 -0
  112. data/lib/badline/storage/d81_image.rb +4 -0
  113. data/lib/badline/storage/disk_image.rb +99 -13
  114. data/lib/badline/storage/host_directory.rb +30 -4
  115. data/lib/badline/storage/sid_file.rb +274 -0
  116. data/lib/badline/storage/song_lengths.rb +65 -0
  117. data/lib/badline/storage/t64.rb +67 -0
  118. data/lib/badline/storage/tap.rb +68 -0
  119. data/lib/badline/storage.rb +19 -0
  120. data/lib/badline/time_of_day.rb +105 -52
  121. data/lib/badline/version.rb +1 -1
  122. data/lib/badline/vic/bank.rb +14 -6
  123. data/lib/badline/vic/border_mask.rb +76 -0
  124. data/lib/badline/vic/collisions.rb +104 -0
  125. data/lib/badline/vic/color_patches.rb +33 -0
  126. data/lib/badline/vic/display_state.rb +92 -29
  127. data/lib/badline/vic/graphics_mode.rb +78 -53
  128. data/lib/badline/vic/graphics_shifter.rb +140 -0
  129. data/lib/badline/vic/register_log.rb +63 -0
  130. data/lib/badline/vic/registers.rb +3 -0
  131. data/lib/badline/vic/sequencer.rb +163 -137
  132. data/lib/badline/vic/sequencer_output.rb +116 -0
  133. data/lib/badline/vic/sprite/internal_bus.rb +49 -0
  134. data/lib/badline/vic/sprite/shifter.rb +129 -0
  135. data/lib/badline/vic/sprite.rb +275 -65
  136. data/lib/badline/vic/sprites.rb +220 -58
  137. data/lib/badline/vic.rb +389 -55
  138. data/lib/badline.rb +5 -0
  139. data/vendor/.gitkeep +0 -0
  140. metadata +104 -4
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module GUI
5
+ # Host game controllers wired onto the control ports.
6
+ #
7
+ # The first controller found drives joystick 2, the port most games read;
8
+ # a second one drives joystick 1. D-pad and left stick both steer, every
9
+ # face and shoulder button fires.
10
+ #
11
+ # SDL announces arrivals by device index but reports button presses by
12
+ # instance id, and the binding gives no way to line the two up, so this
13
+ # class polls the controllers every frame. It only releases directions it
14
+ # pressed itself, so the keyboard mapping keeps working with a pad
15
+ # connected.
16
+ class Gamepads
17
+ DEADZONE = 8_000
18
+ PORTS = [2, 1].freeze
19
+
20
+ BUTTONS = {
21
+ up: [SDL2::GameController::Button::DPAD_UP],
22
+ down: [SDL2::GameController::Button::DPAD_DOWN],
23
+ left: [SDL2::GameController::Button::DPAD_LEFT],
24
+ right: [SDL2::GameController::Button::DPAD_RIGHT],
25
+ fire: [SDL2::GameController::Button::A, SDL2::GameController::Button::B,
26
+ SDL2::GameController::Button::X, SDL2::GameController::Button::Y,
27
+ SDL2::GameController::Button::LEFTSHOULDER,
28
+ SDL2::GameController::Button::RIGHTSHOULDER]
29
+ }.freeze
30
+
31
+ AXES = {
32
+ up: [SDL2::GameController::Axis::LEFTY, -1],
33
+ down: [SDL2::GameController::Axis::LEFTY, 1],
34
+ left: [SDL2::GameController::Axis::LEFTX, -1],
35
+ right: [SDL2::GameController::Axis::LEFTX, 1]
36
+ }.freeze
37
+
38
+ def initialize(computer)
39
+ SDL2.init(SDL2::INIT_GAMECONTROLLER)
40
+ @computer = computer
41
+ @controllers = []
42
+ @pressed = Array.new(PORTS.size) { [] }
43
+ rescan
44
+ end
45
+
46
+ def names = @controllers.map(&:name)
47
+
48
+ def rescan
49
+ close
50
+ @controllers = (0...SDL2::Joystick.num_connected_joysticks)
51
+ .select { |index| SDL2::Joystick.game_controller?(index) }
52
+ .first(PORTS.size)
53
+ .map { |index| SDL2::GameController.open(index) }
54
+ end
55
+
56
+ def poll
57
+ @controllers.each_with_index { |controller, index| apply(controller, index) }
58
+ end
59
+
60
+ def close
61
+ @controllers.each { |controller| controller.destroy unless controller.destroy? }
62
+ @controllers = []
63
+ @pressed.size.times { |index| release(index) }
64
+ end
65
+
66
+ private
67
+
68
+ def apply(controller, index)
69
+ active = Joystick::DIRECTIONS.keys.select { |dir| active?(controller, dir) }
70
+ (@pressed[index] - active).each { |dir| joystick(index).release(dir) }
71
+ active.each { |dir| joystick(index).press(dir) }
72
+ @pressed[index] = active
73
+ end
74
+
75
+ def release(index)
76
+ @pressed[index].each { |dir| joystick(index).release(dir) }
77
+ @pressed[index] = []
78
+ end
79
+
80
+ def active?(controller, direction)
81
+ BUTTONS.fetch(direction).any? { |button| controller.button_pressed?(button) } ||
82
+ tilted?(controller, direction)
83
+ end
84
+
85
+ def tilted?(controller, direction)
86
+ axis, sign = AXES[direction]
87
+ return false unless axis
88
+
89
+ controller.axis(axis) * sign > DEADZONE
90
+ end
91
+
92
+ def joystick(index)
93
+ PORTS[index] == 1 ? @computer.joystick1 : @computer.joystick2
94
+ end
95
+ end
96
+ end
97
+ end
@@ -2,17 +2,25 @@
2
2
 
3
3
  module Badline
4
4
  module GUI
5
+ # Maps host keys onto the two control ports while joystick mode is on.
6
+ #
7
+ # The arrow cluster plus space drives joystick 2, WASD plus left shift
8
+ # joystick 1, so both ports can be played at once.
5
9
  class JoyMap
6
- MAP = {
7
- "Up" => :up,
8
- "Down" => :down,
9
- "Left" => :left,
10
- "Right" => :right,
11
- "Space" => :fire
10
+ PORTS = {
11
+ 2 => { "Up" => :up, "Down" => :down, "Left" => :left, "Right" => :right, "Space" => :fire }.freeze,
12
+ 1 => { "W" => :up, "S" => :down, "A" => :left, "D" => :right, "Left Shift" => :fire }.freeze
12
13
  }.freeze
13
14
 
14
15
  def self.parse(event)
15
- MAP[SDL2::Key.name_of(event.sym)]
16
+ name = SDL2::Key.name_of(event.sym)
17
+
18
+ PORTS.each do |port, map|
19
+ direction = map[name]
20
+ return [port, direction] if direction
21
+ end
22
+
23
+ nil
16
24
  end
17
25
  end
18
26
  end
data/lib/badline/gui.rb CHANGED
@@ -5,6 +5,7 @@ require "sdl2"
5
5
  require "badline/gui/palette"
6
6
  require "badline/gui/key_map"
7
7
  require "badline/gui/joy_map"
8
+ require "badline/gui/gamepads"
8
9
  require "badline/gui/pane"
9
10
  require "badline/gui/screen_pane"
10
11
  require "badline/gui/window"
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Input
5
+ # Commodore 1351 mouse in proportional mode.
6
+ #
7
+ # Each axis drives a 6-bit counter that the mouse presents on POTX/POTY
8
+ # shifted up one bit. A driver recovers movement by masking off bit 7,
9
+ # subtracting the previous reading and halving the difference. The counters
10
+ # wrap, so a single reading says nothing on its own, and the driver has to
11
+ # sample before the mouse travels 32 counts.
12
+ #
13
+ # #move takes host deltas with y counting down the screen; the counter
14
+ # counts the other way. The left button sits on the joystick fire line, the
15
+ # right button on the up line.
16
+ class Mouse1351
17
+ BUTTONS = { left: :fire, right: :up }.freeze
18
+
19
+ def initialize
20
+ @lines = Joystick.new
21
+ @x = 0
22
+ @y = 0
23
+ end
24
+
25
+ def press(button) = @lines.press(BUTTONS[button])
26
+
27
+ def release(button) = @lines.release(BUTTONS[button])
28
+
29
+ def port_bits = @lines.port_bits
30
+
31
+ def move(delta_x, delta_y)
32
+ @x = (@x + delta_x) & 0x3f
33
+ @y = (@y - delta_y) & 0x3f
34
+ end
35
+
36
+ def pot_x = @x << 1
37
+
38
+ def pot_y = @y << 1
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Input
5
+ # A pair of paddles on one control port.
6
+ #
7
+ # Paddle A sits on POTX with its button on the joystick left line, paddle B
8
+ # on POTY with its button on the right line. Positions are the 0-255 counts
9
+ # SID reads back, and the knobs stop at either end.
10
+ #
11
+ # Buttons are named after the host mouse buttons, as on the 1351: the left
12
+ # one fires paddle A, the right one paddle B.
13
+ class Paddles
14
+ BUTTONS = { left: :left, right: :right }.freeze
15
+
16
+ attr_reader :pot_x, :pot_y
17
+
18
+ def initialize
19
+ @lines = Joystick.new
20
+ @pot_x = 0x80
21
+ @pot_y = 0x80
22
+ end
23
+
24
+ def press(button) = @lines.press(BUTTONS[button])
25
+
26
+ def release(button) = @lines.release(BUTTONS[button])
27
+
28
+ def port_bits = @lines.port_bits
29
+
30
+ def move(turn_a, turn_b)
31
+ @pot_x = (@pot_x + turn_a).clamp(0, 0xff)
32
+ @pot_y = (@pot_y + turn_b).clamp(0, 0xff)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/input/mouse1351"
4
+ require "badline/input/paddles"
@@ -15,19 +15,18 @@ module Badline
15
15
  # $79 - absolute_y - 4+ cycles
16
16
  # $7D - absolute_x - 4+ cycles
17
17
  def adc(_addr, value)
18
- v = resolve(value)
19
- result = a + v + status.carry
18
+ result = a + value + status.carry
20
19
  status.zero = result.nobits?(0xff)
21
20
 
22
21
  if status.decimal?
23
- result = (a & 0x0f) + (v & 0x0f) + status.carry
22
+ result = (a & 0x0f) + (value & 0x0f) + status.carry
24
23
  result += 0x06 if result > 0x09
25
24
  c = result > 0x0f ? 1 : 0
26
- result = (a & 0xf0) + (v & 0xf0) + (c << 4) + (result & 0x0f)
27
- update_calculation_flags(v, result)
25
+ result = (a & 0xf0) + (value & 0xf0) + (c << 4) + (result & 0x0f)
26
+ update_calculation_flags(value, result)
28
27
  result += 0x60 if result > 0x9f
29
28
  else
30
- update_calculation_flags(v, result)
29
+ update_calculation_flags(value, result)
31
30
  end
32
31
 
33
32
  status.carry = result > 0xff
@@ -46,9 +45,8 @@ module Badline
46
45
  # $D9 - absolute_y - 4+ cycles
47
46
  # $DD - absolute_x - 4+ cycles
48
47
  def cmp(_addr, value)
49
- v = resolve(value)
50
- status.carry = (@a >= v)
51
- update_number_flags(@a - v)
48
+ status.carry = (@a >= value)
49
+ update_number_flags(@a - value)
52
50
  end
53
51
 
54
52
  # Compare memory with X register.
@@ -58,9 +56,8 @@ module Badline
58
56
  # $E4 - zeropage - 3 cycles
59
57
  # $EC - absolute - 4 cycles
60
58
  def cpx(_addr, value)
61
- v = resolve(value)
62
- status.carry = @x >= v
63
- update_number_flags(@x - v)
59
+ status.carry = @x >= value
60
+ update_number_flags(@x - value)
64
61
  end
65
62
 
66
63
  # Compare memory with Y register.
@@ -70,9 +67,8 @@ module Badline
70
67
  # $C4 - zeropage - 3 cycles
71
68
  # $CC - absolute - 4 cycles
72
69
  def cpy(_addr, value)
73
- v = resolve(value)
74
- status.carry = @y >= v
75
- update_number_flags(@y - v)
70
+ status.carry = @y >= value
71
+ update_number_flags(@y - value)
76
72
  end
77
73
 
78
74
  # Subtract memory from accumulator with borrow.
@@ -87,20 +83,19 @@ module Badline
87
83
  # $F9 - absolute_y - 4+ cycles
88
84
  # $FD - absolute_x - 4+ cycles
89
85
  def sbc(_addr, value)
90
- v = resolve(value)
91
- v_inv = ~v & 0xff
86
+ inverted = ~value & 0xff
92
87
  carry = status.carry? ? 0 : -1
93
88
 
94
- result = a + v_inv + status.carry
89
+ result = a + inverted + status.carry
95
90
  status.zero = result.nobits?(0xff)
96
91
  status.carry = result > 0xff
97
- update_calculation_flags(v_inv, result)
92
+ update_calculation_flags(inverted, result)
98
93
 
99
94
  if status.decimal?
100
- al = (a & 0x0f) - (v & 0x0f) + carry
95
+ al = (a & 0x0f) - (value & 0x0f) + carry
101
96
  al = ((al - 0x06) & 0x0F) - 0x10 if al.negative?
102
97
 
103
- result = (a & 0xf0) - (v & 0xf0) + al
98
+ result = (a & 0xf0) - (value & 0xf0) + al
104
99
  result -= 0x60 if result.negative?
105
100
  end
106
101
 
@@ -15,7 +15,7 @@ module Badline
15
15
  # $39 - absolute_y - 4+ cycles
16
16
  # $3D - absolute_x - 4+ cycles
17
17
  def and(_addr, value)
18
- @a &= resolve(value)
18
+ @a &= value
19
19
  update_number_flags(@a)
20
20
  end
21
21
 
@@ -28,10 +28,9 @@ module Badline
28
28
  # $16 - zeropage_x - 6 cycles
29
29
  # $1E - absolute_x - 7 cycles
30
30
  def asl(addr, value)
31
- v = resolve(value)
32
- result = (v << 1) & 0xff
33
- status.carry = v[7]
34
- write_modified(addr, v, result)
31
+ result = (value << 1) & 0xff
32
+ status.carry = value[7]
33
+ write_modified(addr, result)
35
34
  update_number_flags(result)
36
35
  end
37
36
 
@@ -41,10 +40,9 @@ module Badline
41
40
  # $24 - zeropage - 3 cycles
42
41
  # $2C - absolute - 4 cycles
43
42
  def bit(_addr, value)
44
- v = resolve(value)
45
43
  status.value = ((status.value & 0b00111111) +
46
- (v & 0b11000000)).to_i
47
- status.zero = (a & v).nobits?(0xff)
44
+ (value & 0b11000000)).to_i
45
+ status.zero = (a & value).nobits?(0xff)
48
46
  end
49
47
 
50
48
  # Performs bitwise XOR on accumulator with memory.
@@ -59,7 +57,7 @@ module Badline
59
57
  # $59 - absolute_y - 4+ cycles
60
58
  # $5D - absolute_x - 4+ cycles
61
59
  def eor(_addr, value)
62
- @a = (@a ^ resolve(value)) & 0xff
60
+ @a = (@a ^ value) & 0xff
63
61
  update_number_flags(@a)
64
62
  end
65
63
 
@@ -72,10 +70,9 @@ module Badline
72
70
  # $56 - zeropage_x - 6 cycles
73
71
  # $5E - absolute_x - 7 cycles
74
72
  def lsr(addr, value)
75
- v = resolve(value)
76
- result = (v >> 1) & 0xff
77
- status.carry = v[0]
78
- write_modified(addr, v, result)
73
+ result = (value >> 1) & 0xff
74
+ status.carry = value[0]
75
+ write_modified(addr, result)
79
76
  update_number_flags(result)
80
77
  end
81
78
 
@@ -91,7 +88,7 @@ module Badline
91
88
  # $19 - absolute_y - 4+ cycles
92
89
  # $1D - absolute_x - 4+ cycles
93
90
  def ora(_addr, value)
94
- @a |= resolve(value)
91
+ @a |= value
95
92
  update_number_flags(@a)
96
93
  end
97
94
 
@@ -104,10 +101,9 @@ module Badline
104
101
  # $36 - zeropage_x - 6 cycles
105
102
  # $3E - absolute_x - 7 cycles
106
103
  def rol(addr, value)
107
- v = resolve(value)
108
- result = ((v << 1) + status.carry) & 0xff
109
- status.carry = v[7]
110
- write_modified(addr, v, result)
104
+ result = ((value << 1) + status.carry) & 0xff
105
+ status.carry = value[7]
106
+ write_modified(addr, result)
111
107
  update_number_flags(result)
112
108
  end
113
109
 
@@ -120,10 +116,9 @@ module Badline
120
116
  # $76 - zeropage_x - 6 cycles
121
117
  # $7E - absolute_x - 7 cycles
122
118
  def ror(addr, value)
123
- v = resolve(value)
124
- result = ((v >> 1) + (status.carry? ? 0x80 : 0)) & 0xff
125
- status.carry = v[0]
126
- write_modified(addr, v, result)
119
+ result = ((value >> 1) + (status.carry? ? 0x80 : 0)) & 0xff
120
+ status.carry = value[0]
121
+ write_modified(addr, result)
127
122
  update_number_flags(result)
128
123
  end
129
124
  end
@@ -7,71 +7,72 @@ module Badline
7
7
  #
8
8
  # Opcodes:
9
9
  # $90 - relative - 2+ cycles
10
- def bcc(addr, _value)
11
- branch(addr) unless status.carry?
10
+ def bcc(_addr, _value)
11
+ take_branch unless status.carry?
12
12
  end
13
13
 
14
14
  # Branch if carry set (C=1).
15
15
  #
16
16
  # Opcodes:
17
17
  # $B0 - relative - 2+ cycles
18
- def bcs(addr, _value)
19
- branch(addr) if status.carry?
18
+ def bcs(_addr, _value)
19
+ take_branch if status.carry?
20
20
  end
21
21
 
22
22
  # Branch if equal (Z=1).
23
23
  #
24
24
  # Opcodes:
25
25
  # $F0 - relative - 2+ cycles
26
- def beq(addr, _value)
27
- branch(addr) if status.zero?
26
+ def beq(_addr, _value)
27
+ take_branch if status.zero?
28
28
  end
29
29
 
30
30
  # Branch if minus (N=1).
31
31
  #
32
32
  # Opcodes:
33
33
  # $30 - relative - 2+ cycles
34
- def bmi(addr, _value)
35
- branch(addr) if status.negative?
34
+ def bmi(_addr, _value)
35
+ take_branch if status.negative?
36
36
  end
37
37
 
38
38
  # Branch if not equal (Z=0).
39
39
  #
40
40
  # Opcodes:
41
41
  # $D0 - relative - 2+ cycles
42
- def bne(addr, _value)
43
- branch(addr) unless status.zero?
42
+ def bne(_addr, _value)
43
+ take_branch unless status.zero?
44
44
  end
45
45
 
46
46
  # Branch if plus (N=0).
47
47
  #
48
48
  # Opcodes:
49
49
  # $10 - relative - 2+ cycles
50
- def bpl(addr, _value)
51
- branch(addr) unless status.negative?
50
+ def bpl(_addr, _value)
51
+ take_branch unless status.negative?
52
52
  end
53
53
 
54
54
  # Branch if overflow clear (V=0).
55
55
  #
56
56
  # Opcodes:
57
57
  # $50 - relative - 2+ cycles
58
- def bvc(addr, _value)
59
- branch(addr) unless status.overflow?
58
+ def bvc(_addr, _value)
59
+ take_branch unless status.overflow?
60
60
  end
61
61
 
62
62
  # Branch if overflow set (V=1).
63
63
  #
64
64
  # Opcodes:
65
65
  # $70 - relative - 2+ cycles
66
- def bvs(addr, _value)
67
- branch(addr) if status.overflow?
66
+ def bvs(_addr, _value)
67
+ take_branch if status.overflow?
68
68
  end
69
69
 
70
70
  private
71
71
 
72
- def branch(addr)
73
- cycle if high_byte(addr) != high_byte(@program_counter)
74
- cycle { @program_counter = addr }
72
+ # Marks the branch as taken. Operations#op_relative reads the flag
73
+ # and runs the extra cycles.
74
+ def take_branch
75
+ @branch_taken = true
75
76
  end
76
77
  end
77
78
  end
@@ -8,7 +8,7 @@ module Badline
8
8
  # Opcodes:
9
9
  # $18 - implied - 2 cycles
10
10
  def clc(_addr, _value)
11
- cycle { status.carry = false }
11
+ status.carry = false
12
12
  end
13
13
 
14
14
  # Clear decimal mode flag.
@@ -16,7 +16,7 @@ module Badline
16
16
  # Opcodes:
17
17
  # $D8 - implied - 2 cycles
18
18
  def cld(_addr, _value)
19
- cycle { status.decimal = false }
19
+ status.decimal = false
20
20
  end
21
21
 
22
22
  # Clear interrupt disable flag.
@@ -24,7 +24,7 @@ module Badline
24
24
  # Opcodes:
25
25
  # $58 - implied - 2 cycles
26
26
  def cli(_addr, _value)
27
- cycle { status.interrupt = false }
27
+ status.interrupt = false
28
28
  end
29
29
 
30
30
  # Clear overflow flag.
@@ -32,7 +32,7 @@ module Badline
32
32
  # Opcodes:
33
33
  # $B8 - implied - 2 cycles
34
34
  def clv(_addr, _value)
35
- cycle { status.overflow = false }
35
+ status.overflow = false
36
36
  end
37
37
 
38
38
  # Set carry flag.
@@ -40,7 +40,7 @@ module Badline
40
40
  # Opcodes:
41
41
  # $38 - implied - 2 cycles
42
42
  def sec(_addr, _value)
43
- cycle { status.carry = true }
43
+ status.carry = true
44
44
  end
45
45
 
46
46
  # Set decimal mode flag.
@@ -48,7 +48,7 @@ module Badline
48
48
  # Opcodes:
49
49
  # $F8 - implied - 2 cycles
50
50
  def sed(_addr, _value)
51
- cycle { status.decimal = true }
51
+ status.decimal = true
52
52
  end
53
53
 
54
54
  # Set interrupt disable flag.
@@ -56,7 +56,7 @@ module Badline
56
56
  # Opcodes:
57
57
  # $78 - implied - 2 cycles
58
58
  def sei(_addr, _value)
59
- cycle { status.interrupt = true }
59
+ status.interrupt = true
60
60
  end
61
61
  end
62
62
  end
@@ -9,7 +9,7 @@ module Badline
9
9
  # Opcodes:
10
10
  # $4B - immediate - 2 cycles
11
11
  def alr(_addr, value)
12
- @a &= resolve(value)
12
+ @a &= value
13
13
  status.carry = @a[0]
14
14
  @a = (@a >> 1) & 0xff
15
15
  update_number_flags(@a)
@@ -25,13 +25,14 @@ module Badline
25
25
  end
26
26
 
27
27
  # Unstable instruction. AND's value with A|magic_const and X.
28
- # Magic constant varies by CPU.
28
+ # Magic constant varies by CPU. A VIC stall between the opcode and
29
+ # operand fetches clears its bits 0 and 4.
29
30
  #
30
31
  # Opcodes:
31
32
  # $8B - immediate - 2 cycles
32
33
  def ane(_addr, value)
33
- magic_const = 0xee
34
- @a = (a | magic_const) & x & resolve(value)
34
+ magic_const = stalled_before_this_cycle? ? @ane_constant & 0xee : @ane_constant
35
+ @a = (a | magic_const) & x & value
35
36
  update_number_flags(@a)
36
37
  end
37
38
 
@@ -40,7 +41,7 @@ module Badline
40
41
  # Opcodes:
41
42
  # $6B - immediate - 2 cycles
42
43
  def arr(_addr, value)
43
- tmp = a & resolve(value)
44
+ tmp = a & value
44
45
  result = (tmp >> 1) | (status.carry? ? 0x80 : 0)
45
46
  status.zero = result.zero?
46
47
  status.negative = status.carry
@@ -94,23 +95,12 @@ module Badline
94
95
  sbc(addr, inc(addr, value))
95
96
  end
96
97
 
97
- # Freezes the CPU by forcing an infinite loop.
98
- #
99
- # Opcodes:
100
- # $02, $12, $22, $32, $42, $52, $62, $72, $92, $B2, $D2, $F2
101
- def jam(_addr, _value)
102
- # TODO: Handle jam
103
- # It is possible to implement with loop { cycle }, but makes
104
- # testing problematic.
105
- 10.times { cycle }
106
- end
107
-
108
98
  # Loads A and S with value AND stack pointer.
109
99
  #
110
100
  # Opcodes:
111
101
  # $BB - absolute_y - 4 cycles
112
102
  def las(_addr, value)
113
- @a = @x = @stack_pointer = resolve(value) & stack_pointer
103
+ @a = @x = @stack_pointer = value & stack_pointer
114
104
  update_number_flags(@a)
115
105
  end
116
106
 
@@ -124,7 +114,7 @@ module Badline
124
114
  # $B7 - zeropage_y - 4 cycles
125
115
  # $BF - absolute_y - 4+ cycles
126
116
  def lax(_addr, value)
127
- @a = @x = resolve(value)
117
+ @a = @x = value
128
118
 
129
119
  update_number_flags(@a)
130
120
  end
@@ -136,7 +126,7 @@ module Badline
136
126
  # $AB - immediate - 2 cycles
137
127
  def lxa(_addr, value)
138
128
  magic_const = 0xee
139
- @a = @x = (a | magic_const) & resolve(value)
129
+ @a = @x = (a | magic_const) & value
140
130
  update_number_flags(@a)
141
131
  end
142
132
 
@@ -191,8 +181,7 @@ module Badline
191
181
  # Opcodes:
192
182
  # $CB - immediate - 2 cycles
193
183
  def sbx(_addr, value)
194
- v = resolve(value)
195
- result = (@x & @a) - v
184
+ result = (@x & @a) - value
196
185
  status.carry = result >= 0
197
186
  @x = result & 0xff
198
187
  update_number_flags(@x)
@@ -263,6 +252,9 @@ module Badline
263
252
 
264
253
  private
265
254
 
255
+ # A VIC stall right before the dummy read, the second-to-last cycle,
256
+ # drops the & (H+1) from the stored value but not from the high byte
257
+ # of a page-crossing target.
266
258
  def store_high_and(register, addr)
267
259
  base_high = if boundary_crossed
268
260
  (high_byte(addr) - 1) & 0xff
@@ -271,7 +263,7 @@ module Badline
271
263
  end
272
264
  result = register & ((base_high + 1) & 0xff)
273
265
  target = boundary_crossed ? uint16(low_byte(addr), result) : addr
274
- write_byte(target, result)
266
+ write_byte(target, stalled_before_previous_cycle? ? register : result)
275
267
  end
276
268
  end
277
269
  end