badline 0.3.0 → 0.4.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/hooks/session-start.sh +57 -0
  3. data/.claude/settings.json +14 -0
  4. data/CHANGELOG.md +65 -0
  5. data/CLAUDE.md +45 -16
  6. data/CONTRIBUTING.md +95 -3
  7. data/README.md +111 -61
  8. data/SECURITY.md +9 -0
  9. data/doc/pinned-behaviour.md +275 -33
  10. data/exe/badline-ruby +59 -0
  11. data/lib/badline/address_bus.rb +22 -10
  12. data/lib/badline/audio/aiff.rb +8 -3
  13. data/lib/badline/audio/cli.rb +17 -12
  14. data/lib/badline/audio/console.rb +12 -58
  15. data/lib/badline/audio/playback.rb +3 -0
  16. data/lib/badline/audio/sdl_sink.rb +20 -52
  17. data/lib/badline/audio/terminal.rb +90 -0
  18. data/lib/badline/audio.rb +2 -2
  19. data/lib/badline/cartridge/geo_ram.rb +63 -0
  20. data/lib/badline/cartridge.rb +1 -0
  21. data/lib/badline/cia/interrupt_register.rb +34 -10
  22. data/lib/badline/cia.rb +15 -8
  23. data/lib/badline/computer.rb +28 -13
  24. data/lib/badline/gui/application.rb +33 -21
  25. data/lib/badline/gui/gamepads.rb +22 -22
  26. data/lib/badline/gui/joy_map.rb +1 -1
  27. data/lib/badline/gui/key_map.rb +6 -1
  28. data/lib/badline/gui/pane.rb +7 -6
  29. data/lib/badline/gui/screen_pane.rb +9 -13
  30. data/lib/badline/gui/window.rb +35 -20
  31. data/lib/badline/gui.rb +1 -1
  32. data/lib/badline/input/mouse1351.rb +21 -6
  33. data/lib/badline/input/paddles.rb +11 -6
  34. data/lib/badline/kernal_trap/channel.rb +3 -0
  35. data/lib/badline/kernal_trap/drive/block_commands.rb +33 -4
  36. data/lib/badline/kernal_trap/drive/status.rb +3 -0
  37. data/lib/badline/kernal_trap/drive/write_file.rb +41 -0
  38. data/lib/badline/kernal_trap/drive/writes.rb +92 -0
  39. data/lib/badline/kernal_trap/drive.rb +51 -51
  40. data/lib/badline/kernal_trap/file.rb +6 -9
  41. data/lib/badline/kernal_trap/save.rb +9 -4
  42. data/lib/badline/keyboard.rb +19 -3
  43. data/lib/badline/media.rb +42 -11
  44. data/lib/badline/options.rb +209 -0
  45. data/lib/badline/ram_expansion/banking.rb +30 -0
  46. data/lib/badline/ram_expansion/plus256k.rb +38 -0
  47. data/lib/badline/ram_expansion/plus60k.rb +31 -0
  48. data/lib/badline/ram_expansion/unexpanded.rb +22 -0
  49. data/lib/badline/ram_expansion.rb +21 -0
  50. data/lib/badline/region.rb +51 -0
  51. data/lib/badline/sdl/events.rb +47 -0
  52. data/lib/badline/sdl/functions.rb +73 -0
  53. data/lib/badline/sdl.rb +67 -0
  54. data/lib/badline/sid/waveform/noise_writeback.rb +32 -14
  55. data/lib/badline/sid/waveform.rb +1 -1
  56. data/lib/badline/sid.rb +3 -1
  57. data/lib/badline/storage/d64_image.rb +10 -0
  58. data/lib/badline/storage/d71_image.rb +12 -0
  59. data/lib/badline/storage/d81_image.rb +9 -0
  60. data/lib/badline/storage/disk_image/bam.rb +111 -0
  61. data/lib/badline/storage/disk_image/directory.rb +79 -0
  62. data/lib/badline/storage/disk_image/writing.rb +110 -0
  63. data/lib/badline/storage/disk_image.rb +27 -4
  64. data/lib/badline/storage/host_directory.rb +2 -1
  65. data/lib/badline/storage.rb +15 -0
  66. data/lib/badline/time_of_day.rb +4 -2
  67. data/lib/badline/version.rb +1 -1
  68. data/lib/badline/via/control_lines.rb +104 -0
  69. data/lib/badline/via/interrupt_register.rb +58 -0
  70. data/lib/badline/via/shift_register.rb +135 -0
  71. data/lib/badline/via/timer1.rb +82 -0
  72. data/lib/badline/via/timer2.rb +84 -0
  73. data/lib/badline/via.rb +262 -0
  74. data/lib/badline/vic/bank.rb +4 -3
  75. data/lib/badline/vic/color_patches.rb +6 -3
  76. data/lib/badline/vic/display_state.rb +4 -0
  77. data/lib/badline/vic/graphics_shifter.rb +42 -7
  78. data/lib/badline/vic/registers.rb +3 -0
  79. data/lib/badline/vic/sequencer.rb +18 -18
  80. data/lib/badline/vic/sequencer_output.rb +4 -4
  81. data/lib/badline/vic/sprite/internal_bus.rb +4 -0
  82. data/lib/badline/vic/sprite/shifter.rb +25 -3
  83. data/lib/badline/vic/sprite.rb +5 -4
  84. data/lib/badline/vic/sprites.rb +8 -3
  85. data/lib/badline/vic.rb +93 -44
  86. data/lib/badline.rb +4 -0
  87. data/packaging/homebrew/badline.rb +34 -0
  88. metadata +30 -20
  89. data/exe/badline +0 -78
  90. data/exe/badline-sid +0 -34
  91. data/lib/badline/audio/options.rb +0 -137
data/README.md CHANGED
@@ -10,22 +10,36 @@ are modelled at the cycle level.
10
10
 
11
11
  It runs programs, disk and tape images, cartridges and SID tunes, and
12
12
  the SDL2 front end supports the keyboard, joysticks, game controllers,
13
- paddles and a 1351 mouse. Emulation runs slower than a real C64, so
14
- live sound, which is off by default, stutters. See
15
- [What's emulated](#whats-emulated) for the details.
13
+ paddles and a 1351 mouse. See [What's emulated](#whats-emulated) for the
14
+ details.
16
15
 
17
- ## Requirements
16
+ It comes in two builds of the same emulator:
18
17
 
19
- Ruby 4.0 or newer, and SDL2:
18
+ - **`badline`**, compiled ahead of time with
19
+ [Spinel](https://github.com/matz/spinel). It runs in real time with
20
+ sound, so it's the one to play games with.
21
+ - **`badline-ruby`**, which comes with the Ruby gem and runs on CRuby. It
22
+ runs slower than a real C64, so its sound is off by default. The gem is
23
+ also a library, which runs a `Badline::Computer` from Ruby without a
24
+ window.
25
+
26
+ ## Installation
27
+
28
+ The native `badline`, with Homebrew:
20
29
 
21
30
  ```sh
22
- brew install sdl2 # macOS
23
- apt install libsdl2-dev # Debian/Ubuntu
31
+ brew install elektronaut/tap/badline
24
32
  ```
25
33
 
26
- ## Installation
34
+ The formula builds it from the release's generated C, so it needs no
35
+ Spinel or Ruby. To build it from a checkout instead, see
36
+ [native/README.md](native/README.md).
37
+
38
+ The gem needs Ruby 4.0 or newer and the SDL2 library:
27
39
 
28
40
  ```sh
41
+ brew install sdl2 # macOS
42
+ apt install libsdl2-2.0-0 # Debian/Ubuntu
29
43
  gem install badline
30
44
  ```
31
45
 
@@ -37,22 +51,27 @@ Run `badline` with no arguments to boot to the BASIC prompt, or give it
37
51
  something to load:
38
52
 
39
53
  ```sh
40
- badline # READY.
41
- badline game.prg # Load and run a program
42
- badline game.d64 # Mount a disk image as device 8 and load it
43
- badline game.tap # Insert a tape and load it
44
- badline game.crt # Attach a cartridge
45
- badline tune.sid # Play a SID tune
46
- badline ~/c64 # Mount a directory as device 8
54
+ badline # READY.
55
+ badline game.prg # Load and run a program
56
+ badline game.d64 # Mount a disk image as device 8 and load it
57
+ badline game.tap # Insert a tape and load it
58
+ badline game.crt # Attach a cartridge
59
+ badline tune.sid # Play a SID tune
60
+ badline ~/c64 # Mount a directory as device 8
47
61
  ```
48
62
 
63
+ `badline-ruby` takes the same media and the same options, with the
64
+ differences noted below.
65
+
49
66
  Programs, disk and tape images and SID tunes start automatically, and
50
67
  a cartridge starts itself. A mounted directory waits for you to `LOAD`
51
68
  from it. `--no-autostart` attaches the media and stops at `READY.`, so
52
- you can type the `LOAD` yourself. `--song N` picks a subtune of a
53
- `.sid` file, `--sid 8580` fits the newer SID, and `--disable-jit` runs
54
- without YJIT, which is otherwise switched on at startup.
55
- `badline --help` lists the options.
69
+ you can type the `LOAD` yourself. `--read-only` mounts a disk image
70
+ write-protected, so the drive reports `26,WRITE PROTECT ON` for any
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
73
+ takes `--disable-jit`, which runs without YJIT, otherwise switched on at
74
+ startup. `--help` lists the options.
56
75
 
57
76
  The KERNAL, BASIC and character ROMs come with the gem. To run other
58
77
  images, such as a patched KERNAL, point `BADLINE_ROM_PATH` at a
@@ -61,20 +80,22 @@ plus `eapi/eapi-am29f040-14` if you attach EasyFlash cartridges. From
61
80
  Ruby, `Badline.rom_path = dir` does the same before a
62
81
  `Badline::Computer` is built, and `nil` restores the bundled set.
63
82
 
64
- `--sound` plays the SID through the host's audio device, and `F10`
65
- mutes and unmutes it. Sound is off by default. While it plays, the
66
- audio device sets the pace instead of the display, so the machine never
67
- runs ahead of the sound or drifts behind it. The whole machine runs
68
- below real time, though, so the sound stutters: it plays in bursts with
69
- silent gaps between them, at the right pitch, and never slows the
70
- emulation down.
83
+ `badline` plays the SID through the host's audio device, and `F10`
84
+ mutes and unmutes it. `--no-sound` turns it off. The window is paced by
85
+ the display's vsync; `--no-vsync` paces it by a timer, or by the sound
86
+ while it plays.
87
+
88
+ In `badline-ruby` sound is off by default, and `--sound` turns it on.
89
+ The machine runs below real time there, so the sound stutters: it plays
90
+ in bursts with silent gaps between them, at the right pitch, and never
91
+ slows the emulation down.
71
92
 
72
93
  ## Media
73
94
 
74
95
  | Format | Handling |
75
96
  |--------|----------|
76
97
  | `.prg`, `.p00` | Loaded into memory after boot. A program at the BASIC start (`$0801`) is `RUN`, anything else is left for you to `SYS` |
77
- | `.d64`, `.d71`, `.d81` | Mounted read-only as device 8, then `LOAD"*",8,1` and `RUN` |
98
+ | `.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 |
78
99
  | `.t64` | Mounted read-only as device 8 and loaded like a disk image. The files load by name, and no tape is involved |
79
100
  | `.tap` | Inserted in the datasette with PLAY pressed, then `LOAD` and `RUN`. It loads at the speed of a real tape |
80
101
  | `.crt` | The hardware types listed under [Cartridges](#whats-emulated). Other types are rejected |
@@ -85,36 +106,47 @@ There is no 1541. Device 8 works by trapping the KERNAL's `LOAD` and
85
106
  `SAVE` routines and its serial bus primitives, so files open by name
86
107
  through `OPEN` and `CHRIN` as well. The command channel answers `I`,
87
108
  `B-P` and `U1` block reads, which covers loaders that read blocks
88
- directly. Loaders that upload their own code to the drive with `M-W` and
89
- `M-E`, and copy protection that reads raw GCR, won't work.
109
+ directly. On a disk image it also takes `SAVE` (with `@0:` to replace a
110
+ file), files opened for writing or appending, `S` to scratch, `U2` and
111
+ `B-W` block writes, and `B-A` and `B-F`. Loaders that upload their own
112
+ code to the drive with `M-W` and `M-E`, and copy protection that reads
113
+ raw GCR, won't work.
114
+
115
+ `Badline::Media.insert_disk(computer, path)` swaps the disk image or
116
+ directory in device 8 while the machine runs, for software that asks for
117
+ another disk. It takes `read_only: true`, and `Badline::Media.attach`
118
+ takes `disk: { read_only: true }`, to mount a disk image write-protected,
119
+ as the test harnesses under `bin/` do.
90
120
 
91
121
  ## Playing and rendering SID tunes
92
122
 
93
- `badline-sid` plays a `.sid` tune on the host's audio device, or with
94
- `--output` (or `-o`) renders it to a 16-bit PCM file instead. The
95
- output extension picks the format, `.wav` or `.aiff`.
123
+ `badline-ruby --headless` plays a `.sid` tune on the host's audio
124
+ device without opening the window, and `--audio-out` renders it to a
125
+ 16-bit PCM file instead. The file's extension picks the format, `.wav`
126
+ or `.aiff`. The native `badline` has both modes too.
96
127
 
97
128
  ```sh
98
- badline-sid tune.sid # play, length from HVSC
99
- badline-sid -s 3 tune.sid # play the third subtune
100
- badline-sid --seconds 180 tune.sid -o out.aiff
101
- badline-sid -s 3 --rate 48000 tune.sid -o out.wav
102
- badline-sid --sid 8580 tune.sid
103
- badline-sid --filter-chunk 1 tune.sid -o out.wav # exact filter, slower
129
+ badline-ruby --headless tune.sid # play, length from HVSC
130
+ badline-ruby --headless -s 3 tune.sid # play the third subtune
131
+ badline-ruby --seconds 180 tune.sid --audio-out out.aiff
132
+ badline-ruby -s 3 --rate 48000 tune.sid --audio-out out.wav
133
+ badline-ruby --headless --sid 8580 tune.sid
134
+ badline-ruby --filter-chunk 1 tune.sid --audio-out out.wav # exact filter, slower
104
135
  ```
105
136
 
106
- Both modes take the same options. `--song` (or `-s`) picks the subtune,
107
- counting from 1 as HVSC does, and defaults to the tune's own start
108
- song. Playback asks the device for 44.1 kHz and takes whatever rate it
137
+ Both modes take the same options. The window's own, `--no-autostart`
138
+ and `--sound`, don't apply to them. `--song` (or `-s`) picks the
139
+ subtune, counting from 1 as HVSC does, and defaults to the tune's own
140
+ start song. Playback asks the device for 44.1 kHz and takes whatever rate it
109
141
  offers, unless `--rate` says otherwise. Ctrl-C stops it.
110
142
 
111
- Played on a terminal, `badline-sid` shows the tune's name, author and
143
+ Played on a terminal, `--headless` shows the tune's name, author and
112
144
  release, the song number and the time played against the song's
113
145
  length. `n` or → skips to the next song, `p` or ← goes back one, space
114
146
  pauses and `q` quits. `--no-tui`, or output that isn't a terminal,
115
147
  gives plain progress output instead.
116
148
 
117
- A `.sid` file doesn't store its length, so `badline-sid` looks the
149
+ A `.sid` file doesn't store its length, so `badline-ruby` looks the
118
150
  tune up by MD5 in HVSC's `Songlengths.md5`. It finds the database
119
151
  through `--songlengths`, in a `DOCUMENTS` directory in any of the
120
152
  tune's parent directories (the layout of an HVSC collection), or under
@@ -124,10 +156,15 @@ tune's parent directories (the layout of an HVSC collection), or under
124
156
  PSID tunes run on a CPU and RAM with only the SID clocked, at about
125
157
  twice real time, so they play smoothly. RSID tunes set up their own
126
158
  interrupts, so they boot a full C64 first and run at about half real
127
- time. They render fine but stutter when played, and `badline-sid` says
159
+ time. They render fine but stutter when played, and `badline-ruby` says
128
160
  so when it falls behind. The filter steps four cycles at a time;
129
161
  `--filter-chunk 1` steps it every cycle, which is exact and takes about
130
- twice as long. `badline-sid --help` lists the options.
162
+ twice as long. `badline-ruby --help` lists the options.
163
+
164
+ The native `badline` takes the same options and runs the same code, so
165
+ it renders the same file sample for sample, and it plays RSID tunes
166
+ without stuttering. See
167
+ [native/README.md](native/README.md#without-the-window).
131
168
 
132
169
  ## Input
133
170
 
@@ -140,15 +177,15 @@ same-named host key:
140
177
  | `RUN/STOP` | `Escape` |
141
178
  | `CLR/HOME` | `Home` |
142
179
  | `INST/DEL` | `Backspace` |
143
- | `CRSR ⇔` / `CRSR ⇕` | `Right` / `Down` (add Shift for left and up) |
144
- | `←` / `↑` | `Left` / `Up` |
180
+ | `CRSR ⇔` / `CRSR ⇕` | `Right` / `Down`, and `Left` / `Up` for the shifted directions |
181
+ | `←` / `↑` | `` ` `` / `]` |
145
182
  | `CTRL` | `Left Ctrl` |
146
183
  | `C=` | `Left Alt` |
147
184
  | `@` | `\` |
148
185
  | `:` | `'` |
149
186
  | `£` | `End` |
150
187
  | `+` / `*` | Keypad `+` / Keypad `*` |
151
- | `RESTORE` | Not mapped |
188
+ | `RESTORE` | `Page Up` |
152
189
 
153
190
  `Tab` steps through the input modes and `Shift-Tab` steps back. The
154
191
  window title shows the current mode:
@@ -175,8 +212,8 @@ Control port 1's fire line is also the VIC-II's light pen input, so
175
212
  joystick 1's fire button and the 1351's left button in port 1 latch the
176
213
  light pen registers.
177
214
 
178
- With `--sound`, `F10` mutes and unmutes the sound, and the window
179
- title shows `[MUTED]` while it's off.
215
+ `F10` mutes and unmutes the sound, and the window title shows `[MUTED]`
216
+ while it's off.
180
217
 
181
218
  ## What's emulated
182
219
 
@@ -185,14 +222,21 @@ title shows `[MUTED]` while it's off.
185
222
  [65x02 single step tests](https://github.com/SingleStepTests/65x02).
186
223
  `JAM` opcodes halt the CPU until reset.
187
224
  - **Memory**: banking through the 6510 port, including the cartridge
188
- `EXROM`/`GAME` lines and Ultimax mode.
225
+ `EXROM`/`GAME` lines and Ultimax mode. The +60K and +256K RAM
226
+ expansions fit with `Badline::Computer.new(ram_expansion: :plus60k)` or
227
+ `:plus256k`, banked through their register at `$D100`.
189
228
  - **VIC-II** (PAL 6569): the five standard graphics modes and the
190
229
  invalid ones, sprites with multicolour, expansion, priority and
191
230
  pixel-level collisions, raster interrupts, bad lines, sprite DMA, the
192
231
  border, VIC banks and the light pen.
232
+ `Badline::Computer.new(vic_model: :mos8565)` fits the C64C's 8565
233
+ instead, with its grey dots on colour register writes and its own
234
+ timing for mode splits, sprite multicolour splits and the light pen.
193
235
  - **CIA 1 and 2**: timers, time-of-day clocks with alarms, the serial
194
236
  shift register, interrupts, the keyboard matrix with its ghost keys,
195
- the control ports and the paddle multiplexer.
237
+ the control ports and the paddle multiplexer. The machine has the
238
+ original 6526s; `Badline::Computer.new(cia_model: :mos6526a)` fits the
239
+ C64C's 6526As instead, whose interrupt register timing differs.
196
240
  - **SID**: the 6581 and the 8580, with oscillators, ring modulation and
197
241
  sync, the envelope generator including the ADSR delay bug, the filter,
198
242
  and the RC network on the board that removes the DC offset from the
@@ -217,24 +261,30 @@ title shows `[MUTED]` while it's off.
217
261
  `Media.attach(computer, path, cartridge: { flash_jumper: true })`, with
218
262
  `bank_jumper: true` to run from the second 64K of a 128K image. The
219
263
  GMod2 EEPROM and the Retro Replay clock port aren't there.
264
+ - **GEO-RAM**: 64K to 4M of RAM seen through the `$DE00` page, with the
265
+ `$DFFE`/`$DFFF` page and block registers. It takes the expansion port,
266
+ so it can't sit alongside a cartridge:
267
+ `computer.attach_cartridge(Badline::Cartridge::GeoRAM.new(size: 512))`.
268
+ Its contents are lost when the emulator quits.
220
269
 
221
270
  Known gaps:
222
271
 
223
- - Live audio in the emulator window stutters, because the whole machine
224
- runs below real time. `badline-sid` plays PSID tunes smoothly on
225
- their own.
272
+ - `badline-ruby` runs below real time, so its live audio stutters.
273
+ `badline` plays smoothly, and so do PSID tunes under
274
+ `badline-ruby --headless`.
226
275
  - No drive emulation, so fast loaders and anything else that runs code
227
- on the drive won't work (see [Media](#media)). Disk images are
228
- read-only.
229
- - No NTSC machine, no REU, and no `RESTORE` key.
276
+ on the drive won't work (see [Media](#media)). The command channel
277
+ doesn't rename, copy, format or validate disks, and `LOAD"$",8`
278
+ doesn't list a disk's directory yet.
279
+ - No NTSC machine and no REU.
230
280
  - The emulator window has no freeze button yet, so a freezer cartridge
231
281
  runs its menu but can't freeze a program.
232
282
 
233
283
  ## Contributing
234
284
 
235
- Bug reports and pull requests are welcome on
236
- [GitHub](https://github.com/elektronaut/badline).
237
- [CONTRIBUTING.md](CONTRIBUTING.md) covers running the tests and the
285
+ Bug reports, feature requests, and pull requests are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) first. Report security vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
286
+
287
+ [CONTRIBUTING.md](CONTRIBUTING.md) also covers running the tests and the
238
288
  commit format, and the project has a
239
289
  [code of conduct](CODE_OF_CONDUCT.md).
240
290
 
data/SECURITY.md ADDED
@@ -0,0 +1,9 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Don't report security vulnerabilities in public issues, pull requests, or discussions.
6
+
7
+ Report them privately through GitHub: open the repository's **Security** tab and choose **Report a vulnerability**, or go directly to https://github.com/elektronaut/badline/security/advisories/new.
8
+
9
+ The principles in [CONTRIBUTING.md](CONTRIBUTING.md) apply here too: describe what you observed, give minimal steps you actually ran, and include raw output. Report what you verified; the maintainers will assess the impact.