badline 0.4.2 → 0.5.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/CLAUDE.md +32 -27
  4. data/CONTRIBUTING.md +5 -0
  5. data/README.md +55 -24
  6. data/doc/pinned-behaviour.md +223 -0
  7. data/exe/badline-ruby +7 -4
  8. data/lib/badline/address_bus/ultimax_pages.rb +38 -0
  9. data/lib/badline/address_bus.rb +19 -29
  10. data/lib/badline/audio/jukebox.rb +6 -4
  11. data/lib/badline/audio/machine_player.rb +10 -6
  12. data/lib/badline/cartridge/kcs_power.rb +3 -3
  13. data/lib/badline/cia.rb +21 -3
  14. data/lib/badline/computer.rb +86 -7
  15. data/lib/badline/cpu.rb +39 -0
  16. data/lib/badline/drive1541/bus.rb +197 -0
  17. data/lib/badline/drive1541/disk.rb +256 -0
  18. data/lib/badline/drive1541/disk_via.rb +33 -0
  19. data/lib/badline/drive1541/gcr.rb +57 -0
  20. data/lib/badline/drive1541/idle.rb +136 -0
  21. data/lib/badline/drive1541/mechanism.rb +277 -0
  22. data/lib/badline/drive1541/orbit.rb +81 -0
  23. data/lib/badline/drive1541/rotation.rb +111 -0
  24. data/lib/badline/drive1541/sector_reader.rb +76 -0
  25. data/lib/badline/drive1541/serial_port.rb +67 -0
  26. data/lib/badline/drive1541/sleep.rb +114 -0
  27. data/lib/badline/drive1541/track.rb +98 -0
  28. data/lib/badline/drive1541.rb +234 -0
  29. data/lib/badline/gui/application.rb +15 -3
  30. data/lib/badline/gui/drive_led_pane.rb +29 -0
  31. data/lib/badline/gui.rb +1 -0
  32. data/lib/badline/iec_bus.rb +109 -0
  33. data/lib/badline/kernal_trap/channel.rb +9 -1
  34. data/lib/badline/kernal_trap/serial.rb +9 -3
  35. data/lib/badline/media/true_drive.rb +54 -0
  36. data/lib/badline/media.rb +22 -1
  37. data/lib/badline/memory.rb +3 -0
  38. data/lib/badline/options.rb +24 -4
  39. data/lib/badline/region.rb +35 -10
  40. data/lib/badline/reu/dma.rb +228 -0
  41. data/lib/badline/reu/ram.rb +65 -0
  42. data/lib/badline/reu/trigger.rb +32 -0
  43. data/lib/badline/reu.rb +276 -0
  44. data/lib/badline/roms/README +10 -0
  45. data/lib/badline/roms/dos1541.rom +0 -0
  46. data/lib/badline/sdl/functions.rb +1 -0
  47. data/lib/badline/storage/d64_image.rb +18 -9
  48. data/lib/badline/storage/d81_image.rb +1 -0
  49. data/lib/badline/storage/disk_image/catalog.rb +52 -0
  50. data/lib/badline/storage/disk_image/writing.rb +16 -0
  51. data/lib/badline/storage/disk_image.rb +2 -0
  52. data/lib/badline/storage/g64_image.rb +222 -0
  53. data/lib/badline/storage/host_directory.rb +21 -1
  54. data/lib/badline/storage/listing.rb +118 -0
  55. data/lib/badline/storage/t64.rb +14 -0
  56. data/lib/badline/storage.rb +2 -0
  57. data/lib/badline/traps.rb +4 -0
  58. data/lib/badline/version.rb +1 -1
  59. data/lib/badline/via/control_lines.rb +3 -0
  60. data/lib/badline/via/fast_forward.rb +53 -0
  61. data/lib/badline/via/shift_register.rb +8 -3
  62. data/lib/badline/via/timer1.rb +36 -0
  63. data/lib/badline/via/timer2.rb +31 -0
  64. data/lib/badline/via.rb +2 -0
  65. data/lib/badline/vic/display_state.rb +7 -7
  66. data/lib/badline/vic/sprite/internal_bus.rb +11 -7
  67. data/lib/badline/vic/sprite/shifter.rb +10 -15
  68. data/lib/badline/vic/sprite/timing.rb +60 -0
  69. data/lib/badline/vic/sprite.rb +14 -12
  70. data/lib/badline/vic/sprites.rb +2 -2
  71. data/lib/badline/vic.rb +86 -39
  72. data/lib/badline.rb +3 -0
  73. metadata +28 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a05f51e8da6e71ad0d80cc763b3daa5d806245164be400d4736ab63de9c0006
4
- data.tar.gz: 58353de430b44bec2aff4f1b401186f1684b7a1898a1f3bb903d2e43b2abbe88
3
+ metadata.gz: 9175400f11b212812092f887bea13e0880417fb151011fdbc568c4789050dcf1
4
+ data.tar.gz: dbd07633e45bfcc06fd6b4ab1b680389276ff81340cc6c72d26bd4e7fdf6baad
5
5
  SHA512:
6
- metadata.gz: d58d47e49a8618017a7cf36ed5c7627a4cd3b88f0a0862497ced34d67935be5d96276d5ab2b8cccb2f07a69c9a48acc4188eeac308839a94d68c20e6ac0435b8
7
- data.tar.gz: caa5504d1cd1f9ebacd1661a85b1ea39e86756d91fc2a0b276a4a416113510b836f9f0f61643712a99874c7c0e0ced5b40b35f84696e77bd36dec159c875c7c9
6
+ metadata.gz: d00a54fba0e530ff578a35b3b5953c0f011b6ea32a2b5ac3f8ccaf19dc8c8f3e26bb81dedfac4c53b026b0e2d0a8e3a35c9f212369624995765c0c8a99678542
7
+ data.tar.gz: '0918a31ea33114c1f46d76ec3c0e9978a5e922bd7b39f6abe9494ee03423fad48f4aecbf8c4a2e0af4414ee19b2ce2d33c6cb21774a473138db15a588c149f2b'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.1](https://github.com/elektronaut/badline/compare/v0.5.0...v0.5.1) (2026-09-29)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * build the native badline with a Spinel that writes .d64 images through the true drive ([97f537a](https://github.com/elektronaut/badline/commit/97f537afe69b10ae044f83ccf4dd018402ebe1be))
9
+
10
+
11
+ ### Performance Improvements
12
+
13
+ * sleep the idle true drive through its timer interrupts ([0482e43](https://github.com/elektronaut/badline/commit/0482e439cfb160ab69027b166b1059ed8fc9dfb3)), closes [#367](https://github.com/elektronaut/badline/issues/367)
14
+
15
+ ## [0.5.0](https://github.com/elektronaut/badline/compare/v0.4.2...v0.5.0) (2026-09-29)
16
+
17
+
18
+ ### Features
19
+
20
+ * add a --true-drive option ([3df4183](https://github.com/elektronaut/badline/commit/3df4183dec298ff6b386a6b6a1c12fb04ce3cca0))
21
+ * run the 1541 as its own machine ([9a4bafa](https://github.com/elektronaut/badline/commit/9a4bafab4c53fc7dc479a8c78527e8711d96c8cf))
22
+ * join the C64 and the 1541 over the IEC serial bus ([7671045](https://github.com/elektronaut/badline/commit/7671045bd47fdf363eb9a57faae3c463835c7544))
23
+ * read D64 disks through the 1541's GCR mechanism ([4c812fa](https://github.com/elektronaut/badline/commit/4c812fa25bad92c5c18219fc22c7a0ed804fe159))
24
+ * write and format disks through the 1541 ([22a2a8b](https://github.com/elektronaut/badline/commit/22a2a8be6b9f84b9fc4d7e214603165462c7ddef))
25
+ * read and write .g64 disk images ([884ac19](https://github.com/elektronaut/badline/commit/884ac19baeb83468444e0631c2fd5840bdffc327))
26
+ * take .g64 images in the native badline ([8d9ab92](https://github.com/elektronaut/badline/commit/8d9ab9285555eaa0681a3113290af872a79d9f0b))
27
+ * skip the 1541's cycles while it idles ([d8502cd](https://github.com/elektronaut/badline/commit/d8502cd589aed3ab7590cf2467b9ea57a76330fc))
28
+ * list a mounted disk with LOAD"$",8 through the KERNAL traps ([c634e18](https://github.com/elektronaut/badline/commit/c634e18e3aa8e1b5ba3096d9b6b4ec9d41afa77b)), closes [#324](https://github.com/elektronaut/badline/issues/324)
29
+ * emulate the REU and run the testbench REU rows ([3d5d69e](https://github.com/elektronaut/badline/commit/3d5d69e7570b9961bfd823adf00c2924c65d45e9)), closes [#232](https://github.com/elektronaut/badline/issues/232)
30
+ * run NTSC machines and the testbench's NTSC rows ([e01ebf1](https://github.com/elektronaut/badline/commit/e01ebf17a03d606ff4a71afdb89506af962300e6)), closes [#306](https://github.com/elektronaut/badline/issues/306)
31
+ * power RAM on in a $00/$ff pattern ([59822c3](https://github.com/elektronaut/badline/commit/59822c3dc2956cd871a6e218cac213cfb38f6bad)), closes [#319](https://github.com/elektronaut/badline/issues/319)
32
+ * go on to the next subtune when one ends in the SID player ([0b2f2a2](https://github.com/elektronaut/badline/commit/0b2f2a209f3cb47d8117126f80217e4cc9030342)), closes [#359](https://github.com/elektronaut/badline/issues/359)
33
+
34
+
35
+ ### Bug Fixes
36
+
37
+ * keep the KCS Power Cartridge's RAM apart from the C64's ([c538b43](https://github.com/elektronaut/badline/commit/c538b43e6794cbeedcc4056fb71b38598525bfa9))
38
+ * write stdout as it goes, so errors appear in order ([42ccb02](https://github.com/elektronaut/badline/commit/42ccb026d6fe92c1ec5b67ab6f561084c5b154b2)), closes [#369](https://github.com/elektronaut/badline/issues/369)
39
+
3
40
  ## [0.4.2](https://github.com/elektronaut/badline/compare/v0.4.1...v0.4.2) (2026-09-28)
4
41
 
5
42
  ### Features
data/CLAUDE.md CHANGED
@@ -17,6 +17,9 @@ live in subdirectories:
17
17
  - **VIC-II**: `vic.rb` and `vic/`, covering the sequencer, sprites, graphics
18
18
  modes, border and register timing
19
19
  - **CIA**: `cia.rb` and `cia/` (timers, serial), plus `time_of_day.rb`
20
+ - **REU**: `reu.rb` and `reu/`, the RAM Expansion Unit's registers and
21
+ its DMA, which `Computer#cycle!` clocks in place of the CPU while it
22
+ holds the bus
20
23
  - **SID**: `sid.rb` and `sid/`. `audio/` plays and renders tunes for
21
24
  `badline-ruby --headless` and `--audio-out`
22
25
  - **Media and host I/O**: `storage/` (disk, tape and cartridge image
@@ -43,8 +46,9 @@ requires only the namespace file.
43
46
  | rspec (`spec/`) | `bundle exec rspec` | Unit behaviour, all of `lib/` |
44
47
  | SingleStepTests 65x02 | `rake test` (100 sampled cases per opcode) | CPU, per-cycle bus traces |
45
48
  | Wolfgang Lorenz suite | `bin/lorenz` | CPU, CIA, interrupts |
46
- | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, and cartridges with `--carts` |
49
+ | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, cartridges with `--carts`, and the true 1541 drive with `--drive` |
47
50
  | VICE SID testprogs | `bin/sidtests` | SID |
51
+ | Drive scenarios | `bin/drive_scenarios` | The true 1541 running the DOS ROM: save, format, the error channel, idle, write protect and autostart |
48
52
  | CIA offline grids | `bundle exec rspec --tag slow spec/badline/cia` | CIA timers and shift register, against a bare CIA in about 2 min |
49
53
 
50
54
  The CIA offline grids replay Lorenz's `cia1ta` and `cia1tb` sweeps (about
@@ -136,7 +140,7 @@ overrides that, capped by the core count, but keep the default, because
136
140
  other worktrees share the machine. Whole runs at 4 shards on an M-series
137
141
  laptop take 15 min for `testbench` (`VICII/`), 11 for `testbench-cia`, 1 for
138
142
  `testbench-interrupts`, 37 for `testbench-irqdma`, 11 for
139
- `testbench-cpu`, 2 for `testbench-carts` and 10 for `testbench-expansions`. `sid` takes about 12 min. `lorenz` chains itself and takes
143
+ `testbench-cpu`, 2 for `testbench-carts` and about 25 for `testbench-expansions`. `sid` takes about 12 min. `lorenz` chains itself and takes
140
144
  about 2.5 h on CI whole. `rake regression:lorenz-1` to `lorenz-4` run it as
141
145
  four stretches of about 40 min each, and they can run side by side.
142
146
  `test/baselines/README.md` has the full table. A killed `bin/testbench` run
@@ -153,42 +157,43 @@ the rows your change can't reach tell you nothing about it.
153
157
  `ruby --yjit bin/lorenz [image] [start_test] [max_cycles]` (resumes the
154
158
  chain at a named test, and `--stop-after NAME` ends it once that test is
155
159
  done). These take seconds to minutes
156
- - When your change moves rows, re-record only those rows, in the same
157
- change: `rake "regression:record:<suite>[filter,...]"` (quote it, because
158
- zsh globs the brackets). It runs only the matching tests and splices
159
- their rows into the baseline, and every other row keeps its verdict.
160
+ - A pull request's CI is the verdict. It runs every suite but
161
+ `testbench-drive` on the Spinel build, compared row by row against
162
+ `test/baselines/`, and every job is a required check. A row that moved
163
+ fails it. Re-record only those rows, in the same change:
164
+ `rake "regression:record:<suite>[filter,...]"` (quote it, because zsh
165
+ globs the brackets). It runs only the matching tests on CRuby and
166
+ splices their rows into the baseline, and every other row keeps its
167
+ verdict. CI runs the same rows on Spinel, so the two have to agree.
160
168
  `lorenz` chains itself, so it takes a stretch of the chain instead:
161
169
  `rake "regression:record:lorenz[first,last]"` resumes just ahead of
162
170
  `first`, stops after `last` and leaves the `(suite)` row alone.
163
171
  `[first,(suite)]` runs on to the end of the chain and records the
164
- `(suite)` row too. Cover every test your change can reach, and explain
165
- every moved row. Never re-record just to make a diff go away
166
- - CI's nightly run owns the whole-suite verdict. The Regression workflow
167
- runs `testbench`, `lorenz` and `sid` against `main` each night, and skips
168
- the night when nothing relevant has changed since the last successful
169
- nightly run. It also runs from the Actions tab on demand. It never runs
170
- on push or on pull requests, so one verdict can cover a day's merges.
171
- The `testbench-*` and `sid-8580` suites run from the Actions tab on
172
- demand. The Spinel workflow runs the Spinel suites on pull requests
173
- that touch emulation, harness or build paths. Its jobs aren't required
174
- checks yet, and the CRuby Regression nightly runs as before
175
- - The one exception is a change whose reach you can't bound to a set of
176
- filters, such as reordering `Computer#cycle!` or changing the LOAD trap
177
- every suite loads through. Ask before running a full suite for it, and
178
- don't start one on your own judgement
172
+ `(suite)` row too. Explain every moved row in the pull request. Never
173
+ re-record just to make a diff go away
174
+ - For a change whose reach you can't bound to a set of filters, such as
175
+ reordering `Computer#cycle!` or changing the LOAD trap every suite loads
176
+ through, push and let CI run the suites
177
+ - `testbench-drive` needs the true drive, which the Spinel testbench
178
+ harness doesn't have, so CI leaves it out. Run the rows your change can
179
+ reach locally
180
+ - The Regression workflow runs the suites on CRuby, started by hand from
181
+ the Actions tab, for a person checking that CRuby and Spinel agree.
182
+ Agents don't start it
179
183
 
180
184
  Pick the filters from the suites your change can move: VIC → `testbench`,
181
185
  plus `testbench-vicii-new` (`bin/testbench --vicii-new`, the 8565) for
182
- anything the VIC model reaches;
186
+ anything the VIC model reaches, and `testbench-ntsc` (`bin/testbench --ntsc`,
187
+ the 6567R8 and 6567R56A) for anything a region's timing reaches;
183
188
  CPU, interrupts or timing → the matching `testbench-*` suite, plus
184
189
  `rake test` for CPU; CIA → the slow CIA specs first, then the matching
185
190
  `testbench-cia` rows, plus `testbench-cia-new` (`bin/testbench --cia-new`,
186
191
  the 6526A) for anything the interrupt register or the CIA model reaches; cartridge mappers, banking or power-on state →
187
- `testbench-carts`, plus `testbench-expansions` for banking; GEO-RAM, +60K
188
- or +256K → `testbench-expansions`; SID → `sid`, plus `sid-8580` for anything the 8580
189
- model reaches (`bin/sidtests --sid 8580`). Lorenz isn't a per-change check:
190
- its full chain runs nightly, and the planner assigns any row it moves. The
191
- exception is code whose rule in `doc/pinned-behaviour.md` names Lorenz
192
+ `testbench-carts`, plus `testbench-expansions` for banking; GEO-RAM, +60K,
193
+ +256K, the REU or the VIC's BA line the REU follows → `testbench-expansions`; the 1541 drive, VIA or IEC bus →
194
+ `testbench-drive` (`bin/testbench --drive`) and `drive-scenarios` (`bin/drive_scenarios <filter>`); SID → `sid`, plus `sid-8580` for anything the 8580
195
+ model reaches (`bin/sidtests --sid 8580`). Leave the Lorenz chain to CI,
196
+ unless your code has a rule in `doc/pinned-behaviour.md` that names Lorenz
192
197
  tests: the interrupt polling, CPU port and CIA timer rules. Run just the
193
198
  tests that rule names, one at a time, with
194
199
  `ruby --yjit bin/lorenz --resume <test> --stop-after <test>`. A stretch
data/CONTRIBUTING.md CHANGED
@@ -85,6 +85,11 @@ bundle exec rubocop
85
85
  ## Tests and commits
86
86
 
87
87
  - Add tests for any behavior you change.
88
+ - CI runs rubocop, the specs and every regression suite on each pull
89
+ request, and fails on any suite row that differs from its baseline in
90
+ `test/baselines/`. When a change moves rows on purpose, re-record just
91
+ those rows as [test/baselines/README.md](test/baselines/README.md)
92
+ describes, and say why in the pull request.
88
93
  - Write commit messages using
89
94
  [Conventional Commits](https://www.conventionalcommits.org). The
90
95
  changelog and releases are generated from them, so the `feat:` and
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  [![Version](https://img.shields.io/gem/v/badline.svg?style=flat)](https://rubygems.org/gems/badline)
2
- [![Build](https://github.com/elektronaut/badline/actions/workflows/build.yml/badge.svg)](https://github.com/elektronaut/badline/actions/workflows/build.yml)
2
+ [![CI](https://github.com/elektronaut/badline/actions/workflows/ci.yml/badge.svg)](https://github.com/elektronaut/badline/actions/workflows/ci.yml)
3
3
 
4
4
  # Badline
5
5
 
6
6
  Badline is a Commodore 64 emulator written in Ruby. It emulates a PAL
7
- machine one clock cycle at a time, stepping the 6510, the VIC-II, both
7
+ or NTSC machine one clock cycle at a time, stepping the 6510, the VIC-II, both
8
8
  CIAs and the SID together, so raster timing, bad lines and sprite DMA
9
9
  are modelled at the cycle level.
10
10
 
@@ -69,7 +69,9 @@ from it. `--no-autostart` attaches the media and stops at `READY.`, so
69
69
  you can type the `LOAD` yourself. `--read-only` mounts a disk image
70
70
  write-protected, so the drive reports `26,WRITE PROTECT ON` for any
71
71
  write and the image file stays as it was. `--song N` picks a subtune of a
72
- `.sid` file and `--sid 8580` fits the newer SID. `badline-ruby` also
72
+ `.sid` file, `--sid 8580` fits the newer SID and `--reu 512` plugs in a
73
+ 512K RAM Expansion Unit. `--ntsc` runs an NTSC C64, with the 6567R8
74
+ VIC-II, instead of a PAL one. `badline-ruby` also
73
75
  takes `--disable-jit`, which runs without YJIT, otherwise switched on at
74
76
  startup. `--help` lists the options.
75
77
 
@@ -100,27 +102,42 @@ slows the emulation down.
100
102
  |--------|----------|
101
103
  | `.prg`, `.p00` | Loaded into memory after boot. A program at the BASIC start (`$0801`) is `RUN`, anything else is left for you to `SYS` |
102
104
  | `.d64`, `.d71`, `.d81` | Mounted read-write as device 8, then `LOAD"*",8,1` and `RUN`. Writes go straight back to the image file. With `--read-only`, or an image the host can't write, it acts as a write-protected disk |
105
+ | `.g64` | Put in a true 1541, which is plugged in as device 8 for it, then `LOAD"*",8,1` and `RUN`. The image holds the disk's raw GCR, half tracks and all, so copy protection and fast loaders that read it work. Tracks the drive writes go back to the image file. With `--read-only`, or an image the host can't write, it acts as a write-protected disk |
103
106
  | `.t64` | Mounted read-only as device 8 and loaded like a disk image. The files load by name, and no tape is involved |
104
107
  | `.tap` | Inserted in the datasette with PLAY pressed, then `LOAD` and `RUN`. It loads at the speed of a real tape |
105
108
  | `.crt` | The hardware types listed under [Cartridges](#whats-emulated). Other types are rejected |
106
109
  | `.sid` | PSID and RSID tunes, started through a small driver after boot |
107
110
  | A directory | Mounted read-write as device 8. It serves the `.prg` and `.p00` files in it and the contents of any `.t64`, and `SAVE` writes a new `.prg` |
108
111
 
109
- There is no 1541. Device 8 works by trapping the KERNAL's `LOAD` and
110
- `SAVE` routines and its serial bus primitives, so files open by name
111
- through `OPEN` and `CHRIN` as well. The command channel answers `I`,
112
- `B-P` and `U1` block reads, which covers loaders that read blocks
113
- directly. On a disk image it also takes `SAVE` (with `@0:` to replace a
114
- file), files opened for writing or appending, `S` to scratch, `U2` and
115
- `B-W` block writes, and `B-A` and `B-F`. Loaders that upload their own
116
- code to the drive with `M-W` and `M-E`, and copy protection that reads
117
- raw GCR, won't work.
112
+ Apart from a `.g64`, which only a true drive can read, there is no 1541
113
+ unless `--true-drive` asks for one. Device 8 works by trapping the
114
+ KERNAL's `LOAD` and `SAVE` routines and its serial bus primitives, so
115
+ files open by name through `OPEN` and `CHRIN` as well. `LOAD"$",8` lists
116
+ the directory of any medium mounted there, as a 1541 does. The command
117
+ channel answers `I`, `B-P` and `U1` block reads, which covers loaders
118
+ that read blocks directly. On a disk image it also takes `SAVE` (with
119
+ `@0:` to replace a file), files opened for writing or appending, `S` to
120
+ scratch, `U2` and `B-W` block writes, and `B-A` and `B-F`. Loaders that
121
+ upload their own code to the drive with `M-W` and `M-E`, and copy
122
+ protection that reads raw GCR, won't work there, but do on a true
123
+ drive.
124
+
125
+ `--true-drive` puts an emulated 1541 on device 8 instead, running its
126
+ own DOS ROM on its own 6502 and talking to the machine over the serial
127
+ bus. It reads `.d64` and `.g64` images, which it autostarts with the
128
+ same `LOAD"*",8,1` and `RUN`, but not `.d71`, `.d81` or `.t64` images
129
+ or directories. `--read-only` puts the disk in write-protected. Loading
130
+ runs at the speed of a real 1541, and the drive's red LED lights in the
131
+ bottom right corner of the border. Both executables take it.
118
132
 
119
133
  `Badline::Media.insert_disk(computer, path)` swaps the disk image or
120
134
  directory in device 8 while the machine runs, for software that asks for
121
135
  another disk. It takes `read_only: true`, and `Badline::Media.attach`
122
136
  takes `disk: { read_only: true }`, to mount a disk image write-protected,
123
- as the test harnesses under `bin/` do.
137
+ as the test harnesses under `bin/` do. A `.g64` goes in the true 1541,
138
+ and takes out a disk mounted through the traps, so `LOAD` and `SAVE`
139
+ reach the 1541 too. With the 1541 in device 8, a `.d64` goes in its
140
+ drive as well.
124
141
 
125
142
  ## Playing and rendering SID tunes
126
143
 
@@ -139,16 +156,19 @@ badline-ruby --filter-chunk 1 tune.sid --audio-out out.wav # exact filter, slo
139
156
  ```
140
157
 
141
158
  Both modes take the same options. The window's own, `--no-autostart`,
142
- `--sound` and `--verbose`, don't apply to them. `--song` (or `-s`) picks the
143
- subtune, counting from 1 as HVSC does, and defaults to the tune's own
144
- start song. Playback asks the device for 44.1 kHz and takes whatever rate it
145
- offers, unless `--rate` says otherwise. Ctrl-C stops it.
159
+ `--read-only`, `--sound`, `--true-drive`, `--reu`, `--ntsc` and
160
+ `--verbose`, don't apply to them. `--song` (or `-s`) picks the subtune, counting from 1 as HVSC
161
+ does, and defaults to the tune's own start song. Playback asks the
162
+ device for 44.1 kHz and takes whatever rate it offers, unless `--rate`
163
+ says otherwise. Ctrl-C stops it.
146
164
 
147
165
  Played on a terminal, `--headless` shows the tune's name, author and
148
166
  release, the song number and the time played against the song's
149
- length. `n` or → skips to the next song, `p` or ← goes back one, space
150
- pauses and `q` quits. `--no-tui`, or output that isn't a terminal,
151
- gives plain progress output instead.
167
+ length. When a song reaches its length it goes on to the next, and it
168
+ stops after the last. `n` or → skips to the next song, `p` or ← goes
169
+ back one, space pauses and `q` quits. `--no-tui`, or output that isn't a
170
+ terminal, gives plain progress output instead and plays just the one
171
+ song, as `--audio-out` renders just the one.
152
172
 
153
173
  A `.sid` file doesn't store its length, so `badline-ruby` looks the
154
174
  tune up by MD5 in HVSC's `Songlengths.md5`. It finds the database
@@ -236,6 +256,13 @@ while it's off.
236
256
  `Badline::Computer.new(vic_model: :mos8565)` fits the C64C's 8565
237
257
  instead, with its grey dots on colour register writes and its own
238
258
  timing for mode splits, sprite multicolour splits and the light pen.
259
+ `Badline::Computer.new(region: Badline::Region::NTSC)` builds an NTSC
260
+ machine instead: the 6567R8's 65 cycles by 263 lines at 1,022,727 Hz,
261
+ with its later sprite fetches and its X counter, and TOD clocks on
262
+ 60 Hz mains. `Badline::Region::NTSC_OLD` is the first NTSC C64s'
263
+ 6567R56A, 64 cycles by 262 lines. The stock KERNAL tells them from PAL
264
+ by the raster, so every region boots the same ROMs. `--ntsc` picks the
265
+ 6567R8.
239
266
  - **CIA 1 and 2**: timers, time-of-day clocks with alarms, the serial
240
267
  shift register, interrupts, the keyboard matrix with its ghost keys,
241
268
  the control ports and the paddle multiplexer. The machine has the
@@ -246,6 +273,11 @@ while it's off.
246
273
  and the RC network on the board that removes the DC offset from the
247
274
  output. The machine has a 6581 unless a `.sid` tune asks for an 8580
248
275
  in its header, and `--sid 6581` or `--sid 8580` overrides either.
276
+ - **REU**: the 1700, 1764 and 1750 RAM Expansion Units, and the bigger
277
+ units up to 16M built on the same REC chip, with DMA timed against the
278
+ VIC's bad lines and sprites. `--reu SIZE` plugs one in, and
279
+ `Badline::Computer.new(reu: 512)` does the same from Ruby. An
280
+ REU beside a cartridge loses I/O 2 to the cartridge.
249
281
  - **Datasette**: `.tap` playback into CIA 1's FLAG line, with the motor
250
282
  and sense lines on the 6510 port.
251
283
  - **Cartridges**: standard 8K, 16K and Ultimax, Simons' BASIC, Ocean,
@@ -276,11 +308,10 @@ Known gaps:
276
308
  - `badline-ruby` runs below real time, so its live audio stutters.
277
309
  `badline` plays smoothly, and so do PSID tunes under
278
310
  `badline-ruby --headless`.
279
- - No drive emulation, so fast loaders and anything else that runs code
311
+ - Without `--true-drive`, fast loaders and anything else that runs code
280
312
  on the drive won't work (see [Media](#media)). The command channel
281
- doesn't rename, copy, format or validate disks, and `LOAD"$",8`
282
- doesn't list a disk's directory yet.
283
- - No NTSC machine and no REU.
313
+ doesn't rename, copy, format or validate disks.
314
+ - No PAL-N (Drean) machine.
284
315
  - The emulator window has no freeze button yet, so a freezer cartridge
285
316
  runs its menu but can't freeze a program.
286
317
 
@@ -25,10 +25,15 @@ only catches the rows that happen to move.
25
25
  - [VIC phi1 bus](#vic-phi1-bus)
26
26
  - [VIC light pen](#vic-light-pen)
27
27
  - [VIC-II 8565](#vic-ii-8565)
28
+ - [VIC-II NTSC](#vic-ii-ntsc)
28
29
  - [CIA 6526 timer pipeline](#cia-6526-timer-pipeline)
29
30
  - [CIA 6526A interrupt register](#cia-6526a-interrupt-register)
30
31
  - [CIA serial shift register](#cia-serial-shift-register)
31
32
  - [6510 I/O port](#6510-io-port)
33
+ - [RAM power-on pattern](#ram-power-on-pattern)
34
+ - [1541 serial port](#1541-serial-port)
35
+ - [1541 disk mechanism](#1541-disk-mechanism)
36
+ - [REU DMA](#reu-dma)
32
37
  - [SID oscillator](#sid-oscillator)
33
38
  - [SID register writes](#sid-register-writes)
34
39
  - [SID data bus](#sid-data-bus)
@@ -888,6 +893,46 @@ What the 8565 references don't settle, and so what stays as it is:
888
893
  6569's idle access, without the 6569's `$38ff` read where a DMA delay
889
894
  starts.
890
895
 
896
+ ## VIC-II NTSC
897
+
898
+ The 6567R8 (`Region::NTSC`) and the 6567R56A (`Region::NTSC_OLD`) run
899
+ every VIC rule above, on a line of 65 or 64 cycles, except where the
900
+ region moves the sprites. The positions live in `Region::Profile` and
901
+ `VIC::Sprite::Timing`.
902
+
903
+ - **Sprite fetches.** Sprite 0's p-access runs in cycle 59 instead of
904
+ 58, and everything that hangs off the fetches moves with it: the BA
905
+ windows (`VIC#layout_columns`), the DMA compares (columns 54 and 55
906
+ instead of 53 and 54), the phi1 accesses (`VIC#phi1_address`, where the
907
+ 6567R8 idles in cycle 10), the column whose BA a late DMA start misses
908
+ (`Sprite::Timing#ba_column`), the reload pixels
909
+ (`Sprite::Timing#reload_x`) and the idle-bus row of sprites 3-7
910
+ (`Sprite::InternalBus#row`). On the 6567R8 sprite 3's fetch then falls
911
+ in the last two cycles of the line, so it reloads with the next line's
912
+ row as sprites 0-2 do. The expansion flip-flop still toggles in column
913
+ 54.
914
+ - Pinned by `spritesteal_ntsc` and `_ntscold`, `spritex/testsuite_ntsc`
915
+ and `_ntscold`, `phi1timing_ntsc` and `_ntscold`, and
916
+ `spriteenable1_ntsc` and `2_ntsc` and their `_ntscold` twins (235 and
917
+ 458 px), all in `testbench-ntsc`: with sprite 0 back in cycle 58 each
918
+ of them fails, and every other row of the sprite, phi1, dmadelay, vsp
919
+ and screenpos subsets passes as before.
920
+ - Spec guard: *sprite DMA cycle stealing on NTSC* and *#phi1_data on
921
+ NTSC* in [`vic_spec.rb`](../spec/badline/vic_spec.rb), and
922
+ [`timing_spec.rb`](../spec/badline/vic/sprite/timing_spec.rb).
923
+ - **Display compare and X counter.** The 6567R8 turns a sprite's display
924
+ on and off in cycle 59 (`sprite_display_cycle`), where the 6567R56A
925
+ keeps the 6569's cycle 58. Its X counter holds for 8 pixels
926
+ (`x_hold`), reading $184-$187 three times, so it reaches $1ff on a
927
+ 520-pixel line. The 6567R56A's runs straight through 512 pixels.
928
+ Neither is pinned yet: moving the 6567R8's display compare to 58, the
929
+ 6567R56A's to 59, or running the 6567R8's counter straight through and
930
+ wrapping it at 512 fails no row of those subsets.
931
+ - **Blanking and crop.** The blanked lines are the ones VICE's NTSC view
932
+ leaves out, 12-27 on the 6567R8 and 13-27 on the 6567R56A, so the
933
+ `testbench-ntsc` screenshots, VICE's 247 lines from line 28 running on
934
+ into the next frame, see every line they show painted.
935
+
891
936
  ## CIA 6526 timer pipeline
892
937
 
893
938
  - Start and stop go through two stages. A force load lands one tick after
@@ -1091,6 +1136,184 @@ and each was knocked out: removing it fails the rows named.
1091
1136
  - Spec guard: *when a program writes to the port* in
1092
1137
  [`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
1093
1138
 
1139
+ ## RAM power-on pattern
1140
+
1141
+ - RAM powers on in runs of `$00,$00,$ff,$ff,$ff,$ff,$00,$00`, inverted in
1142
+ `$4000-$7fff` and `$c000-$ffff`, on every power cycle. The RAM under
1143
+ `$00`/`$01` follows the pattern like the rest. It is the pattern of a
1144
+ C64C (ASSY 250469 R4) in `C64/raminitpattern/readme.txt`
1145
+ (`-raminitstartvalue 0 -raminitvalueinvert 4 -raminitvalueoffset 2
1146
+ -raminitpatterninvert 16384 -raminitpatterninvertvalue 255`), without
1147
+ that machine's occasional random bytes. `AddressBus::RAM_POWER_ON`
1148
+ holds it.
1149
+ - Pinned by `C64/raminitpattern`: `cyberloadtest` fails when
1150
+ `$f379-$f478` holds one value, `darkstarbbstest` when the first ten
1151
+ bytes of one of the pages `$4000`, `$5000` … `$9000` do, `platoontest`
1152
+ when `$1000-$10ff` holds five equal bytes in a row and 140 or more
1153
+ bytes of one value, and `typicaltest`
1154
+ checks `$3fff` against a table of values the Typical demo survives.
1155
+ All-`$00` fails the first three, and each other pattern the readme
1156
+ lists fails at least one of the four.
1157
+ - Spec guard: *RAM at power-on* in
1158
+ [`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
1159
+ - The RAM expansions' extra banks still power on at `$00`.
1160
+
1161
+ ## 1541 serial port
1162
+
1163
+ - The drive sees the C64's side of the serial bus a host cycle late. The
1164
+ drive runs after the C64 in each host cycle, so `SerialPort` reads the
1165
+ C64's lines as `latch_host` took them at the end of the previous host
1166
+ cycle, both on VIA 1's port B (DATA IN, CLK IN, ATN IN) and on CA1,
1167
+ which ATN reaches. A CIA 2 write lands for the drive's cycles of the
1168
+ next host cycle, the way the CIA's pins change at the end of the cycle
1169
+ that writes them. The drive's own lines, and what the C64 reads back,
1170
+ stay live.
1171
+ - The 2-bit loader that uploads the drive tests' code times its transfer
1172
+ from an ATN edge, and seeing that edge a cycle early loses the first bit
1173
+ pair.
1174
+ - Pinned by the `testbench-drive` rows below. Each was checked by having
1175
+ the drive read the live lines (`@bus.atn_low?` and `@bus.low_lines`
1176
+ with no argument) and rerunning the rows:
1177
+ - Fail without the latch (`exit=$ff`): `drive/selftest`,
1178
+ `drive/diskid/diskid1`, `drive/interrupts/timera`, all six
1179
+ `drive/scanner` rows, both `drive/openbus` rows and
1180
+ `drive/viavarious/via1`, `via3a` and `via10`.
1181
+ - Pass either way: `drive/defaults`, `drive/rpm/rpm1` and `rpm2`,
1182
+ `drive/skew/skew1` and `drive/iecdelay/iec-bus-delay-auto`.
1183
+ - Spec guard: *sees an assertion from the host cycle after the one it
1184
+ lands in* and *shows the drive the C64's CLK from the host cycle after
1185
+ the write* in [`iec_bus_spec.rb`](../spec/badline/iec_bus_spec.rb).
1186
+
1187
+ ## 1541 disk mechanism
1188
+
1189
+ - A step out against the stop at track 1 slips the stepper's phases: the
1190
+ head stays on half track 2, and the phase that pulled it outwards
1191
+ becomes that half track's. The DOS's bump steps out 92 half tracks, a
1192
+ whole number of phase turns, and then takes the phase it ends on as
1193
+ track 1's: `N:` formats track 1 there and steps in two phases a track.
1194
+ With each half track holding its own phase from power-on instead, the
1195
+ bump ended two half tracks off track 1's phase, the first step in
1196
+ pulled the head against the stop, and the DOS wrote track 2 over track
1197
+ 1 and every later track one track out, at half track 2n - 2.
1198
+ - The DOS reads its own format back either way, since each header
1199
+ carries the track the DOS thought it was on: `drive/format` passes
1200
+ without the slip, and so doesn't pin it. Reading the disk back into a
1201
+ D64 does, since each header has to name the track the image holds it
1202
+ on.
1203
+ - Spec guard: *steps in from the stop with the first phase after the
1204
+ bump* in
1205
+ [`mechanism_spec.rb`](../spec/badline/drive1541/mechanism_spec.rb),
1206
+ and the `format/name-and-id`, `format/bam-free` and
1207
+ `format/trap-readable` rows of the `drive-scenarios` suite
1208
+ ([`drive_scenarios.rb`](../test/drive_scenarios.rb)), which read the
1209
+ formatted disk back from its image.
1210
+ - The disk turns at 300 rpm whatever bit rate VIA 2's PB5-6 select: a
1211
+ turn is 200,000 drive cycles over every track and over a half track
1212
+ without data. Each track's bits pass under the head at the rate they
1213
+ were written at: spread evenly over the turn, so a track as long as a
1214
+ turn holds at its zone's rate passes at that rate and a `.g64` track
1215
+ longer or shorter than that passes faster or slower, and a `.g64`
1216
+ speed map widens each byte's cells by its zone. The read clock runs at
1217
+ the selected rate and each flux transition brings it back into step,
1218
+ so a track read in its own zone reads clean and one read far enough
1219
+ off garbles (zone 0 against zone 3). With the rate selected turning
1220
+ the disk instead, a zone 3 track read in zone 0 read clean and took
1221
+ 246 ms a turn, and a `.g64` track turned in its length of bytes.
1222
+ - Pinned by `drive/rpm` (`rpm1`, `rpm2` and `rpm3`, on the `.d64` and
1223
+ the `.g64`): each times a turn and passes within 297 to 303 rpm.
1224
+ - Pinned by `drive/skew/skew2`, on a `.g64` whose tracks all start
1225
+ sector 0 at the same angle: the head keeps the disk's angle across
1226
+ the tracks and half tracks it steps over only while every track
1227
+ turns in the same time.
1228
+ - Pinned by `drive/scanner` (all six programs, on the `.d64` and the
1229
+ `.g64`), which reads every track and each track's error map.
1230
+ - Spec guard: the *at 300 rpm* examples and *turns once in 200 ms* in
1231
+ [`mechanism_spec.rb`](../spec/badline/drive1541/mechanism_spec.rb),
1232
+ and *#cell_at* in
1233
+ [`track_spec.rb`](../spec/badline/drive1541/track_spec.rb).
1234
+ - A write lays its bits one to a cell from the cell under the head. A
1235
+ half track without data gets a blank track first, as long as a turn at
1236
+ the rate the drive writes at, not at the rate of the track's own zone,
1237
+ and a track written in another zone is laid out again as a turn at the
1238
+ rate written, its flux kept at its angle to within a cell. A track
1239
+ written in the selected zone keeps its length, so a `.g64` track
1240
+ longer or shorter than a turn keeps the sectors around the one written.
1241
+ - Pinned by `drive/rpm/rpm3`, which writes track 36, past a 35-track
1242
+ D64, at the zone 2 rate the DOS leaves selected there: a turn and a
1243
+ half of SYNC and five bytes, and times a turn by reading it back.
1244
+ With a zone 0 track of 6250 bytes a turn took 175,001 cycles
1245
+ (342.86 rpm, `exit=$ff`). With the last byte's leftover part of a
1246
+ cell widening the track's last cell, instead of the cells spread
1247
+ evenly over the turn, the SYNC broke there and the turn read as
1248
+ 194,728 cycles (`exit=$ff`).
1249
+ - Spec guard: *gives a half track a blank track 7142 bytes around,
1250
+ written in zone 2* in
1251
+ [`disk_spec.rb`](../spec/badline/drive1541/disk_spec.rb), and *lays
1252
+ a track written in another zone out again as a turn at the zone
1253
+ written* and *writes a turn of SYNC that reads back without a break*
1254
+ in [`mechanism_spec.rb`](../spec/badline/drive1541/mechanism_spec.rb).
1255
+ - A disk made from a `.d64` starts sector 0 of each track where the DOS's
1256
+ `N:` leaves it: track 1 at the index angle, and each track after it
1257
+ round from the last by the skew `N:` leaves in its zone, 0.6869 of a
1258
+ turn in zone 3, 0.8915 in zone 2, 0.0897 in zone 1 and 0.2916 in zone
1259
+ 0. Those are measured off a disk formatted here with `N:`.
1260
+ - Pinned by `drive/skew/skew1`, which expects a `.d64` to read with
1261
+ the skew a DOS format leaves. With every track starting sector 0 at
1262
+ the same angle it read `kernal format, tracks are aligned`
1263
+ (`exit=$ff`).
1264
+ - The skew moves which block the DOS meets first on each track, and
1265
+ `drive/scanner`'s three error-map rows (`scanner35e`, `40e`, `42e`)
1266
+ read track 4's error 22 only when the header comes first. They pass
1267
+ with this skew at 300 rpm. With the rate selected turning the disk,
1268
+ the same skew made them read track 4 as error 27, which is why
1269
+ `skew1` failed before.
1270
+ - Spec guard: *starts sector 0 of track N where the DOS's N: leaves
1271
+ it* in [`disk_spec.rb`](../spec/badline/drive1541/disk_spec.rb).
1272
+ - The CPU sees BYTE READY on SO a cycle late: BYTE READY sets V for the
1273
+ next cycle's instruction step, not the one in the cycle it falls in.
1274
+ - Pinned by `drive/hls-protection`. Its drive code counts the bytes
1275
+ from one SYNC mark to the next, reading `$1C00` for SYNC once per
1276
+ byte, 20 to 22 cycles after each BYTE READY with the delay (19 to 21
1277
+ without). The SYNC marks it has to find follow an `$AF`, whose four
1278
+ trailing 1 bits make SYNC six bits, 19 cycles in zone 3, after its
1279
+ BYTE READY; the ones it has to miss follow a `$2B`, eight bits on.
1280
+ With V set in the same cycle, the read sometimes came a cycle
1281
+ before SYNC, depending on the bit phase: on track 1 two of the 16
1282
+ counts read `$02C8`, two sectors' worth, and the testbench row failed
1283
+ (`exit=$ff`) on track 17. With the delay, tracks 1, 5, 10, 12 and 17
1284
+ (the 4-cycle loop) and 18, 20 and 24 (the 6-cycle loop) all read as
1285
+ the test expects.
1286
+ - Spec guard: *sets V through SO while CA2 is high, a cycle after BYTE
1287
+ READY* in
1288
+ [`mechanism_spec.rb`](../spec/badline/drive1541/mechanism_spec.rb).
1289
+
1290
+ ## REU DMA
1291
+
1292
+ The REC's timing against the VIC, each rule derived from the REU
1293
+ testprogs named.
1294
+
1295
+ - A requested transfer takes the bus on the CPU's next read cycle with BA
1296
+ high, and moves its first byte there. Starting a cycle later moves every
1297
+ `REU/xfertiming` row and `REU/reutiming/reutiming`.
1298
+ - After reading C64 memory the REC waits out every BA-low cycle. After
1299
+ writing it goes on through the first BA-low cycle and waits from the
1300
+ second, and a fetch or swap whose last write it waited out takes one
1301
+ more cycle before handing the bus back (`REU::DMA#note_ba`,
1302
+ `#wind_down`). Pinned by `REU/bonzai/spritetiming`, whose 45 bytes a
1303
+ line with eight sprites on is 63 less the 18 cycles this leaves.
1304
+ - On the line whose raster matches sprite 0's Y, the REU doesn't see BA
1305
+ fall on the first cycle of sprite 0's window (`VIC#reu_ba_low?`).
1306
+ Without it `REU/bonzai/spritetiming` reads `$5a,$87` where a real REU
1307
+ gives `$5b,$88`.
1308
+ - A swap's read that falls on the first BA-low cycle, after its write, is
1309
+ held open while AEC stays high and takes the byte on the bus two cycles
1310
+ later. If it is the transfer's last byte it is read there instead, its
1311
+ write follows on the next cycle, and the REC hands the bus back
1312
+ (`REU::DMA#swap_read_on_ba`). Pinned by `REU/reutiming2/e5-m2`, `f3-m2`
1313
+ and `f4-m2`, whose patterns were captured on a breadbin. The
1314
+ `reutiming2` references without `-m2` read `$42` or a timer value there
1315
+ instead, and still fail.
1316
+
1094
1317
  ## SID oscillator
1095
1318
 
1096
1319
  - The phase accumulator powers on at `$555555` (all bits high, with the odd
data/exe/badline-ruby CHANGED
@@ -6,6 +6,8 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
6
 
7
7
  require "badline/options"
8
8
 
9
+ $stdout.sync = true
10
+
9
11
  begin
10
12
  options = Badline::Options.parse(ARGV)
11
13
  rescue Badline::Options::Error => e
@@ -47,12 +49,13 @@ end
47
49
 
48
50
  begin
49
51
  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?, verbose: options.verbose?,
52
+ song: options.song, sound: options.sound?, verbose: options.verbose?,
53
+ machine: { sid_model: options.sid_model, reu: options.reu,
54
+ true_drive: options.true_drive?, ntsc: options.ntsc? },
52
55
  disk: { read_only: options.read_only? })
53
- rescue Badline::Storage::SIDFile::FormatError, Badline::Storage::T64::FormatError,
56
+ rescue Badline::Media::TrueDrive::Error, Badline::Storage::SIDFile::FormatError, Badline::Storage::T64::FormatError,
54
57
  Badline::Storage::TAP::FormatError, Badline::Storage::CRTFile::FormatError,
55
- Badline::Cartridge::UnsupportedTypeError => e
58
+ Badline::Storage::G64Image::FormatError, Badline::Cartridge::UnsupportedTypeError => e
56
59
  warn "badline-ruby: #{options.media_path}: #{e.message}"
57
60
  exit 1
58
61
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class AddressBus
5
+ # The page tables of an Ultimax cartridge, which AddressBus lays out
6
+ # in place of the $01 banking while the cartridge holds GAME low and
7
+ # EXROM high.
8
+ module UltimaxPages
9
+ private
10
+
11
+ # Ultimax cartridges ignore the $01 lines: 4K of RAM, ROML/ROMH windows,
12
+ # I/O always visible and open address space everywhere else. The ROML
13
+ # and ROMH selects fire on writes as well, so cartridge RAM or flash in
14
+ # either window takes the writes there.
15
+ def map_ultimax_pages
16
+ @read_pages.fill(@open_bus, 0x10, 0xf0)
17
+ @write_pages.fill(@open_bus, 0x10, 0xf0)
18
+ @read_pages.fill(@cartridge.roml, 0x80, 0x20) if @cartridge.roml
19
+ map_ultimax_writes(@cartridge.roml, 0x80)
20
+ @read_pages.fill(@cartridge.romh, 0xe0, 0x20) if @cartridge.romh
21
+ map_ultimax_writes(@cartridge.romh, 0xe0)
22
+ map_ultimax_a000
23
+ map_io_pages
24
+ end
25
+
26
+ def map_ultimax_writes(bank, first_page)
27
+ @write_pages.fill(bank, first_page, 0x20) if bank.respond_to?(:poke)
28
+ end
29
+
30
+ def map_ultimax_a000
31
+ return unless (window = @cartridge.ultimax_a000)
32
+
33
+ @read_pages.fill(window, 0xa0, 0x20)
34
+ @write_pages.fill(window, 0xa0, 0x20)
35
+ end
36
+ end
37
+ end
38
+ end