badline 0.11.1 → 0.12.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 (175) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/CLAUDE.md +8 -7
  4. data/CONTRIBUTING.md +7 -6
  5. data/README.md +12 -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/address_bus/saved_state.rb +4 -10
  10. data/lib/badline/address_bus.rb +8 -27
  11. data/lib/badline/audio/bare_player.rb +1 -1
  12. data/lib/badline/c128/bus.rb +17 -52
  13. data/lib/badline/c128/bus_state.rb +5 -5
  14. data/lib/badline/c128/drives.rb +35 -22
  15. data/lib/badline/c128/fast_serial.rb +1 -0
  16. data/lib/badline/c128/io_pages.rb +35 -0
  17. data/lib/badline/c128/mmu.rb +9 -7
  18. data/lib/badline/c128/modes.rb +17 -4
  19. data/lib/badline/c128/saved_state.rb +22 -0
  20. data/lib/badline/c128/z80.rb +49 -0
  21. data/lib/badline/c128/z80_pages.rb +118 -0
  22. data/lib/badline/c128/z80_turns.rb +106 -0
  23. data/lib/badline/c128.rb +21 -83
  24. data/lib/badline/cartridge/action_replay.rb +2 -2
  25. data/lib/badline/cartridge/comal80.rb +1 -7
  26. data/lib/badline/cartridge/dinamic.rb +0 -8
  27. data/lib/badline/cartridge/final_cartridge3.rb +6 -6
  28. data/lib/badline/cartridge/fun_play.rb +0 -8
  29. data/lib/badline/cartridge/game_system.rb +0 -8
  30. data/lib/badline/cartridge/kcs_power.rb +10 -7
  31. data/lib/badline/cartridge/mach5.rb +2 -4
  32. data/lib/badline/cartridge/magic_desk.rb +0 -8
  33. data/lib/badline/cartridge/ocean.rb +0 -8
  34. data/lib/badline/cartridge/pagefox.rb +2 -2
  35. data/lib/badline/cartridge/retro_replay.rb +7 -6
  36. data/lib/badline/cartridge/rex_utility.rb +2 -6
  37. data/lib/badline/cartridge/rgcd.rb +2 -2
  38. data/lib/badline/cartridge/simons_basic.rb +0 -5
  39. data/lib/badline/cartridge/super_games.rb +4 -2
  40. data/lib/badline/cartridge/westermann.rb +2 -6
  41. data/lib/badline/cartridge.rb +37 -9
  42. data/lib/badline/cia/fast_forward.rb +21 -3
  43. data/lib/badline/cia.rb +8 -5
  44. data/lib/badline/computer/attachments.rb +14 -95
  45. data/lib/badline/computer/common.rb +34 -0
  46. data/lib/badline/computer/kernal_traps.rb +65 -5
  47. data/lib/badline/computer/true_drives.rb +69 -0
  48. data/lib/badline/computer.rb +9 -68
  49. data/lib/badline/core.rb +4 -2
  50. data/lib/badline/cpu_port.rb +112 -0
  51. data/lib/badline/drive/core.rb +8 -33
  52. data/lib/badline/drive/fast_serial.rb +73 -0
  53. data/lib/badline/drive/idle.rb +1 -11
  54. data/lib/badline/drive/orbit.rb +2 -5
  55. data/lib/badline/drive/sleep.rb +0 -2
  56. data/lib/badline/drive/vias.rb +84 -0
  57. data/lib/badline/drive.rb +2 -0
  58. data/lib/badline/drive1541/disk.rb +14 -17
  59. data/lib/badline/drive1541/disk_state.rb +7 -15
  60. data/lib/badline/drive1541.rb +1 -0
  61. data/lib/badline/drive1571/wd1770.rb +94 -8
  62. data/lib/badline/drive1571.rb +8 -58
  63. data/lib/badline/drive1581/bus.rb +137 -0
  64. data/lib/badline/drive1581/cpu.rb +11 -0
  65. data/lib/badline/drive1581/disk.rb +135 -0
  66. data/lib/badline/drive1581/disk_state.rb +87 -0
  67. data/lib/badline/drive1581/mechanism.rb +117 -0
  68. data/lib/badline/drive1581/serial_port.rb +52 -0
  69. data/lib/badline/drive1581/slot.rb +78 -0
  70. data/lib/badline/drive1581/track.rb +100 -0
  71. data/lib/badline/drive1581/track_bytes.rb +75 -0
  72. data/lib/badline/drive1581/wd1772/registers.rb +89 -0
  73. data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
  74. data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
  75. data/lib/badline/drive1581/wd1772/state.rb +66 -0
  76. data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
  77. data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
  78. data/lib/badline/drive1581/wd1772/turn.rb +73 -0
  79. data/lib/badline/drive1581/wd1772.rb +216 -0
  80. data/lib/badline/drive1581.rb +233 -0
  81. data/lib/badline/frontend/app.rb +1 -3
  82. data/lib/badline/frontend/audio_device.rb +67 -0
  83. data/lib/badline/frontend/audio_sink.rb +16 -50
  84. data/lib/badline/frontend/boot.rb +24 -67
  85. data/lib/badline/frontend/buttons.rb +7 -7
  86. data/lib/badline/frontend/keys.rb +33 -8
  87. data/lib/badline/frontend/media_slots.rb +42 -0
  88. data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
  89. data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
  90. data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
  91. data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
  92. data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +5 -8
  93. data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
  94. data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
  95. data/lib/badline/frontend/menu/theme.rb +18 -0
  96. data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
  97. data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
  98. data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
  99. data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
  100. data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
  101. data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
  102. data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
  103. data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
  104. data/lib/badline/frontend/player/theme.rb +18 -0
  105. data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
  106. data/lib/badline/frontend/sound.rb +19 -38
  107. data/lib/badline/frontend/timeline.rb +3 -14
  108. data/lib/badline/frontend.rb +24 -20
  109. data/lib/badline/iec_bus.rb +14 -2
  110. data/lib/badline/kernal_trap/{file.rb → file_routine.rb} +1 -1
  111. data/lib/badline/kernal_trap/load.rb +1 -1
  112. data/lib/badline/kernal_trap/save.rb +1 -1
  113. data/lib/badline/kernal_trap.rb +1 -1
  114. data/lib/badline/machine/clocking.rb +50 -0
  115. data/lib/badline/machine.rb +63 -6
  116. data/lib/badline/media/boot_disk.rb +50 -0
  117. data/lib/badline/media/disk_list.rb +3 -3
  118. data/lib/badline/media/disk_set.rb +1 -1
  119. data/lib/badline/media/extensions.rb +33 -0
  120. data/lib/badline/media/not_disk.rb +8 -0
  121. data/lib/badline/media/program.rb +28 -0
  122. data/lib/badline/media/true_drive.rb +38 -17
  123. data/lib/badline/media/tune.rb +35 -0
  124. data/lib/badline/media/vic20_cartridge.rb +1 -7
  125. data/lib/badline/media/vic20_media.rb +38 -80
  126. data/lib/badline/media.rb +90 -81
  127. data/lib/badline/options/event.rb +4 -2
  128. data/lib/badline/options/family.rb +3 -9
  129. data/lib/badline/options/help.rb +2 -1
  130. data/lib/badline/roms/README +9 -0
  131. data/lib/badline/roms/dos1581.rom +0 -0
  132. data/lib/badline/sdl.rb +37 -20
  133. data/lib/badline/sid.rb +1 -1
  134. data/lib/badline/snapshot/machine_state.rb +9 -1
  135. data/lib/badline/snapshot/state.rb +6 -4
  136. data/lib/badline/snapshot.rb +11 -9
  137. data/lib/badline/storage/d64_image.rb +15 -7
  138. data/lib/badline/storage/disk_image/writing.rb +2 -2
  139. data/lib/badline/storage/disk_image.rb +1 -1
  140. data/lib/badline/storage/host_directory.rb +1 -1
  141. data/lib/badline/storage/p00.rb +8 -0
  142. data/lib/badline/storage/t64.rb +1 -1
  143. data/lib/badline/time_of_day.rb +3 -3
  144. data/lib/badline/version.rb +1 -1
  145. data/lib/badline/vic/bank.rb +1 -1
  146. data/lib/badline/vic/border_mask.rb +1 -1
  147. data/lib/badline/vic/collisions.rb +1 -1
  148. data/lib/badline/vic/color_patches.rb +1 -1
  149. data/lib/badline/vic/display_state.rb +1 -1
  150. data/lib/badline/vic/graphics_mode.rb +1 -1
  151. data/lib/badline/vic/graphics_shifter.rb +1 -1
  152. data/lib/badline/vic/register_log.rb +1 -1
  153. data/lib/badline/vic/registers.rb +1 -1
  154. data/lib/badline/vic/sequencer.rb +1 -1
  155. data/lib/badline/vic/sequencer_output.rb +1 -1
  156. data/lib/badline/vic/sequencer_state.rb +1 -1
  157. data/lib/badline/vic/sprite/internal_bus.rb +1 -1
  158. data/lib/badline/vic/sprite/saved_state.rb +1 -1
  159. data/lib/badline/vic/sprite/shifter.rb +1 -1
  160. data/lib/badline/vic/sprite/timing.rb +1 -1
  161. data/lib/badline/vic/sprite.rb +1 -1
  162. data/lib/badline/vic/sprites.rb +1 -1
  163. data/lib/badline/vic/sprites_state.rb +1 -1
  164. data/lib/badline/vic.rb +6 -5
  165. data/lib/badline/vic20/attachments.rb +15 -71
  166. data/lib/badline/vic20/saved_state.rb +2 -30
  167. data/lib/badline/vic20.rb +4 -37
  168. data/lib/badline/z80/core.rb +2 -0
  169. data/lib/badline/z80/saved_state.rb +69 -0
  170. metadata +61 -23
  171. data/lib/badline/cycleable.rb +0 -38
  172. /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
  173. /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
  174. /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
  175. /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: c2e262f4dbf07ef2d84f3a5eb673302102ecc9d4a5aa3ac87a908c6f0323c041
4
- data.tar.gz: 026b7b26ce4cad94cf386a4b944c4ce34d34635e8dd8a568a352090320deabd9
3
+ metadata.gz: 8872c5d5117a691aa38d054acd5dd7e84f7abf737c977a2dc309c6dc3072381d
4
+ data.tar.gz: fa4f4e5b3729989c78443f73bd655c2083555d62a8d3ee45437d147bfe0e5fb8
5
5
  SHA512:
6
- metadata.gz: d99d4042bdb920cfbaa9501a2fff2a6cc155dda76518b28a58e863d95bd18ef3d891f7dff5d935cc726ae380c9659bc09f5f11f7cefda657a4926bfc06974e76
7
- data.tar.gz: 670579ee105286f5b5c04aaa63d5ae6a9fee9161f6735ccd9f7764152c43e421f6024a1ba7cfead0da92a7b87cab6f03b1b53a2d2e44693a62807b4f52ab81a3
6
+ metadata.gz: d278ba0e0341242af3e1782a8c003bc4cbdb1594be434021b5bbccae126ddf12dd89ffa0520dd615075a77f89b1285595d2101868db0e6679b69130586ed210c
7
+ data.tar.gz: 5dabb1d53a29dc43b619f90abd67495ab3847d3ba8d8f89275d45f276aac4fbe3ca37a4963f048e300e88cd50228b35bd39da7efbd431f46527b893b1c4bd95c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.12.1](https://github.com/elektronaut/badline/compare/v0.12.0...v0.12.1) (2026-10-10)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * keep the 1581's idle deadline within a native build's integers ([a98848f](https://github.com/elektronaut/badline/commit/a98848f0889ca7ad4c12007976b63d54bbbf122b))
9
+ * refuse snapshot values a native build can't hold, on CRuby too ([5ec51e8](https://github.com/elektronaut/badline/commit/5ec51e81cfabfdb585367bb2f8183516ac64d2b0))
10
+
11
+ ## [0.12.0](https://github.com/elektronaut/badline/compare/v0.11.1...v0.12.0) (2026-10-10)
12
+
13
+
14
+ ### Features
15
+
16
+ * add the 1581's WD1772 and its .d81 disks at sector level ([b834218](https://github.com/elektronaut/badline/commit/b834218bb686ceef295f2aafada86910fe8b61a2))
17
+ * boot C128 boot disks such as CP/M at power-on ([69359b6](https://github.com/elektronaut/badline/commit/69359b60bfe91ec0d9634e3dec4e4039044814e7))
18
+ * emulate the 1581 drive on the C64, the VIC-20 and the C128 ([4696c5b](https://github.com/elektronaut/badline/commit/4696c5b205689b2a86f91503ce153a63ff0f6816))
19
+ * put the C128's Z80 on its bus and boot the C128 through it ([b3d0e60](https://github.com/elektronaut/badline/commit/b3d0e603a8dc606d090917a67a52c4f31854f307))
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * 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))
25
+ * parse --at insert of a disk list without the rest of badline ([5d6b93a](https://github.com/elektronaut/badline/commit/5d6b93a86cf4b57be3312bfb7da32980d38f2800))
26
+ * save the KCS Power cartridge's I/O RAM in snapshots ([54fab40](https://github.com/elektronaut/badline/commit/54fab408d0d7447d5b7b977905aa7aaabf8ad92a))
27
+ * uninstall the serial traps when the VIC-20 unmounts ([b2acc3f](https://github.com/elektronaut/badline/commit/b2acc3ff495f10ec5bff3b56aae0e89b2741bd34))
28
+
3
29
  ## [0.11.1](https://github.com/elektronaut/badline/compare/v0.11.0...v0.11.1) (2026-10-09)
4
30
 
5
31
 
data/CLAUDE.md CHANGED
@@ -51,7 +51,7 @@ requires only the namespace file.
51
51
  | SingleStepTests 65x02 | `rake test` (100 sampled cases per opcode) | CPU, per-cycle bus traces |
52
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 |
53
53
  | Wolfgang Lorenz suite | `bin/lorenz` | CPU, CIA, interrupts |
54
- | 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` |
55
55
  | VICE SID testprogs | `bin/sidtests` | SID |
56
56
  | Drive scenarios | `bin/drive_scenarios` | The true 1541 running the DOS ROM: save, format, the error channel, idle, write protect and autostart |
57
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 |
@@ -136,10 +136,11 @@ worktree and owns a different set of files.
136
136
  fails a whole-suite run (every spec file, no filters) below 90%, while
137
137
  single-file and filtered runs skip the floor. `:slow` specs are excluded
138
138
  by default: run them with `bundle exec rspec --tag slow`
139
- - CI splits the spec files into shards, plain and `:slow`, by the run times
140
- in `test/spec_times.json` (`bin/spec_shard`), and holds the plain shards'
141
- merged coverage to the same 90%. After adding or moving expensive specs,
142
- re-record the times with `bin/spec_shard --record` (its header says how)
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
143
144
  - Keep each plain example to a few hundred ms. A million cycles of a C64 or
144
145
  a C128 cost 3 to 5 s of CPU, and about three times that under coverage,
145
146
  so an example that boots a machine or runs more than about a million
@@ -215,13 +216,13 @@ the 6526A) for anything the interrupt register or the CIA model reaches; cartrid
215
216
  `testbench-drive` (`bin/testbench --drive`) and `drive-scenarios` (`bin/drive_scenarios <filter>`), plus `testbench-vic20` for the VIA; the VIC-20 (`vic20/`) →
216
217
  `testbench-vic20` (`bin/testbench --vic20`); the C128 (`c128.rb`, `c128/`), or
217
218
  the VIC-IIe, CPU or PLA code it shares with the C64 → `testbench-c128c64`
218
- (`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
219
220
  model reaches (`bin/sidtests --sid 8580`). Leave the Lorenz chain to CI,
220
221
  unless your code has a rule in `doc/pinned-behaviour.md` that names Lorenz
221
222
  tests: the interrupt polling, CPU port and CIA timer rules. Run just the
222
223
  tests that rule names, one at a time, with
223
224
  `ruby --yjit bin/lorenz --resume <test> --stop-after <test>`. A stretch
224
- 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
225
226
  `trap1`, where the tests start carrying state from one to the next. Both Lorenz
226
227
  and testbench load through the LOAD trap and type through the keyboard
227
228
  buffer, so storage, IEC and keyboard changes can move them too. A broken
data/CONTRIBUTING.md CHANGED
@@ -55,17 +55,18 @@ 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
72
  [65x02 single step tests](https://github.com/SingleStepTests/65x02), and
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
 
@@ -185,8 +186,11 @@ 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
188
  through the KERNAL traps in either mode, or with `--true-drive` by the
188
- C128D's 1571. The C128's Z80 isn't emulated:
189
- the 8502 starts the machine. `--model` picks the board: `c128` (the default, the flat C128
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
190
194
  and the plastic C128D, PAL, with the 6581 SID and a VDC with 16K of
191
195
  RAM), `c128dcr` (the metal C128DCR, with the 8580, 6526As and a VDC with
192
196
  64K), and `c128ntsc` and `c128dcrntsc` for their NTSC versions. `--sid`
@@ -349,11 +353,14 @@ program or `.sid` dropped on it opens the menu to ask first.
349
353
  - **C128**: C128 mode with BASIC 7.0 and C64 mode, on the 8502 with its
350
354
  port, FAST mode, the VIC-IIe with its extra keyboard lines, the 8722
351
355
  MMU, the 8563 or 8568 VDC with its 80 column display, and 128K of RAM
352
- (`badline c128`). The Z80 isn't emulated yet.
356
+ (`badline c128`), and the Z80, which boots the machine.
353
357
  - **1541**: an emulated drive running its own DOS (`--true-drive`).
354
358
  - **1571**: the C128D's drive running DOS 3.0, in 1541 mode and in 1571
355
359
  mode at 2 MHz with both sides of a `.d71` or `.g71`, and fast serial
356
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`).
357
364
  - **Cartridges**: standard 8K, 16K and Ultimax, Simons' BASIC, Ocean,
358
365
  Fun Play / Power Play, Super Games, Epyx FastLoad, Westermann
359
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)
@@ -17,15 +17,12 @@ module Badline
17
17
 
18
18
  # The 6510 port's direction and output registers, and the charge left
19
19
  # on its floating bits.
20
- def port_state = [@port_ddr, @port_out, @port_floating]
20
+ def port_state = [@cpu_port.ddr, @cpu_port.data, @cpu_port.floating]
21
21
 
22
22
  # Sets the port as port_state reads it, as a snapshot restores it,
23
23
  # leaving the RAM under $00 and $01 alone.
24
24
  def restore_port(ddr, out, floating)
25
- @port_ddr = ddr
26
- @port_out = out
27
- @port_floating = floating
28
- update_port!
25
+ @cpu_port.restore(ddr, out, floating)
29
26
  end
30
27
 
31
28
  # The machine's memory and everything on the bus but the keyboard, the
@@ -33,7 +30,7 @@ module Badline
33
30
  # already in the port, built from its setup (Computer#load_state).
34
31
  def save_state(out)
35
32
  out.marker("BUS")
36
- out.int(@port_ddr).int(@port_out).int(@port_floating)
33
+ out.int(@cpu_port.ddr).int(@cpu_port.data).int(@cpu_port.floating)
37
34
  @ram.save_state(out)
38
35
  @color_ram.save_state(out)
39
36
  @ram_expansion.save_state(out)
@@ -55,10 +52,7 @@ module Badline
55
52
  @ram_expansion.load_state(input)
56
53
  @cartridge&.load_state(input)
57
54
  @datasette.load_state(input)
58
- @port_ddr = ddr
59
- @port_out = port_out
60
- @port_floating = floating
61
- @io_port.value = port_value
55
+ @cpu_port.load(ddr, port_out, floating)
62
56
  update_overlays!
63
57
  @vic.load_state(input)
64
58
  @cia1.load_state(input)
@@ -52,9 +52,9 @@ module Badline
52
52
  def poke(_addr, _value); end
53
53
  end
54
54
 
55
+ # The 6510's port pins with pull-ups, and the floating ones (CPUPort).
55
56
  PORT_PULLUPS = 0b0001_0111
56
57
  PORT_FLOATING = 0b1100_1000
57
- TAPE_SENSE = 0b0001_0000
58
58
 
59
59
  # RAM powers on in runs of $00 $00 $FF $FF $FF $FF $00 $00, inverted in
60
60
  # the second and fourth 16K: a C64C (ASSY 250469 R4) from
@@ -92,7 +92,7 @@ module Badline
92
92
 
93
93
  @datasette = Datasette.new
94
94
  @datasette.on_flag { @cia1.flag! }
95
- @datasette.on_sense_change { @io_port.value = port_value }
95
+ @datasette.on_sense_change { @cpu_port.refresh }
96
96
 
97
97
  @color_ram = ColorMemory.new(@vic)
98
98
  @vic.vic_bank.connect(cia2: @cia2, color_ram: @color_ram)
@@ -100,10 +100,9 @@ module Badline
100
100
  @open_bus = OpenBus.new(@vic)
101
101
  @cpu_off_bus = false
102
102
 
103
- @port_ddr = 0x00
104
- @port_out = 0x00
105
- @port_floating = 0x00
106
- @io_port = PortStatus.new(%i[basic kernal io tape_out tape_switch tape_motor], value: port_value)
103
+ @cpu_port = CPUPort.new(@datasette, pullups: PORT_PULLUPS, floating: PORT_FLOATING)
104
+ @cpu_port.on_change { update_overlays! }
105
+ @io_port = @cpu_port.status
107
106
 
108
107
  @read_pages = Array.new(256)
109
108
  @write_pages = Array.new(256)
@@ -134,9 +133,7 @@ module Badline
134
133
  # their charge.
135
134
  def reset!
136
135
  @ram_expansion.reset!
137
- @port_ddr = 0x00
138
- @port_out = 0x00
139
- update_port!
136
+ @cpu_port.reset!
140
137
  end
141
138
 
142
139
  def disable_overlays!
@@ -150,7 +147,7 @@ module Badline
150
147
  end
151
148
 
152
149
  def peek(addr)
153
- return @port_ddr if addr.zero?
150
+ return @cpu_port.ddr if addr.zero?
154
151
  return @io_port.value if addr == 0x01
155
152
 
156
153
  @read_pages[addr >> 8].peek(addr)
@@ -162,8 +159,7 @@ module Badline
162
159
  def poke(addr, value)
163
160
  if addr < 0x02
164
161
  @ram.poke(addr, @vic.phi1_data)
165
- addr.zero? ? @port_ddr = value : @port_out = value
166
- update_port!
162
+ addr.zero? ? @cpu_port.write_ddr(value) : @cpu_port.write_data(value)
167
163
  elsif !@cpu_off_bus
168
164
  @write_pages[addr >> 8].poke(addr, value)
169
165
  end
@@ -185,21 +181,6 @@ module Badline
185
181
 
186
182
  private
187
183
 
188
- def update_port!
189
- driven = @port_ddr & PORT_FLOATING
190
- @port_floating = (@port_floating & ~driven) | (@port_out & driven)
191
- @io_port.value = port_value
192
- # $01 bit 5 drives the motor through an inverter: low runs it.
193
- @datasette.motor = !@io_port.tape_motor?
194
- update_overlays!
195
- end
196
-
197
- def port_value
198
- input = PORT_PULLUPS | (@port_floating & PORT_FLOATING)
199
- input &= ~TAPE_SENSE if @datasette.sense_low?
200
- (@port_out & @port_ddr) | (input & ~@port_ddr & 0xff)
201
- end
202
-
203
184
  # Banking changes only on $01 writes and cartridge line/bank changes,
204
185
  # so reads and writes dispatch through per-page handler tables instead
205
186
  # of range checks.
@@ -116,7 +116,7 @@ module Badline
116
116
  cycles
117
117
  end
118
118
 
119
- def clock_hz = @tune.ntsc? ? NTSC_CLOCK_HZ : TimeOfDay::CLOCK_HZ
119
+ def clock_hz = @tune.ntsc? ? NTSC_CLOCK_HZ : Region::PAL.clock_hz
120
120
 
121
121
  private
122
122
 
@@ -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
@@ -45,7 +47,6 @@ module Badline
45
47
  PORT_PULLUPS = 0b0001_0111
46
48
  PORT_FLOATING = 0b1000_1000
47
49
  CAPS_LOCK = 0b0100_0000
48
- TAPE_SENSE = 0b0001_0000
49
50
 
50
51
  # The C64's power-on pattern (AddressBus::RAM_POWER_ON) in both 64K
51
52
  # banks. The C128's own DRAMs' pattern is unmeasured.
@@ -124,9 +125,6 @@ module Badline
124
125
  # KERNAL and the character ROM's C128 set.
125
126
  attr_reader :basic_low_rom, :basic_high_rom, :editor_rom, :c128_kernal_rom, :c128_character_rom
126
127
 
127
- # Whether CAPS LOCK is down, holding P6 low.
128
- attr_reader :caps_lock
129
-
130
128
  # The chips +model+, a Model::Profile, names, with the SID
131
129
  # +sid_model+. +mode+ is the mode the MMU resets into.
132
130
  def initialize(model, sid_model: model.sid_model, mode: :c64)
@@ -137,7 +135,6 @@ module Badline
137
135
  @mmu = MMU.new(mode)
138
136
  @cartridge = nil
139
137
  @debug_page = nil
140
- @caps_lock = false
141
138
 
142
139
  load_roms
143
140
  load_c128_roms
@@ -148,19 +145,18 @@ module Badline
148
145
 
149
146
  @datasette = Datasette.new
150
147
  @datasette.on_flag { @cia1.flag! }
151
- @datasette.on_sense_change { @io_port.value = port_value }
148
+ @datasette.on_sense_change { @cpu_port.refresh }
152
149
 
153
150
  @color_lines = ColorLines.new(@vic, @cia2, character_rom, @c128_character_rom)
154
151
  @color_ram = @color_lines.color_ram(1)
152
+ build_z80_pages
155
153
  @vic_writes = VICWrites.new(@vic, @control_ports)
156
154
  @open_bus = AddressBus::OpenBus.new(@vic)
157
155
  @configuration_reads = ConfigurationPage.new(@mmu, @open_bus)
158
156
  @configuration_writes = ConfigurationPage.new(@mmu, @open_bus)
159
157
 
160
- @port_ddr = 0x00
161
- @port_out = 0x00
162
- @port_floating = 0x00
163
- @io_port = PortStatus.new(%i[basic kernal io tape_out tape_switch tape_motor], value: port_value)
158
+ @cpu_port = CPUPort.new(@datasette, pullups: PORT_PULLUPS, floating: PORT_FLOATING, caps_lock: CAPS_LOCK)
159
+ @io_port = @cpu_port.status
164
160
 
165
161
  @read_pages = Array.new(256)
166
162
  @write_pages = Array.new(256)
@@ -168,6 +164,7 @@ module Badline
168
164
  @data = 0
169
165
  @io_mapped = false
170
166
  @mmu.on_change { update_overlays! }
167
+ @cpu_port.on_change { update_overlays! }
171
168
  update_overlays!
172
169
  end
173
170
 
@@ -188,19 +185,19 @@ module Badline
188
185
  @ram.clear!(RAM_POWER_ON)
189
186
  end
190
187
 
191
- # The RES line clears the port's direction and output registers and
192
- # resets the MMU. The port's floating bit keeps its charge.
188
+ # The RES line resets the MMU and clears the port's direction and
189
+ # output registers. The port's floating bit keeps its charge.
193
190
  def reset!
194
- @port_ddr = 0x00
195
- @port_out = 0x00
196
191
  @mmu.reset!
197
- update_port!
192
+ @cpu_port.reset!
198
193
  end
199
194
 
195
+ # Whether CAPS LOCK is down, holding P6 low.
196
+ def caps_lock = @cpu_port.caps_lock
197
+
200
198
  # Holds CAPS LOCK down, or lets it up.
201
199
  def caps_lock=(down)
202
- @caps_lock = down
203
- @io_port.value = port_value
200
+ @cpu_port.caps_lock = down
204
201
  end
205
202
 
206
203
  # Calls the block whenever MCR bit 6 takes the machine into C64 mode or
@@ -233,7 +230,7 @@ module Badline
233
230
  @data = if addr > 0x01
234
231
  @read_pages[addr >> 8].peek(addr)
235
232
  else
236
- addr.zero? ? @port_ddr : @io_port.value
233
+ addr.zero? ? @cpu_port.ddr : @io_port.value
237
234
  end
238
235
  end
239
236
 
@@ -251,8 +248,7 @@ module Badline
251
248
  @data = value
252
249
  if addr < 0x02
253
250
  @write_pages[0].poke(addr, @vic.phi1_data)
254
- addr.zero? ? @port_ddr = value : @port_out = value
255
- update_port!
251
+ addr.zero? ? @cpu_port.write_ddr(value) : @cpu_port.write_data(value)
256
252
  else
257
253
  @write_pages[addr >> 8].poke(addr, value)
258
254
  end
@@ -291,21 +287,6 @@ module Badline
291
287
  @vdc = VDC.new(model: model.vdc_model, ram_kb: model.vdc_ram_kb, clock_hz: @region.clock_hz)
292
288
  end
293
289
 
294
- def update_port!
295
- driven = @port_ddr & PORT_FLOATING
296
- @port_floating = (@port_floating & ~driven) | (@port_out & driven)
297
- @io_port.value = port_value
298
- @datasette.motor = !@io_port.tape_motor?
299
- update_overlays!
300
- end
301
-
302
- def port_value
303
- input = PORT_PULLUPS | (@port_floating & PORT_FLOATING)
304
- input |= CAPS_LOCK unless @caps_lock
305
- input &= ~TAPE_SENSE if @datasette.sense_low?
306
- (@port_out & @port_ddr) | (input & ~@port_ddr & 0xff)
307
- end
308
-
309
290
  def update_overlays!
310
291
  @ultimax = @cartridge ? @cartridge.ultimax? : false
311
292
  @phi1_ultimax = @cartridge ? @cartridge.phi1_ultimax? : false
@@ -318,6 +299,7 @@ module Badline
318
299
  romh: @cartridge&.romh)
319
300
  @color_ram = @color_lines.cpu_color_ram(c64_mode, port)
320
301
  c64_mode ? map_pla_pages : map_c128_pages
302
+ update_z80!
321
303
  mode_changed if c64_mode != @c64_mode
322
304
  end
323
305
 
@@ -325,23 +307,6 @@ module Badline
325
307
  @c64_mode = !@c64_mode
326
308
  @on_mode_change&.call
327
309
  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
310
  end
346
311
  end
347
312
  end
@@ -11,7 +11,7 @@ module Badline
11
11
  # host holds. A cartridge is already in the port, built from its setup.
12
12
  def save_state(out)
13
13
  out.marker("C128 BUS")
14
- out.int(@port_ddr).int(@port_out).int(@port_floating).int(@address).int(@data)
14
+ out.int(@cpu_port.ddr).int(@cpu_port.data).int(@cpu_port.floating).int(@address).int(@data)
15
15
  @mmu.save_state(out)
16
16
  @ram.save_state(out)
17
17
  @color_lines.color_ram(0).save_state(out)
@@ -27,9 +27,9 @@ module Badline
27
27
 
28
28
  def load_state(input)
29
29
  input.marker("C128 BUS")
30
- @port_ddr = input.int
31
- @port_out = input.int
32
- @port_floating = input.int
30
+ ddr = input.int
31
+ data = input.int
32
+ floating = input.int
33
33
  @address = input.int
34
34
  @data = input.int
35
35
  @mmu.load_state(input)
@@ -38,7 +38,7 @@ module Badline
38
38
  @color_lines.color_ram(1).load_state(input)
39
39
  @cartridge&.load_state(input)
40
40
  @datasette.load_state(input)
41
- @io_port.value = port_value
41
+ @cpu_port.load(ddr, data, floating)
42
42
  update_overlays!
43
43
  @vic.load_state(input)
44
44
  @control_ports.extra_rows = 0xf8 | @vic.extra_keyboard_lines