badline 0.3.0 → 0.4.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 (91) 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 +65 -0
  5. data/CLAUDE.md +45 -16
  6. data/CONTRIBUTING.md +95 -3
  7. data/README.md +111 -61
  8. data/SECURITY.md +9 -0
  9. data/doc/pinned-behaviour.md +275 -33
  10. data/exe/badline-ruby +59 -0
  11. data/lib/badline/address_bus.rb +22 -10
  12. data/lib/badline/audio/aiff.rb +8 -3
  13. data/lib/badline/audio/cli.rb +17 -12
  14. data/lib/badline/audio/console.rb +12 -58
  15. data/lib/badline/audio/playback.rb +3 -0
  16. data/lib/badline/audio/sdl_sink.rb +20 -52
  17. data/lib/badline/audio/terminal.rb +90 -0
  18. data/lib/badline/audio.rb +2 -2
  19. data/lib/badline/cartridge/geo_ram.rb +63 -0
  20. data/lib/badline/cartridge.rb +1 -0
  21. data/lib/badline/cia/interrupt_register.rb +34 -10
  22. data/lib/badline/cia.rb +15 -8
  23. data/lib/badline/computer.rb +28 -13
  24. data/lib/badline/gui/application.rb +33 -21
  25. data/lib/badline/gui/gamepads.rb +22 -22
  26. data/lib/badline/gui/joy_map.rb +1 -1
  27. data/lib/badline/gui/key_map.rb +6 -1
  28. data/lib/badline/gui/pane.rb +7 -6
  29. data/lib/badline/gui/screen_pane.rb +9 -13
  30. data/lib/badline/gui/window.rb +35 -20
  31. data/lib/badline/gui.rb +1 -1
  32. data/lib/badline/input/mouse1351.rb +21 -6
  33. data/lib/badline/input/paddles.rb +11 -6
  34. data/lib/badline/kernal_trap/channel.rb +3 -0
  35. data/lib/badline/kernal_trap/drive/block_commands.rb +33 -4
  36. data/lib/badline/kernal_trap/drive/status.rb +3 -0
  37. data/lib/badline/kernal_trap/drive/write_file.rb +41 -0
  38. data/lib/badline/kernal_trap/drive/writes.rb +92 -0
  39. data/lib/badline/kernal_trap/drive.rb +51 -51
  40. data/lib/badline/kernal_trap/file.rb +6 -9
  41. data/lib/badline/kernal_trap/save.rb +9 -4
  42. data/lib/badline/keyboard.rb +19 -3
  43. data/lib/badline/media.rb +42 -11
  44. data/lib/badline/options.rb +209 -0
  45. data/lib/badline/ram_expansion/banking.rb +30 -0
  46. data/lib/badline/ram_expansion/plus256k.rb +38 -0
  47. data/lib/badline/ram_expansion/plus60k.rb +31 -0
  48. data/lib/badline/ram_expansion/unexpanded.rb +22 -0
  49. data/lib/badline/ram_expansion.rb +21 -0
  50. data/lib/badline/region.rb +51 -0
  51. data/lib/badline/sdl/events.rb +47 -0
  52. data/lib/badline/sdl/functions.rb +73 -0
  53. data/lib/badline/sdl.rb +67 -0
  54. data/lib/badline/sid/waveform/noise_writeback.rb +32 -14
  55. data/lib/badline/sid/waveform.rb +1 -1
  56. data/lib/badline/sid.rb +3 -1
  57. data/lib/badline/storage/d64_image.rb +10 -0
  58. data/lib/badline/storage/d71_image.rb +12 -0
  59. data/lib/badline/storage/d81_image.rb +9 -0
  60. data/lib/badline/storage/disk_image/bam.rb +111 -0
  61. data/lib/badline/storage/disk_image/directory.rb +79 -0
  62. data/lib/badline/storage/disk_image/writing.rb +110 -0
  63. data/lib/badline/storage/disk_image.rb +27 -4
  64. data/lib/badline/storage/host_directory.rb +2 -1
  65. data/lib/badline/storage.rb +15 -0
  66. data/lib/badline/time_of_day.rb +4 -2
  67. data/lib/badline/version.rb +1 -1
  68. data/lib/badline/via/control_lines.rb +104 -0
  69. data/lib/badline/via/interrupt_register.rb +58 -0
  70. data/lib/badline/via/shift_register.rb +135 -0
  71. data/lib/badline/via/timer1.rb +82 -0
  72. data/lib/badline/via/timer2.rb +84 -0
  73. data/lib/badline/via.rb +262 -0
  74. data/lib/badline/vic/bank.rb +4 -3
  75. data/lib/badline/vic/color_patches.rb +6 -3
  76. data/lib/badline/vic/display_state.rb +4 -0
  77. data/lib/badline/vic/graphics_shifter.rb +42 -7
  78. data/lib/badline/vic/registers.rb +3 -0
  79. data/lib/badline/vic/sequencer.rb +18 -18
  80. data/lib/badline/vic/sequencer_output.rb +4 -4
  81. data/lib/badline/vic/sprite/internal_bus.rb +4 -0
  82. data/lib/badline/vic/sprite/shifter.rb +25 -3
  83. data/lib/badline/vic/sprite.rb +5 -4
  84. data/lib/badline/vic/sprites.rb +8 -3
  85. data/lib/badline/vic.rb +93 -44
  86. data/lib/badline.rb +4 -0
  87. data/packaging/homebrew/badline.rb +34 -0
  88. metadata +30 -20
  89. data/exe/badline +0 -78
  90. data/exe/badline-sid +0 -34
  91. data/lib/badline/audio/options.rb +0 -137
@@ -24,7 +24,9 @@ only catches the rows that happen to move.
24
24
  - [VIC graphics pipeline](#vic-graphics-pipeline)
25
25
  - [VIC phi1 bus](#vic-phi1-bus)
26
26
  - [VIC light pen](#vic-light-pen)
27
+ - [VIC-II 8565](#vic-ii-8565)
27
28
  - [CIA 6526 timer pipeline](#cia-6526-timer-pipeline)
29
+ - [CIA 6526A interrupt register](#cia-6526a-interrupt-register)
28
30
  - [CIA serial shift register](#cia-serial-shift-register)
29
31
  - [6510 I/O port](#6510-io-port)
30
32
  - [SID oscillator](#sid-oscillator)
@@ -400,6 +402,14 @@ only catches the rows that happen to move.
400
402
  - Spec guard: *#collide_upto* in
401
403
  [`vic/sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb). Its first
402
404
  four examples each fail on a one-pixel change.
405
+ - $D01E and $D01F are read-only. A write, including the write-back of a
406
+ read-modify-write such as `LSR $D01E`, changes neither register.
407
+ - Pinned by `general/fuxxortest/ef2-inst4a`, which shifts each frame's
408
+ collision out of $D01E with `LSR`. Storing the shifted value back
409
+ leaves bit 0 set for the next frame, which reads as a collision one
410
+ pixel to the right of every real one.
411
+ - Spec guard: *with a collision register* in
412
+ [`vic/registers_spec.rb`](../spec/badline/vic/registers_spec.rb).
403
413
 
404
414
  ## VIC border and idle state
405
415
 
@@ -576,6 +586,31 @@ only catches the rows that happen to move.
576
586
  cells take their colour from the halted opcode.
577
587
  - Spec guard: *an FLI match in column 13* in
578
588
  [`vic_spec.rb`](../spec/badline/vic_spec.rb).
589
+ - On the 6569, a match that opens display state in a g-access column, which
590
+ is a DMA delay out of idle state, moves that column's idle g-access from
591
+ `$3fff` (or `$39ff`) to `$38ff`, but only when YSCROLL is nonzero, so
592
+ only when the trigger line's low three bits are nonzero. The address wins over
593
+ ECM's `$39ff`. Nothing else enters the condition: not the column, BMM,
594
+ RC, VC or the previous fetch. The column is the one just before the
595
+ first display-state g-access. This rule is empirical and fitted to the
596
+ two tests below. It is the same condition the Denise emulator converged
597
+ on, and no hardware explanation for the YSCROLL gate is known. The
598
+ readme lists `$38ff` for every 6569 it measured. Among 8565s it lists
599
+ `$3807`, `$38c7`, `$38d7` and `$38ff`, varying from chip to chip, and no
600
+ testbench row pins one, so the 8565 keeps `$3fff` there.
601
+ `VIC::DMA_DELAY_IDLE_ADDRESS` holds the 6569's address.
602
+ - Pinned by `vsp-tester`, which triggers on raster `$32` with YSCROLL 2
603
+ and reads the address back through sprite collisions. It passes on
604
+ `$38ff`, `$3807`, `$38c7` or `$38d7`, and reports `$3fff` (exit `$ff`)
605
+ without this rule.
606
+ - Pinned by `colorfetchbug/main`, whose only idle trigger is on raster
607
+ `$30` with YSCROLL 0 and whose reference shows `$3fff` there. Without
608
+ the YSCROLL gate it fails by 7 px. `sequencer-bug` (YSCROLL 3) reads
609
+ `$38ff` and passes either way.
610
+ - Spec guard: *reads the idle byte at $38ff when YSCROLL is nonzero* and
611
+ *reads the idle byte at $3fff when YSCROLL is 0*, plus the 8565's
612
+ *reads the idle byte at $3fff when YSCROLL is nonzero*, in
613
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
579
614
  - These rows can't be read as pixel counts. The sweep became readable by
580
615
  OCRing each reference PNG against `lib/badline/roms/character.rom` and
581
616
  matching every display row back to its offset in screen memory, so a diff
@@ -734,12 +769,125 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
734
769
  reference, as `makeref` corrects it: the test fixes up the tail bytes of
735
770
  pre-R03 dumps before comparing. All five pages match, the raster-read
736
771
  pages included (see [VIC raster IRQ phase](#vic-raster-irq-phase)).
737
- - The 6569's offset is 2 half-pixels and the 8565's is 1, so
738
- `lp-trigger/test2new`, which wants the 8565, would fail by design.
739
- badline models only the 6569, and `bin/testbench` skips `vicii-new` rows.
772
+ - The 6569's offset is 2 half-pixels and the 8565's is 1 (see
773
+ [VIC-II 8565](#vic-ii-8565)).
740
774
  - Pinned by `lplatency`, `lp-trigger`, and the `fldscroll` tests, which sync
741
775
  through the light pen instead of the double IRQ.
742
776
 
777
+ ## VIC-II 8565
778
+
779
+ The 8565 (`VIC.new(model: :mos8565)`, `Computer.new(vic_model:
780
+ :mos8565)`), fitted to the C64C, runs every rule above except the ones
781
+ below. They follow VICE x64sc's model checks (`color_latency` clear), and
782
+ each is gated on the model and was knocked out: removing it fails the rows
783
+ named, all in `testbench-vicii-new`, and moves no 6569 row. Pixel counts
784
+ are the row's diff with the rule removed.
785
+
786
+ - **Grey dots.** Where the 6569 still shows a colour register's old value
787
+ on the first pixel after a write, the 8565 shows light grey
788
+ (`VIC::GREY_DOT`, `$f`), for the border, the background registers
789
+ (`VIC::ColorPatches`) and the sprite colours (`VIC#log_sprite_change`).
790
+ A write that leaves the value as it was shows its dot too.
791
+ - The background and border dot is pinned by `rmwtest` (1655 px), every
792
+ spritesplit row whose `$d021` staircase it crosses (60 px each) and
793
+ `vicii_reg_timing` (275 px, 282/289 for `-a5`/`-ff`). The sprite dot
794
+ by `ss-hires-color` and `ss-mc-color0`/`1`/`2` (44 px each) and
795
+ `vicii_reg_timing` (136 px). The same-value dot by `rmwtest` (775 px)
796
+ and `vicii_reg_timing` (22 px), whose read-modify-write instructions
797
+ store each value twice.
798
+ - Spec guard: the *on the 8565* group in
799
+ [`color_patches_spec.rb`](../spec/badline/vic/color_patches_spec.rb),
800
+ *shows a grey dot on the pixel before a new sprite color* in
801
+ [`sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb) and *shows a
802
+ grey dot for a background write of the same color* in
803
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
804
+ - **Sprite multicolour.** `$d01c` reaches the sprite sequencers a pixel
805
+ sooner, 6 pixels on instead of 7, and works on the multicolour
806
+ flip-flop (VICE `update_sprite_mc_bits_8565`). The X match leaves the
807
+ flip-flop set in hi-res as well as clear after the first multicolour
808
+ pixel. A change flips it only when it lands between the two halves of
809
+ an expanded pixel, where a switch to hi-res sets it instead. A hi-res
810
+ pixel with the flip-flop clear sets it and loads nothing, so the latch
811
+ holds its last pixel.
812
+ - The delay is pinned by `ss-hires-mc`, `ss-mc-hires` and their `-exp`
813
+ twins (528/88/1452/88 px at 7). The flip-flop after the X match by
814
+ `ss-hires-mc`, `ss-pri`, `ss-unexp-exp-hires` (168 px each) and
815
+ `ss-hires-mc-exp`, `ss-pri-exp`, `ss-exp-unexp-hires` (336 px each).
816
+ The flip on a change by `ss-hires-mc-exp` (1364 px) and
817
+ `ss-mc-hires-exp` (44 px), and the hi-res hold by `ss-mc-hires` and
818
+ `ss-mc-hires-exp` (44 px each).
819
+ - Spec guard: *reaches the multicolor flip-flop six pixels on* in
820
+ [`sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb) and the *with
821
+ an 8565* group of *mid-line writes* in
822
+ [`sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
823
+ - **The g-access addresses with the `$d011` of the column before**,
824
+ whole, instead of the 6569's hold of BMM and, on the character ROM, ECM.
825
+ - Pinned by `modesplit` (598 px), `vicii_reg_timing` (92, 99/106 px),
826
+ `fetchsplit` (180 px) and every videomode row but `videomode1` (3 to
827
+ 16 px).
828
+ - Spec guard: *addresses with the $d011 of the column before* in
829
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
830
+ - **ECM and BMM take hold at the next group's pixel 0**, rising or
831
+ falling, where the 6569 takes them at pixel 4 as they rise and 5 or 6 as
832
+ they fall. MCM keeps the 6569's timing, but an MCM that falls out of
833
+ ECM+MCM does so on time instead of a pixel late.
834
+ - The pixel 0 is pinned by `modesplit` (916 px), `vicii_reg_timing` (86,
835
+ 100/114 px) and every videomode row (6 to 13 px). The on-time MCM by
836
+ `videomode1` and `videomode-z` (2 px each).
837
+ - Spec guard: the *on the 8565* group in
838
+ [`graphics_shifter_spec.rb`](../spec/badline/vic/graphics_shifter_spec.rb).
839
+ - **Out of an invalid mode into multicolour bitmap, a background pixel 0
840
+ is still black, and into hi-res text any pixel 0 is.** Into multicolour
841
+ text and ECM text it shows on time.
842
+ - The multicolour bitmap case is pinned by `videomode-v` and `videomode2`
843
+ (1 px each) and `modesplit` (92 → 124 px), whose `%00` and `%01` pairs
844
+ after an ECM+MCM or ECM+BMM+MCM split keep their first pixel black.
845
+ `videomode-y` shows a `%01` pair on time into multicolour text.
846
+ - The hi-res text case is empirical, pinned by the E+B row of
847
+ `vicii_reg_timing-a5` and `-ff` (pass and 7 px, 7 and 14 px without
848
+ it), which drops ECM+BMM back to text. All three `vicii_reg_timing`
849
+ references keep that pixel 0 black, a foreground pixel in `-a5` and
850
+ `-ff`. `$d021` is black there, so no reference tells a background
851
+ pixel 0 apart, and the rule blackens both.
852
+ - `videomode-w` shows ECM+BMM into ECM text on time (1 px if it goes
853
+ black), while `modesplit`'s section 1 keeps the same move black (48 of
854
+ its 92 px). The two references disagree about the same move, and the
855
+ videomode one is kept.
856
+ - Spec guard: *keeps a background pixel 0 black out of an invalid mode
857
+ into multicolour bitmap*, *keeps pixel 0 black out of an invalid mode
858
+ into hi-res text*, *shows pixel 0 on time out of an invalid mode into
859
+ multicolour text* and *shows pixel 0 on time out of an invalid mode into
860
+ ECM text* in
861
+ [`graphics_shifter_spec.rb`](../spec/badline/vic/graphics_shifter_spec.rb).
862
+ - **The light pen latches one extra half-pixel**, where the 6569 adds two.
863
+ - Pinned by `lp-trigger/test2new`, which measures the trigger delay and
864
+ fails with two.
865
+ - Spec guard: *adds one extra half-pixel on the 8565* in
866
+ [`vic_spec.rb`](../spec/badline/vic_spec.rb).
867
+
868
+ What the 8565 references don't settle, and so what stays as it is:
869
+
870
+ - The pixel 0 of a group that leaves ECM or BMM is not consistent across
871
+ the references. Moving every falling edge to pixel 1 passes `modesplit`
872
+ but fails every videomode row but `videomode1` and all three
873
+ `vicii_reg_timing` rows, and the same move out of hi-res bitmap into
874
+ text is the new mode in `vicii_reg_timing-a5` and the old one in `-ff`.
875
+ The videomode readme says these delays "may depend on the type of VICII,
876
+ and the temperature of the chip". So `modesplit` (92 px) and
877
+ `vicii_reg_timing-ff` (7 px, the hi-res bitmap into text row) stay FAIL.
878
+ No emulator in the testbench results passes `vicii_reg_timing-a5` or
879
+ `-ff` on the 8565, and only Hoxs64 passes `modesplit`, against the
880
+ `8565early` reference.
881
+ - `fetchsplit` (154 px) stays FAIL: its readme says its 8565 artefacts
882
+ differ from chip to chip and change as the machine warms up, and its
883
+ `M1` and `M7` captures differ. The differences sit in the first character
884
+ after a `$dd00` bank switch on some lines. Delaying every rising, or
885
+ every falling, bank bit by a cycle on the 8565 makes it 656 or 752 px.
886
+ - VICE's idle g-access also reads ECM from the `$d011` of the column
887
+ before on the 8565. No testprog tells it apart, so the 8565 keeps the
888
+ 6569's idle access, without the 6569's `$38ff` read where a DMA delay
889
+ starts.
890
+
743
891
  ## CIA 6526 timer pipeline
744
892
 
745
893
  - Start and stop go through two stages. A force load lands one tick after
@@ -771,13 +919,17 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
771
919
  `CIA/ciavarious/cia3` K/L, `cia3a` D/H, `cia4` X, `cia8` A/C/F/J/L
772
920
  (the readme's old-versus-new CIA cells) and `CIA/cia-timer/cia-timer-oldcias`,
773
921
  and ported from VICE's `CIA_IM_TBB`.
774
- - The modelled revision is the **6526**, not the 6526A, matching
775
- `Lorenz.d81`. That is all the `(*1)` cells of `cia1ta`/`cia1tb` measure,
776
- and it is an ICR difference, not a counter one. Those cells read the ICR
777
- on the very cycle the underflow flag rises, so the source bit is up while
778
- IR is not: they read `$01`/`$02`, where a 6526A reads `$81`/`$82`. Counter
779
- readback is identical on both revisions. `Lorenznew.d81` expects the
780
- 6526A and must not be mixed in.
922
+ - Every rule in this section is the **6526**'s, the chip `CIA.new`
923
+ builds unless given `model: :mos6526a`, and the one `Lorenz.d81`
924
+ expects. That is all the `(*1)` cells of `cia1ta`/`cia1tb` measure, and
925
+ it is an ICR difference, not a counter one. Those cells read the ICR on
926
+ the very cycle the underflow flag rises, so the source bit is up while
927
+ IR is not: they read `$01`/`$02`, where a 6526A reads `$81`/`$82`.
928
+ Counter readback is identical on both revisions. `Lorenznew.d81` expects
929
+ the 6526A and must not be mixed into the `lorenz` chain. Its tests run
930
+ one at a time on a 6526A machine in `testbench-cia-new` instead. See
931
+ [CIA 6526A interrupt register](#cia-6526a-interrupt-register) for what
932
+ changes.
781
933
  - Timer B's cascade decodes CRB, not CRA. Every count source (ø2, a CNT
782
934
  edge, a cascaded timer A underflow) drives the same two-stage
783
935
  count-enable pipeline, so a timer A underflow decrements timer B two
@@ -805,6 +957,65 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
805
957
  acknowledge* and *6526 timer B bug* groups guard the three ICR rules
806
958
  above, on a bare CIA.
807
959
 
960
+ ## CIA 6526A interrupt register
961
+
962
+ The 6526A (`CIA.new(model: :mos6526a)`, `Computer.new(cia_model:
963
+ :mos6526a)`), fitted to the C64C, differs from the 6526 only in its
964
+ interrupt control register. Its timers and shift register run the rules
965
+ above unchanged: every `testbench-cia-new` row passes on the 6526's
966
+ timer and serial code, the long counter sweeps `cia1tanew`, `cia1tbnew`,
967
+ `cia2tanew` and `cia2tbnew` included. VICE's `ciatimer.c` has no model
968
+ check either. Each rule below is gated on the model in
969
+ [`cia/interrupt_register.rb`](../lib/badline/cia/interrupt_register.rb),
970
+ and each was knocked out: removing it fails the rows named.
971
+
972
+ - **IR on the flag's cycle.** A source flag whose mask bit is set raises
973
+ IR on the cycle the flag rises, not a cycle later. The exception is a
974
+ flag that rises on the cycle after an ICR read: that one raises IR a
975
+ cycle later, as on the 6526 (VICE's `rdi + 1 == rclk`).
976
+ - The early IR is pinned by `CIA/irqdelay/irqdelay-new`,
977
+ `irqdelay-oneshot-new`, `irqdelay2-new`, `irqdelay-cia1-4-new` and
978
+ `irqdelay-cia1-oneshot-4-new`, `interrupts/branchquirk/branchquirk-new`
979
+ and `branchquirk-nminew`, `interrupts/cia-int/cia-int-irq-new` and
980
+ `cia-int-nmi-new`, `interrupts/irq-ackn-bug/cia1new` and `cia2new`,
981
+ `interrupts/irqnmi/irqnmi-new`, `CIA/CIA-AcountsB/cmp-b-counts-a-new`,
982
+ `CIA/cia-timer/cia-timer-newcias`, `CIA/timerbasics/timer_test1_new`
983
+ and `CIA/dd0dtest/dd0dtest`.
984
+ - The exception after a read is pinned by `cia-int-irq-new`,
985
+ `cia-int-nmi-new` and `dd0dtest` test 17, which fail when every flag
986
+ raises IR at once.
987
+ - **A read sees an IR about to rise.** A read on the cycle before IR is
988
+ due returns IR set, and cancels the assert as on the 6526. On the 6526,
989
+ only a read on the next cycle sees it.
990
+ - Pinned by `dd0dtest` test 17. The two reads of `inc $dd0d,x` land on
991
+ the cycles before and of timer A's flag, so the second sees `$81` and
992
+ the RMW writes `$81`/`$82` back, arming timer B, whose NMI handler
993
+ then reads `$82`. Without the rule the second read sees `$01`, timer B
994
+ stays masked and no NMI comes.
995
+ - **The acknowledge holds every bit.** A read releases the interrupt line
996
+ at once, but the bits it read, sources and IR alike, stay readable on
997
+ the next cycle, along with anything flagged since. They clear the cycle
998
+ after that. The 6526 holds only IR (see *Old-CIA acknowledge* above).
999
+ - Pinned by `dd0dtest` tests 18 and 19. Their `inc $dd0d,x` reads `$81`
1000
+ on the flag cycle and again on the next cycle, where a 6526 reads
1001
+ `$80`, so the RMW writes `$82` and arms timer B.
1002
+ - **A mask write raises IR a cycle later**, where the 6526 takes two.
1003
+ - Pinned by Lorenz `imrnew`.
1004
+ - **No timer B bug.** An underflow on the cycle after an ICR read keeps
1005
+ its flag.
1006
+ - Pinned by `CIA/ciavarious/cia3new`, `cia3anew`, `cia4new` and
1007
+ `cia8new` (the readme's old-versus-new cells) and
1008
+ `CIA/cia-timer/cia-timer-newcias`.
1009
+ - Two of VICE's model checks are left out, because no testprog tells
1010
+ them apart. VICE stops a mask write from raising IR on the cycle after
1011
+ an ICR read, and applies the 6526's mask cancel (*Old-CIA mask cancel*
1012
+ above) only to the 6526. Adding the first, or dropping the second for
1013
+ the 6526A, passes every `testbench-cia-new` row, so the 6526A keeps the
1014
+ 6526's behaviour on both.
1015
+ - Spec guard: *the 6526A interrupt register* in
1016
+ [`cia_spec.rb`](../spec/badline/cia_spec.rb), one example per rule,
1017
+ each failing when its rule is knocked out.
1018
+
808
1019
  ## CIA serial shift register
809
1020
 
810
1021
  - The register counts itself empty on the 15th timer A underflow, when the
@@ -870,6 +1081,15 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
870
1081
  before the KERNAL does.
871
1082
  - Spec guard: *when powered on* in
872
1083
  [`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
1084
+ - A write to `$00` or `$01` goes to the port, and the RAM below takes the
1085
+ byte the VIC fetched in the phi1 half of the same cycle, as in VICE's
1086
+ `zero_store`. Only the VIC reads that RAM.
1087
+ - Pinned by `general/ram0001/test1`, which puts the byte in `$3fff` for
1088
+ the idle fetch, and `general/fuxxortest/ef2-inst4a`, which uses two
1089
+ sprite pointer fetches. Both read the RAM back through sprite
1090
+ collisions.
1091
+ - Spec guard: *when a program writes to the port* in
1092
+ [`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
873
1093
 
874
1094
  ## SID oscillator
875
1095
 
@@ -914,6 +1134,15 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
914
1134
  [`sid_spec.rb`](../spec/badline/sid_spec.rb): a catch-up that
915
1135
  fast-forwards across a rise carries the shift still in flight into the
916
1136
  next span.
1137
+ - Setting the test bit onto noise combined with another waveform writes
1138
+ that combination's output back into the LFSR first, from the accumulator
1139
+ it had, before the bit clears it.
1140
+ - Pinned by `SID/noiselfsrinit`'s `simple` and `scan` (both chips). Their
1141
+ `$f8`/`$80` pairs set the test bit onto all four waveforms from noise
1142
+ alone, and zero the register only if this writes back. The release that
1143
+ follows goes to noise alone, which writes nothing back (below).
1144
+ - Spec guard: *as the test bit rises* in
1145
+ [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
917
1146
  - The test bit does not clear the LFSR. It stalls it halfway through a
918
1147
  shift with bit 22 forced high. While the bit is held, every bit bleeds up
919
1148
  to `$7fffff`: after `$950000` cycles on the 8580 (`SID/bitfade`'s
@@ -933,28 +1162,33 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
933
1162
  that varies with the chip's temperature (`SID/wf12nsr`'s
934
1163
  `quicktest.prg` on a 6581), which nothing scored depends on.
935
1164
  - Spec guard: *bleeding through a held test bit* in
936
- [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb). Before it does, the old waveform's output is
1165
+ [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
1166
+ - Before the bit clocks in on release, the old waveform's output is
937
1167
  written back only for some waveform changes. Noise has to have been
938
- combined before the release and still be selected after it. A change to
939
- noise alone writes nothing back unless all four waveforms were selected
940
- before. A change to pulse+noise writes nothing back, nor does a change
941
- from pulse+noise to sawtooth+noise. On the 6581, trading triangle for
942
- sawtooth or back writes nothing back. The rule follows libresidfp's
943
- `do_writeback`. What is written is the writeback shape below, not the
944
- OSC3 read. One more case is not in that rule: on the 6581, a change that
945
- keeps only pulse+noise of the old waveform (`D`/`E`/`F`→`C`, `D`→`E`)
946
- writes the three lowest noise lines low (`$f8`).
947
- - Pinned by `SID/wb_testsuite` (the `9`/`A`/`D`/`E`→`8` rows on both
1168
+ combined before the release and still be combined after it, so a change
1169
+ to noise alone writes nothing back. A change to pulse+noise writes
1170
+ nothing back, nor does a change from pulse+noise to sawtooth+noise. On
1171
+ the 6581, trading triangle for sawtooth or back writes nothing back. The
1172
+ rule follows libresidfp's `do_writeback`. What is written is the
1173
+ writeback shape below, not the OSC3 read. Two more cases are not in that
1174
+ rule. On the 6581, a change that keeps only pulse+noise of the old
1175
+ waveform (`D`/`E`/`F`→`C`, `D`→`E`) writes the three lowest noise lines
1176
+ low (`$f8`). On the 8580, a change from pulse+noise to all four
1177
+ (`C`→`F`) writes every line low.
1178
+ - Pinned by `SID/wb_testsuite` (the `9`/`A`/`D`/`E`/`F`→`8` rows on both
948
1179
  chips, and the 6581's `9`↔`A`, `9`/`A`→`C` and `D`→`A` rows) and by
949
1180
  `SID/noisewriteback`'s `noise_writeback_test1`. The 6581's `$f8` case
950
1181
  is pinned by its `D`→`C`, `D`→`E`, `E`→`C` and `F`→`C` rows, and
951
1182
  dropping it fails all four. The 8580's `C`→`A` row pins the
952
1183
  pulse+noise to sawtooth+noise exception: without it the release writes
953
- the 8580's `$fc` and the row fails.
954
- - Known conflict: the 6581's `F`→`8` row expects no writeback, but
955
- `SID/noiselfsrinit`'s `simple` and `scan` zero the register with
956
- `$f8`/`$80` pairs, which needs `F`→`8` to write back. The row stays
957
- failing.
1184
+ the 8580's `$fc` and the row fails. The 8580's `C`→`F` row pins the
1185
+ all-low case: writing the old waveform's `$fc` back makes the first read
1186
+ `$fc`, where the row expects the `$6c` that `F`→`F` reads. Both 8580
1187
+ samplings in `SID/wb_testsuite/samplings` read `$6c` for `C`→`F`.
1188
+ - `F`→`8` wrote back until the test bit's rise (above) took over
1189
+ zeroing the register for `SID/noiselfsrinit`. Every sampling in
1190
+ `SID/wb_testsuite/samplings`, both chips, reads `F`→`8` as a plain
1191
+ shift.
958
1192
  - Spec guard: *as the test bit falls* in
959
1193
  [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
960
1194
  - A combined waveform shorts the shapers onto the lines the oscillator reads
@@ -998,11 +1232,15 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
998
1232
  fails wfc and the `C`→`9`/`C`→`E` rows; applying the lone-line rule to
999
1233
  the read as well makes `noise_writeback_test2` read `$00` on both
1000
1234
  chips.
1001
- - Not fitted: the 6581's pulse+noise row of `SID/wf12nsr`. The program
1002
- never sets voice 3's pulse width, and the 6581 reference reads as if
1003
- the pulse was low when the test bit fell, grounding every line. From
1004
- power-on the width is zero and the pulse high. Forcing a width of
1005
- `$800` before the row makes it pass.
1235
+ - Can't pass: the 6581's pulse+noise row of `SID/wf12nsr`. The program
1236
+ never writes voice 3's pulse width, so the row depends on whatever width
1237
+ the machine it was recorded on held. The 6581 reference reads `$00`
1238
+ right after the release into pulse+noise at frequency `$ffff`, as if
1239
+ the pulse was still low and grounding every line. After power-on or
1240
+ reset the width is zero and the pulse is high. With the width set
1241
+ before the program runs, the row passes from `$1a0` up and fails from
1242
+ `$198` down. The 8580 program passes from a zero width. `SID/wf12nsr`'s `quicktest.prg` doesn't set the width
1243
+ either. The row stays failing.
1006
1244
  - Spec guard: *writes a lone line of a noise combination back low*, *with
1007
1245
  pulse+noise on the 8580* and *as the test bit falls* in
1008
1246
  [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
@@ -1110,9 +1348,13 @@ VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
1110
1348
  - A tune's init and play routines must run with the ROMs banked to match
1111
1349
  the address they live at, following libsidplayfp's iomap: `$37` below
1112
1350
  `$a000`, `$36` under BASIC, `$34` in the `$d000` I/O window, `$35` under
1113
- the KERNAL. Restore `$01` afterwards so the caller's banking survives.
1114
- This entry is the rule, and it should outlive whichever code carries it.
1351
+ the KERNAL. A routine below `$a000` gets `$36` too when the tune's image
1352
+ reaches under BASIC, as VSID does, since it may call into that part.
1353
+ Restore `$01` afterwards so the caller's banking survives. This entry is
1354
+ the rule, and it should outlive whichever code carries it.
1115
1355
  - Derived against six OneLoad64 tunes (Galway, Tel, Gray, Dunn, Cooksey).
1356
+ Green Beret's init sits at `$9fe3` and calls its own code at `$aaca`,
1357
+ which forces the `$36` below `$a000`.
1116
1358
  `SIDFile#bank_for` is the one copy of the map: `Driver` wraps both calls
1117
1359
  in a `$01` save/bank/restore, and `BarePlayer#dispatch` pokes it before
1118
1360
  handing the CPU the stub.
data/exe/badline-ruby ADDED
@@ -0,0 +1,59 @@
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/options"
8
+
9
+ begin
10
+ options = Badline::Options.parse(ARGV)
11
+ rescue Badline::Options::Error => e
12
+ warn "badline-ruby: #{e.message}"
13
+ warn "Try 'badline-ruby --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
+ if options.headless?
28
+ begin
29
+ Badline::Audio::CLI.new(options, sink: Badline::Audio::SDLSink.method(:new),
30
+ console: Badline::Audio::Console.method(:new)).run
31
+ rescue Badline::Audio::CLI::Error => e
32
+ warn "badline-ruby: #{e.message}"
33
+ exit 1
34
+ rescue Interrupt
35
+ exit 130
36
+ end
37
+ exit
38
+ end
39
+
40
+ begin
41
+ require "badline/gui"
42
+ rescue Badline::SDL::Error => e
43
+ warn "badline-ruby: can't open the window: #{e.message}. " \
44
+ "Install SDL2 with 'brew install sdl2' or 'apt install libsdl2-2.0-0'."
45
+ exit 1
46
+ end
47
+
48
+ begin
49
+ application = Badline::GUI::Application.new(media_path: options.media_path, autostart: options.autostart?,
50
+ song: options.song, sid_model: options.sid_model,
51
+ sound: options.sound?, disk: { read_only: options.read_only? })
52
+ rescue Badline::Storage::SIDFile::FormatError, Badline::Storage::T64::FormatError,
53
+ Badline::Storage::TAP::FormatError, Badline::Storage::CRTFile::FormatError,
54
+ Badline::Cartridge::UnsupportedTypeError => e
55
+ warn "badline-ruby: #{options.media_path}: #{e.message}"
56
+ exit 1
57
+ end
58
+
59
+ application.run
@@ -49,10 +49,14 @@ module Badline
49
49
 
50
50
  attr_reader :io_port, :ram, :basic_rom, :character_rom, :kernal_rom,
51
51
  :vic, :sid, :color_ram, :cia1, :cia2, :keyboard, :joystick1, :joystick2,
52
- :control_ports, :cartridge, :ultimax, :phi1_ultimax, :datasette
52
+ :control_ports, :cartridge, :ultimax, :phi1_ultimax, :datasette, :region, :video_ram
53
53
 
54
- def initialize(sid_model: :mos6581)
54
+ # ram_expansion fits a +60K (:plus60k) or +256K (:plus256k).
55
+ def initialize(sid_model: :mos6581, cia_model: :mos6526, vic_model: :mos6569, region: Region::PAL,
56
+ ram_expansion: nil)
57
+ @region = region
55
58
  @ram = Memory.new(RAM_POWER_ON, length: 2**16, start: 0)
59
+ @ram_expansion = RAMExpansion.build(ram_expansion, @ram) { update_overlays! }
56
60
  @cartridge = nil
57
61
  @debug_register = nil
58
62
 
@@ -64,9 +68,9 @@ module Badline
64
68
  @joystick1 = Joystick.new
65
69
  @joystick2 = Joystick.new
66
70
  @control_ports = ControlPorts.new(keyboard: @keyboard, joystick1: @joystick1, joystick2: @joystick2)
67
- @vic = VIC.new(self)
68
- @cia1 = CIA.new(start: 0xdc00, peripheral: @control_ports)
69
- @cia2 = CIA.new(start: 0xdd00)
71
+ @vic = VIC.new(self, model: vic_model, region:)
72
+ @cia1 = CIA.new(start: 0xdc00, peripheral: @control_ports, model: cia_model, region:)
73
+ @cia2 = CIA.new(start: 0xdd00, model: cia_model, region:)
70
74
  @control_ports.port_a_source = @cia1
71
75
  @cia1.on_port_b4_change { |high| @vic.lightpen_level(high) }
72
76
  @sid = SID.new(model: sid_model, pots: @control_ports)
@@ -97,11 +101,14 @@ module Badline
97
101
 
98
102
  def power_on!
99
103
  @ram.clear!(RAM_POWER_ON)
104
+ @ram_expansion.power_on!
100
105
  end
101
106
 
102
- # The 6510's RES line clears the port's direction and output registers.
103
- # The floating bits keep their charge.
104
- def reset_port!
107
+ # The RES line clears the 6510 port's direction and output registers,
108
+ # and a RAM expansion's bank register. The port's floating bits keep
109
+ # their charge.
110
+ def reset!
111
+ @ram_expansion.reset!
105
112
  @port_ddr = 0x00
106
113
  @port_out = 0x00
107
114
  update_port!
@@ -124,8 +131,12 @@ module Badline
124
131
  @read_pages[addr >> 8].peek(addr)
125
132
  end
126
133
 
134
+ # A write to $00 or $01 goes to the port, but the RAM below is still
135
+ # write-enabled, and takes whatever is left on the bus: the byte the VIC
136
+ # fetched in the phi1 half of the cycle.
127
137
  def poke(addr, value)
128
138
  if addr < 0x02
139
+ @ram.poke(addr, @vic.phi1_data)
129
140
  addr.zero? ? @port_ddr = value : @port_out = value
130
141
  update_port!
131
142
  else
@@ -162,8 +173,8 @@ module Badline
162
173
  def update_overlays!
163
174
  @ultimax = @cartridge ? @cartridge.ultimax? : false
164
175
  @phi1_ultimax = @cartridge ? @cartridge.phi1_ultimax? : false
165
- @read_pages.fill(@ram)
166
- @write_pages.fill(@ram)
176
+ @ram_expansion.map(@read_pages, @write_pages)
177
+ @video_ram = @ram_expansion.video_ram
167
178
 
168
179
  @ultimax ? map_ultimax_pages : map_banked_pages
169
180
  end
@@ -238,6 +249,7 @@ module Badline
238
249
  }.each do |chip, pages|
239
250
  pages.each { |p| @read_pages[p] = @write_pages[p] = chip }
240
251
  end
252
+ @ram_expansion.map_io(@read_pages, @write_pages)
241
253
  @read_pages[0xd7] = @write_pages[0xd7] = @debug_register if @debug_register
242
254
  map_cartridge_io if @cartridge
243
255
  end
@@ -25,11 +25,16 @@ module Badline
25
25
  end
26
26
 
27
27
  # Sign bit, 15-bit biased exponent, then a 64-bit mantissa that keeps
28
- # its leading one rather than hiding it.
28
+ # its leading one rather than hiding it, packed as two 32-bit halves.
29
29
  def extended(value)
30
30
  exponent = EXPONENT_BIAS + value.bit_length - 1
31
- mantissa = value << (64 - value.bit_length)
32
- [exponent, mantissa >> 32, mantissa & 0xffff_ffff].pack("nNN")
31
+ [exponent, *mantissa(value, 64 - value.bit_length)].pack("nNN")
32
+ end
33
+
34
+ def mantissa(value, shift)
35
+ return [value << (shift - 32), 0] if shift >= 32
36
+
37
+ [value >> (32 - shift), (value & ((1 << (32 - shift)) - 1)) << shift]
33
38
  end
34
39
  end
35
40
  end
@@ -2,19 +2,24 @@
2
2
 
3
3
  module Badline
4
4
  module Audio
5
- # Runs `badline-sid` once its options have parsed: picks the song and
6
- # its length, then plays it or renders it to a file. Played on a
7
- # terminal, the console lets the listener step between songs.
5
+ # Runs `badline-ruby --headless` or `--audio-out` once its options have
6
+ # parsed: picks the song and its length, then plays it or renders it
7
+ # to a file. Played on a terminal, the console lets the listener step
8
+ # between songs.
8
9
  class CLI
9
10
  class Error < StandardError; end
10
11
 
11
- # `sink` builds the audio device for playback, given the rate to ask
12
- # for and whether it has to be exact.
13
- def initialize(options, out: $stdout, input: $stdin, sink: SDLSink.method(:new))
12
+ # Playing needs `sink`, which builds the audio device given the rate
13
+ # to ask for and whether it has to be exact, and, on a terminal,
14
+ # `console`, which builds the terminal to play on given the input and
15
+ # output. The front end hands them in, so the native badline can hand
16
+ # in its own: badline-ruby's are SDLSink and Console.
17
+ def initialize(options, sink: nil, console: nil, out: $stdout, input: $stdin)
14
18
  @options = options
15
19
  @out = out
16
20
  @input = input
17
21
  @sink = sink
22
+ @console = console
18
23
  @lengths = {}
19
24
  end
20
25
 
@@ -39,7 +44,7 @@ module Badline
39
44
  def seconds = length(song)
40
45
 
41
46
  def length(song)
42
- @lengths[song] ||= @options.seconds || songlength(song) || Options::FALLBACK_SECONDS
47
+ @lengths[song] ||= @options.seconds || songlength(song) || @options.fallback_seconds
43
48
  end
44
49
 
45
50
  def sid_model = @options.sid_model || tune.sid_model
@@ -57,9 +62,9 @@ module Badline
57
62
  def render
58
63
  renderer = renderer(song, @options.rate)
59
64
  @out.puts describe
60
- @out.puts "Rendering #{seconds}s for the #{model_name} to #{@options.output} at #{@options.rate} Hz..."
65
+ @out.puts "Rendering #{seconds}s for the #{model_name} to #{@options.audio_out} at #{@options.rate} Hz..."
61
66
  started = now
62
- renderer.render(@options.output) { |done| progress(done) }
67
+ renderer.render(@options.audio_out) { |done| progress(done) }
63
68
  report(now - started)
64
69
  end
65
70
 
@@ -81,7 +86,7 @@ module Badline
81
86
  end
82
87
 
83
88
  def play_interactively(sink)
84
- console = Console.new(input: @input, output: @out)
89
+ console = @console.call(input: @input, output: @out)
85
90
  jukebox = Jukebox.new(sink, console, songs: tune.songs, renderer: method(:renderer), length: method(:length))
86
91
  console.session do
87
92
  console.header([tune.name, tune.author, tune.released].reject(&:empty?) +
@@ -92,7 +97,7 @@ module Badline
92
97
 
93
98
  def open_sink
94
99
  @sink.call(rate: @options.rate, exact_rate: @options.rate_given?)
95
- rescue SDLSink::Error => e
100
+ rescue Playback::DeviceError => e
96
101
  raise Error, "can't open the audio device: #{e.message}"
97
102
  end
98
103
 
@@ -124,7 +129,7 @@ module Badline
124
129
  def report(elapsed)
125
130
  @out.print "\r" unless @options.quiet?
126
131
  @out.puts format("Wrote %<path>s in %<elapsed>.1fs (%<speed>.2fx real time).",
127
- path: @options.output, elapsed:, speed: seconds / elapsed)
132
+ path: @options.audio_out, elapsed:, speed: seconds / elapsed)
128
133
  end
129
134
 
130
135
  def now = Process.clock_gettime(Process::CLOCK_MONOTONIC)