badline 0.11.0 → 0.12.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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/CLAUDE.md +24 -6
  4. data/CONTRIBUTING.md +15 -9
  5. data/README.md +16 -5
  6. data/doc/media.md +28 -1
  7. data/doc/pinned-behaviour.md +31 -1
  8. data/exe/badline-ruby +1 -1
  9. data/lib/badline/c128/bus.rb +4 -17
  10. data/lib/badline/c128/drives.rb +108 -0
  11. data/lib/badline/c128/fast_serial.rb +63 -0
  12. data/lib/badline/c128/io_pages.rb +35 -0
  13. data/lib/badline/c128/mmu.rb +24 -7
  14. data/lib/badline/c128/modes.rb +17 -4
  15. data/lib/badline/c128/saved_state.rb +23 -0
  16. data/lib/badline/c128/z80.rb +49 -0
  17. data/lib/badline/c128/z80_pages.rb +118 -0
  18. data/lib/badline/c128/z80_turns.rb +106 -0
  19. data/lib/badline/c128.rb +25 -26
  20. data/lib/badline/cartridge/action_replay.rb +2 -2
  21. data/lib/badline/cartridge/comal80.rb +1 -7
  22. data/lib/badline/cartridge/dinamic.rb +0 -8
  23. data/lib/badline/cartridge/final_cartridge3.rb +6 -6
  24. data/lib/badline/cartridge/fun_play.rb +0 -8
  25. data/lib/badline/cartridge/game_system.rb +0 -8
  26. data/lib/badline/cartridge/kcs_power.rb +10 -7
  27. data/lib/badline/cartridge/mach5.rb +2 -4
  28. data/lib/badline/cartridge/magic_desk.rb +0 -8
  29. data/lib/badline/cartridge/ocean.rb +0 -8
  30. data/lib/badline/cartridge/pagefox.rb +2 -2
  31. data/lib/badline/cartridge/retro_replay.rb +7 -6
  32. data/lib/badline/cartridge/rex_utility.rb +2 -6
  33. data/lib/badline/cartridge/rgcd.rb +2 -2
  34. data/lib/badline/cartridge/simons_basic.rb +0 -5
  35. data/lib/badline/cartridge/super_games.rb +4 -2
  36. data/lib/badline/cartridge/westermann.rb +2 -6
  37. data/lib/badline/cartridge.rb +37 -9
  38. data/lib/badline/cia/fast_forward.rb +60 -0
  39. data/lib/badline/cia/serial.rb +12 -0
  40. data/lib/badline/cia/timer.rb +39 -0
  41. data/lib/badline/cia.rb +10 -5
  42. data/lib/badline/computer/attachments.rb +4 -50
  43. data/lib/badline/computer/true_drives.rb +80 -0
  44. data/lib/badline/computer.rb +4 -0
  45. data/lib/badline/core.rb +2 -0
  46. data/lib/badline/drive/core.rb +147 -0
  47. data/lib/badline/drive/fast_serial.rb +73 -0
  48. data/lib/badline/{drive1541 → drive}/idle.rb +18 -29
  49. data/lib/badline/drive/idle_cpu.rb +28 -0
  50. data/lib/badline/{drive1541 → drive}/orbit.rb +3 -5
  51. data/lib/badline/drive/serial_lines.rb +73 -0
  52. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -5
  53. data/lib/badline/drive/vias.rb +84 -0
  54. data/lib/badline/drive/watch.rb +150 -0
  55. data/lib/badline/drive.rb +11 -0
  56. data/lib/badline/drive1541/bus.rb +5 -129
  57. data/lib/badline/drive1541/byte_ready.rb +48 -0
  58. data/lib/badline/drive1541/cpu.rb +1 -19
  59. data/lib/badline/drive1541/disk.rb +77 -20
  60. data/lib/badline/drive1541/disk_state.rb +7 -2
  61. data/lib/badline/drive1541/disk_via.rb +3 -0
  62. data/lib/badline/drive1541/mechanism.rb +48 -14
  63. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  64. data/lib/badline/drive1541/rotation.rb +5 -5
  65. data/lib/badline/drive1541/serial_port.rb +4 -60
  66. data/lib/badline/drive1541.rb +14 -172
  67. data/lib/badline/drive1571/bus.rb +111 -0
  68. data/lib/badline/drive1571/cpu.rb +11 -0
  69. data/lib/badline/drive1571/serial_port.rb +37 -0
  70. data/lib/badline/drive1571/serial_via.rb +25 -0
  71. data/lib/badline/drive1571/wd1770.rb +120 -0
  72. data/lib/badline/drive1571.rb +175 -0
  73. data/lib/badline/drive1581/bus.rb +137 -0
  74. data/lib/badline/drive1581/cpu.rb +11 -0
  75. data/lib/badline/drive1581/disk.rb +135 -0
  76. data/lib/badline/drive1581/disk_state.rb +87 -0
  77. data/lib/badline/drive1581/mechanism.rb +117 -0
  78. data/lib/badline/drive1581/serial_port.rb +52 -0
  79. data/lib/badline/drive1581/slot.rb +78 -0
  80. data/lib/badline/drive1581/track.rb +100 -0
  81. data/lib/badline/drive1581/track_bytes.rb +75 -0
  82. data/lib/badline/drive1581/wd1772/registers.rb +89 -0
  83. data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
  84. data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
  85. data/lib/badline/drive1581/wd1772/state.rb +66 -0
  86. data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
  87. data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
  88. data/lib/badline/drive1581/wd1772/turn.rb +73 -0
  89. data/lib/badline/drive1581/wd1772.rb +216 -0
  90. data/lib/badline/drive1581.rb +233 -0
  91. data/lib/badline/frontend/app.rb +2 -4
  92. data/lib/badline/frontend/audio_device.rb +67 -0
  93. data/lib/badline/frontend/audio_sink.rb +16 -50
  94. data/lib/badline/frontend/boot.rb +18 -10
  95. data/lib/badline/frontend/buttons.rb +7 -7
  96. data/lib/badline/frontend/drive_led.rb +1 -1
  97. data/lib/badline/frontend/keys.rb +33 -8
  98. data/lib/badline/frontend/media_slots.rb +42 -0
  99. data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
  100. data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
  101. data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
  102. data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
  103. data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +7 -9
  104. data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
  105. data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
  106. data/lib/badline/frontend/menu/theme.rb +18 -0
  107. data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
  108. data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
  109. data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
  110. data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
  111. data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
  112. data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
  113. data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
  114. data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
  115. data/lib/badline/frontend/player/theme.rb +18 -0
  116. data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
  117. data/lib/badline/frontend/sound.rb +19 -38
  118. data/lib/badline/frontend/timeline.rb +3 -14
  119. data/lib/badline/frontend.rb +24 -20
  120. data/lib/badline/iec_bus.rb +68 -6
  121. data/lib/badline/machine.rb +9 -6
  122. data/lib/badline/media/boot_disk.rb +50 -0
  123. data/lib/badline/media/not_disk.rb +8 -0
  124. data/lib/badline/media/true_drive.rb +48 -25
  125. data/lib/badline/media.rb +9 -3
  126. data/lib/badline/options/event.rb +2 -2
  127. data/lib/badline/options/family.rb +3 -9
  128. data/lib/badline/options/help.rb +3 -1
  129. data/lib/badline/options/table.rb +1 -1
  130. data/lib/badline/roms/README +18 -0
  131. data/lib/badline/roms/dos1571.rom +0 -0
  132. data/lib/badline/roms/dos1581.rom +0 -0
  133. data/lib/badline/sdl.rb +37 -20
  134. data/lib/badline/snapshot/state.rb +5 -4
  135. data/lib/badline/storage/d64_image.rb +3 -0
  136. data/lib/badline/storage/d71_image.rb +9 -4
  137. data/lib/badline/storage/g64_image.rb +7 -2
  138. data/lib/badline/storage/p00.rb +8 -0
  139. data/lib/badline/time_of_day.rb +9 -0
  140. data/lib/badline/version.rb +1 -1
  141. data/lib/badline/via/fast_forward.rb +3 -3
  142. data/lib/badline/vic20/attachments.rb +23 -3
  143. data/lib/badline/vic20/saved_state.rb +2 -0
  144. data/lib/badline/vic20.rb +3 -0
  145. data/lib/badline/z80/arithmetic.rb +117 -0
  146. data/lib/badline/z80/bit_page.rb +55 -0
  147. data/lib/badline/z80/block_io.rb +58 -0
  148. data/lib/badline/z80/block_transfers.rb +59 -0
  149. data/lib/badline/z80/bus_cycles.rb +111 -0
  150. data/lib/badline/z80/core.rb +113 -0
  151. data/lib/badline/z80/exchanges.rb +50 -0
  152. data/lib/badline/z80/extended_page.rb +89 -0
  153. data/lib/badline/z80/flags.rb +37 -0
  154. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  155. data/lib/badline/z80/jumps.rb +82 -0
  156. data/lib/badline/z80/loads.rb +98 -0
  157. data/lib/badline/z80/main_page.rb +134 -0
  158. data/lib/badline/z80/registers.rb +120 -0
  159. data/lib/badline/z80/saved_state.rb +69 -0
  160. data/lib/badline/z80/shifts.rb +64 -0
  161. data/lib/badline/z80.rb +11 -0
  162. metadata +89 -24
  163. /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
  164. /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
  165. /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
  166. /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f8a451e4b936c57d5cf39cc7253481e5c8deff31898cb015641ed54246a68da
4
- data.tar.gz: 8d1e5c8aab0d9352e31a5f7b870896aaec254dbcd55c823fe24496d820b2befc
3
+ metadata.gz: 7e5111f801416999c9748d88475f3bdb54e1a1b4b25b3a4246a64c4b3b59bdd0
4
+ data.tar.gz: c924f57fce55ffd0e9c6134c22618106828e831feed277411e8e6d56bec7c3aa
5
5
  SHA512:
6
- metadata.gz: d97d1b2876eafb8a51cd290c651904c798be975e0f3f7ea9ba4960a3dcaeebb6a76cc9cdad8bb5bf3ac5c732af3ec0eeddb779916b9e793292350d0749ed730f
7
- data.tar.gz: 2b7f0307a605b0270490ae3155c1671e1197473a0c22edf836bce953c08ff8884d1b09ca92fd7b3bc8c4038de63b66d5b92e3e82297ecfaddd5cde592ef74493
6
+ metadata.gz: 6a7f251456552573fab6b3041c4cc0c2ce83550c81547953ba9bfb6550e331458bf307f58a3e47ffd2bfaa98cb5c7f8938adc7c68299e1ffd6adb604460f0241
7
+ data.tar.gz: 66632ccc5e1851a028837833e792a4ce2ff71cdd1e66113bed8b652618e73d0c90990359b00812c6368f949db6532b46a43684bf2d9c3334ab9876e7ea6836b6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.12.0](https://github.com/elektronaut/badline/compare/v0.11.1...v0.12.0) (2026-10-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * add the 1581's WD1772 and its .d81 disks at sector level ([b834218](https://github.com/elektronaut/badline/commit/b834218bb686ceef295f2aafada86910fe8b61a2))
9
+ * boot C128 boot disks such as CP/M at power-on ([69359b6](https://github.com/elektronaut/badline/commit/69359b60bfe91ec0d9634e3dec4e4039044814e7))
10
+ * emulate the 1581 drive on the C64, the VIC-20 and the C128 ([4696c5b](https://github.com/elektronaut/badline/commit/4696c5b205689b2a86f91503ce153a63ff0f6816))
11
+ * put the C128's Z80 on its bus and boot the C128 through it ([b3d0e60](https://github.com/elektronaut/badline/commit/b3d0e603a8dc606d090917a67a52c4f31854f307))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * let the 1571's WD1770 run its commands, so CP/M's disk BIOS finds no MFM disk ([a380768](https://github.com/elektronaut/badline/commit/a380768ae02ce5da33e73dc23147b4edb72c72ff))
17
+ * parse --at insert of a disk list without the rest of badline ([5d6b93a](https://github.com/elektronaut/badline/commit/5d6b93a86cf4b57be3312bfb7da32980d38f2800))
18
+ * save the KCS Power cartridge's I/O RAM in snapshots ([54fab40](https://github.com/elektronaut/badline/commit/54fab408d0d7447d5b7b977905aa7aaabf8ad92a))
19
+ * uninstall the serial traps when the VIC-20 unmounts ([b2acc3f](https://github.com/elektronaut/badline/commit/b2acc3ff495f10ec5bff3b56aae0e89b2741bd34))
20
+
21
+ ## [0.11.1](https://github.com/elektronaut/badline/compare/v0.11.0...v0.11.1) (2026-10-09)
22
+
23
+
24
+ ### Features
25
+
26
+ * emulate the C128D's 1571 drive in 1541 and 1571 mode ([2c32d99](https://github.com/elektronaut/badline/commit/2c32d993a0449b4b537d5f82e9711b0e14604f95))
27
+ * fast serial between the C128 and its 1571 ([34fe02f](https://github.com/elektronaut/badline/commit/34fe02f2098664c94233da9e40828b40e41b6b1a))
28
+
3
29
  ## [0.11.0](https://github.com/elektronaut/badline/compare/v0.10.1...v0.11.0) (2026-10-09)
4
30
 
5
31
 
data/CLAUDE.md CHANGED
@@ -49,8 +49,9 @@ requires only the namespace file.
49
49
  | --- | --- | --- |
50
50
  | rspec (`spec/`) | `bundle exec rspec` | Unit behaviour, all of `lib/` |
51
51
  | SingleStepTests 65x02 | `rake test` (100 sampled cases per opcode) | CPU, per-cycle bus traces |
52
+ | SingleStepTests z80 | `rake test` (the first 100 cases per opcode, `Z80_SAMPLE=all` for all 1,604,000) | `Z80`: registers, flags, ports and the bus pins on every T-state |
52
53
  | Wolfgang Lorenz suite | `bin/lorenz` | CPU, CIA, interrupts |
53
- | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, cartridges with `--carts`, the true 1541 drive with `--drive`, the VIC-20 with `--vic20`, and the C128 in C64 mode with `--c128c64` |
54
+ | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, cartridges with `--carts`, the true 1541 drive with `--drive`, the VIC-20 with `--vic20`, the C128 in C64 mode with `--c128c64`, in C128 mode with `--c128`, and its Z80 with `--c128-z80` |
54
55
  | VICE SID testprogs | `bin/sidtests` | SID |
55
56
  | Drive scenarios | `bin/drive_scenarios` | The true 1541 running the DOS ROM: save, format, the error channel, idle, write protect and autostart |
56
57
  | CIA offline grids | `bundle exec rspec --tag slow spec/badline/cia` | CIA timers and shift register, against a bare CIA in about 2 min |
@@ -103,7 +104,7 @@ worktree and owns a different set of files.
103
104
  - The external suites under `vendor/` are gitignored, so a new worktree has
104
105
  only `vendor/.gitkeep`. From the worktree root, symlink the main
105
106
  checkout's suites into it:
106
- `for d in 65x02 VICE-testprogs; do ln -sfn ../../../../vendor/$d vendor/$d; done`.
107
+ `for d in 65x02 z80-sample VICE-testprogs; do ln -sfn ../../../../vendor/$d vendor/$d; done`.
107
108
  The main checkout also has `vendor/OneLoad64-Games-Collection-v5`, which
108
109
  `rake vendor:checkout` doesn't fetch. Link it the same way only for
109
110
  `.sid` or media work. Don't re-run `rake vendor:checkout` there, and
@@ -129,12 +130,29 @@ worktree and owns a different set of files.
129
130
 
130
131
  ## Testing and baselines
131
132
 
132
- - `rake vendor:checkout` fetches SingleStepTests and VICE-testprogs into
133
- `vendor/`
133
+ - `rake vendor:checkout` fetches SingleStepTests 65x02, the Z80 sample
134
+ and VICE-testprogs into `vendor/`
134
135
  - `bundle exec rspec`: line coverage is about 97%. `spec/spec_helper.rb`
135
136
  fails a whole-suite run (every spec file, no filters) below 90%, while
136
137
  single-file and filtered runs skip the floor. `:slow` specs are excluded
137
138
  by default: run them with `bundle exec rspec --tag slow`
139
+ - `bundle exec parallel_rspec` is the whole run, `:slow` specs included,
140
+ in a process per core, held to the same 90% over the merged coverage.
141
+ CI runs it as two jobs, the plain examples (with the floor) and the
142
+ `:slow` ones. Agents run only the spec files they touch, with
143
+ plain `bundle exec rspec`, and leave whole runs to CI
144
+ - Keep each plain example to a few hundred ms. A million cycles of a C64 or
145
+ a C128 cost 3 to 5 s of CPU, and about three times that under coverage,
146
+ so an example that boots a machine or runs more than about a million
147
+ cycles goes under `:slow`
148
+ - Don't boot per example. Restore a machine from a State taken once
149
+ (`spec/support/taken_once.rb`), and share one machine between examples
150
+ that only read it. Assert on the cheapest oracle that proves the point: a
151
+ register or a RAM byte over a booted screen, a bare chip over a whole
152
+ machine, and the fewest cycles that reach the state
153
+ - Before opening a pull request, run `bundle exec rspec --profile 10` (with
154
+ `--tag slow` for `:slow` ones) on the spec files you added or changed,
155
+ and hold them to that budget
138
156
  - `rake test`: SingleStepTests, plus the Minitest tests for the regression
139
157
  runners in `test/`. Run it for any CPU change
140
158
 
@@ -198,13 +216,13 @@ the 6526A) for anything the interrupt register or the CIA model reaches; cartrid
198
216
  `testbench-drive` (`bin/testbench --drive`) and `drive-scenarios` (`bin/drive_scenarios <filter>`), plus `testbench-vic20` for the VIA; the VIC-20 (`vic20/`) →
199
217
  `testbench-vic20` (`bin/testbench --vic20`); the C128 (`c128.rb`, `c128/`), or
200
218
  the VIC-IIe, CPU or PLA code it shares with the C64 → `testbench-c128c64`
201
- (`bin/testbench --c128c64`); SID → `sid`, plus `sid-8580` for anything the 8580
219
+ (`bin/testbench --c128c64`); the C128's Z80, MMU or hand-over → `testbench-c128-z80` (`bin/testbench --c128-z80`) and `testbench-c128`; SID → `sid`, plus `sid-8580` for anything the 8580
202
220
  model reaches (`bin/sidtests --sid 8580`). Leave the Lorenz chain to CI,
203
221
  unless your code has a rule in `doc/pinned-behaviour.md` that names Lorenz
204
222
  tests: the interrupt polling, CPU port and CIA timer rules. Run just the
205
223
  tests that rule names, one at a time, with
206
224
  `ruby --yjit bin/lorenz --resume <test> --stop-after <test>`. A stretch
207
- resumes on a fresh machine, so its cuts in the Rakefile have to stay before
225
+ resumes on a fresh machine, so its cuts in `test/support/suites.rb` have to stay before
208
226
  `trap1`, where the tests start carrying state from one to the next. Both Lorenz
209
227
  and testbench load through the LOAD trap and type through the keyboard
210
228
  buffer, so storage, IEC and keyboard changes can move them too. A broken
data/CONTRIBUTING.md CHANGED
@@ -55,25 +55,31 @@ brew install sdl2 # macOS
55
55
  apt install libsdl2-2.0-0 # Debian/Ubuntu
56
56
  ```
57
57
 
58
- Install the dependencies and run the specs:
58
+ Install the dependencies and run the whole suite, a process per core, as
59
+ CI does:
59
60
 
60
61
  ```sh
61
62
  bundle install
62
- bundle exec rspec
63
+ bundle exec parallel_rspec
63
64
  ```
64
65
 
65
- A run of the whole suite fails if line coverage drops below 90%. Runs of
66
- single files or filtered examples skip that check. Specs tagged `:slow`
67
- boot the whole machine and are skipped by default. Run them with
68
- `bundle exec rspec --tag slow`.
66
+ A run of the whole suite fails if line coverage drops below 90%. Run
67
+ single files or filtered examples with `bundle exec rspec`, which skips
68
+ that check. Specs tagged `:slow` boot the whole machine, and a plain
69
+ `bundle exec rspec` skips them. Run them with `bundle exec rspec --tag slow`.
69
70
 
70
71
  The CPU is verified separately against the
71
- [65x02 single step tests](https://github.com/SingleStepTests/65x02).
72
- `rake test` checks the fixtures out into `vendor/65x02` first, which
73
- takes a moment the first time:
72
+ [65x02 single step tests](https://github.com/SingleStepTests/65x02), and
73
+ the Z80 against the [Z80 ones](https://github.com/SingleStepTests/z80).
74
+ `rake test` checks the 65x02 fixtures out into `vendor/65x02` first,
75
+ which takes a moment the first time. For the Z80 it fetches only the
76
+ first 100 cases of each opcode's file into `vendor/z80-sample`.
77
+ `Z80_SAMPLE=all` checks the whole Z80 suite out into `vendor/z80`
78
+ (about 1.5 GB) and runs every case:
74
79
 
75
80
  ```sh
76
81
  bundle exec rake test
82
+ Z80_SAMPLE=all bundle exec rake test
77
83
  ```
78
84
 
79
85
  Check style before pushing:
data/README.md CHANGED
@@ -125,7 +125,7 @@ library, are covered in [doc/library.md](doc/library.md).
125
125
  | Format | Handling |
126
126
  |--------|----------|
127
127
  | `.prg`, `.p00` | Loaded into memory after boot. A program at the BASIC start (`$0801`) is `RUN`, anything else is left for you to `SYS` |
128
- | `.d64`, `.d71`, `.d81` | Mounted as device 8, then `LOAD"*",8,1` and `RUN`. Write-protected unless `--writable` |
128
+ | `.d64`, `.d71`, `.d81` | Mounted as device 8, then `LOAD"*",8,1` and `RUN`. On a C128 in C128 mode a boot disk goes in a true drive instead and boots at power-on. See [Media](doc/media.md#c128-boot-disks). Write-protected unless `--writable` |
129
129
  | `.g64` | Put in a true 1541, then `LOAD"*",8,1` and `RUN`. It holds the disk's raw GCR, so copy protection that reads it works. Write-protected unless `--writable` |
130
130
  | `.m3u`, `.vfl` | A list of disk images, one path a line, relative to the list: an `.m3u` playlist, or a VICE flip list, which only unit 8's entries are taken from. Its first disk goes in as above, and the list is its set for the pause menu |
131
131
  | `.t64` | Mounted read-only as device 8 and loaded like a disk image. The files load by name, and no tape is involved |
@@ -139,7 +139,8 @@ Without `--true-drive`, device 8 is no drive at all but traps on the
139
139
  KERNAL's disk routines. Loading is instant, but fast loaders and copy
140
140
  protection that run code on the drive need `--true-drive`, which puts
141
141
  an emulated 1541 there, running its own DOS at a real 1541's speed. Its
142
- red LED lights in the bottom right corner of the border.
142
+ red LED lights in the bottom right corner of the border. With
143
+ `--true-drive`, a `.d81` goes in an emulated 1581 in place of the 1541.
143
144
  [doc/media.md](doc/media.md) has the details, and how to attach media
144
145
  from Ruby.
145
146
 
@@ -184,8 +185,12 @@ starts, and a `.sid` tune start C64 mode the same way, and so does a C64
184
185
  cartridge, through the KERNAL. `GO64` gets there from BASIC 7.0. A
185
186
  program that loads at `$1C01`, where BASIC 7.0 starts, runs, and other
186
187
  programs load at their own address. The disk in device 8 is served
187
- through the KERNAL traps in either mode. The C128's Z80 isn't emulated:
188
- the 8502 starts the machine. `--model` picks the board: `c128` (the default, the flat C128
188
+ through the KERNAL traps in either mode, or with `--true-drive` by the
189
+ C128D's 1571. A boot disk boots in C128 mode, as on a real C128: CP/M's
190
+ system disks, a `.d81` such as VICE-testprogs' `c128-cpm/cpm3.d81` on a
191
+ 1581 among them, and boot games. `badline c128 cpm3.d81` comes up at
192
+ CP/M's `A>` prompt. The Z80 starts the machine, as on a real C128, and hands
193
+ it to the 8502. `--model` picks the board: `c128` (the default, the flat C128
189
194
  and the plastic C128D, PAL, with the 6581 SID and a VDC with 16K of
190
195
  RAM), `c128dcr` (the metal C128DCR, with the 8580, 6526As and a VDC with
191
196
  64K), and `c128ntsc` and `c128dcrntsc` for their NTSC versions. `--sid`
@@ -348,8 +353,14 @@ program or `.sid` dropped on it opens the menu to ask first.
348
353
  - **C128**: C128 mode with BASIC 7.0 and C64 mode, on the 8502 with its
349
354
  port, FAST mode, the VIC-IIe with its extra keyboard lines, the 8722
350
355
  MMU, the 8563 or 8568 VDC with its 80 column display, and 128K of RAM
351
- (`badline c128`). The Z80 and the 1571 aren't emulated yet.
356
+ (`badline c128`), and the Z80, which boots the machine.
352
357
  - **1541**: an emulated drive running its own DOS (`--true-drive`).
358
+ - **1571**: the C128D's drive running DOS 3.0, in 1541 mode and in 1571
359
+ mode at 2 MHz with both sides of a `.d71` or `.g71`, and fast serial
360
+ (burst) to a C128 in C128 mode (`badline c128 --true-drive`).
361
+ - **1581**: the 3.5" drive running its own DOS on its WD1772 and 8520,
362
+ reading and writing `.d81` images, on the C64, the VIC-20 and the C128
363
+ (`--true-drive` with a `.d81`).
353
364
  - **Cartridges**: standard 8K, 16K and Ultimax, Simons' BASIC, Ocean,
354
365
  Fun Play / Power Play, Super Games, Epyx FastLoad, Westermann
355
366
  Learning, Rex Utility, C64 Game System / System 3, Dinamic, Zaxxon /
data/doc/media.md CHANGED
@@ -36,6 +36,31 @@ drive's red LED lights in the bottom right corner of the border.
36
36
  A `.g64` holds the disk's raw GCR, half tracks and all, so copy
37
37
  protection and fast loaders that read it work.
38
38
 
39
+ ## The true 1581
40
+
41
+ With a true drive on device 8, a `.d81` goes in an emulated 1581 in
42
+ its place: the 3.5" drive's own DOS on its 6502, with its WD1772 reading
43
+ and writing the image's sectors and its 8520 on the serial bus. Another
44
+ disk image swaps the 1541 (or on the C128 the 1571) back in. On the
45
+ C128 in C128 mode the 1581 talks fast serial (burst), as the 1571 does.
46
+
47
+ ## C128 boot disks
48
+
49
+ At power-on the C128 KERNAL reads track 1, sector 0 of the disk in
50
+ device 8, and when the block starts with `CBM` it loads and runs it.
51
+ CP/M's system disks and boot games boot that way. A 1581 first loads and
52
+ runs a `USR` file named `COPYRIGHT CBM 86` when the disk has one, which
53
+ is how CP/M's 1581 disks hand the KERNAL their boot block.
54
+
55
+ On a C128 in C128 mode, a `.d64`, `.d71` or `.d81` that boots, with that
56
+ block or that file, goes in a true drive as device 8, a 1581 for a
57
+ `.d81` and the C128D's 1571 otherwise, and nothing is typed, so the
58
+ KERNAL boots it. `badline c128 cpm3.d81` comes up at CP/M's `A>`
59
+ prompt. Other disks keep the traps and `LOAD"*",8,1`, `--true-drive`
60
+ puts any disk in a true drive, and on the C64, the VIC-20 and a C128 in
61
+ C64 mode nothing changes. A boot disk inserted while the machine runs
62
+ boots at the next reset.
63
+
39
64
  ## Write protection
40
65
 
41
66
  Disks given on the command line, inserted from the pause menu or by an
@@ -56,7 +81,9 @@ another disk. Both write to disk images unless told otherwise:
56
81
  `disk: { read_only: true }`, as the test harnesses under `bin/` do. A
57
82
  `.g64` goes in the true 1541, and takes out a disk mounted through the
58
83
  traps, so `LOAD` and `SAVE` reach the 1541 too. With the 1541 in device
59
- 8, a `.d64` goes in its drive as well.
84
+ 8, a `.d64` goes in its drive as well, and a `.d81` in a 1581 that takes
85
+ the 1541's place. On a C128 in C128 mode, `attach` puts a boot disk in a
86
+ true drive and types nothing (`Badline::Media::BootDisk`).
60
87
 
61
88
  `Badline::Media::DiskSet.around(path)` lists the disks of the set a disk
62
89
  image belongs to, which the pause menu's PREVIOUS DISK and NEXT DISK step
@@ -26,6 +26,7 @@ only catches the rows that happen to move.
26
26
  - [VIC light pen](#vic-light-pen)
27
27
  - [VIC-II 8565](#vic-ii-8565)
28
28
  - [VIC-IIe 2 MHz and TEST bit](#vic-iie-2-mhz-and-test-bit)
29
+ - [C128 Z80 bus](#c128-z80-bus)
29
30
  - [VIC-II NTSC](#vic-ii-ntsc)
30
31
  - [VIC-II 6572 (Drean)](#vic-ii-6572-drean)
31
32
  - [CIA 6526 timer pipeline](#cia-6526-timer-pipeline)
@@ -1001,6 +1002,35 @@ $1C0E and checks the readings they print.
1001
1002
  per-cycle blanking. Their 8566 shows no grey dots. The 2mhzVIC
1002
1003
  references have none either.
1003
1004
 
1005
+ ## C128 Z80 bus
1006
+
1007
+ - The Z80 gets 2 T-states in every cycle BA leaves it the bus, with no
1008
+ wait states for memory or I/O: each of the 1,868 `c128z80timing`
1009
+ programs measures `T-states / 2` 1 MHz cycles with the screen blanked.
1010
+ Pinned by `c128/z80/c128z80timing`. The timing rows can't pin where in
1011
+ an I/O machine cycle the chips see an IN or OUT, since the timer starts
1012
+ and stops through the same OUT: `Z80Turns::IO_DELAY` is 2 because
1013
+ `c128modez80-23`'s IN at `$D700`, which reads open bus, passes at 1 or 2
1014
+ and fails at 0, 3 and 4.
1015
+ - In C128 mode with CR bits 6-7 both 0, `$0000-$0FFF` reads the BIOS
1016
+ (`$D000-$DFFF` of the KERNAL ROM), and a write, IN or OUT there reaches
1017
+ the RAM at `$Dxxx` of bank 0. CR `$BF` (bank 2, which is bank 0 on a
1018
+ 128K machine) leaves the BIOS out, as does bank 1: `$0xxx` is RAM, common
1019
+ RAM's or the bank's. `$D000-$DFFF` is RAM to the Z80's memory accesses.
1020
+ Pinned by `c128modez80-15` to `-22` and `-25`, `outi/tstouti2` and
1021
+ `mmu_zp_sp/tstz80bk` (x128 fails the last).
1022
+ - IN and OUT reach the chips at `$D000-$DFFF` whatever CR's I/O bit says,
1023
+ in both modes. The MMU at `$D500` takes an OUT either way in C128 mode,
1024
+ but answers an IN only with I/O on. Pinned by `c128modez80-10` (the IN)
1025
+ and `tstz80bk`, which programs page relocation through OUT with CR
1026
+ `$BF`.
1027
+ - While I/O is mapped in, the colour RAM the 8502 sees also answers at
1028
+ `$1000-$13FF`, in memory and through IN and OUT. Pinned by
1029
+ `c128modez80-02` to `-09` and `c64modez80-03` and `-05`.
1030
+ - In C64 mode the Z80's memory accesses decode as the 8502's do through
1031
+ the PLA, with no BIOS. Pinned by `c64modez80-00` to `-06`.
1032
+ - Spec guard: [`z80_bus_spec.rb`](../spec/badline/c128/z80_bus_spec.rb).
1033
+
1004
1034
  ## VIC-II NTSC
1005
1035
 
1006
1036
  The 6567R8 (`Region::NTSC`) and the 6567R56A (`Region::NTSC_OLD`) run
@@ -1507,7 +1537,7 @@ wasn't read.
1507
1537
  [`mechanism_spec.rb`](../spec/badline/drive1541/mechanism_spec.rb),
1508
1538
  and the `format/name-and-id`, `format/bam-free` and
1509
1539
  `format/trap-readable` rows of the `drive-scenarios` suite
1510
- ([`drive_scenarios.rb`](../test/drive_scenarios.rb)), which read the
1540
+ ([`drive_scenarios.rb`](../test/support/drive_scenarios.rb)), which read the
1511
1541
  formatted disk back from its image.
1512
1542
  - The disk turns at 300 rpm whatever bit rate VIA 2's PB5-6 select: a
1513
1543
  turn is 200,000 drive cycles over every track and over a half track
data/exe/badline-ruby CHANGED
@@ -39,7 +39,7 @@ if options.headless?
39
39
  console = if options.player_window?
40
40
  lambda do |input:, output:|
41
41
  load_sdl
42
- Badline::Frontend::PlayerWindow.new(input:, output:)
42
+ Badline::Frontend::PlayerController.new(input:, output:)
43
43
  end
44
44
  else
45
45
  Badline::Audio::Console.method(:new)
@@ -37,6 +37,8 @@ module Badline
37
37
  include AddressBus::PLA
38
38
  include AddressBus::ROMs
39
39
  include MMUPages
40
+ include IOPages
41
+ include Z80Pages
40
42
  include SavedState
41
43
 
42
44
  # The 8502's port has seven pins. P0-P5 are the 6510's, and P6 senses
@@ -152,6 +154,7 @@ module Badline
152
154
 
153
155
  @color_lines = ColorLines.new(@vic, @cia2, character_rom, @c128_character_rom)
154
156
  @color_ram = @color_lines.color_ram(1)
157
+ build_z80_pages
155
158
  @vic_writes = VICWrites.new(@vic, @control_ports)
156
159
  @open_bus = AddressBus::OpenBus.new(@vic)
157
160
  @configuration_reads = ConfigurationPage.new(@mmu, @open_bus)
@@ -318,6 +321,7 @@ module Badline
318
321
  romh: @cartridge&.romh)
319
322
  @color_ram = @color_lines.cpu_color_ram(c64_mode, port)
320
323
  c64_mode ? map_pla_pages : map_c128_pages
324
+ update_z80!
321
325
  mode_changed if c64_mode != @c64_mode
322
326
  end
323
327
 
@@ -325,23 +329,6 @@ module Badline
325
329
  @c64_mode = !@c64_mode
326
330
  @on_mode_change&.call
327
331
  end
328
-
329
- def map_io_pages
330
- @io_mapped = true
331
- @read_pages.fill(@vic, 0xd0, 4)
332
- @write_pages.fill(@vic_writes, 0xd0, 4)
333
- @read_pages[0xd4] = @write_pages[0xd4] = @sid
334
- @read_pages[0xd5] = @write_pages[0xd5] = @mmu.c64_mode? ? @open_bus : @mmu
335
- @read_pages[0xd6] = @write_pages[0xd6] = @vdc
336
- @read_pages[0xd7] = @write_pages[0xd7] = @debug_page || @open_bus
337
- @read_pages.fill(@color_ram, 0xd8, 4)
338
- @write_pages.fill(@color_ram, 0xd8, 4)
339
- @read_pages[0xdc] = @write_pages[0xdc] = @cia1
340
- @read_pages[0xdd] = @write_pages[0xdd] = @cia2
341
- @read_pages.fill(@open_bus, 0xde, 2)
342
- @write_pages.fill(@open_bus, 0xde, 2)
343
- map_cartridge_io if @cartridge
344
- end
345
332
  end
346
333
  end
347
334
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # The true drives on the C128's serial bus: a Drive1541, the C128D's
6
+ # built-in 1571 (Drive1571) or a 1581 (Drive1581::Slot), each in a
7
+ # field of its own, so none makes another's per-cycle call a union.
8
+ module Drives
9
+ attr_reader :drive1571
10
+
11
+ # Plugs in a Drive1571, which then runs alongside the C128 on its own
12
+ # clock and talks to it over the serial bus, as attach_drive1541 does
13
+ # for a 1541.
14
+ def attach_drive1571(drive)
15
+ @iec_bus.detach(@drive1571) if @drive1571
16
+ drive.host_clock_hz = region.clock_hz
17
+ @drive1571 = drive
18
+ drive.connect(iec_bus)
19
+ @serial_trap&.device = serial_trap_device
20
+ end
21
+
22
+ # Unplugs the Drive1571.
23
+ def detach_drive1571
24
+ return unless @drive1571
25
+
26
+ @iec_bus.detach(@drive1571)
27
+ @drive1571 = nil
28
+ @serial_trap&.device = serial_trap_device
29
+ end
30
+
31
+ # The true drive on the bus, a 1581 ahead of the 1571 ahead of a 1541.
32
+ def true_drive = @drive1581 || @drive1571 || @drive1541
33
+
34
+ # Plugs in the C128D's 1571 as device 8, in place of a 1581 there, and
35
+ # returns it.
36
+ def plug_true_drive
37
+ detach_drive1581 if drive1581_on_device8?
38
+ Drive1571.new.tap { |drive| attach_drive1571(drive) }
39
+ end
40
+
41
+ private
42
+
43
+ # The serial bus, with no disk in device 8's traps and no true drive.
44
+ def init_drives
45
+ @drive = nil
46
+ @serial_trap = nil
47
+ @save_trap = nil
48
+ @drive1541 = nil
49
+ @drive1571 = nil
50
+ @drive1581 = nil
51
+ plug_serial_bus
52
+ plug_fast_serial
53
+ end
54
+
55
+ # Device 8, unless a true drive is on the bus as device 8.
56
+ def serial_trap_device
57
+ device = KernalTrap::Routine::DEVICE
58
+ @drive1571&.device == device || @drive1541&.device == device || drive1581_on_device8? ? nil : device
59
+ end
60
+
61
+ # A 1581 plugged in holds the machine's on_init handlers back until
62
+ # its DOS has booted, which takes longer than the C128 KERNAL's.
63
+ def drive1581_attached
64
+ @init_threshold = [@init_threshold, Drive1581::BOOT_CYCLES].max if @cycles < @init_threshold
65
+ end
66
+
67
+ # Unplugs the 1541 and the 1571 if either is device 8, for a 1581 to
68
+ # take its place.
69
+ def unplug_device8
70
+ device = KernalTrap::Routine::DEVICE
71
+ detach_drive1541 if @drive1541&.device == device
72
+ detach_drive1571 if @drive1571&.device == device
73
+ end
74
+
75
+ # A true 1541 (Computer::Attachments#save_serial_bus), then a 1571 and
76
+ # a 1581.
77
+ def save_serial_bus(out)
78
+ out.boolean(true).boolean(!@drive1541.nil?)
79
+ if @drive1541
80
+ out.int(@drive1541.device)
81
+ @drive1541.save_state(out)
82
+ end
83
+ out.boolean(!@drive1571.nil?)
84
+ if @drive1571
85
+ out.int(@drive1571.device)
86
+ @drive1571.save_state(out)
87
+ end
88
+ save_drive1581(out)
89
+ end
90
+
91
+ def load_serial_bus(input)
92
+ input.boolean?
93
+ load_drive1541(input)
94
+ load_drive1571(input)
95
+ load_drive1581(input)
96
+ end
97
+
98
+ def load_drive1571(input)
99
+ return detach_drive1571 unless input.boolean?
100
+
101
+ device = input.int
102
+ detach_drive1571 if @drive1571 && @drive1571.device != device
103
+ attach_drive1571(Drive1571.new(device:)) unless @drive1571
104
+ @drive1571.load_state(input)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # Fast serial: CIA 1's CNT and SP on the serial bus's SRQ and DATA,
6
+ # through buffers MCR's FSDIR turns round. Outwards, the machine pushes
7
+ # the lines the pins pull into the bus as they move. Inwards, CIA 1
8
+ # hears the lines whenever a drive moves its own fast serial pins
9
+ # (IECBus#on_fast_change).
10
+ module FastSerial
11
+ private
12
+
13
+ def plug_fast_serial
14
+ @fast_serial_out = false
15
+ @bus.mmu.on_fast_serial_change { turn_fast_serial }
16
+ @iec_bus.on_fast_change { hear_fast_serial unless @fast_serial_out }
17
+ end
18
+
19
+ # The machine's side of the serial bus each cycle: CIA 1's pins
20
+ # pushed while FSDIR has them out, then the drives.
21
+ def clock_serial_bus
22
+ drive_fast_serial if @fast_serial_out
23
+ @drive1541&.host_cycle!
24
+ @drive1571&.host_cycle!
25
+ @drive1581&.host_cycle!
26
+ end
27
+
28
+ def turn_fast_serial
29
+ push_fast_serial
30
+ hear_fast_serial unless @fast_serial_out
31
+ end
32
+
33
+ # Pushes the lines CIA 1's pins pull while FSDIR has them out. A
34
+ # restored machine pushes them again, its CIA 1 hearing the lines as
35
+ # it last did.
36
+ def push_fast_serial
37
+ @fast_serial_out = @bus.mmu.fast_serial_out?
38
+ @iec_bus.host_fast_lines = @fast_serial_out ? fast_pins : 0
39
+ end
40
+
41
+ def drive_fast_serial
42
+ @iec_bus.host_fast_lines = fast_pins
43
+ end
44
+
45
+ # The lines CIA 1's CNT and SP pull while its serial port drives
46
+ # them.
47
+ def fast_pins
48
+ serial = @cia1.serial
49
+ return 0 unless serial.output?
50
+
51
+ low = serial.cnt ? 0 : IECBus::SRQ
52
+ serial.sp_out ? low : low | IECBus::DATA
53
+ end
54
+
55
+ def hear_fast_serial
56
+ low = @iec_bus.low_lines
57
+ serial = @cia1.serial
58
+ serial.cnt_in = low.nobits?(IECBus::SRQ)
59
+ serial.sp_in = low.nobits?(IECBus::DATA)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ class Bus
6
+ # The chips at $D000-$DFFF, which the 8502 sees while I/O is mapped
7
+ # in and the Z80 reaches through IN and OUT.
8
+ module IOPages
9
+ private
10
+
11
+ def map_io_pages
12
+ @io_mapped = true
13
+ lay_out_io(@read_pages, @write_pages)
14
+ @read_pages[0xd5] = @write_pages[0xd5] = @mmu.c64_mode? ? @open_bus : @mmu
15
+ map_cartridge_io if @cartridge
16
+ end
17
+
18
+ # The chips into +reads+ and +writes+, but for the MMU's page.
19
+ def lay_out_io(reads, writes)
20
+ reads.fill(@vic, 0xd0, 4)
21
+ writes.fill(@vic_writes, 0xd0, 4)
22
+ reads[0xd4] = writes[0xd4] = @sid
23
+ reads[0xd6] = writes[0xd6] = @vdc
24
+ reads[0xd7] = writes[0xd7] = @debug_page || @open_bus
25
+ reads.fill(@color_ram, 0xd8, 4)
26
+ writes.fill(@color_ram, 0xd8, 4)
27
+ reads[0xdc] = writes[0xdc] = @cia1
28
+ reads[0xdd] = writes[0xdd] = @cia2
29
+ reads.fill(@open_bus, 0xde, 2)
30
+ writes.fill(@open_bus, 0xde, 2)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -19,12 +19,11 @@ module Badline
19
19
  # A write to P0H or P1H waits until the matching P0L or P1L write
20
20
  # takes it.
21
21
  #
22
- # On the C128 the Z80 runs first and hands the bus to the 8502. Here the
23
- # 8502 runs from reset, with MCR as the Z80 leaves it. A machine built
24
- # for C64 mode resets into C64 mode, the state the C128 KERNAL leaves
25
- # when C= is held at power-on. In C64 mode the MMU answers nowhere, but
26
- # the CPU's RAM bank, common RAM and the VIC's bank it holds stay in
27
- # force.
22
+ # A reset clears MCR bit 0, so the Z80 runs first, boots and hands the
23
+ # bus to the 8502. A machine built for C64 mode resets into C64 mode
24
+ # instead, with the 8502 running, the state the C128 KERNAL leaves when
25
+ # C= is held at power-on. In C64 mode the MMU answers nowhere, but the
26
+ # CPU's RAM bank, common RAM and the VIC's bank it holds stay in force.
28
27
  class MMU
29
28
  CR = 0
30
29
  MCR = 5
@@ -36,6 +35,7 @@ module Badline
36
35
  VR = 11
37
36
 
38
37
  MCR_8502 = 0x01
38
+ MCR_FSDIR = 0x08
39
39
  MCR_C64_MODE = 0x40
40
40
  VERSION = 0x20
41
41
 
@@ -68,6 +68,7 @@ module Badline
68
68
  @game = 1
69
69
  @exrom = 1
70
70
  @on_change = nil
71
+ @on_fast_serial_change = nil
71
72
  reset!
72
73
  end
73
74
 
@@ -76,18 +77,32 @@ module Badline
76
77
  @on_change = block
77
78
  end
78
79
 
80
+ # Calls the block whenever FSDIR turns the fast serial buffers round.
81
+ def on_fast_serial_change(&block)
82
+ @on_fast_serial_change = block
83
+ end
84
+
79
85
  def reset!
86
+ fast_serial_out = fast_serial_out?
80
87
  @registers.fill(0)
81
- @registers[MCR] = @reset_mode == :c64 ? MCR_C64_MODE | MCR_8502 : MCR_8502
88
+ @registers[MCR] = @reset_mode == :c64 ? MCR_C64_MODE | MCR_8502 : 0
82
89
  @registers[P1L] = 0x01
83
90
  @registers[VR] = VERSION
84
91
  @p0h_latch = 0
85
92
  @p1h_latch = 0
86
93
  @on_change&.call
94
+ @on_fast_serial_change&.call if fast_serial_out
87
95
  end
88
96
 
89
97
  def c64_mode? = @registers[MCR].anybits?(MCR_C64_MODE)
90
98
 
99
+ # Whether MCR bit 3, FSDIR, turns the fast serial buffers outwards, so
100
+ # CIA 1's CNT and SP drive SRQ and DATA.
101
+ def fast_serial_out? = @registers[MCR].anybits?(MCR_FSDIR)
102
+
103
+ # Whether MCR bit 0 gives the bus to the Z80.
104
+ def z80? = @registers[MCR].nobits?(MCR_8502)
105
+
91
106
  # :c64 or :c128, the mode MCR bit 6 selects.
92
107
  def mode = c64_mode? ? :c64 : :c128
93
108
 
@@ -174,8 +189,10 @@ module Badline
174
189
  end
175
190
 
176
191
  def write(offset, value)
192
+ fast_serial_out = fast_serial_out?
177
193
  @registers[offset] = value
178
194
  @on_change&.call
195
+ @on_fast_serial_change&.call if fast_serial_out? != fast_serial_out
179
196
  end
180
197
  end
181
198
  end