badline 0.9.0 → 0.10.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/CLAUDE.md +6 -4
  4. data/README.md +39 -4
  5. data/doc/library.md +15 -1
  6. data/doc/pinned-behaviour.md +146 -0
  7. data/doc/snapshots.md +17 -3
  8. data/lib/badline/address_bus/fittings.rb +38 -0
  9. data/lib/badline/address_bus/pla.rb +160 -0
  10. data/lib/badline/address_bus/roms.rb +12 -5
  11. data/lib/badline/address_bus.rb +15 -90
  12. data/lib/badline/chrout_trap.rb +4 -3
  13. data/lib/badline/computer/attachments.rb +47 -7
  14. data/lib/badline/computer/saved_state.rb +1 -0
  15. data/lib/badline/computer.rb +52 -37
  16. data/lib/badline/control_ports.rb +10 -3
  17. data/lib/badline/core.rb +3 -0
  18. data/lib/badline/datasette.rb +39 -1
  19. data/lib/badline/drive1541/serial_port.rb +5 -8
  20. data/lib/badline/drive1541/sleep.rb +5 -6
  21. data/lib/badline/drive1541.rb +3 -3
  22. data/lib/badline/frontend/app.rb +62 -64
  23. data/lib/badline/frontend/boot.rb +33 -8
  24. data/lib/badline/frontend/controls.rb +12 -6
  25. data/lib/badline/frontend/drive_led.rb +9 -10
  26. data/lib/badline/frontend/frame_rate.rb +4 -4
  27. data/lib/badline/frontend/machine_window.rb +87 -0
  28. data/lib/badline/frontend/menu_media.rb +49 -24
  29. data/lib/badline/frontend/menu_pages.rb +23 -40
  30. data/lib/badline/frontend/menu_ports.rb +69 -0
  31. data/lib/badline/frontend/pacer.rb +6 -5
  32. data/lib/badline/frontend/pause_menu.rb +30 -33
  33. data/lib/badline/frontend/player_screen.rb +1 -1
  34. data/lib/badline/frontend/player_window.rb +1 -1
  35. data/lib/badline/frontend/screen.rb +39 -37
  36. data/lib/badline/frontend/sid_view.rb +1 -1
  37. data/lib/badline/frontend/sound.rb +11 -9
  38. data/lib/badline/frontend/timeline.rb +36 -5
  39. data/lib/badline/frontend.rb +2 -0
  40. data/lib/badline/iec_bus.rb +16 -28
  41. data/lib/badline/kernal_trap/drive.rb +3 -0
  42. data/lib/badline/kernal_trap/file.rb +1 -1
  43. data/lib/badline/kernal_trap/layout.rb +112 -0
  44. data/lib/badline/kernal_trap/load.rb +16 -27
  45. data/lib/badline/kernal_trap/routine.rb +11 -27
  46. data/lib/badline/kernal_trap/save.rb +13 -23
  47. data/lib/badline/kernal_trap/serial.rb +21 -19
  48. data/lib/badline/kernal_trap.rb +1 -0
  49. data/lib/badline/keyboard.rb +28 -14
  50. data/lib/badline/machine.rb +60 -0
  51. data/lib/badline/media/vic20_basic.rb +54 -0
  52. data/lib/badline/media/vic20_cartridge.rb +74 -0
  53. data/lib/badline/media/vic20_media.rb +132 -0
  54. data/lib/badline/media.rb +32 -18
  55. data/lib/badline/model.rb +29 -12
  56. data/lib/badline/options/event.rb +5 -3
  57. data/lib/badline/options/help.rb +11 -3
  58. data/lib/badline/options/table.rb +12 -2
  59. data/lib/badline/options/validation.rb +55 -4
  60. data/lib/badline/options.rb +33 -33
  61. data/lib/badline/roms/README +51 -0
  62. data/lib/badline/roms/c64gs.rom +0 -0
  63. data/lib/badline/roms/kernal-pet64.rom +0 -0
  64. data/lib/badline/roms/vic20/basic.rom +0 -0
  65. data/lib/badline/roms/vic20/character.rom +0 -0
  66. data/lib/badline/roms/vic20/kernal-ntsc.rom +0 -0
  67. data/lib/badline/roms/vic20/kernal-pal.rom +0 -0
  68. data/lib/badline/snapshot/container.rb +2 -0
  69. data/lib/badline/snapshot/image.rb +15 -0
  70. data/lib/badline/snapshot/setup.rb +15 -10
  71. data/lib/badline/snapshot/state.rb +5 -3
  72. data/lib/badline/snapshot/vice.rb +2 -1
  73. data/lib/badline/snapshot.rb +10 -0
  74. data/lib/badline/storage/crt_file.rb +16 -5
  75. data/lib/badline/storage/disk_image.rb +3 -0
  76. data/lib/badline/storage/host_directory.rb +3 -0
  77. data/lib/badline/storage/t64.rb +3 -0
  78. data/lib/badline/storage/tap.rb +38 -4
  79. data/lib/badline/storage/unavailable.rb +2 -0
  80. data/lib/badline/timing.rb +12 -0
  81. data/lib/badline/version.rb +1 -1
  82. data/lib/badline/via/shift_register.rb +43 -16
  83. data/lib/badline/vic/bank.rb +55 -17
  84. data/lib/badline/vic/registers.rb +20 -4
  85. data/lib/badline/vic.rb +48 -15
  86. data/lib/badline/vic20/attachments.rb +103 -0
  87. data/lib/badline/vic20/bus.rb +308 -0
  88. data/lib/badline/vic20/cpu.rb +10 -0
  89. data/lib/badline/vic20/keyboard_via_ports.rb +53 -0
  90. data/lib/badline/vic20/port_wiring.rb +50 -0
  91. data/lib/badline/vic20/saved_state.rb +152 -0
  92. data/lib/badline/vic20/sound/output.rb +83 -0
  93. data/lib/badline/vic20/sound.rb +233 -0
  94. data/lib/badline/vic20/user_via_ports.rb +47 -0
  95. data/lib/badline/vic20/vic/painter.rb +239 -0
  96. data/lib/badline/vic20/vic/saved_state.rb +55 -0
  97. data/lib/badline/vic20/vic/text_window.rb +71 -0
  98. data/lib/badline/vic20/vic.rb +267 -0
  99. data/lib/badline/vic20.rb +220 -0
  100. metadata +31 -2
  101. data/lib/badline/address_bus/ultimax_pages.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2916044b89e174698de9dd60a2a72121dc6aa91058a9de914dfbaa5e70170f29
4
- data.tar.gz: 56adae369f806a6adb31dea00b5164d6e02e4b88d6a6cc08209808abdae0392b
3
+ metadata.gz: e82b9f5bb012d15fd7744f164b82420c3b71c00b14b43843093e1a2b3722beeb
4
+ data.tar.gz: cf94f9a74e8c9d5d6b9d3a94a5404d29376a4c57da7f18b78c9dd87da4a7c880
5
5
  SHA512:
6
- metadata.gz: cac3c6ba20fff4bbabd7e77dd8eae4e53cb12a3785f946908407627eb29783764684c0e2cf208b6a8030ad31bcfb35811526873119d51cb5badd0f2598257b41
7
- data.tar.gz: 93cecca78dbb7f4ca6393a629d113fa80a68a573359193ae4bcc54b87aadea883fdba0f84ad3ccb8acbad54539867b676e82e9f940420b50365f0fc7b93c33ff
6
+ metadata.gz: 14986d25fea5add5cecf4c10001b4f9cf9e087023f07bf8a8fc27d950f799ff41ae086518165ca67b840c440e5f56cf661d335ac5d4f5de347260c0bd44864bb
7
+ data.tar.gz: 12ba3fa5d2276efb1003edaa884e8f1c58ee7d2e2ac5e05b0f7d927e6d8b5a37869893f012d92560772b3adf18ea2d886c058737bf9e10aac25aec54e36afe6b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.1](https://github.com/elektronaut/badline/compare/v0.10.0...v0.10.1) (2026-10-08)
4
+
5
+
6
+ ### Features
7
+
8
+ * add the C64GS as --model c64gs ([ea04c0d](https://github.com/elektronaut/badline/commit/ea04c0d03b3a8c34426ff68a80ae0fc36db8123b))
9
+ * add the C64GS as --model c64gs ([e533932](https://github.com/elektronaut/badline/commit/e533932360befa88d05cf1369cc417f40cababa7))
10
+ * add the MAX Machine as --model ultimax ([fc48c40](https://github.com/elektronaut/badline/commit/fc48c40035892409175bf3d8fd08add6af0bbac9))
11
+ * add the MAX Machine as --model ultimax ([22aca64](https://github.com/elektronaut/badline/commit/22aca643abc0746e362209fb48977cee94c2b985))
12
+ * add the PET 64 as --model pet64 ([e17c0bd](https://github.com/elektronaut/badline/commit/e17c0bd0d13d15caf6d0b5377a5ac0a5756847cf))
13
+ * add the PET 64 as --model pet64 ([2320ac7](https://github.com/elektronaut/badline/commit/2320ac7cd41ef6a4865e56d433585ddebb7ef01f))
14
+ * bundle the C64GS's ROM, 390852-01 ([e31b280](https://github.com/elektronaut/badline/commit/e31b280044c18e639cc65df62a8b39d97dd874be))
15
+ * bundle the PET 64's KERNAL, 901246-01 ([9a029ec](https://github.com/elektronaut/badline/commit/9a029ecfe25c0d9a806d8858253c4c172e175b7f))
16
+
17
+
18
+ ### Miscellaneous Chores
19
+
20
+ * release 0.10.1 ([9d6f6ba](https://github.com/elektronaut/badline/commit/9d6f6ba71a647f24f00a830fbc772531fbcb4d92))
21
+
22
+ ## [0.10.0](https://github.com/elektronaut/badline/compare/v0.9.0...v0.10.0) (2026-10-08)
23
+
24
+
25
+ ### Features
26
+
27
+ * add the VIC-20's bus and CPU ([184f4c2](https://github.com/elektronaut/badline/commit/184f4c28c609fe0d46358c0143fabe2beba2fa70))
28
+ * name the machine family with a c64 or vic20 subcommand ([586a02f](https://github.com/elektronaut/badline/commit/586a02fbd80c6e6351d85c91263b5a39438ba13e))
29
+ * open the pause menu from the timeline and screenshot it ([63dc744](https://github.com/elektronaut/badline/commit/63dc74468432397c1ec0273a64fcd28ff46008d2))
30
+ * paint the VIC-20's display from the VIC-I's fetches ([76f67c3](https://github.com/elektronaut/badline/commit/76f67c3709363f682715681d17ea799441412f12))
31
+ * play the VIC-20's sound from the VIC-I's voices ([c490a86](https://github.com/elektronaut/badline/commit/c490a86959e18e0fecdcf21bf0139b63be25d9a9))
32
+ * read the VIC-20 .crt signature and the .tap platform byte ([fc0b130](https://github.com/elektronaut/badline/commit/fc0b130ad3a9481e7a9e61d909286281026797b1))
33
+ * run the VIC-20 in the window, in both builds ([24c056c](https://github.com/elektronaut/badline/commit/24c056c531fb356d3d47a9aad94c50ba0a0b0031))
34
+ * run the VIC-20 with its VIC-I registers and VIAs ([6089cee](https://github.com/elektronaut/badline/commit/6089ceee361e65d59c33a1eaeb3395f315eeed2d))
35
+ * save and restore the VIC-20 in badline's own snapshots ([8620849](https://github.com/elektronaut/badline/commit/8620849be5177069e16a559a35b239d75d7cc82a))
36
+ * serve the VIC-20's device 8 through the KERNAL traps and autostart its media ([356695e](https://github.com/elektronaut/badline/commit/356695e7529f999acf4bdc82951b107460ecdf39))
37
+ * wire the VIC-20's datasette and serial bus to its VIAs, with a true 1541 ([2f06e88](https://github.com/elektronaut/badline/commit/2f06e88cf5276780cc03f7d8121480e95b05480f))
38
+ * wire the VIC-20's keyboard, joystick and RESTORE to its VIAs ([32139f6](https://github.com/elektronaut/badline/commit/32139f647aeb3f5e7601928150b85973db6b9b5e))
39
+
40
+
41
+ ### Bug Fixes
42
+
43
+ * show the disk device 8 holds on the menu's Drive page ([c8b0eaf](https://github.com/elektronaut/badline/commit/c8b0eaf05573246114af6b6f8404095424a1981c))
44
+ * start the VIA shift register only when idle, on the 6522's clock ([bdcb1c8](https://github.com/elektronaut/badline/commit/bdcb1c88bb729e48ffdaaffd91e1274778c4ee29))
45
+
46
+
47
+ ### Performance Improvements
48
+
49
+ * type Memory's storage as Array[Integer] for Spinel ([797c9b1](https://github.com/elektronaut/badline/commit/797c9b1cab26e6ace4f0d0a20011a3a084e73bc9))
50
+
3
51
  ## [0.9.0](https://github.com/elektronaut/badline/compare/v0.8.0...v0.9.0) (2026-10-05)
4
52
 
5
53
 
data/CLAUDE.md CHANGED
@@ -50,7 +50,7 @@ requires only the namespace file.
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
52
  | Wolfgang Lorenz suite | `bin/lorenz` | CPU, CIA, interrupts |
53
- | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, cartridges with `--carts`, and the true 1541 drive with `--drive` |
53
+ | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, cartridges with `--carts`, the true 1541 drive with `--drive`, and the VIC-20 with `--vic20` |
54
54
  | VICE SID testprogs | `bin/sidtests` | SID |
55
55
  | Drive scenarios | `bin/drive_scenarios` | The true 1541 running the DOS ROM: save, format, the error channel, idle, write protect and autostart |
56
56
  | CIA offline grids | `bundle exec rspec --tag slow spec/badline/cia` | CIA timers and shift register, against a bare CIA in about 2 min |
@@ -144,7 +144,7 @@ overrides that, capped by the core count, but keep the default, because
144
144
  other worktrees share the machine. Whole runs at 4 shards on an M-series
145
145
  laptop take 15 min for `testbench` (`VICII/`), 11 for `testbench-cia`, 1 for
146
146
  `testbench-interrupts`, 37 for `testbench-irqdma`, 11 for
147
- `testbench-cpu`, 2 for `testbench-carts` and about 25 for `testbench-expansions`. `sid` takes about 12 min. `lorenz` chains itself and takes
147
+ `testbench-cpu`, 2 for `testbench-carts`, about 25 for `testbench-expansions` and under 1 for `testbench-vic20`. `sid` takes about 12 min. `lorenz` chains itself and takes
148
148
  about 2.5 h on CI whole. `rake regression:lorenz-1` to `lorenz-4` run it as
149
149
  four stretches of about 40 min each, and they can run side by side.
150
150
  `test/baselines/README.md` has the full table. A killed `bin/testbench` run
@@ -187,14 +187,16 @@ the rows your change can't reach tell you nothing about it.
187
187
  Pick the filters from the suites your change can move: VIC → `testbench`,
188
188
  plus `testbench-vicii-new` (`bin/testbench --vicii-new`, the 8565) for
189
189
  anything the VIC model reaches, and `testbench-ntsc` (`bin/testbench --ntsc`,
190
- the 6567R8 and 6567R56A) for anything a region's timing reaches;
190
+ the 6567R8 and 6567R56A) and `testbench-drean` (`bin/testbench --drean`,
191
+ the 6572) for anything a region's timing reaches;
191
192
  CPU, interrupts or timing → the matching `testbench-*` suite, plus
192
193
  `rake test` for CPU; CIA → the slow CIA specs first, then the matching
193
194
  `testbench-cia` rows, plus `testbench-cia-new` (`bin/testbench --cia-new`,
194
195
  the 6526A) for anything the interrupt register or the CIA model reaches; cartridge mappers, banking or power-on state →
195
196
  `testbench-carts`, plus `testbench-expansions` for banking; GEO-RAM, +60K,
196
197
  +256K, the REU or the VIC's BA line the REU follows → `testbench-expansions`; the 1541 drive, VIA or IEC bus →
197
- `testbench-drive` (`bin/testbench --drive`) and `drive-scenarios` (`bin/drive_scenarios <filter>`); SID → `sid`, plus `sid-8580` for anything the 8580
198
+ `testbench-drive` (`bin/testbench --drive`) and `drive-scenarios` (`bin/drive_scenarios <filter>`), plus `testbench-vic20` for the VIA; the VIC-20 (`vic20/`) →
199
+ `testbench-vic20` (`bin/testbench --vic20`); SID → `sid`, plus `sid-8580` for anything the 8580
198
200
  model reaches (`bin/sidtests --sid 8580`). Leave the Lorenz chain to CI,
199
201
  unless your code has a rule in `doc/pinned-behaviour.md` that names Lorenz
200
202
  tests: the interrupt polling, CPU port and CIA timer rules. Run just the
data/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
 
4
4
  # Badline
5
5
 
6
+ ![A C64 booted to BASIC in badline, its cursor blinking](doc/images/boot.gif)
7
+
6
8
  Badline is a Commodore 64 emulator written in Ruby. It emulates a PAL
7
9
  or NTSC machine one clock cycle at a time, stepping the 6510, the VIC-II, both
8
10
  CIAs and the SID together, so raster timing, bad lines and sprite DMA
@@ -10,8 +12,8 @@ are modelled at the cycle level.
10
12
 
11
13
  It runs programs, disk and tape images, cartridges and SID tunes, and
12
14
  the SDL2 front end supports the keyboard, joysticks, game controllers,
13
- paddles and a 1351 mouse. See [What's emulated](#whats-emulated) for the
14
- details.
15
+ paddles and a 1351 mouse. It also runs a PAL VIC-20, as
16
+ `badline vic20`. See [What's emulated](#whats-emulated) for the details.
15
17
 
16
18
  It comes in two builds of the same emulator:
17
19
 
@@ -65,6 +67,10 @@ Programs, disk and tape images and SID tunes start automatically, and
65
67
  a cartridge starts itself. A mounted directory waits for you to `LOAD`
66
68
  from it.
67
69
 
70
+ The first argument can name the machine, as in `badline c64 game.prg`.
71
+ The C64 is the default, and `--model` picks which C64. See
72
+ [The VIC-20](#the-vic-20) for `badline vic20`.
73
+
68
74
  `badline-ruby` takes the same media and the same options, with the
69
75
  differences noted in the table. `--help` lists the options for either.
70
76
 
@@ -77,8 +83,9 @@ differences noted in the table. `--help` lists the options for either.
77
83
  | `--true-drive` | Put an emulated 1541 on device 8 instead of the KERNAL traps. See [Media](#media) |
78
84
  | `-s`, `--subtune N` | Pick a subtune of a `.sid` file, counting from 1 |
79
85
  | `--sid 6581`, `--sid 8580` | Fit the older or newer SID. `--sid auto`, the default, takes a `.sid` tune's own |
86
+ | `--ram SIZE` | Give a VIC-20 the RAM expansion `SIZE`: `unexpanded`, `3k`, `8k`, `16k`, `24k`, `32k` or `all`. Without it, the VIC-20 gets the expansion its media needs. See [The VIC-20](#the-vic-20) |
80
87
  | `--reu SIZE` | Plug in a RAM Expansion Unit of `SIZE` K: 128, 256, 512 (a 1750) or up to 16384 |
81
- | `--model NAME` | Run another C64, named as in VICE: `c64` (the default, a PAL C64 with the 6569 VIC-II, 6526 CIAs and the 6581 SID), `c64c` (the PAL C64C, with the 8565, 6526As and the 8580), `ntsc` (the 6567R8), `newntsc` (the NTSC C64C, with the 8562, 6526As and the 8580), `oldntsc` (the first NTSC C64s' 6567R56A), `drean` (the Drean C64 of Argentina, PAL-N with the 6572) or `sx64` (the portable SX-64, with its own KERNAL and no datasette). `--sid` or a `.sid` tune's own SID takes the model's place |
88
+ | `--model NAME` | Run another model of the machine, named as in VICE. For the C64: `c64` (the default, a PAL C64 with the 6569 VIC-II, 6526 CIAs and the 6581 SID), `c64c` (the PAL C64C, with the 8565, 6526As and the 8580), `ntsc` (the 6567R8), `newntsc` (the NTSC C64C, with the 8562, 6526As and the 8580), `oldntsc` (the first NTSC C64s' 6567R56A), `drean` (the Drean C64 of Argentina, PAL-N with the 6572), `sx64` (the portable SX-64, with its own KERNAL and no datasette), `pet64` (the PET 64 or Educator 64, a PAL C64 in a PET's case with its own KERNAL and a green monochrome monitor) `c64gs` (the C64GS games console, a PAL C64C with its own BASIC and KERNAL, and no keyboard or datasette) or `ultimax` (the MAX Machine of Japan, NTSC with 2K of RAM, one CIA and no ROMs, which runs only a cartridge). `--sid` or a `.sid` tune's own SID takes the model's place |
82
89
  | `--ntsc` | Run an NTSC C64, as `--model ntsc` does |
83
90
  | `--no-sound` | Don't play the SID (`badline` only, where sound is on by default) |
84
91
  | `--sound` | Play the SID (`badline-ruby`, where sound is off by default) |
@@ -94,7 +101,7 @@ differences noted in the table. `--help` lists the options for either.
94
101
  | --- | --- |
95
102
  | `--frames N` | Quit after N frames |
96
103
  | `--unpaced` | Run as fast as it can, without vsync or pacing |
97
- | `--at FRAME:EVENT` | Press keys, type, swap media or take a screenshot once `FRAME` frames have run |
104
+ | `--at FRAME:EVENT` | Press keys, type, swap media, open the pause menu or take a screenshot once `FRAME` frames have run |
98
105
  | `--script FILE` | Run the events in `FILE`, one `FRAME:EVENT` a line |
99
106
  | `--screenshot FILE` | Save the last frame as a `.bmp` |
100
107
  | `--save-snapshot FILE` | Save the machine as a `.vsf` snapshot after the last frame |
@@ -134,6 +141,28 @@ red LED lights in the bottom right corner of the border.
134
141
  [doc/media.md](doc/media.md) has the details, and how to attach media
135
142
  from Ruby.
136
143
 
144
+ ## The VIC-20
145
+
146
+ `badline vic20` runs a PAL VIC-20, and takes the same media as the C64
147
+ apart from SID tunes:
148
+
149
+ ```sh
150
+ badline vic20 # READY.
151
+ badline vic20 game.prg # Load and run a program
152
+ badline vic20 game.d64 # Mount a disk image as device 8 and load it
153
+ badline vic20 game.tap # Insert a tape and load it
154
+ badline vic20 game.crt # Attach a cartridge
155
+ ```
156
+
157
+ Without `--ram`, it picks the RAM expansion from where the program, or
158
+ the first program on a disk, loads: a BASIC program gets the expansion
159
+ its start of BASIC comes with, and more of BLK1 to BLK3 when it runs
160
+ past `$3FFF`. A program that loads at `$A000` is a cartridge's ROM, and
161
+ goes in as a `.crt` does. Disks go through the KERNAL traps or, with
162
+ `--true-drive`, a 1541, as on the C64. `F11`, `F12` and the pause menu
163
+ save and load the VIC-20 too, in badline's own snapshots: xvic's `.vsf`
164
+ files don't load.
165
+
137
166
  ## Snapshots
138
167
 
139
168
  `F11` quicksaves the whole machine to the next of five slots in
@@ -219,6 +248,8 @@ through the rows, Right goes into a page and Left back out, and Return or
219
248
  Space presses a row. A row with choices steps to the next. The mouse works
220
249
  too.
221
250
 
251
+ ![The pause menu's Drive 8 page, with a disk in](doc/images/pause-menu.png)
252
+
222
253
  | Page | What it holds |
223
254
  |------|---------------|
224
255
  | Snapshots | Quicksave, save under a name, load a named save, and the latest quicksaves and autosaves |
@@ -259,6 +290,10 @@ program or `.sid` dropped on it opens the menu to ask first.
259
290
  - **REU**: the 1700, 1764 and 1750 and bigger units up to 16M
260
291
  (`--reu`), with DMA timed against the VIC's bad lines and sprites.
261
292
  - **Datasette**: `.tap` playback.
293
+ - **VIC-20**: the 6502, the PAL 6561 VIC-I with its picture and sound,
294
+ both 6522 VIAs, the keyboard, the joystick and RESTORE, RAM
295
+ expansions up to 35K (`--ram`), cartridges, the datasette and the
296
+ serial bus to device 8 (`badline vic20`).
262
297
  - **1541**: an emulated drive running its own DOS (`--true-drive`).
263
298
  - **Cartridges**: standard 8K, 16K and Ultimax, Simons' BASIC, Ocean,
264
299
  Fun Play / Power Play, Super Games, Epyx FastLoad, Westermann
data/doc/library.md CHANGED
@@ -42,6 +42,19 @@ model (`Badline::Model::ALL`):
42
42
  - `kernal: :sx64` fits the SX-64's KERNAL, and `datasette: false`
43
43
  leaves the cassette port empty, so inserting a tape raises
44
44
  `Badline::Datasette::Missing`. The SX-64 has both.
45
+ - `kernal: :pet64` fits the PET 64's KERNAL, and `board: :pet64` its
46
+ green monochrome monitor: the VIC's `palette` is
47
+ `Badline::VIC::GREEN_PALETTE`, each colour's luminance in green. The
48
+ PET 64 has both.
49
+ - `kernal: :gs` fits the C64GS's BASIC and KERNAL, and `board: :gs` its
50
+ case, which has no keyboard: `computer.keyboard` takes no keys. The
51
+ C64GS has both, and no datasette.
52
+ - `board: :max` builds the MAX Machine's board: 2K of RAM at
53
+ `$0000-$07FF`, no ROMs mapped, CIA 1 alone, answering all through
54
+ `$DC00-$DFFF` but for the I/O 1 page a cartridge takes, and Ultimax
55
+ mode with or without a cartridge. CIA 2 is never on the bus, so the
56
+ VIC stays in bank 0. `Badline::Model::ULTIMAX` runs it on
57
+ `Badline::Region::NTSC_OLD`.
45
58
 
46
59
  ## Cartridges
47
60
 
@@ -63,7 +76,8 @@ Its contents are lost when the emulator quits.
63
76
  The KERNAL, BASIC and character ROMs come with the gem. To run other
64
77
  images, such as a patched KERNAL, point `BADLINE_ROM_PATH` at a
65
78
  directory that holds `kernal.rom`, `basic.rom` and `character.rom`, plus
66
- `kernal-sx64.rom` for the SX-64 and `eapi/eapi-am29f040-14` if you
79
+ `kernal-sx64.rom` for the SX-64, `kernal-pet64.rom` for the PET 64,
80
+ `c64gs.rom` for the C64GS and `eapi/eapi-am29f040-14` if you
67
81
  attach EasyFlash cartridges.
68
82
  `Badline.rom_path = dir` does the same before a `Badline::Computer` is
69
83
  built, and `nil` restores the bundled set.
@@ -30,8 +30,12 @@ only catches the rows that happen to move.
30
30
  - [CIA 6526 timer pipeline](#cia-6526-timer-pipeline)
31
31
  - [CIA 6526A interrupt register](#cia-6526a-interrupt-register)
32
32
  - [CIA serial shift register](#cia-serial-shift-register)
33
+ - [VIA shift register](#via-shift-register)
33
34
  - [6510 I/O port](#6510-io-port)
34
35
  - [RAM power-on pattern](#ram-power-on-pattern)
36
+ - [VIC-20 RAM power-on pattern](#vic-20-ram-power-on-pattern)
37
+ - [VIC-I fetches and the V-bus](#vic-i-fetches-and-the-v-bus)
38
+ - [VIC-I sound](#vic-i-sound)
35
39
  - [1541 serial port](#1541-serial-port)
36
40
  - [1541 disk mechanism](#1541-disk-mechanism)
37
41
  - [REU DMA](#reu-dma)
@@ -1196,6 +1200,43 @@ and each was knocked out: removing it fails the rows named.
1196
1200
  [`cia_spec.rb`](../spec/badline/cia_spec.rb) covers the flag timing, the
1197
1201
  waiting byte, both mode-change reports and the zero-latch stall.
1198
1202
 
1203
+ ## VIA shift register
1204
+
1205
+ - A read or write of SR starts a byte only while the register is idle
1206
+ and enabled. An access in mode 0 starts nothing, even once the mode
1207
+ changes, and an access before the eighth bit is in leaves the count
1208
+ alone. The byte in progress runs on, and the first access after it
1209
+ ends starts the next.
1210
+ - In the φ2 modes (2 and 6) CB1 falls on the second cycle after the
1211
+ access, rises on the third, and so on. Mode 6 puts a bit out on cycles
1212
+ +2, +4 … +16, mode 2 takes one in on cycles +3, +5 … +17, and the flag
1213
+ rises with the eighth rising edge, on cycle +17.
1214
+ - In the timer 2 modes (1, 4 and 5) each clock edge comes two cycles
1215
+ after the timer 2 low byte underflows, on the cycle after the low byte
1216
+ reloads from its latch. The half period stays N + 2 cycles.
1217
+ - Pinned by the `VIC20/via_sr` rows, each checked by breaking the rule and
1218
+ rerunning all 32. The plain and `exp` rows dump SR every 7 cycles, the
1219
+ `ifr` and `iex` rows dump IFR, and the SR write comes before the ACR
1220
+ write that sets the mode:
1221
+ - Mode 0 starts nothing: `viasr08` and `viasr18`, and the `ifr`/`iex`
1222
+ rows of modes 04, 08, 14 and 18, which would see the SR flag.
1223
+ - No restart before the eighth bit: `viasr14` and `viasr18`. Their
1224
+ reads come every 7 cycles, faster than a byte.
1225
+ - The φ2 start a cycle late: `viasr08` and `viasr18`. Ticking from the
1226
+ first cycle, or from the third, fails both.
1227
+ - The timer 2 edge two cycles late: `viasr04`, `viasr10` and `viasr14`.
1228
+ A delay of 0, 1 or 3 cycles fails all three.
1229
+ - Every rule also fails the row's `exp` twin, the same test on an
1230
+ expanded VIC-20.
1231
+ - Nothing pins whether the timer 2 delay keeps running while the register
1232
+ is idle. It does, and freezing it passes every row too. Nothing pins
1233
+ the flag's cycle in the φ2 modes either: no row starts a byte and then
1234
+ reads IFR.
1235
+ - Spec guard: the mode blocks in
1236
+ [`shift_register_spec.rb`](../spec/badline/via/shift_register_spec.rb)
1237
+ and *the shift register* in
1238
+ [`via_spec.rb`](../spec/badline/via_spec.rb).
1239
+
1199
1240
  ## 6510 I/O port
1200
1241
 
1201
1242
  - DDR at `$00`/`$01`: inputs are pulled up, bit 5 reads low, and bits 3, 6
@@ -1239,6 +1280,111 @@ and each was knocked out: removing it fails the rows named.
1239
1280
  [`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
1240
1281
  - The RAM expansions' extra banks still power on at `$00`.
1241
1282
 
1283
+ ## VIC-20 RAM power-on pattern
1284
+
1285
+ - The VIC-20's RAM powers on in alternating bytes, `$ff` at the even
1286
+ addresses and `$00` at the odd ones, on every power cycle. The internal
1287
+ RAM and the expansion blocks (RAM1-3, BLK1-3, BLK5) all follow it.
1288
+ Colour RAM powers on at zero. `Vic20::Bus::RAM_POWER_ON` holds it.
1289
+ - Pinned by `VIC20/raminitpattern`: `ae.crt` fails unless `$0288` reads
1290
+ `$ff` and `jellymonsters.crt` unless `$1046` does, read from a
1291
+ cartridge before the KERNAL clears RAM. The readme says AE and Jelly
1292
+ Monsters need `$ff` there. Its dumps of real machines' `$1000-$1fff`
1293
+ show bytes alternating `$00` and `$ff` on most machines, with either
1294
+ phase, and noise or other patterns on the rest. They show nothing
1295
+ for colour RAM or expansion RAM that a pattern could be read from.
1296
+ - Spec guard: *RAM at power-on* in
1297
+ [`vic20/bus_spec.rb`](../spec/badline/vic20/bus_spec.rb).
1298
+
1299
+ ## VIC-I fetches and the V-bus
1300
+
1301
+ - On a line of the text window the 6561 fetches the first character code
1302
+ 4 cycles after the column in `$9000`, 16 cycles after the raster count
1303
+ moves with the KERNAL's origin of 12, then the code's pattern byte in
1304
+ the next cycle, two cycles a character. Outside the window it fetches
1305
+ nothing.
1306
+ - A read where no chip answers in I/O 0 (`$9100`, `$9200`) returns the
1307
+ V-bus's last byte: the VIC's fetch in a cycle it fetches in, and the
1308
+ CPU's own last byte on the V-bus in the other cycles.
1309
+ - Pinned by `VIC20/split-tests/timing`: all 1,024 bytes of its readings
1310
+ of `$9003`, `$9004`, `$9100` and `$9200` over 256 cycles match
1311
+ `dumps/dump6561e.prg`, a real 6561E. The later 6561-101's dump reads
1312
+ `$20` in the idle cycles instead; badline follows the 6561E.
1313
+ - Spec guard:
1314
+ [`vic20/vic_split_tests_timing_spec.rb`](../spec/badline/vic20/vic_split_tests_timing_spec.rb)
1315
+ (`:slow`), and *the fetches* in
1316
+ [`vic20/vic_video_spec.rb`](../spec/badline/vic20/vic_video_spec.rb).
1317
+ - A write to `$900F` or `$900E` takes hold from the second of the four
1318
+ pixels of the cycle it lands in, the reverse bit two pixels later, and
1319
+ a character's pixels come out in the two cycles after its pattern
1320
+ fetch. These match xvic's screenshots of a test of cycle-timed writes
1321
+ pixel for pixel; no testprog or hardware capture checks them.
1322
+ - Spec guard: *a colour write* in
1323
+ [`vic20/vic_video_spec.rb`](../spec/badline/vic20/vic_video_spec.rb).
1324
+
1325
+ ## VIC-I sound
1326
+
1327
+ No testprog checks the VIC-I's sound, so these rules are pinned by xvic
1328
+ (VICE 3.10) recordings, run as a black box: `-sounddev wav` at 44.1 kHz of
1329
+ programs that set the registers, the same programs run on badline, and
1330
+ the two compared by period, harmonics and spectrum. xvic's own source
1331
+ wasn't read.
1332
+
1333
+ - A voice's counter comes round every 128 - ((value + 1) & 127) ticks of
1334
+ 16, 8, 4 and 2 cycles from bass to noise, and a tone voice's shift
1335
+ register plays 16 shifts a period, as Marko Mäkelä's `VIC-I.txt`
1336
+ (revision 1.2, Levente Hársfalvi's measurements) gives the frequencies.
1337
+ - Pinned by xvic: `$900A` = `$FE`, `$80` and `$FF` measure 4329.70,
1338
+ 34.0922 and 33.8256 Hz, `$900B` = `$F0` 577.29 Hz, and `$900C` = `$F0`
1339
+ and `$A0` 1154.59 and 182.30 Hz, each within 0.001 Hz of the formula
1340
+ on the PAL clock.
1341
+ - Spec guard: *a tone voice's counter* in
1342
+ [`vic20/sound_spec.rb`](../spec/badline/vic20/sound_spec.rb).
1343
+ - A tone voice's shift register takes bit 7 back into bit 0 inverted while
1344
+ the voice is on and a zero while it's off, and keeps shifting either way.
1345
+ Bit 0 is the output, not gated by the enable bit. This is the die-shot
1346
+ reading of the 6561 on the Denial forum (Lance Ewing: the feedback is a
1347
+ NOR of bit 7 and the inverted enable bit, and the counter keeps shifting
1348
+ a voice that's off).
1349
+ - Pinned by xvic: a bass voice loaded with 10101010, 11000000, 10000000
1350
+ and 11101000 by turning it on and off a shift at a time, then left
1351
+ on, has the same harmonics in xvic's recording and badline's to
1352
+ 0.1 dB through the 12th. 10101010 puts the 7th and 9th harmonics
1353
+ above the fundamental, which a register cleared while off couldn't.
1354
+ - Spec guard: *a tone voice* in
1355
+ [`vic20/sound_spec.rb`](../spec/badline/vic20/sound_spec.rb).
1356
+ - The noise voice's counter steps a 16-bit LFSR: bit 0 takes the XOR of
1357
+ bits 3, 12, 14 and 15 while the voice is on and a one while it's off.
1358
+ Each rise of the LFSR's bit 0 shifts the noise voice's own shift
1359
+ register, bit 7 back into bit 0, inverted while the voice is on, and
1360
+ its bit 0 is the output while the voice is on. These are the die-shot
1361
+ readings on the Denial forum (Lance Ewing and nippur72), which also
1362
+ say the voice is silent while off.
1363
+ - Pinned by xvic: noise at `$FE` and `$FD` repeats every 0.11825 s and
1364
+ 0.2365 s (autocorrelation 0.98 at those lags, in both xvic's
1365
+ recording and badline's), 65535 steps of 2 and 4 cycles. The octave
1366
+ band levels of noise at `$FE`, `$FD`, `$F0`, `$C0` and `$80` match
1367
+ xvic's within 0.5 dB up to 8 kHz.
1368
+ - Spec guard: *the noise voice* in
1369
+ [`vic20/sound_spec.rb`](../spec/badline/vic20/sound_spec.rb).
1370
+ - The output is the voices that are high plus 2/9 of a voice, times the
1371
+ volume, so a volume write steps the output with every voice off, as
1372
+ 4-bit samples played through `$900E` need.
1373
+ - Pinned by xvic: a volume step from 0 to 15 with every voice off peaks
1374
+ at 0.218 of a voice turned on at volume 15.
1375
+ - Spec guard: *the volume* in
1376
+ [`vic20/sound_spec.rb`](../spec/badline/vic20/sound_spec.rb).
1377
+ - Not pinned: the volume scales the output linearly. xvic's tone level
1378
+ per volume step isn't linear (volume 1 is 0.099 of volume 15 and 14
1379
+ is 1.015), and nothing here says whether the hardware is.
1380
+ - The output stage is a one-pole low-pass at 1,420 Hz and a one-pole
1381
+ high-pass at 158 Hz.
1382
+ - Pinned by xvic: a bass voice turned on at volume 15 fits those two RC
1383
+ stages at 1,417 and 158.0 Hz, with an RMS error of 6 against a peak of
1384
+ 11,280. badline's own recording fits 1,420 and 157.6 Hz.
1385
+ - Spec guard: *a step* in
1386
+ [`vic20/sound/output_spec.rb`](../spec/badline/vic20/sound/output_spec.rb).
1387
+
1242
1388
  ## 1541 serial port
1243
1389
 
1244
1390
  - The drive sees the C64's side of the serial bus a host cycle late. The
data/doc/snapshots.md CHANGED
@@ -62,9 +62,23 @@ Every model saves this way, PAL, NTSC, old NTSC and Drean alike, and so
62
62
  does a machine with an REU, with its RAM, its registers and a transfer
63
63
  part way through. A snapshot only restores in the badline version that
64
64
  wrote it, into a machine with the same chip models, region, RAM
65
- expansion, REU, KERNAL and datasette. One saved before badline recorded
66
- the KERNAL and the datasette restores as a machine with the C64's KERNAL
67
- and a datasette.
65
+ expansion, REU, KERNAL, datasette and board. One saved before badline
66
+ recorded the KERNAL and the datasette restores as a machine with the
67
+ C64's KERNAL and a datasette, and one saved before it recorded the board
68
+ restores on a C64's.
69
+
70
+ ## The VIC-20
71
+
72
+ `badline vic20` saves and restores the same way, with the same keys,
73
+ slots and pause menu page. A VIC-20 snapshot holds its RAM and colour
74
+ RAM, a cartridge's ROM, the VIC with its picture and sound, both VIAs,
75
+ the CPU, the tape with its place on it, the disk or directory device 8
76
+ serves through the traps and a true 1541, and restores into a VIC-20
77
+ with the same RAM expansion. Its file names its machine `VIC20`, as
78
+ xvic's do, and holds only the `BADLINE` module, so xvic doesn't open
79
+ it, and badline doesn't open xvic's. Restoring a
80
+ VIC-20 snapshot while a C64 runs, or a C64 one while a VIC-20 runs,
81
+ swaps the machine in the window.
68
82
 
69
83
  ## VICE
70
84
 
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class AddressBus
5
+ # What a model fits around the chips: its KERNAL, a datasette or none,
6
+ # and the board and case they sit in.
7
+ module Fittings
8
+ # The keywords #fit takes.
9
+ NAMES = %i[kernal datasette board].freeze
10
+
11
+ # The C64's board and case, the PET 64's, whose built-in monitor
12
+ # shows the VIC's output in shades of green, the C64GS's, which has
13
+ # no keyboard, or the MAX Machine's (:max): 2K of RAM, no ROMs, CIA 1
14
+ # alone at $DC00-$DFFF and the cartridge port in Ultimax mode.
15
+ BOARDS = %i[c64 pet64 gs max].freeze
16
+
17
+ attr_reader :board
18
+
19
+ # Fits the KERNAL ROM ROMs::KERNALS names, leaves the cassette port
20
+ # empty unless `datasette`, and puts the chips in the board BOARDS
21
+ # names.
22
+ def fit(kernal: :c64, datasette: true, board: :c64)
23
+ self.kernal = kernal unless kernal == :c64
24
+ @datasette.disconnect! unless datasette
25
+ self.board = board unless board == :c64
26
+ end
27
+
28
+ def board=(name)
29
+ raise ArgumentError, "no board named #{name}" unless BOARDS.include?(name)
30
+
31
+ @board = name
32
+ vic.palette = name == :pet64 ? VIC::GREEN_PALETTE : VIC::PALETTE
33
+ keyboard.connected = name != :gs
34
+ update_overlays!
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class AddressBus
5
+ # The C64's PLA: what each page of the CPU's view maps to, from the
6
+ # CPU port's LORAM, HIRAM and CHAREN lines and the cartridge's EXROM
7
+ # and GAME. The bus that includes it supplies the page tables, the
8
+ # ROMs, the chips, and #map_ram_pages, which lays out the RAM that the
9
+ # ROMs, the cartridge and I/O map over.
10
+ module PLA
11
+ # A chip's registers seen at another page, which reads and writes
12
+ # them as at the page the chip starts at.
13
+ class Mirror
14
+ def initialize(chip, start)
15
+ @chip = chip
16
+ @start = start
17
+ end
18
+
19
+ def peek(addr) = @chip.peek(@start | (addr & 0xff))
20
+ def poke(addr, value) = @chip.poke(@start | (addr & 0xff), value)
21
+ end
22
+
23
+ private
24
+
25
+ def map_pla_pages
26
+ map_ram_pages
27
+ @ultimax ? map_ultimax_pages : map_banked_pages
28
+ end
29
+
30
+ def map_banked_pages
31
+ map_rom_overlays
32
+ map_cartridge_ram
33
+ @write_pages.fill(@cartridge.romh_writes, 0xe0, 0x20) if @cartridge&.romh_writes
34
+
35
+ if io?
36
+ map_io_pages
37
+ elsif character?
38
+ @read_pages.fill(character_rom, 0xd0, 0x10)
39
+ end
40
+ end
41
+
42
+ # Ultimax mode ignores the $01 lines: 4K of RAM, ROML/ROMH windows,
43
+ # I/O always visible and open address space everywhere else. The ROML
44
+ # and ROMH selects fire on writes as well, so cartridge RAM or flash in
45
+ # either window takes the writes there. The MAX has 2K of RAM, and is
46
+ # in Ultimax mode with no cartridge too.
47
+ def map_ultimax_pages
48
+ first = @board == :max ? 0x08 : 0x10
49
+ @read_pages.fill(@open_bus, first, 0x100 - first)
50
+ @write_pages.fill(@open_bus, first, 0x100 - first)
51
+ map_ultimax_cartridge if @cartridge
52
+ map_io_pages
53
+ map_max_io if @board == :max
54
+ end
55
+
56
+ def map_ultimax_cartridge
57
+ @read_pages.fill(@cartridge.roml, 0x80, 0x20) if @cartridge.roml
58
+ map_ultimax_writes(@cartridge.roml, 0x80)
59
+ @read_pages.fill(@cartridge.romh, 0xe0, 0x20) if @cartridge.romh
60
+ map_ultimax_writes(@cartridge.romh, 0xe0)
61
+ map_ultimax_a000
62
+ end
63
+
64
+ def map_ultimax_writes(bank, first_page)
65
+ @write_pages.fill(bank, first_page, 0x20) if bank.respond_to?(:poke)
66
+ end
67
+
68
+ def map_ultimax_a000
69
+ return unless (window = @cartridge.ultimax_a000)
70
+
71
+ @read_pages.fill(window, 0xa0, 0x20)
72
+ @write_pages.fill(window, 0xa0, 0x20)
73
+ end
74
+
75
+ # The MAX has no CIA 2 and no I/O 2: CIA 1 answers all through
76
+ # $DC00-$DFFF, but for the I/O 1 page a cartridge takes.
77
+ def map_max_io
78
+ @cia1_mirror ||= Mirror.new(cia1, 0xdc00)
79
+ (0xdd..0xdf).each { |page| @read_pages[page] = @write_pages[page] = @cia1_mirror }
80
+ return unless @cartridge
81
+
82
+ @write_pages[0xde] = @cartridge
83
+ @read_pages[0xde] = @cartridge if @cartridge.readable_io_pages.include?(0xde)
84
+ end
85
+
86
+ # EXROM and GAME select the cartridge whether or not it has a chip
87
+ # there, and an empty socket leaves the bus floating.
88
+ def map_rom_overlays
89
+ @read_pages.fill(@cartridge.roml || @open_bus, 0x80, 0x20) if roml?
90
+ if romh?
91
+ @read_pages.fill(@cartridge.romh || @open_bus, 0xa0, 0x20)
92
+ elsif basic?
93
+ @read_pages.fill(basic_rom, 0xa0, 0x20)
94
+ end
95
+ @read_pages.fill(kernal_rom, 0xe0, 0x20) if kernal?
96
+ end
97
+
98
+ # Cartridge RAM in the ROML or ROMH window decodes writes itself,
99
+ # whatever the $01 lines say.
100
+ def map_cartridge_ram
101
+ return unless @cartridge&.exrom&.zero?
102
+
103
+ map_cartridge_ram_bank(@cartridge.roml, 0x80)
104
+ map_cartridge_ram_bank(@cartridge.romh, 0xa0) if @cartridge.game.zero?
105
+ end
106
+
107
+ def map_cartridge_ram_bank(bank, first_page)
108
+ @write_pages.fill(bank, first_page, 0x20) if bank.is_a?(Cartridge::RAMBank)
109
+ end
110
+
111
+ def map_io_pages
112
+ {
113
+ vic => 0xd0..0xd3, sid => 0xd4..0xd7, color_ram => 0xd8..0xdb,
114
+ cia1 => 0xdc..0xdc, cia2 => 0xdd..0xdd, @open_bus => 0xde..0xdf
115
+ }.each do |chip, pages|
116
+ pages.each { |p| @read_pages[p] = @write_pages[p] = chip }
117
+ end
118
+ @ram_expansion.map_io(@read_pages, @write_pages)
119
+ @read_pages[0xd7] = @write_pages[0xd7] = @debug_register if @debug_register
120
+ @read_pages[0xdf] = @write_pages[0xdf] = @reu if @reu
121
+ map_cartridge_io if @cartridge
122
+ map_extra_sids unless @sid_slots.empty?
123
+ end
124
+
125
+ def map_cartridge_io
126
+ @write_pages.fill(@cartridge, 0xde, 2)
127
+ @cartridge.readable_io_pages.each { |p| @read_pages[p] = @cartridge }
128
+ end
129
+
130
+ def basic?
131
+ io_port.kernal? && io_port.basic? && game_high?
132
+ end
133
+
134
+ def game_high?
135
+ @cartridge.nil? || @cartridge.game == 1
136
+ end
137
+
138
+ def roml?
139
+ @cartridge&.exrom&.zero? && io_port.kernal? && io_port.basic?
140
+ end
141
+
142
+ def romh?
143
+ @cartridge&.exrom&.zero? && @cartridge.game.zero? && io_port.kernal?
144
+ end
145
+
146
+ # In 16K mode LORAM alone leaves $d000 as RAM, where it still maps I/O.
147
+ def character?
148
+ (io_port.kernal? || (io_port.basic? && game_high?)) && !io_port.io?
149
+ end
150
+
151
+ def io?
152
+ (io_port.basic? || io_port.kernal?) && io_port.io?
153
+ end
154
+
155
+ def kernal?
156
+ io_port.kernal?
157
+ end
158
+ end
159
+ end
160
+ end