badline 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/README.md +22 -16
  4. data/doc/library.md +10 -3
  5. data/doc/media.md +11 -2
  6. data/doc/pinned-behaviour.md +53 -9
  7. data/doc/sid-player.md +8 -4
  8. data/doc/snapshots.md +66 -33
  9. data/lib/badline/address_bus/roms.rb +32 -0
  10. data/lib/badline/address_bus.rb +4 -4
  11. data/lib/badline/audio/bare_player.rb +24 -0
  12. data/lib/badline/audio/checkpoints.rb +42 -0
  13. data/lib/badline/audio/jukebox.rb +11 -4
  14. data/lib/badline/audio/machine_player.rb +16 -0
  15. data/lib/badline/audio/renderer.rb +80 -9
  16. data/lib/badline/audio/stereo.rb +3 -5
  17. data/lib/badline/audio.rb +1 -0
  18. data/lib/badline/computer/attachments.rb +5 -5
  19. data/lib/badline/computer.rb +12 -10
  20. data/lib/badline/core.rb +48 -0
  21. data/lib/badline/data_path.rb +37 -0
  22. data/lib/badline/datasette.rb +24 -9
  23. data/lib/badline/drive1541/disk.rb +3 -3
  24. data/lib/badline/drive1541/disk_state.rb +14 -5
  25. data/lib/badline/frontend/app.rb +4 -3
  26. data/lib/badline/frontend/boot.rb +9 -4
  27. data/lib/badline/frontend/confirmation.rb +17 -11
  28. data/lib/badline/frontend/menu_dialogs.rb +52 -14
  29. data/lib/badline/frontend/menu_media.rb +36 -7
  30. data/lib/badline/frontend/menu_pages.rb +24 -2
  31. data/lib/badline/frontend/name_field.rb +88 -0
  32. data/lib/badline/frontend/pause_menu.rb +25 -7
  33. data/lib/badline/frontend/player_window.rb +3 -3
  34. data/lib/badline/frontend/snapshots.rb +145 -17
  35. data/lib/badline/frontend/sound.rb +11 -5
  36. data/lib/badline/frontend.rb +1 -0
  37. data/lib/badline/kernal_trap/load.rb +10 -0
  38. data/lib/badline/kernal_trap/routine.rb +28 -0
  39. data/lib/badline/kernal_trap/serial.rb +19 -7
  40. data/lib/badline/media/disk_list.rb +75 -0
  41. data/lib/badline/media/disk_set.rb +13 -3
  42. data/lib/badline/media.rb +8 -3
  43. data/lib/badline/model.rb +62 -0
  44. data/lib/badline/options/event.rb +5 -3
  45. data/lib/badline/options/help.rb +16 -12
  46. data/lib/badline/options/table.rb +5 -1
  47. data/lib/badline/options/validation.rb +9 -0
  48. data/lib/badline/options.rb +7 -4
  49. data/lib/badline/region.rb +16 -4
  50. data/lib/badline/reu/ram.rb +27 -0
  51. data/lib/badline/reu/saved_state.rb +27 -0
  52. data/lib/badline/roms/README +10 -0
  53. data/lib/badline/roms/kernal-sx64.rom +0 -0
  54. data/lib/badline/sid/decimator.rb +13 -0
  55. data/lib/badline/sid/filter.rb +6 -3
  56. data/lib/badline/sid.rb +19 -0
  57. data/lib/badline/snapshot/fields.rb +1 -0
  58. data/lib/badline/snapshot/setup.rb +20 -8
  59. data/lib/badline/snapshot/state.rb +14 -7
  60. data/lib/badline/snapshot/vice/main_cpu.rb +1 -1
  61. data/lib/badline/snapshot/vice/peripherals.rb +12 -2
  62. data/lib/badline/snapshot/vice/restore.rb +3 -1
  63. data/lib/badline/snapshot/vice/reu1764.rb +55 -0
  64. data/lib/badline/snapshot/vice/vicii.rb +59 -13
  65. data/lib/badline/snapshot/vice/vicii_import.rb +14 -3
  66. data/lib/badline/snapshot/vice.rb +25 -23
  67. data/lib/badline/storage/t64.rb +7 -4
  68. data/lib/badline/storage/tap.rb +7 -5
  69. data/lib/badline/storage/unavailable.rb +1 -1
  70. data/lib/badline/storage.rb +5 -5
  71. data/lib/badline/version.rb +1 -1
  72. data/lib/badline/vic/sprite/timing.rb +5 -9
  73. data/lib/badline/vic/sprite.rb +4 -3
  74. data/lib/badline.rb +2 -38
  75. metadata +10 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: feaedb95b99aa7efed205645c112426a2649ad7a08c975708d085bf69a7e9555
4
- data.tar.gz: b284281a2e1256f0fe94abf5174990ac8dc8a98cd7035f1cd53aebd3ccbefbd6
3
+ metadata.gz: 2916044b89e174698de9dd60a2a72121dc6aa91058a9de914dfbaa5e70170f29
4
+ data.tar.gz: 56adae369f806a6adb31dea00b5164d6e02e4b88d6a6cc08209808abdae0392b
5
5
  SHA512:
6
- metadata.gz: 870ff9cb5c9a9d65c8e9dd8dee6cc87a45871c1a07c79c4b9dc69a692600c3167f92b1debca0f860ab96b783a81bb608276542322b17e305acb82e95c9fc560b
7
- data.tar.gz: 7c12bf1fd073b2b0e58e41195d67316f0c518a0f5b8c013ded19f025b48456fea738e5ad3df26a796c84151638e8c0449b5bf14b7d013fcb876c61db5d2d9469
6
+ metadata.gz: cac3c6ba20fff4bbabd7e77dd8eae4e53cb12a3785f946908407627eb29783764684c0e2cf208b6a8030ad31bcfb35811526873119d51cb5badd0f2598257b41
7
+ data.tar.gz: 93cecca78dbb7f4ca6393a629d113fa80a68a573359193ae4bcc54b87aadea883fdba0f84ad3ccb8acbad54539867b676e82e9f940420b50365f0fc7b93c33ff
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.0](https://github.com/elektronaut/badline/compare/v0.8.0...v0.9.0) (2026-10-05)
4
+
5
+
6
+ ### Features
7
+
8
+ * add a per-user data folder for badline's own files ([27bdf0e](https://github.com/elektronaut/badline/commit/27bdf0edc16d95fc57dea940741cc0b7de74bbb1))
9
+ * add the Drean C64, PAL-N with the 6572, as --model drean ([0562d8e](https://github.com/elektronaut/badline/commit/0562d8eb5b11239039eac934fd21eb7824868f98))
10
+ * add the SX-64 as --model sx64 ([e947cba](https://github.com/elektronaut/badline/commit/e947cba8ff46f0f01eeb6e1a49c0bd4bf91324ea))
11
+ * autosave, and a Snapshots page in the pause menu ([78aede9](https://github.com/elektronaut/badline/commit/78aede9a6052ce6c32184952a7b1d380e7c129e2))
12
+ * keep five quicksave slots in the data folder ([6043543](https://github.com/elektronaut/badline/commit/6043543632aa61a42ff69bdff8ea15ae354ce810))
13
+ * pick the C64 to run with --model ([d5539c8](https://github.com/elektronaut/badline/commit/d5539c8ade245b1e96e78765f15906ce4a80da5b))
14
+ * restore snapshots without their tape, .t64 or true drive disk files ([0c0733a](https://github.com/elektronaut/badline/commit/0c0733a5c7eaf9c217466f7569769eb005c5248f))
15
+ * run the testlist's Drean rows as testbench-drean ([dc8a075](https://github.com/elektronaut/badline/commit/dc8a07524168d399fb7c0305c661ab694c53bc2c))
16
+ * save NTSC, Drean and REU machines as .vsf snapshots ([c4126cf](https://github.com/elektronaut/badline/commit/c4126cfa878d8efe7aaaa9e5113c4ee2a4886e6b))
17
+ * seek within a subtune from checkpoints kept as it plays ([3ac57be](https://github.com/elektronaut/badline/commit/3ac57beac448a43a76387a0ff1f370cd07d06d81))
18
+ * take disk sets from .m3u and .vfl lists ([b4e6845](https://github.com/elektronaut/badline/commit/b4e6845eb04cde80f87c1fcb372987311bf1dd97))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * describe F11's quicksave slots, F12 and the autosave in --help ([6cbf148](https://github.com/elektronaut/badline/commit/6cbf148d327699070ef4fba4f2b3e3dd39d41348))
24
+ * keep the board's low-pass stable for long filter steps ([c5af022](https://github.com/elektronaut/badline/commit/c5af0224aad60dbb665e6804bb536746e354da96))
25
+ * leave CIA 1's timer B and the serial lines as the KERNAL's LOAD does ([b4f1a41](https://github.com/elektronaut/badline/commit/b4f1a4173b9413fd59b05bab12d173e0679c4c17))
26
+ * play an NTSC machine's sound at its own clock ([510aab1](https://github.com/elektronaut/badline/commit/510aab1052a58a54b1790802e2a5705fcb208c5c))
27
+ * put CIA 2's port A on the serial bus without a drive ([31f8a4f](https://github.com/elektronaut/badline/commit/31f8a4fad0ea210d4272b4672a84414527b68e31))
28
+ * run the 6567R8 and 6572 X counter over $180-$187 twice ([08a4da3](https://github.com/elektronaut/badline/commit/08a4da33d4bd68a85d61895dcf8c1ea5fa5b539c))
29
+ * set CIA 1's timer B for each byte the serial traps stand in for ([f98e6d7](https://github.com/elektronaut/badline/commit/f98e6d7666d2326ce4d8d3ab7f938dc4fd60cbd1))
30
+ * take .m3u and .vfl lists of disks in --at's insert ([d1e771e](https://github.com/elektronaut/badline/commit/d1e771e6781fc5560dda2e10e6d76abb53be0807))
31
+
3
32
  ## [0.8.0](https://github.com/elektronaut/badline/compare/v0.7.0...v0.8.0) (2026-10-03)
4
33
 
5
34
 
data/README.md CHANGED
@@ -78,7 +78,8 @@ differences noted in the table. `--help` lists the options for either.
78
78
  | `-s`, `--subtune N` | Pick a subtune of a `.sid` file, counting from 1 |
79
79
  | `--sid 6581`, `--sid 8580` | Fit the older or newer SID. `--sid auto`, the default, takes a `.sid` tune's own |
80
80
  | `--reu SIZE` | Plug in a RAM Expansion Unit of `SIZE` K: 128, 256, 512 (a 1750) or up to 16384 |
81
- | `--ntsc` | Run an NTSC C64, with the 6567R8 VIC-II, instead of a PAL one |
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 |
82
+ | `--ntsc` | Run an NTSC C64, as `--model ntsc` does |
82
83
  | `--no-sound` | Don't play the SID (`badline` only, where sound is on by default) |
83
84
  | `--sound` | Play the SID (`badline-ruby`, where sound is off by default) |
84
85
  | `--no-vsync` | Pace the window by a timer, or by the sound while it plays, instead of the display's vsync |
@@ -117,6 +118,7 @@ library, are covered in [doc/library.md](doc/library.md).
117
118
  | `.prg`, `.p00` | Loaded into memory after boot. A program at the BASIC start (`$0801`) is `RUN`, anything else is left for you to `SYS` |
118
119
  | `.d64`, `.d71`, `.d81` | Mounted as device 8, then `LOAD"*",8,1` and `RUN`. Write-protected unless `--writable` |
119
120
  | `.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` |
121
+ | `.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 |
120
122
  | `.t64` | Mounted read-only as device 8 and loaded like a disk image. The files load by name, and no tape is involved |
121
123
  | `.tap` | Inserted in the datasette with PLAY pressed, then `LOAD` and `RUN`. It loads at the speed of a real tape |
122
124
  | `.crt` | The hardware types listed under [Cartridges](#whats-emulated). Other types are rejected |
@@ -134,12 +136,14 @@ from Ruby.
134
136
 
135
137
  ## Snapshots
136
138
 
137
- `F11` saves the whole machine to a new `badline-<date>-<time>.vsf` in
138
- the working directory, and `F12` goes back to the snapshot last saved
139
- or opened. Snapshots use VICE's `.vsf` format: badline opens those
140
- x64sc saves, and x64sc opens badline's. See
141
- [doc/snapshots.md](doc/snapshots.md) for what they hold and how far the
142
- two agree.
139
+ `F11` quicksaves the whole machine to the next of five slots in
140
+ badline's data folder, replacing the oldest, and `F12` restores the
141
+ newest, even after badline has quit and started again. badline also
142
+ autosaves every two minutes of play, and the pause menu's Snapshots page
143
+ saves under a name and loads any of them. Snapshots use
144
+ VICE's `.vsf` format: badline opens those x64sc saves, and x64sc opens
145
+ badline's. See [doc/snapshots.md](doc/snapshots.md) for where they're
146
+ kept, what they hold and how far the two agree.
143
147
 
144
148
  ## Playing and rendering SID tunes
145
149
 
@@ -217,6 +221,7 @@ too.
217
221
 
218
222
  | Page | What it holds |
219
223
  |------|---------------|
224
+ | Snapshots | Quicksave, save under a name, load a named save, and the latest quicksaves and autosaves |
220
225
  | Drive 8 | The disk: insert, eject, the previous or next disk of its set, and whether it's writable |
221
226
  | Datasette | The tape: insert, eject, PLAY and REWIND |
222
227
  | Expansion port | The cartridge: insert, remove, and its freeze button if it has one |
@@ -224,15 +229,17 @@ too.
224
229
  | Sound | Mute, and the SID's model |
225
230
  | Power | Reset, power cycle and quit |
226
231
 
227
- INSERT opens a file browser. A disk's set comes from the names in its
228
- folder: `Disk 1`, `Side B`, `d2`, TOSEC's `(Disk 1 of 2)`, or a trailing
229
- `_1`, `_2` when the first of the set is there. WRITABLE starts as
232
+ INSERT opens a file browser. A disk's set is the `.m3u` or `.vfl` list it
233
+ was opened from, or one in its folder that lists it. Without one, it comes
234
+ from the names in its folder: `Disk 1`, `Side B`, `d2`, TOSEC's
235
+ `(Disk 1 of 2)`, or a trailing `_1`, `_2` when the first of the set is
236
+ there. WRITABLE starts as
230
237
  `--writable` sets it and stays as set for the next disk. Quick open starts
231
238
  a file in a new machine, as the command line does. It asks first, as
232
239
  inserting or removing a cartridge does, since each power cycles the
233
240
  machine.
234
241
 
235
- A disk or tape dropped on the window goes into its drive. A cartridge,
242
+ A disk, list of disks or tape dropped on the window goes into its drive. A cartridge,
236
243
  program or `.sid` dropped on it opens the menu to ask first.
237
244
 
238
245
  ## What's emulated
@@ -242,8 +249,8 @@ program or `.sid` dropped on it opens the menu to ask first.
242
249
  [65x02 single step tests](https://github.com/SingleStepTests/65x02).
243
250
  - **Memory**: banking through the 6510 port, the cartridge lines and
244
251
  Ultimax mode, and the +60K and +256K RAM expansions.
245
- - **VIC-II**: the PAL 6569, the C64C's 8565, and NTSC's 6567R8 (`--ntsc`)
246
- and 6567R56A: every graphics mode, sprites, collisions, bad lines,
252
+ - **VIC-II**: the PAL 6569, the C64C's 8565, NTSC's 6567R8, 8562 and
253
+ 6567R56A, and the Drean's PAL-N 6572 (`--model`): every graphics mode, sprites, collisions, bad lines,
247
254
  sprite DMA, the border and the light pen.
248
255
  - **CIA 1 and 2**: the 6526 and the C64C's 6526A, with timers,
249
256
  time-of-day clocks, the serial shift register, the keyboard matrix
@@ -261,8 +268,8 @@ program or `.sid` dropped on it opens the menu to ask first.
261
268
  Power / Nordic Power, Retro Replay / Nordic Replay, Final Cartridge III
262
269
  / III+ and the KCS Power Cartridge. Flash writes stay in memory.
263
270
 
264
- Machines other than the default, and the cartridges' jumpers, are
265
- built from Ruby: see [doc/library.md](doc/library.md).
271
+ Chip combinations `--model` doesn't name, and the cartridges' jumpers,
272
+ are built from Ruby: see [doc/library.md](doc/library.md).
266
273
 
267
274
  Known gaps:
268
275
 
@@ -270,7 +277,6 @@ Known gaps:
270
277
  turns on, stutters: it plays in bursts with silent gaps between them.
271
278
  - Without `--true-drive`, fast loaders and anything else that runs code
272
279
  on the drive won't work.
273
- - No PAL-N (Drean) machine.
274
280
 
275
281
  ## Contributing
276
282
 
data/doc/library.md CHANGED
@@ -19,7 +19,9 @@ computer.address_bus.peek(0x0400) # screen RAM starts at $0400
19
19
  ## Building a machine
20
20
 
21
21
  `Badline::Computer.new` builds the default machine: a PAL C64 with the
22
- 6569 VIC-II, 6526 CIAs and a 6581 SID. Its keywords build others:
22
+ 6569 VIC-II, 6526 CIAs and a 6581 SID. Its keywords build others, and
23
+ `Badline::Model.named("c64c")` gives the ones `--model` names for each
24
+ model (`Badline::Model::ALL`):
23
25
 
24
26
  - `vic_model: :mos8565` fits the C64C's 8565, with its grey dots on
25
27
  colour register writes and its own timing for mode splits, sprite
@@ -28,7 +30,8 @@ computer.address_bus.peek(0x0400) # screen RAM starts at $0400
28
30
  65 cycles by 263 lines at 1,022,727 Hz, with its later sprite fetches
29
31
  and its X counter, and TOD clocks on 60 Hz mains.
30
32
  `Badline::Region::NTSC_OLD` is the first NTSC C64s' 6567R56A, 64 cycles
31
- by 262 lines. The stock KERNAL tells them from PAL by the raster, so
33
+ by 262 lines, and `Badline::Region::DREAN` the Drean C64's PAL-N
34
+ 6572, 65 cycles by 312 lines at 1,023,440 Hz. The stock KERNAL tells them from PAL by the raster, so
32
35
  every region boots the same ROMs.
33
36
  - `cia_model: :mos6526a` fits the C64C's 6526As, whose interrupt
34
37
  register timing differs.
@@ -36,6 +39,9 @@ computer.address_bus.peek(0x0400) # screen RAM starts at $0400
36
39
  - `ram_expansion: :plus60k` or `:plus256k` fits the +60K or +256K RAM
37
40
  expansion, banked through its register at `$D100`.
38
41
  - `reu: 512` plugs in a RAM Expansion Unit of that many K.
42
+ - `kernal: :sx64` fits the SX-64's KERNAL, and `datasette: false`
43
+ leaves the cassette port empty, so inserting a tape raises
44
+ `Badline::Datasette::Missing`. The SX-64 has both.
39
45
 
40
46
  ## Cartridges
41
47
 
@@ -57,6 +63,7 @@ Its contents are lost when the emulator quits.
57
63
  The KERNAL, BASIC and character ROMs come with the gem. To run other
58
64
  images, such as a patched KERNAL, point `BADLINE_ROM_PATH` at a
59
65
  directory that holds `kernal.rom`, `basic.rom` and `character.rom`, plus
60
- `eapi/eapi-am29f040-14` if you attach EasyFlash cartridges.
66
+ `kernal-sx64.rom` for the SX-64 and `eapi/eapi-am29f040-14` if you
67
+ attach EasyFlash cartridges.
61
68
  `Badline.rom_path = dir` does the same before a `Badline::Computer` is
62
69
  built, and `nil` restores the bundled set.
data/doc/media.md CHANGED
@@ -59,8 +59,17 @@ traps, so `LOAD` and `SAVE` reach the 1541 too. With the 1541 in device
59
59
  8, a `.d64` goes in its drive as well.
60
60
 
61
61
  `Badline::Media::DiskSet.around(path)` lists the disks of the set a disk
62
- image belongs to, found by name in its folder, which the pause menu's
63
- PREVIOUS DISK and NEXT DISK step through.
62
+ image belongs to, which the pause menu's PREVIOUS DISK and NEXT DISK step
63
+ through. An `.m3u` or `.vfl` list in the disk's folder that names it
64
+ gives the set. Without one, the set is found by name in the folder.
65
+ Given a list, it returns the disks the list names.
66
+
67
+ A list names one disk image a line, as a path relative to the list or an
68
+ absolute one. Blank lines and lines starting with `#` are skipped, and
69
+ entries that aren't there are left out. In a VICE `.vfl` flip list, only
70
+ the entries after a `UNIT 8` line, or before any `UNIT` line, count.
71
+ `Media.attach` and the command line take a list as media and attach its
72
+ first disk.
64
73
 
65
74
  ## The light pen
66
75
 
@@ -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-II NTSC](#vic-ii-ntsc)
29
+ - [VIC-II 6572 (Drean)](#vic-ii-6572-drean)
29
30
  - [CIA 6526 timer pipeline](#cia-6526-timer-pipeline)
30
31
  - [CIA 6526A interrupt register](#cia-6526a-interrupt-register)
31
32
  - [CIA serial shift register](#cia-serial-shift-register)
@@ -293,7 +294,9 @@ only catches the rows that happen to move.
293
294
  early in the next one for 3–7.
294
295
  - A sprite still shifting at K loses the rest of its row. Its output
295
296
  holds its last pixel from K through **K+6**, then goes dark.
296
- - A comparator hit in K..K+11 shows nothing.
297
+ - A comparator hit in K..K+11 shows nothing. Where K falls in the
298
+ last 11 pixels of the line, as sprite 3's does on a 65-cycle line,
299
+ the twelve run on into the next line's first pixels.
297
300
  - A hit from K+12 on shows the row the reload brought. For sprites 0–2
298
301
  that is the *next* line's row, a line early. For sprites 3–7 it is the
299
302
  current row, and a hit ahead of their K shows the previous line's row.
@@ -307,6 +310,11 @@ only catches the rows that happen to move.
307
310
  `spritex/demusinterruptus` and `spritegap2` pass on it too.
308
311
  - Spec guard: *the reload* in
309
312
  [`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
313
+ - The run into the next line is pinned by `spritescan_drean`'s 6572
314
+ dump, where sprite 3 shows nothing for X = $193 to $19e, the twelve
315
+ pixels from K = 515 on a 520-pixel line. Without it a hit at $198 to
316
+ $19e collides. Spec guard: *the reload near the end of a 65-cycle
317
+ line*. The `testbench-ntsc` sprite rows pass either way.
310
318
  - The hold's length is pinned by `spritefetchbug/test-136-2a`, whose
311
319
  X-expanded sprite 0 sits at X = $136 and is still shifting at 459. Its
312
320
  reference holds the last pixel through 465. Holding it for one pixel
@@ -941,14 +949,22 @@ region moves the sprites. The positions live in `Region::Profile` and
941
949
  - Spec guard: *sprite DMA cycle stealing on NTSC* and *#phi1_data on
942
950
  NTSC* in [`vic_spec.rb`](../spec/badline/vic_spec.rb), and
943
951
  [`timing_spec.rb`](../spec/badline/vic/sprite/timing_spec.rb).
944
- - **Display compare and X counter.** The 6567R8 turns a sprite's display
945
- on and off in cycle 59 (`sprite_display_cycle`), where the 6567R56A
946
- keeps the 6569's cycle 58. Its X counter holds for 8 pixels
947
- (`x_hold`), reading $184-$187 three times, so it reaches $1ff on a
948
- 520-pixel line. The 6567R56A's runs straight through 512 pixels.
949
- Neither is pinned yet: moving the 6567R8's display compare to 58, the
950
- 6567R56A's to 59, or running the 6567R8's counter straight through and
951
- wrapping it at 512 fails no row of those subsets.
952
+ - **Display compare.** The 6567R8 turns a sprite's display on and off in
953
+ cycle 59 (`sprite_display_cycle`), where the 6567R56A keeps the 6569's
954
+ cycle 58. This isn't pinned yet: moving the 6567R8's to 58 or the
955
+ 6567R56A's to 59 fails no row of those subsets.
956
+ - **X counter.** The 6567R8's X counter spends 8 pixels more on a line
957
+ (`x_hold`): after $187 it runs over $180-$187 a second time, then on
958
+ from $188, so it reaches $1ff on a 520-pixel line. The 6567R56A's runs
959
+ straight through 512 pixels.
960
+ - Pinned for the 6572, which shares the line, by `spritescan_drean`'s
961
+ dump (see [VIC-II 6572 (Drean)](#vic-ii-6572-drean)).
962
+ - The 6567R8's own lightpen dump (`split-tests/lightpen`'s
963
+ `dump6567.prg`, run by hand on `--model ntsc`) agrees: the sample the
964
+ test takes 8 pixels into the hold reads $c2 ($180-$181). Reading
965
+ $184-$187 there instead, as badline did before, gives $c4. The 6572's
966
+ `dump6572.prg` has the same values. The `testbench-ntsc` rows pass
967
+ either way.
952
968
  - **Blanking and crop.** The blanked lines are the ones VICE's NTSC view
953
969
  leaves out, 12-27 on the 6567R8 and 13-27 on the 6567R56A, so the
954
970
  `testbench-ntsc` screenshots, VICE's 247 lines from line 28 running on
@@ -970,6 +986,34 @@ What the NTSC references don't settle, and so what stays as it is:
970
986
  readme says it was matched against screenshots, and the one 6567R8
971
987
  photograph in the testprogs is of revision R01.
972
988
 
989
+ ## VIC-II 6572 (Drean)
990
+
991
+ The 6572 of the Drean C64 (`Region::DREAN`) runs the 6567R8's line, 65
992
+ cycles with its sprite fetches in cycle 59, its display compare in cycle
993
+ 59 and its X counter running over $180-$187 twice, on PAL-N's 312 lines at
994
+ 1,023,440 Hz. The oracle is `split-tests/spritescan/spritescan_drean.prg`,
995
+ which checks itself against its `dump6572.bin`, from a real 6572R1. The
996
+ testlist comments that row out, and `testbench-drean`
997
+ (`bin/testbench --drean`) runs it.
998
+
999
+ - Pinned by that dump: every byte the test compares matches, and the
1000
+ test passes.
1001
+ - The X counter's second run over $180-$187 is pinned by sprite 1,
1002
+ pattern C (a one-pixel sprite against one shifted a pixel left), which
1003
+ collides for X = $180 to $186 on the 6572. Those are inside sprite 1's
1004
+ twelve dead pixels (K = 483), so the sprite only shows from the second
1005
+ time the counter reads its X. Repeating $184-$187 in the hold instead
1006
+ misses $180-$183 (4 bytes), repeating $180-$183 misses $184-$186 (3),
1007
+ and repeating $17c-$183 or $184-$18b moves 18 bytes. Spec guard: the
1008
+ 6567R8 and 6572 examples in
1009
+ [`timing_spec.rb`](../spec/badline/vic/sprite/timing_spec.rb).
1010
+ - The display compare isn't pinned: the dump matches with it in cycle
1011
+ 58 as well as 59.
1012
+ - Not matched: `spritegap3`'s 6572 dump has sprite 0 at X = 0 miss the
1013
+ sprite at X = 1 for every pair it is in, where badline, like the
1014
+ 6567R8 dump, has them collide. The test still passes, because it
1015
+ accepts the 6567R8's dump too.
1016
+
973
1017
  ## CIA 6526 timer pipeline
974
1018
 
975
1019
  - Start and stop go through two stages. A force load lands one tick after
data/doc/sid-player.md CHANGED
@@ -46,7 +46,7 @@ channels.
46
46
  Playback asks the device for 44.1 kHz and takes whatever rate it
47
47
  offers, unless `--rate` says otherwise. Ctrl-C stops it. The emulator
48
48
  window's own options, `--no-autostart`, `--writable`, `--sound`,
49
- `--true-drive`, `--reu`, `--ntsc` and `--verbose`, don't apply.
49
+ `--true-drive`, `--reu`, `--model`, `--ntsc` and `--verbose`, don't apply.
50
50
 
51
51
  ## The window
52
52
 
@@ -86,9 +86,13 @@ first and last. `n` and `p` step to the next and previous tune. `s`
86
86
  turns shuffle on and off, which plays the queue's tunes in a random
87
87
  order, and `l` turns looping on and off, so that the end of the queue
88
88
  goes on to its start. `,` and `.` seek 10 seconds back and forward
89
- within the subtune: a seek forward runs on silently from where the
90
- subtune is, and a seek back plays it again from its start up to the
91
- point asked for. Space pauses and `q` quits. The status line shows which
89
+ within the subtune. As a subtune plays, the player saves its state every
90
+ 10 seconds. A seek back runs silently from the latest of those saved
91
+ states at or before the point asked for, or from the subtune's start
92
+ before the first. A seek forward runs on silently from where the
93
+ subtune is, or from a saved state further on, when an earlier seek back
94
+ left one there. The saved states last until the tune or subtune
95
+ changes. Space pauses and `q` quits. The status line shows which
92
96
  modes are on, and they last until the player quits.
93
97
 
94
98
  `--no-tui`, or output that isn't a terminal, gives plain progress output
data/doc/snapshots.md CHANGED
@@ -1,11 +1,35 @@
1
1
  # Snapshots
2
2
 
3
- In the window, `F11` saves the whole machine to a new
4
- `badline-<date>-<time>.vsf` in the working directory, and `F12` goes
5
- back to the snapshot last saved or opened, in a new machine built as the
6
- saved one was. Both executables do this, and both open a `.vsf` given as
7
- the media. A snapshot that fails to open leaves the machine running as
8
- it was.
3
+ In the window, `F11` quicksaves the whole machine to one of five slots,
4
+ `quicksave-1.vsf` to `quicksave-5.vsf` in the `quicksaves` folder of
5
+ badline's data folder. It fills the empty slots first, then replaces the
6
+ one saved longest ago. `F12` restores the newest quicksave, or a named
7
+ save in the data folder's `saves` folder if one is newer, in a new
8
+ machine built as the saved one was. Quicksaves stay after badline quits,
9
+ so `F12` finds them the next time it starts. What's printed on a save or
10
+ a restore names the slot and the file.
11
+
12
+ A run without `--frames` also autosaves every 6,000 frames the machine
13
+ has run, two minutes on PAL, to `autosave-1.vsf` to `autosave-3.vsf` in
14
+ the `autosaves` folder, replacing the oldest. Time in the pause menu
15
+ doesn't count, nothing is printed, and `F12` never picks an autosave. An
16
+ autosave that fails warns once and stops autosaving.
17
+
18
+ The pause menu's Snapshots page quicksaves, saves under a name in the
19
+ `saves` folder and loads a named save. The name starts as the game's,
20
+ from its cartridge, disk or tape, or BASIC, with the next free number,
21
+ and saving over a name already taken asks first. Below those, the
22
+ newest quicksaves and autosaves, with the time each was saved, load
23
+ with a press.
24
+
25
+ The data folder is `~/Library/Application Support/badline` on macOS,
26
+ and `$XDG_DATA_HOME/badline` or `~/.local/share/badline` elsewhere.
27
+ `BADLINE_DATA_PATH` puts it somewhere else.
28
+
29
+ Both executables do this, and both open a `.vsf` given as the media,
30
+ in a machine built as the saved one was, whatever `--model` says.
31
+ `--save-snapshot FILE` saves one to `FILE` after the last frame. A
32
+ snapshot that fails to open leaves the machine running as it was.
9
33
 
10
34
  ## From Ruby
11
35
 
@@ -25,45 +49,54 @@ a disk or directory mounted through the traps with its open channels,
25
49
  and the tape with its place on it. A restored machine runs on exactly
26
50
  as the saved one would have.
27
51
 
28
- A directory, a tape and a true drive's disk image open again from their
29
- paths when the snapshot is restored, the disk with its tracks as the
30
- drive last saw them. A disk image mounted through the traps comes back
31
- with its contents from the snapshot. What the host holds stays the
52
+ A tape, a `.t64` archive and a disk image mounted through the traps come
53
+ back with their contents from the snapshot, so they need no file to
54
+ restore. A true drive's disk opens again from its image's path, with its
55
+ tracks as the drive last saw them. When that file is gone, the disk
56
+ comes back from the tracks alone, write-protected. A directory mounted
57
+ as device 8 opens again from its path. What the host holds stays the
32
58
  host's: the keyboard, the joysticks, the mouse and paddles, sound, and
33
59
  blocks given to `on_init` that hadn't run yet, which a restore reports.
34
60
 
35
- A snapshot only restores in the badline version that wrote it, into a
36
- machine with the same chip models, RAM expansion and REU.
37
- `computer.snapshot` holds an REU's RAM, registers and transfer too, but
38
- a machine with an REU doesn't save to a file yet, as badline doesn't
39
- write VICE's REU module. Nor does an NTSC machine, as badline doesn't
40
- write an NTSC VIC-II in VICE's terms yet.
61
+ Every model saves this way, PAL, NTSC, old NTSC and Drean alike, and so
62
+ does a machine with an REU, with its RAM, its registers and a transfer
63
+ part way through. A snapshot only restores in the badline version that
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.
41
68
 
42
69
  ## VICE
43
70
 
44
71
  Snapshots use VICE's `.vsf` format. badline writes VICE's modules for
45
- the CPU, RAM and CPU port, both CIAs, the SID and the VIC-II, plus the
46
- ones x64sc needs to open the file, with nothing attached to the
47
- cartridge, tape or user ports and no true drive. A `BADLINE` module,
48
- which VICE skips, holds the whole machine, and badline restores its own
49
- snapshots from it.
72
+ the CPU, RAM and CPU port, both CIAs, the SID and the VIC-II, and an
73
+ REU's `REU1764`, plus the ones x64sc needs to open the file, with
74
+ nothing else attached to the cartridge port, nothing on the tape or user
75
+ ports and no true drive. The VIC-II module names the chip as VICE does:
76
+ the 6569 or 8565 on PAL, the 6567R8 or 8562 on NTSC, the 6567R56A on old
77
+ NTSC and the 6572 on PAL-N. A `BADLINE` module, which VICE skips, holds
78
+ the whole machine, and badline restores its own snapshots from it.
50
79
 
51
80
  x64sc 3.10 opens badline's snapshots, taken at the end of the
52
- instruction the CPU was in, as long as its VIC-II model matches
53
- (`-model c64` for the default machine). It keeps its own drives and
54
- leaves out the cartridge, the expansions and the tape.
81
+ instruction the CPU was in, and of an REU transfer under way, as long
82
+ as its VIC-II model matches: `-model c64`, `c64c`, `ntsc`, `newntsc`,
83
+ `oldntsc` or `drean`, as `--model` names the machine. It takes the REU
84
+ from the snapshot, keeps its own drives and leaves out the cartridge,
85
+ the other expansions and the tape. A program x64sc runs on from badline's
86
+ snapshot reads the raster as badline does, cycle for cycle.
55
87
 
56
88
  Snapshots x64sc saves open in badline through the same modules: the CPU
57
89
  at its instruction boundary, RAM, the CPU port, the CIAs' registers,
58
- timers and clocks, the SID's registers and reSID voice state, and the
59
- VIC-II's registers, beam position, counters and colour RAM. The VIC-II's
60
- pixel pipeline starts empty. badline reads the modules x64sc 3.7 to 3.10
61
- write, and VICE's development versions' `MAINC64CPU` and `VIC-IISC`. A
62
- module version it doesn't know is left out, or fails the restore for the
63
- CPU and RAM. It reports the modules it leaves out, such as the 1541
64
- drives, the cartridge, the datasette and the keyboard, and carries on
65
- without them. A snapshot of an NTSC machine fails, as badline reads only
66
- VICE's PAL VIC-II. Restored into a running machine, a VICE snapshot
90
+ timers and clocks, the SID's registers and reSID voice state, the
91
+ VIC-II's registers, beam position, counters and colour RAM, and an
92
+ REU's size, registers and RAM. The VIC-II's model builds the machine's
93
+ region, and the 6569R1 runs as the 6569. The VIC-II's pixel pipeline
94
+ starts empty. badline reads the modules x64sc 3.7 to 3.10 write, and
95
+ VICE's development versions' `MAINC64CPU` and `VIC-IISC`. A module
96
+ version it doesn't know is left out, or fails the restore for the CPU
97
+ and RAM. It reports the modules it leaves out, such as the 1541 drives,
98
+ the cartridge port, the datasette and the keyboard, and carries on
99
+ without them. Restored into a running machine, a VICE snapshot
67
100
  fails for a machine built another way, such as a C64C's snapshot in a
68
101
  C64, and otherwise takes the cartridge out and switches the machine off
69
102
  and on.
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class AddressBus
5
+ # The BASIC, character and KERNAL ROMs, and which KERNAL is fitted.
6
+ module ROMs
7
+ # The KERNAL ROMs a machine can be fitted with, by name: the C64's
8
+ # and the SX-64's.
9
+ KERNALS = { c64: "kernal.rom", sx64: "kernal-sx64.rom" }.freeze
10
+
11
+ attr_reader :basic_rom, :character_rom, :kernal_rom, :kernal
12
+
13
+ # Fits the KERNAL ROM KERNALS names `name`, in place of the one before.
14
+ def kernal=(name)
15
+ raise ArgumentError, "no KERNAL named #{name}" unless KERNALS.key?(name)
16
+
17
+ @kernal = name
18
+ @kernal_rom = ROM.load(KERNALS[name], 0xe000)
19
+ update_overlays!
20
+ end
21
+
22
+ private
23
+
24
+ def load_roms
25
+ @basic_rom = ROM.load("basic.rom", 0xa000)
26
+ @character_rom = ROM.load("character.rom", 0xd000)
27
+ @kernal = :c64
28
+ @kernal_rom = ROM.load(KERNALS[:c64], 0xe000)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -4,6 +4,7 @@ require "badline/address_bus/saved_state"
4
4
  require "badline/address_bus/sid_slots"
5
5
  require "badline/address_bus/extra_sids"
6
6
  require "badline/address_bus/ultimax_pages"
7
+ require "badline/address_bus/roms"
7
8
 
8
9
  module Badline
9
10
  # Memory layout:
@@ -35,6 +36,7 @@ module Badline
35
36
  include Addressable
36
37
  include UltimaxPages
37
38
  include ExtraSIDs
39
+ include ROMs
38
40
 
39
41
  # I/O 1 and 2, and the Ultimax holes, with nothing on the bus. A read
40
42
  # picks up the byte the VIC fetched in the preceding phi1 half-cycle,
@@ -58,7 +60,7 @@ module Badline
58
60
  # See doc/pinned-behaviour.md.
59
61
  RAM_POWER_ON = Array.new(2**16) { |addr| (((addr + 2) / 4) ^ (addr / 0x4000)).odd? ? 0xff : 0x00 }.freeze
60
62
 
61
- attr_reader :io_port, :ram, :basic_rom, :character_rom, :kernal_rom,
63
+ attr_reader :io_port, :ram,
62
64
  :vic, :sid, :color_ram, :cia1, :cia2, :keyboard, :joystick1, :joystick2,
63
65
  :control_ports, :cartridge, :ultimax, :phi1_ultimax, :datasette, :region, :video_ram, :reu
64
66
 
@@ -71,9 +73,7 @@ module Badline
71
73
  @cartridge = @reu = nil
72
74
  @debug_register = nil
73
75
 
74
- @basic_rom = ROM.load("basic.rom", 0xa000)
75
- @character_rom = ROM.load("character.rom", 0xd000)
76
- @kernal_rom = ROM.load("kernal.rom", 0xe000)
76
+ load_roms
77
77
 
78
78
  @keyboard = Keyboard.new
79
79
  @joystick1 = Joystick.new
@@ -74,6 +74,30 @@ module Badline
74
74
  settle
75
75
  end
76
76
 
77
+ # The rig between frames: the bus and its chips, the extra SIDs, the
78
+ # CPU, and the call waiting for the idle loop.
79
+ def save_state(out)
80
+ out.marker("BARE PLAYER")
81
+ @bus.save_state(out)
82
+ @extras.each { |sid| sid.save_state(out) }
83
+ @cpu.save_state(out)
84
+ out.optional_int(@pending).int(@argument).boolean(@idle)
85
+ end
86
+
87
+ # Takes a rig that hasn't started to where #save_state found one, in
88
+ # place of #start.
89
+ def load_state(input)
90
+ input.marker("BARE PLAYER")
91
+ @bus.load_state(input)
92
+ @extras.each { |sid| sid.load_state(input) }
93
+ @cpu.load_state(input)
94
+ @pending = input.optional_int
95
+ @argument = input.int
96
+ @idle = input.boolean?
97
+ @stereo.synthesize!
98
+ install_dispatch
99
+ end
100
+
77
101
  # Advances one call of play, or `budget` cycles if that is shorter,
78
102
  # then yields whatever the SIDs recorded over it, in stereo. Returns
79
103
  # the cycles advanced.
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Audio
5
+ # The states a subtune's renderer saved as it played, at the first
6
+ # frame end past every `every` seconds, so that a seek starts from the
7
+ # latest one at or before where it goes rather than from the subtune's
8
+ # start. Whoever plays the subtune keeps them across its renderers, and
9
+ # drops them for another subtune.
10
+ class Checkpoints
11
+ EVERY = 10.0
12
+
13
+ def initialize(every: EVERY)
14
+ @every = every
15
+ @cycles = []
16
+ @states = []
17
+ end
18
+
19
+ # Whether the frame ending `cycles` into the subtune is the first past
20
+ # the next mark.
21
+ def due?(cycles, clock_hz) = cycles >= ((@cycles.length + 1) * @every * clock_hz).round
22
+
23
+ def keep(cycles, state)
24
+ @cycles << cycles
25
+ @states << state
26
+ end
27
+
28
+ # The state saved latest at or before `cycles`, or nil.
29
+ def latest(cycles)
30
+ index = @cycles.rindex { |at| at <= cycles }
31
+ index.nil? ? nil : @states[index]
32
+ end
33
+
34
+ # The cycles into the subtune of the state saved latest at or before
35
+ # `cycles`, or 0 for the start.
36
+ def reach(cycles)
37
+ index = @cycles.rindex { |at| at <= cycles }
38
+ index.nil? ? 0 : @cycles[index]
39
+ end
40
+ end
41
+ end
42
+ end