badline 0.10.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd764dccd3b8af3f8e0dcbb7d17debc3fceb24a9899f80871ebbf184ffefd65b
4
- data.tar.gz: 46a62d801813476242a407c09ab3ff4fec1126d10bfee134047f18e90fd86955
3
+ metadata.gz: e82b9f5bb012d15fd7744f164b82420c3b71c00b14b43843093e1a2b3722beeb
4
+ data.tar.gz: cf94f9a74e8c9d5d6b9d3a94a5404d29376a4c57da7f18b78c9dd87da4a7c880
5
5
  SHA512:
6
- metadata.gz: 82a62fb4a3de3ef65e123bacf22ba300efe628f1bde38815dc950a39034d8c2b9423de79b3805fce84e19bcbaa8100a3e67488d5ef0a3ecb2de6434689886883
7
- data.tar.gz: 595b19f034d708db60089e707507e74281059d271527fe61e0a205d9e8e0853a4f6b4d077aeed648f59a86d06bf31d3ee6e5902345fdb52d5fb36ded8de94da3
6
+ metadata.gz: 14986d25fea5add5cecf4c10001b4f9cf9e087023f07bf8a8fc27d950f799ff41ae086518165ca67b840c440e5f56cf661d335ac5d4f5de347260c0bd44864bb
7
+ data.tar.gz: 12ba3fa5d2276efb1003edaa884e8f1c58ee7d2e2ac5e05b0f7d927e6d8b5a37869893f012d92560772b3adf18ea2d886c058737bf9e10aac25aec54e36afe6b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
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
+
3
22
  ## [0.10.0](https://github.com/elektronaut/badline/compare/v0.9.0...v0.10.0) (2026-10-08)
4
23
 
5
24
 
data/README.md CHANGED
@@ -85,7 +85,7 @@ differences noted in the table. `--help` lists the options for either.
85
85
  | `--sid 6581`, `--sid 8580` | Fit the older or newer SID. `--sid auto`, the default, takes a `.sid` tune's own |
86
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) |
87
87
  | `--reu SIZE` | Plug in a RAM Expansion Unit of `SIZE` K: 128, 256, 512 (a 1750) or up to 16384 |
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) 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 |
89
89
  | `--ntsc` | Run an NTSC C64, as `--model ntsc` does |
90
90
  | `--no-sound` | Don't play the SID (`badline` only, where sound is on by default) |
91
91
  | `--sound` | Play the SID (`badline-ruby`, where sound is off by default) |
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.
data/doc/snapshots.md CHANGED
@@ -62,9 +62,10 @@ 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.
68
69
 
69
70
  ## The VIC-20
70
71
 
@@ -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
@@ -8,6 +8,18 @@ module Badline
8
8
  # ROMs, the chips, and #map_ram_pages, which lays out the RAM that the
9
9
  # ROMs, the cartridge and I/O map over.
10
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
+
11
23
  private
12
24
 
13
25
  def map_pla_pages
@@ -27,19 +39,26 @@ module Badline
27
39
  end
28
40
  end
29
41
 
30
- # Ultimax cartridges ignore the $01 lines: 4K of RAM, ROML/ROMH windows,
42
+ # Ultimax mode ignores the $01 lines: 4K of RAM, ROML/ROMH windows,
31
43
  # I/O always visible and open address space everywhere else. The ROML
32
44
  # and ROMH selects fire on writes as well, so cartridge RAM or flash in
33
- # either window takes the writes there.
45
+ # either window takes the writes there. The MAX has 2K of RAM, and is
46
+ # in Ultimax mode with no cartridge too.
34
47
  def map_ultimax_pages
35
- @read_pages.fill(@open_bus, 0x10, 0xf0)
36
- @write_pages.fill(@open_bus, 0x10, 0xf0)
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
37
57
  @read_pages.fill(@cartridge.roml, 0x80, 0x20) if @cartridge.roml
38
58
  map_ultimax_writes(@cartridge.roml, 0x80)
39
59
  @read_pages.fill(@cartridge.romh, 0xe0, 0x20) if @cartridge.romh
40
60
  map_ultimax_writes(@cartridge.romh, 0xe0)
41
61
  map_ultimax_a000
42
- map_io_pages
43
62
  end
44
63
 
45
64
  def map_ultimax_writes(bank, first_page)
@@ -53,6 +72,17 @@ module Badline
53
72
  @write_pages.fill(window, 0xa0, 0x20)
54
73
  end
55
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
+
56
86
  # EXROM and GAME select the cartridge whether or not it has a chip
57
87
  # there, and an empty socket leaves the bus floating.
58
88
  def map_rom_overlays
@@ -4,18 +4,25 @@ module Badline
4
4
  class AddressBus
5
5
  # The BASIC, character and KERNAL ROMs, and which KERNAL is fitted.
6
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
7
+ # The KERNAL ROMs a machine can be fitted with, by name: the C64's,
8
+ # the SX-64's, the PET 64's and the C64GS's. The C64GS's is a 16K
9
+ # image of its BASIC and its KERNAL, and fits both.
10
+ KERNALS = {
11
+ c64: "kernal.rom", sx64: "kernal-sx64.rom", pet64: "kernal-pet64.rom", gs: "c64gs.rom"
12
+ }.freeze
10
13
 
11
14
  attr_reader :basic_rom, :character_rom, :kernal_rom, :kernal
12
15
 
13
- # Fits the KERNAL ROM KERNALS names `name`, in place of the one before.
16
+ # Fits the KERNAL ROM KERNALS names `name`, in place of the one before,
17
+ # with the BASIC of a 16K image or else the C64's.
14
18
  def kernal=(name)
15
19
  raise ArgumentError, "no KERNAL named #{name}" unless KERNALS.key?(name)
16
20
 
17
21
  @kernal = name
18
- @kernal_rom = ROM.load(KERNALS[name], 0xe000)
22
+ data = ROM.read(KERNALS[name])
23
+ basic = data.length == 0x4000 ? data[0, 0x2000] : ROM.read("basic.rom")
24
+ @basic_rom = ROM.new(basic, length: 0x2000, start: 0xa000)
25
+ @kernal_rom = ROM.new(data[-0x2000, 0x2000], length: 0x2000, start: 0xe000)
19
26
  update_overlays!
20
27
  end
21
28
 
@@ -5,6 +5,7 @@ require "badline/address_bus/sid_slots"
5
5
  require "badline/address_bus/extra_sids"
6
6
  require "badline/address_bus/pla"
7
7
  require "badline/address_bus/roms"
8
+ require "badline/address_bus/fittings"
8
9
 
9
10
  module Badline
10
11
  # Memory layout:
@@ -37,6 +38,7 @@ module Badline
37
38
  include PLA
38
39
  include ExtraSIDs
39
40
  include ROMs
41
+ include Fittings
40
42
 
41
43
  # I/O 1 and 2, and the Ultimax holes, with nothing on the bus. A read
42
44
  # picks up the byte the VIC fetched in the preceding phi1 half-cycle,
@@ -68,6 +70,7 @@ module Badline
68
70
  def initialize(sid_model: :mos6581, cia_model: :mos6526, vic_model: :mos6569, region: Region::PAL,
69
71
  ram_expansion: nil)
70
72
  @region = region
73
+ @board = :c64
71
74
  @ram = Memory.new(RAM_POWER_ON, length: 2**16, start: 0)
72
75
  @ram_expansion = RAMExpansion.build(ram_expansion, @ram) { update_overlays! }
73
76
  @cartridge = @reu = nil
@@ -201,8 +204,9 @@ module Badline
201
204
  # so reads and writes dispatch through per-page handler tables instead
202
205
  # of range checks.
203
206
  def update_overlays!
204
- @ultimax = @cartridge ? @cartridge.ultimax? : false
205
- @phi1_ultimax = @cartridge ? @cartridge.phi1_ultimax? : false
207
+ max = @board == :max
208
+ @ultimax = max || (@cartridge ? @cartridge.ultimax? : false)
209
+ @phi1_ultimax = max || (@cartridge ? @cartridge.phi1_ultimax? : false)
206
210
  @video_ram = @ram_expansion.video_ram
207
211
  @vic.vic_bank.map(@video_ram, phi1_ultimax: @phi1_ultimax, ultimax: @ultimax, romh: @cartridge&.romh)
208
212
 
@@ -48,13 +48,12 @@ module Badline
48
48
  # TOD clocks count. The KERNAL tells PAL from NTSC by the raster, so
49
49
  # every region boots the same ROMs. reu plugs an REU of that many K
50
50
  # into the expansion port, where it drives the IRQ line and takes the
51
- # bus for its transfers. kernal: names the KERNAL ROM
52
- # (AddressBus::ROMs::KERNALS), and datasette: false leaves the cassette port
53
- # empty, as on the SX-64.
54
- def initialize(debug: false, reu: nil, kernal: :c64, datasette: true, **machine)
55
- @address_bus = AddressBus.new(**machine)
56
- @address_bus.kernal = kernal unless kernal == :c64
57
- @address_bus.datasette.disconnect! unless datasette
51
+ # bus for its transfers. kernal:, datasette: and board: fit the bus
52
+ # afterwards (AddressBus::Fittings): the KERNAL ROM, the cassette port,
53
+ # which is empty on the SX-64, and the board and case.
54
+ def initialize(debug: false, reu: nil, **machine)
55
+ @address_bus = AddressBus.new(**machine.except(*AddressBus::Fittings::NAMES))
56
+ @address_bus.fit(**machine.slice(*AddressBus::Fittings::NAMES))
58
57
  @cpu = CPU.new(@address_bus, debug:)
59
58
  @vic = @address_bus.vic
60
59
  @vic.open_bus = -> { @address_bus.ram.peek(@cpu.program_counter) }
@@ -195,8 +194,9 @@ module Badline
195
194
 
196
195
  # RESTORE isn't in the key matrix. It fires a one-shot that pulses the
197
196
  # NMI line however long the key is held, modelled here as one cycle.
197
+ # A machine without a keyboard has no RESTORE key either.
198
198
  def press_restore
199
- @restore_pulse = true
199
+ @restore_pulse = true if keyboard.connected?
200
200
  end
201
201
 
202
202
  # The one-shot has pulsed already, so letting go does nothing.
@@ -38,10 +38,21 @@ module Badline
38
38
  @keys = []
39
39
  @row_masks = nil
40
40
  @matrix = matrix
41
+ @connected = true
42
+ end
43
+
44
+ # Whether the machine has a keyboard. The C64GS has none: no key goes
45
+ # down on it.
46
+ def connected? = @connected
47
+
48
+ def connected=(connected)
49
+ @connected = connected
50
+ @keys.clear unless connected
51
+ @row_masks = nil
41
52
  end
42
53
 
43
54
  def press(key)
44
- return unless valid_key?(key)
55
+ return unless @connected && valid_key?(key)
45
56
 
46
57
  @keys << key
47
58
  @row_masks = nil
@@ -47,7 +47,7 @@ module Badline
47
47
  profile = Model.named(model)
48
48
  Computer.new(vic_model: profile.vic_model, cia_model: profile.cia_model,
49
49
  sid_model: sid_model || profile.sid_model, region: profile.region, reu:,
50
- kernal: profile.kernal, datasette: profile.datasette)
50
+ kernal: profile.kernal, datasette: profile.datasette, board: profile.board)
51
51
  end
52
52
 
53
53
  def self.vic20(model, ram)
data/lib/badline/model.rb CHANGED
@@ -8,39 +8,56 @@ module Badline
8
8
  # a datasette. The names are x64sc's.
9
9
  module Model
10
10
  # Each field but the name is a keyword Computer.new takes.
11
- Profile = Data.define(:name, :vic_model, :cia_model, :sid_model, :region, :kernal, :datasette)
11
+ Profile = Data.define(:name, :vic_model, :cia_model, :sid_model, :region, :kernal, :datasette, :board)
12
12
 
13
13
  # The breadbin PAL C64: the 6569 VIC-II, 6526 CIAs and the 6581 SID.
14
14
  C64 = Profile.new(name: "c64", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
15
- region: Region::PAL, kernal: :c64, datasette: true)
15
+ region: Region::PAL, kernal: :c64, datasette: true, board: :c64)
16
16
 
17
17
  # The PAL C64C: the HMOS 8565 VIC-II, 6526A CIAs and the 8580 SID.
18
18
  C64C = Profile.new(name: "c64c", vic_model: :mos8565, cia_model: :mos6526a, sid_model: :mos8580,
19
- region: Region::PAL, kernal: :c64, datasette: true)
19
+ region: Region::PAL, kernal: :c64, datasette: true, board: :c64)
20
20
 
21
21
  # The NTSC C64 with the 6567R8 VIC-II.
22
22
  NTSC = Profile.new(name: "ntsc", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
23
- region: Region::NTSC, kernal: :c64, datasette: true)
23
+ region: Region::NTSC, kernal: :c64, datasette: true, board: :c64)
24
24
 
25
25
  # The NTSC C64C: the 8562, the 6567R8's HMOS successor, with the
26
26
  # C64C's CIAs and SID.
27
27
  NEW_NTSC = Profile.new(name: "newntsc", vic_model: :mos8565, cia_model: :mos6526a, sid_model: :mos8580,
28
- region: Region::NTSC, kernal: :c64, datasette: true)
28
+ region: Region::NTSC, kernal: :c64, datasette: true, board: :c64)
29
29
 
30
30
  # The first NTSC C64s, with the 6567R56A VIC-II.
31
31
  OLD_NTSC = Profile.new(name: "oldntsc", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
32
- region: Region::NTSC_OLD, kernal: :c64, datasette: true)
32
+ region: Region::NTSC_OLD, kernal: :c64, datasette: true, board: :c64)
33
33
 
34
34
  # The Drean C64 of Argentina, on PAL-N with the 6572 VIC-II.
35
35
  DREAN = Profile.new(name: "drean", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
36
- region: Region::DREAN, kernal: :c64, datasette: true)
36
+ region: Region::DREAN, kernal: :c64, datasette: true, board: :c64)
37
37
 
38
38
  # The portable SX-64: a PAL C64's chips with its own KERNAL, a 1541
39
39
  # built in as device 8 and no cassette port.
40
40
  SX64 = Profile.new(name: "sx64", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
41
- region: Region::PAL, kernal: :sx64, datasette: false)
41
+ region: Region::PAL, kernal: :sx64, datasette: false, board: :c64)
42
42
 
43
- ALL = [C64, C64C, NTSC, NEW_NTSC, OLD_NTSC, DREAN, SX64].freeze
43
+ # The PET 64, or Educator 64: a PAL C64's board in a PET's case, with
44
+ # its own KERNAL and a green monochrome monitor built in.
45
+ PET64 = Profile.new(name: "pet64", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
46
+ region: Region::PAL, kernal: :pet64, datasette: true, board: :pet64)
47
+
48
+ # The C64GS games console: a PAL C64C's board with its own BASIC and
49
+ # KERNAL, which show an animation asking for a cartridge when it has
50
+ # none, in a case with no keyboard and no cassette port.
51
+ C64GS = Profile.new(name: "c64gs", vic_model: :mos8565, cia_model: :mos6526a, sid_model: :mos8580,
52
+ region: Region::PAL, kernal: :gs, datasette: false, board: :gs)
53
+
54
+ # The MAX Machine, or Ultimax, of Japan: the 6566, an NTSC VIC-II
55
+ # for static RAM, which runs here as the first NTSC C64s' 6567R56A,
56
+ # one CIA, 2K of RAM and no ROMs, so it runs only a cartridge's.
57
+ ULTIMAX = Profile.new(name: "ultimax", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
58
+ region: Region::NTSC_OLD, kernal: :c64, datasette: true, board: :max)
59
+
60
+ ALL = [C64, C64C, NTSC, NEW_NTSC, OLD_NTSC, DREAN, SX64, PET64, C64GS, ULTIMAX].freeze
44
61
 
45
62
  def self.named(name)
46
63
  model = ALL.find { |candidate| candidate.name == name }
@@ -49,13 +66,13 @@ module Badline
49
66
  model
50
67
  end
51
68
 
52
- # The model whose chips, region, KERNAL and datasette `machine` has, or
53
- # nil for none.
69
+ # The model whose chips, region, KERNAL, datasette and board `machine`
70
+ # has, or nil for none.
54
71
  def self.of(machine)
55
72
  ALL.find do |model|
56
73
  model.vic_model == machine.vic_model && model.cia_model == machine.cia_model &&
57
74
  model.sid_model == machine.sid_model && model.region == machine.region &&
58
- model.kernal == machine.kernal && model.datasette == machine.datasette
75
+ model.kernal == machine.kernal && model.datasette == machine.datasette && model.board == machine.board
59
76
  end
60
77
  end
61
78
  end
@@ -5,7 +5,7 @@ module Badline
5
5
  DEFAULT_RATE = 44_100
6
6
 
7
7
  # The names Badline::Model builds, in the order the help lists them.
8
- MODELS = %w[c64 c64c ntsc newntsc oldntsc drean sx64].freeze
8
+ MODELS = %w[c64 c64c ntsc newntsc oldntsc drean sx64 pet64 c64gs ultimax].freeze
9
9
 
10
10
  # The VIC-20's models, as xvic names its video standards. Only PAL is
11
11
  # emulated.
@@ -27,6 +27,30 @@ Downloaded from
27
27
  https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/c64/kernal.sx.251104-04.bin
28
28
  SHA-1 aa136e91ecf3c5ac64f696b3dbcbfc5ba0871c98.
29
29
 
30
+ kernal-pet64.rom
31
+ ----------------
32
+ The KERNAL of the PET 64, 4064 and Educator 64, 901246-01. It boots
33
+ with the "**** COMMODORE 4064 BASIC V2.0 ****" banner and a white
34
+ cursor, and its interrupt routine keeps the border, background and
35
+ sprite colours black unless bit 7 of $0286 is set. Computer.new(kernal:
36
+ :pet64) fits it, as --model pet64 does.
37
+
38
+ Downloaded from
39
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/c64/kernal.4064.901246-01.bin
40
+ MD5 da92801e3a03b005b746a4dd0b639c7c.
41
+
42
+ c64gs.rom
43
+ ---------
44
+ The C64GS's BASIC and KERNAL, 390852-01, 16 KB: BASIC for $A000-$BFFF
45
+ followed by the KERNAL for $E000-$FFFF. Its BASIC is 901226-01's but
46
+ for 30 unused bytes. Its KERNAL is 901227-03's with most changes in
47
+ $F72C-$FC92, the cassette routines' place, and many jumps redirected to
48
+ I/O error 5. Computer.new(kernal: :gs) fits both, as --model c64gs does.
49
+
50
+ Downloaded from
51
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/c64/64gs.390852-01.bin
52
+ MD5 049e657afafbdd802e8c6a4f88f3d730.
53
+
30
54
  dos1541.rom
31
55
  -----------
32
56
  The 1541 disk drive's DOS 2.6, 16 KB for $C000-$FFFF: 325302-01
Binary file
Binary file
@@ -3,32 +3,36 @@
3
3
  module Badline
4
4
  module Snapshot
5
5
  # How a machine was built: its VIC, CIA and SID models, its region, its
6
- # RAM expansion, the size of its REU, its KERNAL and whether it has a
7
- # datasette, the arguments Computer.new takes. A State starts with them, so a machine to restore it into can
8
- # be built first.
9
- Setup = Data.define(:vic_model, :cia_model, :sid_model, :region, :ram_expansion, :reu, :kernal, :datasette)
6
+ # RAM expansion, the size of its REU, its KERNAL, whether it has a
7
+ # datasette and its board, the arguments Computer.new takes. A State
8
+ # starts with them, so a machine to restore it into can be built first.
9
+ Setup = Data.define(:vic_model, :cia_model, :sid_model, :region, :ram_expansion, :reu, :kernal, :datasette,
10
+ :board)
10
11
 
11
12
  class Setup
12
13
  SID_MODELS = %i[mos6581 mos8580].freeze
13
14
  REGIONS = [Region::PAL, Region::NTSC, Region::NTSC_OLD, Region::DREAN].freeze
14
15
  RAM_EXPANSIONS = [:none, *RAMExpansion::TYPES.keys].freeze
15
16
  KERNALS = AddressBus::ROMs::KERNALS.keys.freeze
17
+ BOARDS = AddressBus::Fittings::BOARDS
16
18
 
17
19
  def self.of(address_bus)
18
20
  new(vic_model: address_bus.vic.model, cia_model: address_bus.cia1.model, sid_model: address_bus.sid.model,
19
21
  region: address_bus.region, ram_expansion: address_bus.ram_expansion.type, reu: address_bus.reu&.size_kb,
20
- kernal: address_bus.kernal, datasette: address_bus.datasette.connected?)
22
+ kernal: address_bus.kernal, datasette: address_bus.datasette.connected?, board: address_bus.board)
21
23
  end
22
24
 
23
- # Layout 3 has no KERNAL or datasette, and stands for the C64's.
25
+ # Layout 3 has no KERNAL or datasette, and stands for the C64's, and
26
+ # layouts before 6 have no board, and stand for the C64's.
24
27
  def self.read(input)
25
28
  new(vic_model: VIC::MODELS.fetch(input.int), cia_model: CIA::MODELS.fetch(input.int),
26
29
  sid_model: SID_MODELS.fetch(input.int), region: REGIONS.fetch(input.int),
27
30
  ram_expansion: ram_expansion(RAM_EXPANSIONS.fetch(input.int)), reu: reu(input.int),
28
31
  kernal: input.schema > 3 ? KERNALS.fetch(input.int) : :c64,
29
- datasette: input.schema > 3 ? input.boolean? : true)
32
+ datasette: input.schema > 3 ? input.boolean? : true,
33
+ board: input.schema > 5 ? BOARDS.fetch(input.int) : :c64)
30
34
  rescue IndexError
31
- raise FormatError, "the state names a chip model, region, RAM expansion or KERNAL badline doesn't know"
35
+ raise FormatError, "the state names a chip model, region, RAM expansion, KERNAL or board badline doesn't know"
32
36
  end
33
37
 
34
38
  def self.ram_expansion(name) = name == :none ? nil : name
@@ -45,12 +49,12 @@ module Badline
45
49
  out.int(VIC::MODELS.index(vic_model)).int(CIA::MODELS.index(cia_model))
46
50
  out.int(SID_MODELS.index(sid_model)).int(REGIONS.index(region))
47
51
  out.int(RAM_EXPANSIONS.index(ram_expansion || :none)).int(reu || 0)
48
- out.int(KERNALS.index(kernal)).boolean(datasette)
52
+ out.int(KERNALS.index(kernal)).boolean(datasette).int(BOARDS.index(board))
49
53
  end
50
54
 
51
55
  # A machine built this way, at power-on.
52
56
  def build
53
- Computer.new(vic_model:, cia_model:, sid_model:, region:, ram_expansion:, reu:, kernal:, datasette:)
57
+ Computer.new(vic_model:, cia_model:, sid_model:, region:, ram_expansion:, reu:, kernal:, datasette:, board:)
54
58
  end
55
59
 
56
60
  # The chips, led by the model's name when they make one of
@@ -63,6 +67,7 @@ module Badline
63
67
  parts << "a #{reu}K REU" if reu
64
68
  parts << "the #{kernal} KERNAL" unless kernal == :c64
65
69
  parts << "no datasette" unless datasette
70
+ parts << "the #{board} board" unless board == :c64
66
71
  parts.join(", ")
67
72
  end
68
73
  end
@@ -14,10 +14,11 @@ module Badline
14
14
  # The layout the chips write their fields in. It goes up whenever a
15
15
  # save_state writes something else, so a State in another layout
16
16
  # fails before anything is read into a machine. Layout 3, without the
17
- # KERNAL and datasette in the setup, and layout 4, without the VIA
18
- # shift register's clock delay, still read.
19
- SCHEMA = 5
20
- READABLE = [3, 4, SCHEMA].freeze
17
+ # KERNAL and datasette in the setup, layout 4, without the VIA shift
18
+ # register's clock delay, and layout 5, without the board in the
19
+ # setup, still read.
20
+ SCHEMA = 6
21
+ READABLE = [3, 4, 5, SCHEMA].freeze
21
22
 
22
23
  attr_reader :values, :strings
23
24
 
@@ -69,7 +69,8 @@ module Badline
69
69
  reu = nil unless reu && REU1764.reads?(reu)
70
70
  Setup.new(vic_model: model, cia_model: model == :mos8565 ? :mos6526a : :mos6526, sid_model: sid,
71
71
  region: vic ? VICII.region(vic) : Region::PAL, ram_expansion: nil,
72
- reu: reu ? REU1764.size_kb(reu) : nil, kernal: :c64, datasette: true)
72
+ reu: reu ? REU1764.size_kb(reu) : nil, kernal: :c64, datasette: true,
73
+ board: :c64)
73
74
  end
74
75
  end
75
76
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- VERSION = "0.10.0"
4
+ VERSION = "0.10.1"
5
5
  end
@@ -110,7 +110,8 @@ module Badline
110
110
  private
111
111
 
112
112
  # In Ultimax mode the cartridge ROMH replaces the character ROM
113
- # shadow, visible at $3000-$3FFF of the window.
113
+ # shadow, visible at $3000-$3FFF of the window. The MAX is in
114
+ # Ultimax mode with no cartridge too.
114
115
  def ultimax_peek(offset)
115
116
  romh = @romh
116
117
  if romh && offset.allbits?(0x3000)
data/lib/badline/vic.rb CHANGED
@@ -24,9 +24,24 @@ module Badline
24
24
  0x8a8a8a, 0xb3ec91, 0x867ade, 0xb3b3b3
25
25
  ].freeze
26
26
 
27
+ # The sixteen colours' luminances, out of 32, as Philip Timmermann
28
+ # (Pepto) measured them on the 6569.
29
+ LUMINANCES = [0, 32, 10, 20, 12, 16, 8, 24, 12, 8, 16, 10, 15, 24, 15, 20].freeze
30
+
31
+ # The sixteen colours on a green monochrome monitor, which shows only
32
+ # their luminance, as the PET 64's does.
33
+ GREEN_PALETTE = LUMINANCES.map do |luminance|
34
+ green = luminance * 255 / 32
35
+ ((green / 4) << 16) | (green << 8) | (green / 4)
36
+ end.freeze
37
+
27
38
  attr_reader :display, :width, :height, :vic_bank, :column,
28
39
  :rasterline, :dirty_lines, :model, :region
29
40
 
41
+ # The RGB colours #display's palette indices stand for: PALETTE, or
42
+ # GREEN_PALETTE on a monochrome monitor.
43
+ attr_accessor :palette
44
+
30
45
  # The parts a VICE snapshot reads and sets.
31
46
  attr_reader :registers, :display_state, :sequencer, :sprites, :character_buffer, :color_buffer,
32
47
  :fetch_d011
@@ -94,6 +109,7 @@ module Badline
94
109
  @iie = %i[mos8566 mos8564].include?(model)
95
110
  @core = model == :mos6569 ? :mos6569 : :mos8565
96
111
  @region = region
112
+ @palette = PALETTE
97
113
  @lightpen_extra = @core == :mos8565 ? 1 : 2
98
114
  @grey_dots = @core == :mos8565
99
115
  @delayed_fetch = @core == :mos8565
@@ -380,8 +396,6 @@ module Badline
380
396
  @dirty_lines.fill(false)
381
397
  end
382
398
 
383
- def palette = PALETTE
384
-
385
399
  private
386
400
 
387
401
  def load_beam(input)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: badline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inge Jørgensen
@@ -50,6 +50,7 @@ files:
50
50
  - lib/badline.rb
51
51
  - lib/badline/address_bus.rb
52
52
  - lib/badline/address_bus/extra_sids.rb
53
+ - lib/badline/address_bus/fittings.rb
53
54
  - lib/badline/address_bus/pla.rb
54
55
  - lib/badline/address_bus/roms.rb
55
56
  - lib/badline/address_bus/saved_state.rb
@@ -247,11 +248,13 @@ files:
247
248
  - lib/badline/rom.rb
248
249
  - lib/badline/roms/README
249
250
  - lib/badline/roms/basic.rom
251
+ - lib/badline/roms/c64gs.rom
250
252
  - lib/badline/roms/character.rom
251
253
  - lib/badline/roms/dos1541.rom
252
254
  - lib/badline/roms/eapi/LICENSE.md
253
255
  - lib/badline/roms/eapi/README
254
256
  - lib/badline/roms/eapi/eapi-am29f040-14
257
+ - lib/badline/roms/kernal-pet64.rom
255
258
  - lib/badline/roms/kernal-sx64.rom
256
259
  - lib/badline/roms/kernal.rom
257
260
  - lib/badline/roms/vic20/basic.rom