Copperline is configured by a TOML file: ./copperline.toml by default, or
any file passed with --config. Every field is optional; missing fields use
the defaults documented here. copperline.example.toml in the repository
root is a commented companion to this reference.
The configuration is validated up front and the emulator refuses to start with a clear error message rather than guessing (unknown CPU or chipset names, out-of-range sizes, missing disk images, and so on).
This applies to every path field below (rom, disk images, hard-drive
files, the SCSI ROMs, and so on). In a TOML double-quoted string the
backslash is an escape character, so a Windows path written the obvious way
(rom = "C:\Kickstarts\KICK31.ROM") is rejected: \K is not a valid escape.
Use any one of:
rom = 'C:\Kickstarts\KICK31.ROM' # single quotes: a literal string, no escaping
rom = "C:\\Kickstarts\\KICK31.ROM" # double quotes: backslashes doubled
rom = "C:/Kickstarts/KICK31.ROM" # forward slashes also work on WindowsSingle-quoted literal strings are the least error-prone. macOS and Linux paths use forward slashes and need none of this.
The most common machine knobs can be set on the command line without writing a config file. These flags layer on top of the config file (or, when there is none, the built-in defaults) and are validated by exactly the same parsers and range checks as the equivalent TOML fields:
| Flag | Overrides | Accepts |
|---|---|---|
--model NAME |
[machine] profile |
A1000, A500, A500OCS, A500Plus, A600, A1200, A3000, A4000, CDTV, CD32 |
--chipset NAME |
[chipset] revision |
OCS, ECS, AGA |
--cpu MODEL |
[cpu] model |
68000, 68010, 68EC020, 68020, 68030, 68040, 68060 |
--cpu-clock MHZ |
[cpu] clock_mhz |
a number of MHz |
--fpu / --no-fpu |
[cpu] fpu |
fit / omit a 68881/68882 |
--jit / --no-jit |
[cpu] jit |
experimental fast batch/trace-JIT CPU execution (68020+; not cycle-exact) |
--chip SIZE |
[memory] chip |
512K, 1M, 2M, ... |
--fast SIZE |
[memory] fast |
0, 1M, 4M, 8M, ... |
--slow SIZE |
[memory] slow |
0, up to 512K |
--motherboard SIZE |
[memory] motherboard |
Ramsey RAM (A3000/A4000): 0, 1M..4M, 8M, 12M, 16M; A4000 up to 64M |
--accelerator SIZE |
[memory] accelerator |
CPU-slot RAM at $08000000 (32-bit CPUs): 0 to 128M |
--floppy-drives COUNT |
[floppy] drives |
1 to 4 wired drives (DF0: plus external drives) |
--floppy-speed PERCENT |
[floppy] speed |
100 (real), 200, 400, 800, or 0 (turbo) |
--floppy-bridge DFN NAME |
[floppy.dfN] bridge |
drive a physical floppy drive: drawbridge, greaseweazle, supercardpro, off |
--floppy-bridge-port DFN PORT |
[floppy.dfN] bridge_port |
that interface's serial port (default: auto-detect) |
--floppy-bridge-cable DFN SEL |
[floppy.dfN] bridge_cable |
drive select: a/b (IBM PC cable) or 0-3 (Shugart) |
--floppy-bridge-mode DFN MODE |
[floppy.dfN] bridge_mode |
how tracks are captured: normal, compatible, stalling |
--floppy-bridge-density DFN D |
[floppy.dfN] bridge_density |
force a density: auto, dd, hd |
--floppy-bridge-speed DFN PCT |
[floppy.dfN] bridge_speed |
serve captured tracks at 100, 125, 150, 175, or 200 percent of real speed |
--floppy-bridge-auto-cache DFN |
[floppy.dfN] bridge_auto_cache = true |
cache disk data while the drive is idle |
--floppy-bridge-writable DFN |
[floppy.dfN] write_protected = false |
allow writing to the real disk |
--joystick MODE |
[input] joystick |
gamepad (default), keyboard |
--mouse-sensitivity N |
[input] mouse_sensitivity |
0-100 host mouse speed (50 default = 1:1) |
--mouse-capture MODE |
[input] mouse_capture |
When the host mouse is grabbed: click (default), auto, manual |
--port1 DEVICE |
[input] port1 |
mouse (default), joystick, cd32, analogue, none |
--port2 DEVICE |
[input] port2 |
same devices; default joystick (cd32 on the CD32 profile) |
--autofire HZ |
[input] autofire_hz |
0 (off, the default) to 30 |
--full-screen / --windowed |
[display] full_screen |
open fullscreen or windowed at start (default windowed) |
--show-status-bar / --hide-status-bar |
[display] status_bar |
status bar at start (default shown) |
--menu-scale SIZE |
[display] menu_scale |
size of the pop-up menu: 1x (default) or 2x |
For example, to boot a stock A1200 profile but with 8 MB of fast RAM and a faster CPU, with no config file at all:
./target/release/copperline --model A1200 --fast 8M --cpu-clock 28 KICK31.ROMA --model profile supplies the chipset, CPU, and memory defaults of a real
machine; the other flags then override individual values on top of it, just as
explicit [cpu]/[chipset]/[memory] sections override a [machine]
profile in a config file.
The audio, serial, parallel, and network surface has matching per-run flags
too -- --audio-device, --audio-channel-mode, --audio-filter,
--audio-stereo-separation, --serial, --midi-in, --midi-out,
--parallel, --sampler-audio-input, --sampler-input-gain, --a2065-net,
--a2065-interface, --hostsocket-net, --hostsocket-interface --
described with their [audio], [serial], [parallel], [a2065], and
[hostsocket] keys below.
rom = "KICK13.ROM" # Kickstart image, 512 KiB (or a 256 KiB 1.x part)
extended_rom = "cd32ext.rom" # optional: CDTV (256K at $F00000) or
# CD32 (512K at $E00000) extended ROM
# identify = false # drop the Copperline identification board
# from the Zorro chain (default: present)identify controls a small, inert Zorro autoconfig board Copperline puts on
the expansion chain (manufacturer 5192 / product 2) so guest software such
as identify.library can detect that it
is running under the emulator. It is on by default and does not change the
machine's usable memory; set identify = false for a chain with no
emulator-identifying board. See for details.
The ROM path can be overridden by a positional CLI argument. Omit rom
entirely (and pass no ROM argument) to boot the bundled AROS open-source
Kickstart replacement, which ships with Copperline as the default boot ROM;
its main and extended halves are located next to the binary (under
share/copperline/aros for a Homebrew install) or set
COPPERLINE_AROS_DIR. You can also fit a different ROM at runtime from the
menu's Load Kickstart ROM... item, which hard-resets the machine. Machine
profiles that need an extended ROM (CDTV, CD32) will tell you if it is
missing.
Both ROM keys accept images in either byte order. Alongside plain CPU-order
dumps, the byte-swapped images prepared for EPROM programmers -- the
single-chip .bin ROM files in Hyperion's Kickstart 3.1.4/3.2 releases, such
as kick.a500a600a2000.46.143.bin, store every 16-bit word with its bytes
exchanged -- are recognised from their header and restored on load, so either
file boots identically. A 256 KiB Kickstart 1.x part is mirrored across the
512 KiB ROM window, as it decodes on real hardware. The split hi/lo chip
pairs for the 32-bit machines are not accepted; use the matching single-file
image instead.
[machine]
profile = "A1200" # A1000, A500, A500OCS, A500Plus (A500+), A600, A1200, A3000, A4000, CDTV, CD32
rtc = true # add a battery RTC (default: only A500+/CDTV/A3000/A4000 ship with one)
# rtc_chip = "RP5C01" # MSM6242 (default) or RP5C01 (A3000/A4000 default)
# rtc_time = "2005-03-18 01:58:29" # seed the clock; it then ticks in emulated time
# rtc_frozen = true # stop the seeded clock at rtc_time exactly
# battmem = "battmem.nvram" # RP5C01 battery-RAM backing file (default when fitted)
mem_controller = "ramsey-07" # none, ramsey-04 (A3000), ramsey-07 (A4000)
rom_scsi_device_disable = true # skip the ROM's scsi.device (default: when its bus has no drives)A machine profile bundles the chipset, CPU, memory, gate array, and
peripheral defaults of a real machine. The key is profile (the deprecated
model alias still parses) so it never collides with [cpu] model. Explicit [cpu], [chipset], and
[memory] sections override individual profile defaults. Without a
[machine] section you get the A500 Rev 6A default (the same as the A500
profile: ECS 8372A Agnus, OCS 8362 Denise, 68000, 512K chip RAM, 512K
trapdoor slow RAM) -- the most common and most-targeted Amiga. An explicit
[chipset] revision overrides the per-machine chips, so revision = "OCS"
gives a plain 8371/8362 OCS machine.
| Profile | Chipset | CPU | Chip RAM | Slow RAM | Extras |
|---|---|---|---|---|---|
A1000 |
OCS (8361/8367 Agnus, OCS Denise) | 68000 @ 7.09 MHz | 256K | 0 | WCS, boot ROM + Kickstart disk |
A500 |
Rev 6A: ECS 8372A Agnus, OCS 8362 Denise | 68000 @ 7.09 MHz | 512K (up to 1M) | 512K | -- |
A500OCS |
OCS (8371 Fat Agnus, OCS Denise) | 68000 @ 7.09 MHz | 512K | 512K | early A500 / A2000 |
A500Plus |
ECS (8375 Agnus, ECS Denise) | 68000 @ 7.09 MHz | 1M | 0 | RTC |
A600 |
ECS (8375 Agnus, ECS Denise) | 68000 @ 7.09 MHz | 1M | 0 | Gayle IDE |
A1200 |
AGA (Alice/Lisa) | 68EC020 @ 14.18 MHz | 2M | 0 | Gayle IDE |
A3000 |
ECS | 68030 @ 25 MHz | 2M | 0 | Ramsey-04, RP5C01 RTC |
A4000 |
AGA (Alice/Lisa) | 68040 @ 25 MHz | 2M | 0 | Ramsey-07, RP5C01 RTC |
CDTV |
ECS | 68000 @ 7.09 MHz | 1M | 0 | DMAC CD controller, RTC, 256K extended ROM |
CD32 |
AGA (Alice/Lisa) | 68EC020 @ 14.18 MHz | 2M | 0 | Akiko, CD32 pad, NVRAM, 512K extended ROM |
rtc exists because most Amigas shipped without a battery-backed clock and
only some carried one. The A500Plus (an OKI RTC soldered to the Rev 8A
board), CDTV, A3000, and A4000 fit one by default; the base
A500/A500OCS, A600, A1200, A1000, and CD32 have none. Set rtc = true to add
one -- for an A600HD or a clock-equipped A1200, say -- so the Workbench clock
keeps time.
rtc_chip names the part in that socket, because Commodore used two with
different register protocols: the OKI MSM6242 on the small boxes, the
CDTV, and the aftermarket clock expansions, and the Ricoh RP5C01 on the
A3000/A4000 motherboards (the Ricoh also carries 26 nibbles of battery RAM,
which AmigaOS uses via battmem.resource on those machines). The default
follows the profile -- RP5C01 on A3000/A4000, MSM6242 everywhere else
-- and setting the key implies rtc = true. AmigaOS probes for either part,
so the choice is mostly invisible to it, but Linux/m68k does not probe: it
drives the chip the machine model dictates, so an A3000/A4000 booting Linux
needs the RP5C01 answering for its clock to work.
battmem persists the RP5C01's battery-backed registers -- the 26 RAM
nibbles behind battmem.resource plus the alarm and 12/24 settings --
across runs, the way the real board's battery does. This is where
scsi.device keeps its per-unit SCSI host settings (an A3000's or A4091's
synchronous-transfer, disconnect, and last-drive options, including
remembering attached CD-ROM drives), so without it those revert every run.
The file uses the same .nvram layout as WinUAE and Amiberry, so backing
files interchange between emulators; only the battery payload loads back --
the time-of-save digits in the file never override the (host- or
rtc_time-driven) clock. It defaults to battmem.nvram in the working
directory whenever an RP5C01 is fitted; point it elsewhere with a path, or
set battmem = "" to keep the battery registers session-only. Note that a
persisted file carries guest-visible state from one run into the next by
design, so delete it (or disable it) where byte-for-byte reproducible
headless runs matter.
rtc_time seeds the clock instead of letting it mirror the host's: the value
is either an integer (Unix seconds, UTC) or a string
"YYYY-MM-DD HH:MM[:SS]" giving exactly the wall-clock time the guest reads
at power-on. A seeded clock ticks with emulated time, so the time the guest
sees is deterministic and reproducible byte-for-byte across runs -- the way
to test time-dependent guest software (TOTP/RFC 6238 vectors, timestamped
logs, date rollovers) or just to boot into a fixed date. Setting a time
implies rtc = true; combining it with an explicit rtc = false is an
error. rtc_frozen = true additionally stops the tick so every read returns
rtc_time exactly. Both are also available as --rtc-time / --rtc-frozen
CLI flags, and a control-protocol session can inspect and move the clock live
with rtc.get / rtc.set (see docs/debugger/control.md).
Two guest-side notes: Kickstart 2.0+ loads the system time from the battery
clock automatically at boot, while Kickstart 1.3 only does so when the
startup-sequence runs SetClock LOAD; and the chip's two-digit year registers
mean AmigaOS applies its usual century window, so seeds outside 1978-2077
will not read back as the year you set. A host-initiated reset or power
cycle restarts the emulated timeline and therefore restarts a seeded clock
from rtc_time; a guest-initiated reboot (the 68000 RESET instruction)
leaves it ticking, like real battery-backed hardware.
The A3000 and A4000 profiles are the big-box machines. They carry a
Ramsey memory controller (mem_controller), which is what the two registers
at $DE0003 and $DE0043 answer as, and they carry Gary rather than Gayle
-- so no PCMCIA and no Gayle IDE.
- The A3000 has its motherboard SCSI: a Super DMAC at
$DD0000driving a WD33C93, which Kickstart's ownscsi.deviceinitialises at boot. Attach drives to it with[scsi] controller = "a3000"(the default controller on an A3000; see the[scsi]section below). - The A4000 has its motherboard IDE interface at
$DD2020; attach drives with[ide], exactly as on a Gayle machine.
rom_scsi_device_disable skips Kickstart's built-in disk driver. It defaults
on when the machine's own controller -- the IDE port of an A600, A1200, or
A4000, or the A3000's SCSI -- has no drives configured: with nothing to boot,
the driver only costs startup time probing an empty bus. Configuring a drive
turns the driver back on automatically (it is the boot path for those
drives), and setting the flag explicitly wins in either direction. The ROM
file itself is never modified.
Both profiles fit their stock 4M of Ramsey-controlled motherboard fast RAM;
[memory] motherboard resizes it up to 16M, and on the A4000 up to 64M
via the motherboard RAM expansion space (see the [memory] section).
[memory] accelerator adds CPU-slot RAM at $08000000 on any 32-bit
machine.
mem_controller is normally left to the profile. It is broken out because
Ramsey's registers collide with nothing else, so it can be fitted to
a wedge machine to exercise diagnostic tools that expect one.
The A1000 profile models the original Amiga, which has no Kickstart ROM.
Its rom is instead the 64K bootstrap ROM ("Amiga ROM Bootstrap"); on
power-up the bootstrap loads Kickstart from the Kickstart disk in DF0 into
256K of writable control store (WCS) at $FC0000, write-protects it, and runs
it -- exactly as the real machine does. So an A1000 config names the bootstrap
ROM as rom and puts the Kickstart disk in [floppy.df0]; leave it in and the
machine boots to Kickstart (which then asks for a Workbench disk). See the
ready-made a1000.example.toml.
The A500 profile models the common Rev 6A board: the ECS "Fatter" 8372A
Agnus (a 1 MiB chip-RAM reach and the software-selectable PAL/NTSC switch via
BEAMCON0) paired with the original OCS 8362 Denise. It is therefore an
Agnus-only ECS upgrade, not a full-ECS machine -- the OCS Denise means no
superhires or BRDRBLNK, exactly as on the real board. Chip RAM defaults to
the stock 512K but accepts up to 1M ([memory] chip = "1M"); more than 1M is
rejected because the 8372A cannot address it. Booting with no [machine]
section uses the same Rev 6A defaults; select A500OCS or set
[chipset] revision = "OCS" for the older 8371/8362 machine.
[emulation]
power_on = true # false = start powered off at the test screen
pacing_budget = "cycles" # "cycles" (hardware-accurate) or "instructions"
realtime_priority = false # true = raise the pacer/audio thread priority
warp_speed = "max" # turbo limit: "2x", "4x", "8x", "16x", or "max"
rewind = false # true = record rewind history from power-on
rewind_budget_mb = 256 # host memory the rewind history may hold
rewind_interval_frames = 25 # emulated frames per rewind stepThe deterministic cycle-driven core is the only emulation timing. It is
paced to wall-clock for the interactive window and runs unthrottled for
headless captures; the emulated result is identical. (An older speed key
here is accepted but ignored -- "real" was the only timing model, so it
carried no information.)
-
power_on = falsestarts the machine powered off showing a test screen until you click the status-bar power button -- useful for arming video capture first. The power button cold-boots (clears RAM). -
pacing_budgetselects how real-time pacing budgets CPU work per frame:"cycles"(default) charges each instruction its actual 68000 cycle cost plus chip-bus waits, matching real hardware speed;"instructions"uses a flatCOPPERLINE_REAL_CPU_CPI(default 4.0) cycles/instruction quota, which is cheaper but runs the CPU faster than hardware.COPPERLINE_REAL_PACING_BUDGEToverrides this for one run. See for the full rationale. -
realtime_priority = trueasks the OS to schedule Copperline's two latency-critical threads -- the wall-clock pacer and the audio callback -- above normal, which reduces frame stutter and audio glitches when the host is busy. It is best effort and off by default, and never fails the run:- macOS -- the pacer thread joins the
USER_INTERACTIVEQoS class. The audio callback is left alone because Core Audio already runs it on a real-time thread (overriding that would only demote it). - Windows -- both threads are raised via
SetThreadPriority; no privilege required. - Linux/other Unix -- raising priority needs privilege (an
rtpriorlimit,CAP_SYS_NICE, or root). Without it the request is logged and declined, and the thread keeps normal scheduling.
COPPERLINE_REALTIME_PRIORITYoverrides this for one run; set it to0/false/offto force it off, or to any other value (or leave it empty) to force it on. - macOS -- the pacer thread joins the
-
warp_speedsets the default speed of Warp Speed (turbo) mode. The window presents with vsync, so emulating one frame per presented frame would pin warp to the host monitor's refresh rate. This option is an output frame skip --"2x","4x","8x","16x", or"max"(default) -- so warp retires that many emulated frames per presented frame, making warp roughly the limit times the refresh rate (host CPU permitting)."max"runs flat out and still presents at vsync. Adjust it live from the Warp Limit menu item orCmd+Shift+W/Alt+Shift+W(see The window and its controls). -
rewind = truerecords rewind history from power-on, soCmd+Z/Alt+Zand the Rewind menu item can step the whole machine backward through it. It rides the same deterministic snapshot ring as the debugger's reverse controls (see ) and is off by default because it is not free:rewind_budget_mb(default 256) of host memory holds the snapshots, and one whole-machine serialize happens everyrewind_interval_frames(default 25, half a second of PAL). One rewind step goes back exactly one interval; oldest snapshots are evicted first, so how much emulated time the budget buys scales with the machine's RAM size. Turning the menu item off releases the retained snapshots. The same determinism preconditions as reverse debugging apply -- a real-time clock and host disk writes are not rolled back.
[cpu]
model = "68000" # 68000, 68010, 68EC020, 68020, 68030, 68040, 68060
clock_mhz = 14.0 # optional; defaults to the model's stock speed
# icache = false # instruction-cache model (on by default on all 020+ models)
# dcache = false # data-cache model (on by default: 030/040/060)
# fpu = true # fit a 68881/68882 (68020/68030; needs the coprocessor
# # interface, so not valid on a 68000). The full 68040's
# # and 68060's on-die FPUs are enabled by default.
# unimplemented = "trap" # 68060 only: "trap" (faithful; the OS needs
# # 68060.library) or "native" (execute the removed
# # instructions directly)
# jit = true # experimental: fast batch/trace-JIT CPU execution
# # (68020+; not cycle-exact)model: the 68010 models the vector base register, the format-stacking exception model, and DBcc loop mode; the 68EC020 is a 68020 instruction set with a 24-bit external address bus.clock_mhzdefaults to the model's stock speed (68000/68010 ~7.09, 020 ~14, 030/040 ~25, 060 50) and is modelled as a whole multiple of the colour clock (3.546895 MHz). Fast RAM and ROM run at the CPU clock; chip and slow RAM stay chip-bus bound, so overclocking speeds up only what a real accelerator would speed up.icache/dcachemodel the on-chip caches and default on for the silicon that has them (instruction cache on the 020/68EC020/030/040/060, data cache on the 030/040/060), matching real hardware where AmigaOS enables them via CACR. The cache is sized to the CPU: 256 bytes on the 020/030, 4 KB on the 040, 8 KB on the 060. Set either tofalseto opt out.unimplemented(68060 only) picks what happens on the instructions the 68060 dropped from silicon: MOVEP, CHK2/CMP2, CAS2, misaligned CAS, 64-bit MUL/DIV, and most of the FPU beyond basic arithmetic (transcendentals, FMOVECR, packed decimal)."trap"(the default) is what the chip does - they raise the unimplemented-instruction exceptions and the OS-side68060.libraryemulates them, exactly as on a real CyberStorm or Blizzard board, so software using them needs that library installed."native"executes them directly for systems without it. Kickstart 3.1 itself boots fine under"trap".fpu = falseon the 68060 models the LC/EC060: FP instructions take the FPU-disabled exception (PCR.DFP), which an OS handler can also use to enable and restart. The 68060's superscalar dual-issue and branch cache are modelled and activate when system software enables them (PCR.ESS and CACR.EBC, which68060.librarydoes at boot); until then the chip runs scalar, as on real silicon. This is not cosmetic: code that loops out of chip RAM otherwise contends with bitplane DMA on every instruction fetch and can run at roughly half speed, which is why an AGA demo's music or animation may pace correctly only with the cache modelled. The data cache caches expansion RAM/ROM only, since chip and slow RAM are DMA-visible and cache-inhibited as on real Amigas.jit(experimental, also--jit/--no-jit) runs the CPU through the m68k core's batch/trace-JIT path instead of the cycle-exact per-instruction model: hot code compiles to native traces and fast-RAM accesses run through a zero-cost direct-memory window. The machine behaves like an ideal accelerator running atclock_mhz: one instruction per CPU clock with zero-wait fast RAM and ROM, so a 50 MHz 68040 delivers on the order of 50 MIPS (raiseclock_mhzfor more). Interrupts are recognized at batch boundaries, so chip-level races and cycle-counted effects no longer line up; leave it off for anything timing-sensitive (games, demos). Chip and slow RAM still arbitrate onto the shared chip bus in order, and the on-chip cache models stay active (as on a real accelerator, they are what lets chip-RAM-resident code run at CPU speed), so displays, blits, and device I/O work normally. Requires a 68020 or later: the 68000/68010 share one bus with the chipset and their floating-bus and prefetch semantics need the precise core, sojit = trueon those models logs a note and stays precise (the launcher greys the toggle). Known issue: the bundled AROS ROM's boot screen may stay grey under JIT on some 020+ configurations (the guest still runs; Kickstart ROMs are unaffected).
[memory]
chip = "512K" # OCS max 512K; ECS/AGA max 2M
fast = "0" # Zorro II fast RAM at $200000: 64K..8M board sizes
slow = "512K" # A500 trapdoor RAM at $C00000: 0 or up to 512K
motherboard = "0" # Ramsey motherboard RAM (A3000/A4000): up to 16M (A4000: 64M)
accelerator = "0" # CPU-slot RAM at $08000000 (32-bit CPUs): up to 128M
z3 = "0" # Zorro III RAM (needs a 32-bit CPU): 64K..1G, power of twoSizes accept K/KB/M/MB (and G/GB for Zorro III) suffixes or
plain byte counts, and must be multiples of 4 KiB.
- Chip RAM is range-checked against the chipset: 512K on OCS, 2M on ECS/AGA (also bounded by the selected Agnus revision's address reach).
- Fast RAM is exposed as a Zorro II autoconfig board at
$200000, so it must be a legal Zorro II board size: 64K, 128K, 256K, 512K, 1M, 2M, 4M, or 8M. - Slow RAM ($C00000 "ranger" RAM) is arbitrated on the chip bus through Agnus exactly like chip RAM -- it is slow in the authentic way.
- Motherboard RAM is the 32-bit local memory Ramsey drives on the
A3000/A4000: it ends at
$08000000and grows downward (16M reaches$07000000), and Kickstart sizes it with its own probe -- no autoconfig involved. It needs a Ramsey ([machine] mem_controller, fitted by the A3000/A4000 profiles, which also fit their stock 4M of this RAM by default) and a 32-bit CPU, and must fill whole Ramsey banks: 1M-4M in 1M steps, or 8M, 12M, 16M. On the A4000 (Ramsey-07), sizes beyond 16M keep growing downward into the$04000000-$06FFFFFFmotherboard RAM expansion space, in 4M steps up to 64M (which reaches$04000000). Setmotherboard = "0"to remove it. - Accelerator RAM is CPU-slot local memory: it starts at
$08000000and grows upward through the coprocessor-slot expansion space, up to 128M (ending at$10000000, where Zorro III space begins). This is the RAM an accelerator/CPU board carries, so it needs a 32-bit CPU but no particular machine profile; any whole number of megabytes fits. Kickstart sizes it with its own probe, like the motherboard bank. - Z3 RAM requires a 68020/68030/68040/68060 (a 24-bit bus cannot reach it);
Kickstart assigns its base address, usually
$40000000.
Additional expansion boards can be described with [[zorro]] metadata
files; see .
[chipset]
revision = "OCS" # OCS, ECS, or AGA preset
video = "PAL" # PAL or NTSC
# agnus = "8372A" # optional fine-grained override
# denise = "OCS" # optional fine-grained overriderevision is a preset; agnus and denise allow the mixed configurations
real machines shipped with (a late A500 with an ECS Agnus but OCS Denise,
for example):
agnus:OCS/8370/8371(OCS),8372/8372A(ECS, 1M chip),8375/8372B(ECS, 2M chip),8374/ALICE(AGA).denise:OCS/8362,ECS/8373,LISA/4203.
The ECS preset picks an 8372A for up to 1M chip RAM and an 8375 above; the A600 profile always uses the 8375 as the real machine did. The AGA preset resolves to Alice and Lisa: 8 bitplanes, the 256-entry 25-bit palette with BPLCON3 BANK/LOCT banking, HAM8, FMODE wide bitplane and sprite fetch (DMA and manual sprites), SSCAN2/BSCAN2 scan doubling, BPLCON4, and CLXCON2 (remaining gaps, such as true 35 ns SuperHires sprite output, are recorded in ).
[display]
overscan = "tv" # "tv" (default) or "full"
pixel_aspect = "tv" # "tv" (default, 4:3 CRT) or "square" (exact 2x2 lo-res)
scaling = "smooth" # "smooth" (default, aspect fit) or "integer" (whole multiples)
deinterlace = true # motion-adaptive interlace weaving (default true)
phosphor = 0.0 # CRT persistence fraction, 0.0 (off) to 0.95
shader = "none" # "none" (default), "scanlines", "mask", "crt", or a .wgsl file
shader_strength = 1.0 # how strongly the shader is mixed in, 0.0-1.0
tint = "none" # "none" (default), "bw", "green", "amber", or "sepia"
menu_scale = "1x" # size of the pop-up menu: "1x" (default) or "2x"
full_screen = false # open fullscreen at start (default false)
status_bar = true # show the status bar at start (default true)The emulated framebuffer always carries the full overscan field Denise
produces. "tv" presents what the monitor's glass shows: the captured
aperture -- the standard window plus the symmetric overscan margin the
framebuffer captures on its right edge -- fills the whole 4:3 glass, the
way a real set's raster overscans its screen, so the picture (border
colour included) reaches every edge with no black bezel columns. The
live window and PNG screenshots / --dump-frames present the same
716x540 glass; PAL and NTSC scans share the one shape because both
apertures fill the same glass -- an NTSC scan's shorter crop (the
200-line standard window plus the same overscan margin) is scaled onto
the same output rows. "full" shows everything, which
is useful when debugging display alignment. COPPERLINE_OVERSCAN=full|tv
overrides this for a single run. In both modes the presentation geometry
holds steady across the blank frames a screen change produces: a frame
showing only border colour keeps the previous frame's aperture and
centring instead of snapping to the full framebuffer, so the picture does
not jump sideways at Kickstart screen changes.
pixel_aspect selects how emulated scanlines map to host rows. The default
"tv" presents the field with the non-square pixel aspect of a 4:3 CRT:
the full overscan scan fills a 4:3 picture, so PAL lo-res pixels come out
slightly wider than tall, exactly as a real TV shows them (a 320x256 screen
spans about 640x482 window pixels). "square" uses one host row per woven
scanline instead, so every low-resolution pixel is an integer 2x2 square
and a 320x256 PAL screen occupies precisely 640x512 window pixels --
slightly taller than a real CRT picture, but exact for side-by-side pixel
comparison with square-pixel emulators. The menu's Pixel Aspect item
flips the mode live without touching the config, and
COPPERLINE_PIXEL_ASPECT=tv|square overrides it for a single run.
scaling selects how that presentation canvas reaches the window, which is
a separate question from what the canvas is. The default "smooth" fits the
canvas to the window preserving its aspect ratio and interpolates, so the
picture always uses the full window height (or width) whatever fraction the
scale works out to. "integer" instead draws the canvas at the largest
whole-number multiple of itself that fits the window, measured in physical
device pixels, centred in black borders and point-sampled: every canvas
pixel becomes the same square block of host pixels, with no row or column
sampled twice, which is the look WinUAE and Amiberry call integer scaling.
The fit is taken in whole canvas pixels against the physical surface --
the canvas is re-rendered at whatever factor fits, rather than drawn at
whole multiples of a fixed high-DPI texture -- so every step exists on
every display: a 2x-DPI laptop whose screen holds three physical pixels
per canvas pixel but not four gets the 3x picture, and fractional desktop
scales such as 150% take their whole physical multiples the same way. The
status bar and menus are rendered at the fitted factor too, so they stay
sharp at any step (the factor is capped at 4x; larger fits continue as
whole multiples of the 4x canvas). Only when the window is too small for
even a 1:1 copy -- smaller than the canvas itself in physical pixels --
does the picture fall back to the smooth fit rather than cropping to what
fits. RTG board modes follow the setting too: their frame is scaled
from its own native resolution, so a 640x480 board screen is drawn at 1x,
2x, 3x of those pixels inside the display area.
pixel_aspect = "square" with scaling = "integer" is the fully
pixel-exact combination: the square-pixel canvas is one host row per woven
scanline, so a whole-number window scale carries the emulated bitmap to the
screen untouched. Integer scaling of the default TV aspect is still crisp,
but crisp pixels of an already-resampled image -- that canvas fits the scan
onto 537 rows for the 4:3 shape before presentation. The monitor-bezel mode
(bezel) composes with either, but its picture opening is a fraction of
the window by design and is not itself integer-exact. The menu's Video
Settings > Scaling item switches modes live without touching the config;
there is no environment-variable override.
deinterlace controls how interlaced (LACE) displays are presented. On
(the default), a motion-adaptive deinterlacer weaves the two fields into a
full-height picture where the content is static and interpolates where it
moves, recovering the full vertical resolution without combing. Off, every
field is simply line-doubled as it arrives, which shows interlace bob and
flicker much as a TV without persistence would. COPPERLINE_DEINTERLACE=0
overrides the config for a single run.
phosphor blends each presented frame with a fraction of the previous
one, approximating the exponential decay of CRT phosphor. Software that
relies on the tube to fuse field-rate flicker -- alternate-field dither
transparency or flicker-dithered animation -- reads as intended with values
around 0.3-0.5,
at the cost of a slight motion trail. Off by default so screenshots and
frame dumps stay frame-exact. COPPERLINE_PHOSPHOR=0.4 overrides the
config for a single run.
shader runs a GPU shader pass over the window's picture, for the tube
look a phosphor trail on its own cannot give. Three presets are built in:
"scanlines"-- the line structure a 15 kHz set leaves between beam passes: a raised-cosine gap at the pitch of the emulated lines, with the brightness the gaps cost compensated back so the picture dims only slightly rather than by half."mask"-- a shadow mask. The picture is modulated through staggered RGB phosphor triads keyed to physical window pixels, so the mask keeps its size whatever the Amiga resolution behind it, again brightness-compensated."crt"-- the lot, in the spirit of the 1084 the Amiga shipped with: a bowed tube face, scanlines that follow the bow, an aperture grille, and a corner vignette, all faded in together. The face geometry is taken from the datasheet of the 1084's picture tube (the Philips M34EAQ10X): the bow reproduces its published screen-edge arcs -- the top and bottom edges bow about twice as far as the sides, as they do on the real screen -- and the corners are rounded at the scale of its 11.6 mm corner arcs. The picture overscans the face like the real raster overscans the glass, filling it to the edges with the bow deepening the crop toward the rounded corners, and on-face black is lifted by a faint glass glow, the room light a real tube reflects, so the face keeps its silhouette even when the picture is dark.
"none" (the default; "off" is accepted for the same thing) presents the
picture untouched, and any value ending in .wgsl is the path of a shader
of your own -- see Custom WGSL shaders below.
The scanline gaps are drawn at the pitch of the emulated field lines the
window is actually showing: 270 in the default TV-overscan presentation
(214 on an NTSC scan) and 285 in "full", so the line structure follows
the picture rather than the window size. TV overscan with pixel_aspect = "square" is 285 as well --
that canvas is taller than the TV aperture and pads it with bezel rows, so
the same 270 lines are rescaled to keep their pitch across the whole
window. Interlaced content is deliberately drawn at field-line pitch
over the woven frame, which is what a 15 kHz set fed an interlaced signal
looks like, rather than one gap per woven row.
shader_strength (0.0 to 1.0, default 1.0) is how strongly the effect is
mixed in, so a preset can be dialled back without editing shaders. At 0.0
the shader arithmetic is an exact no-op, but the pass still resamples the
picture through a plain bilinear sampler, which is a shade softer than the
texel-snapped pass-through the window otherwise uses at magnification.
"none" skips the pass altogether and is the only truly zero-cost setting.
The menu's CRT Shader item cycles the presets live for the rest of the
session without touching the config file; the launcher's A/V & Emu tab
(Video category) has CRT shader and Shader strength rows that do
write it.
COPPERLINE_SHADER=crt|scanlines|mask|none|PATH.wgsl and
COPPERLINE_SHADER_STRENGTH=0.0..1.0 override the config for a single run.
There is no command-line flag.
The pass is presentation and nothing else. Screenshots
(--screenshot-after), frame dumps (--dump-frames), video recordings, the
control protocol's capture methods and the web
frontend all read the CPU presentation buffer, which the shader never
touches, so captures stay comparable whatever is selected here. Individual
frames also skip the pass in three cases: while a menu or overlay panel is
open (a phosphor mask and a curved face make overlay text unreadable), for
frames coming from an RTG board's scanout (see [rtg] below), and for
programmable multisync scan modes -- a 31 kHz scanout has no 15 kHz line
structure to reproduce.
bezel (default false) frames the window's picture with a monitor-style
front bezel, also in the spirit of the 1084: the picture scales down into
the rounded opening of a procedurally drawn plastic face -- warm-grey
moulding, a dark recess around the tube face, rounded case corners, and the
wider bottom band carrying the power LED and a printed Copperline logotype
in the spot the 1084 kept its Commodore badge. The frame is drawn at the window's
resolution, so it stays sharp at any size, and the picture keeps its aspect
inside the opening. It is independent of shader and composes with any
preset: with "crt" the bowed tube face sits inside the opening for the
full monitor look. Cmd+M (macOS) / Alt+M toggles it live for the rest of
the session without touching the config; the launcher's Monitor bezel row
(A/V & Emu, Video) writes it, and COPPERLINE_BEZEL=1|0 overrides the
config for a single run.
Like the shader pass, the bezel is presentation and nothing else: captures never include it, and it is skipped while a menu or overlay panel is open and for RTG scanout frames. Unlike the shader it does stay on for programmable multisync scans -- a frame has no line structure to get wrong.
perf_overlay (default false) shows the performance overlay at start: a
live readout of emulated fps, speed factor, per-frame emulation cost, host
utilisation, audio health, and pacer slips in the top-right corner of the
display, one line per data point (see
the window chapter for what each line means).
Cmd+P (macOS) / Alt+P toggles it live for the rest of the session without
touching the config, --perf-overlay shows it for one run, and
COPPERLINE_PERF_OVERLAY=1|0 overrides the config for a single run; the
launcher's Perf overlay row (A/V & Emu, Video) writes it. Like the
transient message overlay it is presentation only: screenshots, frame
dumps, and recordings never include it.
tint recolours the picture like the phosphor of a monochrome monitor:
"bw" (black and white), "green" and "amber" (the two classic
monochrome phosphors), or "sepia"; "none" (the default; "off" is
accepted) presents full colour. The same five looks the web frontend's
Screen selector offers, produced by the same colour chain, so a tint
chosen in the browser matches the desktop. It composes with shader --
green phosphor under the crt preset makes a convincing monochrome tube.
Like the shader, the tint is presentation only: screenshots, frame dumps,
recordings and headless runs stay untinted, the status bar and overlay
menus keep their colours, and RTG board scanout (the monitor on the
board's own output, not the Amiga's video output) is never tinted. The
menu's Screen Tint item cycles the tints live for the rest of the
session without touching the config file; the launcher's A/V & Emu tab
(Video category) has a Screen tint row that does write it.
COPPERLINE_TINT=bw|green|... overrides the config for a single run.
Pointing shader at a .wgsl file loads a fragment shader of your own into
the same pass. The quickest start is to copy one of the presets --
src/video/window/shaders/scanlines.wgsl, mask.wgsl or crt.wgsl in the
source tree -- and edit its fs_main: everything above the
--- end shared contract --- marker in those files is the contract, and is
byte-identical in all three.
A shader must declare exactly these bindings and both entry points:
struct CrtUniforms {
// Display sub-rect of src_tex in UV space: xy origin, zw size.
src_rect: vec4<f32>,
// xy: viewport size in physical pixels. zw: source display texels.
size: vec4<f32>,
// x: strength, 0 (no-op) to 1 (full). y: scanline count across the
// display height. zw: preset-internal, do not rely on them.
params: vec4<f32>,
// Preset-internal and reserved; zero for a custom shader.
params2: vec4<f32>,
};
@group(0) @binding(0) var src_tex: texture_2d<f32>;
@group(0) @binding(1) var src_samp: sampler;
@group(0) @binding(2) var<uniform> u: CrtUniforms;
struct VOut {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
};
@vertex
fn vs_main(@builtin(vertex_index) idx: u32) -> VOut {
// Fullscreen triangle; the viewport restricts it to the display rect.
let tc = vec2<f32>(f32((idx << 1u) & 2u), f32(idx & 2u));
var out: VOut;
out.uv = tc;
out.pos = vec4<f32>(tc * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0), 0.0, 1.0);
return out;
}
// Sample the display region only, clamped half a texel inside src_rect so
// a linear tap on the bottom edge never blends in the status bar's first
// row underneath it.
fn sample_display(uv: vec2<f32>) -> vec4<f32> {
let half_texel = 0.5 * u.src_rect.zw / max(u.size.zw, vec2<f32>(1.0));
let lo = u.src_rect.xy + half_texel;
let hi = u.src_rect.xy + u.src_rect.zw - half_texel;
let tc = clamp(u.src_rect.xy + uv * u.src_rect.zw, lo, hi);
return textureSample(src_tex, src_samp, tc);
}
@fragment
fn fs_main(in: VOut) -> @location(0) vec4<f32> {
let uv = clamp(in.uv, vec2<f32>(0.0), vec2<f32>(1.0));
let base = sample_display(uv);
let strength = clamp(u.params.x, 0.0, 1.0);
// Your own look goes here. This one is a green monochrome monitor with
// a gap between beam passes at the emulated line pitch.
let lines = max(u.params.y, 1.0);
let profile = 0.5 - 0.5 * cos(6.283185307 * uv.y * lines);
let luma = dot(base.rgb, vec3<f32>(0.299, 0.587, 0.114));
let tube = vec3<f32>(0.2, 1.0, 0.35) * luma * (0.6 + 0.4 * profile);
// Mixing back toward the untouched sample keeps strength 0 a no-op.
return vec4<f32>(mix(base.rgb, tube, strength), 1.0);
}Points worth knowing:
- All three bindings are fragment-visibility only.
vs_maincannot read them, so all the work happens infs_main. - Sampling goes through
src_rect. The pass draws over the display rectangle of a texture that also carries the status bar below it, and the half-texel inset is what keeps the status bar's separator hairline out of the bottom of a magnified picture. - Of the uniforms, a custom shader can count on
src_rect,size,params.x(strength) andparams.y(scanline count).params.z,params.wandparams2carry the built-in presets' own look parameters, are zero for a custom shader, and are reserved for future use. - Making strength
0.0a visual no-op is a convention, not something the loader enforces, but the Shader strength control andCOPPERLINE_SHADER_STRENGTHare only useful if you honour it.
The file is read and checked when the window is created, when the launcher starts a machine, and every time Video Settings > CRT Shader > Custom is chosen -- which re-reads it from disk. That is the live-reload story: leave the emulator running, edit the shader, then pick Custom again to see the new version.
Checking is a parse, a full validation, and a look for the two entry points,
all before any GPU pipeline is built, so a mistake is reported as WGSL with
its line and column rather than as a driver error. Files over 1 MiB are
refused unread, on the grounds that a shader that big is a mistyped path.
Whatever goes wrong -- a missing file, a syntax error, a missing fs_main --
the full diagnostic goes to the log, a one-line summary appears in the
window's on-screen message, and the shader falls back to off. A bad custom
shader never fails the config, and never stops the machine from running.
menu_scale draws the pop-up menu at "1x" (the default) or "2x" -- the
whole menu, rows and text together. It is a start-up preference:
Video Settings > Menu Size changes it live without altering the saved
value, --menu-scale sets it on the command line, and the launcher's A/V &
Emu page (Video category) has a Menu size picker for the same.
full_screen opens the window fullscreen at start (borderless), and
status_bar chooses whether the status bar starts visible. Both are start-up
preferences; the runtime toggles -- Cmd+F / Alt+F for fullscreen and
Cmd+Shift+F / Alt+Shift+F for the status bar, plus their menu items --
still flip either live without changing the saved value. On the command line
--full-screen / --windowed set the fullscreen state and --show-status-bar /
--hide-status-bar set the status bar; the launcher's A/V & Emu page (Video
category) has Start fullscreen and Status bar toggles for the same. Left
unset they keep the defaults: windowed, status bar shown.
Rendering completed frames uses a worker thread by default so emulation can
advance while the previous frame is painted. The worker is an implementation
detail of presentation: screenshots, frame dumps, and recordings wait for
the exact frame they save. COPPERLINE_THREADED_RENDER=0 forces the old
synchronous render path for comparison.
[audio]
floppy_sounds = true # synthesized drive sounds (not sampled)
floppy_sounds_volume = 100 # 0-100, relative to Paula's output
# output_device = "..." # host output device (substring); omit = system default
# output_enabled = true # false = no sound (GUI "Disabled"); --audio/--noaudio still win
channel_mode = "stereo" # "stereo" (default) or "mono"
stereo_separation = 100 # 0-100; 100 = hardware panning, 0 = mono
audio_filter = "auto" # Paula filter: "auto" (guest-driven), "on", or "off"The drive sounds are generated from scratch: motor hum with spin-up/down over a rumble that repeats with each platter revolution, and head-step clacks (an isolated step -- the empty-drive poll, or the track-to-track advance while loading -- lands with its rebound clatter, and fast multi-track seeks blur into the characteristic buzz). Reading adds no noise of its own; the loading sound is the step rhythm over the spinning motor, as on the real mechanism. The synthesis targets were measured from recordings of real Amiga drive mechanisms, but no sample data is used. Only step pulses that actually fire the stepper are audible: like a real 3.5" mechanism, an outward pulse with the head at track 0 is gated by the /TRK0 sensor, so NoClick-style patches silence the empty-drive poll just as they do on real hardware.
output_device picks the host output by a case-insensitive substring of the
names --list-audio-devices prints (--audio-device overrides it); an omitted
or unmatched name uses the system default. channel_mode = "mono" averages the
left and right output into both channels, and stereo_separation narrows the
Amiga's hardware left/right panning between full (100) and mono (0) -- so it is
ignored when channel_mode is mono. output_enabled = false runs with no sound
at all (the launcher and runtime-menu "Disabled" option); the --audio and
--noaudio CLI flags still override it. These are host-output settings that do
not change the emulated audio and are not stored in save states. The equivalent
CLI flags are --audio-device, --audio-channel-mode, --audio-stereo-separation
and --list-audio-devices.
audio_filter controls Paula's analogue low-pass filter, the one a
post-A1000 Amiga switches with the same CIA-A line that drives the power
LED. "auto" (the default) lets the guest engage or bypass it as the
software asks, matching real hardware; "on" and "off" force it either
way as a listener override. Unlike the host-output settings above it is
part of the emulated audio path, so it also affects WAV capture. Also on
--audio-filter, Audio Settings > Audio Filter in the menu, and
Cmd/Alt+A.
The status-bar PWR LED is lit whenever the machine is powered and follows
the guest's /LED line itself -- full brightness while engaged, dimmed like
an A500 rev 6+ board while released -- so this override changes what you
hear, never the LED.
On Linux with PipeWire/PulseAudio, individual sinks are not ALSA devices, so
only the default/pipewire route is offered; pick the output in the desktop
sound settings (or route Copperline in pavucontrol) and it follows. macOS and
Windows select each device directly.
[input]
port1 = "mouse" # mouse | joystick | cd32 | analogue | none
port2 = "joystick" # same values; default "cd32" on the CD32 profile
joystick = "gamepad" # "gamepad" (default) or "keyboard"
mouse_sensitivity = 50 # host mouse speed 0-100 (50 default = 1:1)
mouse_capture = "click" # when to grab the mouse: click | auto | manual
autofire_hz = 0 # pulse a held fire button at this rate; 0 = offport1 and port2 name the controller device plugged into each game port.
Either port accepts any device, exactly as on real hardware:
mouse-- a quadrature mouse. Its three buttons are the left (/FIRx), right (POTxY), and middle (POTxX) lines.joystick-- a digital switch joystick with a fire button and a second button on thePOTxYline.cd32-- a CD32 joypad: a digital joystick plus the serial button protocol lowlevel.library reads (Red/Blue ride the fire/button-2 lines; Green, Yellow, Play, Rewind and Forward exist only serially).analogue-- analogue paddles or a proportional stick presenting resistances on thePOTxX/POTxYpins, with the two paddle buttons on the left/right direction lines. No live host device maps to it yet: drive it with--pot-afterscripting or the control protocol'sinput.analoguemethod (positions default to centre).none-- an empty port.
The defaults are today's stock wiring: a mouse in port 1 and a joystick in
port 2 -- a CD32 pad on the CD32 profile, whose bundled controller the
machine expects (an explicit key beats the profile; a real CD32 accepts any
controller too). --port1 / --port2 override for one run, the runtime
menu's Port 1/2 Device items hot-plug a device live, and the control
protocol's input.set_port does the same from a script.
Putting joysticks in both ports is a real two-player setup: the host gamepad and the keyboard mapping then drive one port each (see below).
joystick selects the initial host source for the joystick/CD32-pad port.
There are two explicit modes, so the active source is always visible rather
than depending on whether a pad happens to be connected:
gamepad(the default) -- only a physical pad drives the joystick port. The keyboard is left to the Amiga, so it passes straight through to a Shell, an editor, or Workbench, and no keys are unexpectedly captured as joystick input. With no pad connected there is simply no joystick input.keyboard-- use the keyboard-joystick mapping (cursor keys plus the fire keys), so the port stays usable without a controller.
With one joystick/CD32-pad port the mode picks its source. With two, both
sources are in play -- the gamepad and the cursor-key mapping drive one
port each -- and the mode picks which source gets the lower-numbered port;
whenever no physical pad is present, a second keyboard mapping on the
numeric keypad (8/2/4/6 directions, 0 fire, . second button)
stands in for the gamepad, so two players can share one keyboard.
The keyboard mapping drives whatever device its port carries. In
particular, with mice in both ports the host mouse takes the
lower-numbered one and, in keyboard mode, the cursor-key mapping drives
the second as an emulated mouse: cursor keys move the pointer, the fire
keys are the left button, X the right, D the middle.
This only sets the starting mode. The status-bar toggle (the gamepad /
keyboard icon next to the volume control), Cmd+J / Alt+J, the menu's
Joystick Input item, and the launcher's Input tab all flip it live
without changing the config. --joystick MODE overrides this for a single
run. (auto is still accepted here as a backward-compatibility alias for
gamepad; the old auto-detect mode has been removed.)
mouse_sensitivity scales how fast the emulated pointer tracks the host
mouse, 0-100. 50 (the default, shown as Default in the GUI) is 1:1 --
exactly the previous behaviour -- 0 is a quarter speed and 100 quadruple,
on an exponential scale so each step is an even ratio. It is a host-input
scale applied to live mouse motion only: it never touches the emulated machine
or scripted --mouse-after input, so headless and recorded runs stay
deterministic. Set it from the launcher's Input tab or
--mouse-sensitivity N, and adjust it live with Cmd+Shift+> / Cmd+Shift+<
(Alt+Shift+> / Alt+Shift+< on Linux and Windows), which ramp while held.
Capturing the mouse confines the host pointer to the window and hides the
host cursor, so the Amiga pointer is the only one on screen. mouse_capture
decides when that grab is taken:
click(the default) -- clicking the display grabs it. That click is a window action and is not passed to the Amiga, so the first click the guest sees is the first one aimed at it.auto-- grab as soon as the window has the focus, and again whenever it regains it, so no host cursor is ever loose over the display. Entering fullscreen grabs too. This suits a mouse-driven game or a fullscreen session where the host desktop is not wanted.manual-- only the shortcut grabs. Clicks on the display go straight to the Amiga and the host cursor is left alone.
Cmd+G / Alt+G releases and re-takes the grab by hand in every mode, and
an explicit release is never undone automatically. Opening a panel or tool
window borrows the cursor and hands the capture back when the last one
closes.
Uncaptured, host cursor motion over the display still drives the emulated
mouse in every mode; this setting only decides when the grab is taken, not
whether motion reaches the machine. Set it from the launcher's Input tab
or --mouse-capture MODE.
autofire_hz turns a held fire button into a pulse train at that many
presses per second; 0 (the default) leaves the button alone. It applies to
live input only -- the gamepad and the keyboard mapping -- and never to
scripted input (--joy-after, --script, the control protocol's
input.joy), which must replay exactly the events it was given.
The phase comes from emulated time, so the rate is the same under warp and
on PAL or NTSC. Nothing about the emulated machine changes: the port sees an
ordinary button being pressed and released on /FIRx. Only the fire button
is pulsed; directions and the second button pass through untouched.
--autofire HZ sets it for one run, and the menu's Autofire item cycles
off / 3 / 5 / 8 / 12 / 16 Hz live. The maximum is 30 Hz -- above that the
assert window is shorter than the frame the guest samples the port on.
The keyboard-to-controller bindings are a host preference, not part of the
emulated machine, so they live beside the gamepad calibration rather than in
a machine config: the menu's Input Mapping... item edits them and Save
writes keymap.toml next to gamepads.toml (see
Gamepad calibration for the per-platform
location). Any control may take several keys -- fire ships with four aliases
so compact keyboards without the right-hand modifiers still work -- and
binding a key removes it from wherever it was before, including from the
other mapping, so the two controllers can never fight over one key. Deleting
the file restores the built-in layouts, as does the panel's Defaults
button.
[serial]
mode = "stdout" # off, stdout, midi, tcp, tcp-connect, or pty
# midi_out = "FluidSynth" # midi mode: host destination, substring match
# midi_in = "Keystation" # midi mode: host source, substring match
# listen = "127.0.0.1:1234" # tcp mode: bind address
# connect = "bbs.example.com:1337" # tcp-connect mode: remote to dialThe Amiga serial port doubles as the MIDI port. mode selects where
Paula's serial in/out is connected:
stdout(the default) -- serial output prints to the host terminal, matching the historical behaviour (DiagROM and similar tools log here).off-- serial output is discarded and there is no serial input.midi-- serial in/out is bridged to host MIDI endpoints. Needs a build with themidifeature (the default);midi_out/midi_inname the endpoints by case-insensitive substring (a USB interface or a virtual port).--list-midiprints the host endpoints.tcp-- serial in/out is bridged to a host TCP port, like UAE'sTCP:device.listensets the bind address (default127.0.0.1:1234); connect with e.g.nc,socat, or a raw-mode telnet client.tcp-connect-- the outbound counterpart oftcp: at startup the serial port dials the remote named byconnect(required,host:port) and the session talks to that service. Point a guest terminal program at a telnet BBS, atcpsermodem bridge, or any TCP byte service. The connection is made once; if the remote hangs up, output drops like an unplugged cable until the next run. Note that the wire carries raw bytes: for telnet servers that insist on option negotiation, put a telnet-aware relay in between, or pick a BBS/port that accepts raw connections (most do).pty-- serial in/out is bridged to a host pseudo-terminal (Unix only). The slave path (/dev/pts/N) is logged at startup; attach a terminal with e.g.minicom -D,screen, orcu -l.
With an AUX: shell on the Amiga side, tcp/pty give a remote AmigaDOS
console. --serial MODE overrides the mode per run,
--serial-connect HOST:PORT sets the dial-out target (and implies
mode = "tcp-connect"), and --midi-out NAME/--midi-in NAME imply
mode = "midi". The launcher's I/O Ports tab (Serial section) and the
in-window MIDI In / MIDI Out menu items select the MIDI endpoints
interactively.
The browser build has its own serial transport (the page bridges the port to a WebSocket); see the browser chapter.
[parallel]
device = "printer" # none | printer | sampler
output = "printer.raw" # printer capture path
# device = "sampler"
# sampler_input = "MacBook Air Microphone" # host input; omit for the default
# sampler_gain = 6.0 # preamp gain in dB (0 = unity)device chooses the peripheral on the parallel port (one at a time). Without
this section the connector is electrically disconnected: CIA-A still produces
its hardware PC strobe on port-B accesses ($BFE101), but no peripheral
acknowledges it and port-B reads see the CIA's own pins. The equivalent per-run
flags are --parallel DEVICE, --sampler-audio-input NAME, and
--sampler-input-gain X; --sampler-list-audio-inputs prints the input-device
names and exits.
"printer" attaches a raw Centronics sink at output (a bare output with no
device still selects the printer, for compatibility). The file is created at
startup, replacing any existing file; each strobed byte is written verbatim and
returns the printer /ACK falling edge through CIA-A FLAG, including the
normal CIA interrupt delay. The printer also drives the Centronics status
lines on CIA-B port A -- SEL high, BUSY and POUT low -- so the guest's
parallel.device sees a ready online printer and starts sending. (Without an
attached device those lines float high, and printing waits forever for a
printer to appear, as on a real machine.) It is intentionally not decoded, since the guest may
emit any printer language; pass it to a converter or spooler afterwards.
"sampler" attaches an 8-bit audio sampler (digitizer) on the data lines -- the
emulated equivalent of a classic parallel-port sampler cartridge, driving
software such as AudioMaster, ProTracker, OctaMED, and TurboSound. It captures
from a host input device (cpal, like live audio output, so it needs a build with
the frontend feature) and presents each read of the data lines as an 8-bit
offset-binary sample in emulated time, mono (host left/right are summed).
sampler_input names the host device (case-insensitive substring, as
--sampler-list-audio-inputs prints; omitted uses the system default);
sampler_gain is the preamp gain in decibels (0 dB = unity) applied before the
ADC, clamped to the sampler's range (roughly -24 to +24 dB). The input device
and gain can also be changed live
from the runtime menu, and the gain with Cmd/Alt+Shift +/-. On macOS the CLI
binary needs microphone permission to capture a real input; routing audio in
through a loopback device such as BlackHole needs none.
[floppy]
drives = 2 # DF0 and DF1 connected; default is DF0 only
speed = 100 # 100/200/400/800 percent, or 0 for turbo
[floppy.df0]
path = "demo.adf" # single image, or:
# paths = ["disk1.adf", "disk2.adf"] # swap playlist (shortcut cycles)
write_protected = true # default true
# enabled = true # implied by path/pathsdrives controls how many mechanisms are wired, from one to four. DF0 is
the internal drive; DF1-DF3 are external drives that answer the standard
Amiga external-drive ID protocol when connected. A configured disk image
also connects that drive automatically, so existing configs that name
[floppy.df1] .. [floppy.df3] keep working.
speed accelerates the emulated drives beyond the authentic data rate.
100 (the default) is real speed. 200, 400, and 800 clock the whole
data path -- platter rotation, the MFM read shifter, sync detection,
DSKBYTR, and DMA pacing -- at that multiple, so everything software can
observe stays bit-identical to real speed, only compressed in time. 0
selects turbo: a started disk DMA transfer completes almost instantly
(deferred by two scanlines, matching other emulators' turbo modes, so
loaders that clear stale interrupt flags right after starting a transfer
still see the completion). Drive mechanics are never accelerated: motor
spin-up, head stepping, and post-seek settle always run at real time.
Faster-than-real speeds are a compatibility trade-off, exactly as in other
emulators: the operating system and most loaders tolerate them, but
software that times its own loading against the beam, CIA timers, or music
playback can break. The setting can be changed live from the runtime menu
("Floppy Speed") without restarting the machine. It applies to image-backed
bays only; a physical drive has its own bridge_speed.
Supported image formats: standard 901120-byte DD ADF, gzip-compressed
images (ADZ), single file ZIP archives, DMS archives, UAE extended ADF, and
read-only IPF and SCP images. DMS, gzip, IPF, and SCP images are decoded at
load time and always treated as write-protected; set write_protected = false
on a plain ADF to allow write-through updates to the image file.
IPF (the SPS/CAPS preservation format) is decoded by Copperline itself rather
than through the closed-source capsimg library, so every build reads IPF on
every platform with nothing to install. Because an IPF preserves the encoded
track -- sync marks, gaps, and sector headers, not just sector contents -- it
carries the custom trackloaders and copy protections that an ADF cannot
express. Each track is decoded to the revolution of MFM the head would pass
over and read back through the same path as a flux capture. Two limits are
worth knowing: tracks recorded with a variable cell rate (the Copylock,
Speedlock, and Brierley density models) are decoded with uniform 2 us cells,
which is logged at load time and leaves a protection that measures cell timing
seeing the wrong answer; and weak ("flakey") bits are replayed as the single
deterministic revolution the file stores rather than varying per revolution.
The browser frontend shares this decoder, so it reads IPF too.
A paths playlist lets multi-disk software that only drives DF0: run
without a second drive: the first entry is the boot disk and the disk-swap
shortcut (Cmd+D on macOS, Alt+D on Linux/Windows) or the status-bar
swap button cycles to the next image, wrapping around.
A bay can be given a physical 3.5" drive instead of an image, over a DrawBridge, Greaseweazle, or Supercard Pro:
[floppy.df0]
bridge = "greaseweazle" # drawbridge/greaseweazle/supercardpro/off
write_protected = true # emulator-level protection, on top of the tab
# bridge_port = "/dev/ttyACM0" # omit to auto-detect the interface
# bridge_cable = "a" # a/b (IBM PC) or 0..3 (Shugart)
# bridge_density = "auto" # auto/dd/hd
# bridge_mode = "compatible" # compatible/stalling
# bridge_speed = 125 # 100, 125, 150, 175, or 200 percent of real speed
# bridge_auto_cache = false # read tracks ahead while the drive is idleA bay takes either a bridge or an image, never both: the disk in the drive
is its media, and naming a path alongside is an error. bridge = "off"
returns the bay to images and keeps the other bridge settings for later.
Nothing needs installing -- Rob Smith's FloppyBridge is built into Copperline
-- but it changes how the machine runs in several ways -- writes need both the disk's tab and
write_protected = false, the status bar's eject and swap do nothing for
that bay, and a machine with a physical drive is paced to wall-clock time and is
not reproducible. covers the whole feature: installing the
library on each platform, what each option does, and what to expect of it.
[machine]
profile = "A600" # IDE needs a machine with an IDE port
# (A600 or A1200 Gayle, or the A4000)
[ide]
master = "AmigaSYS.hdf" # raw flat HDF, read/write
# slave = "scratch.hdf"Images are opened read/write. Both kinds of HDF work directly:
- a full disk image with its own Rigid Disk Block (RDSK/PART chain), and
- a bare partition hardfile (boot block starts with
DOS\x..), which is wrapped in a synthesized RDB on the fly: one extra cylinder of 16-surface x 32-sector geometry holding an RDSK and a bootableDH0PART block, with the image's own dostype. The image must be a multiple of 256 KiB so the partition is an exact cylinder count. Writes to the partition go back to the image file; writes to the synthesized RDB area (re-partitioning) live only for the session.
A path may also name a host directory: its tree is built into an
in-memory FFS volume at startup (volume name = directory name, files and
subdirectories included; entries whose names cannot exist on an Amiga
volume are skipped with a warning). The guest sees an ordinary bootable
FFS disk and may write to it, but the volume lives only in memory --
nothing is written back to the host directory, and changes are lost at
exit. Note that the stock A1200/A600 Kickstart scsi.device only probes
the IDE master; a slave drive needs a guest OS or driver that supports
two units (e.g. Kickstart 3.1.4).
To override the volume name (instead of inheriting the directory name) or
the boot priority, give the drive as a table with path plus name
and/or bootpri:
[ide]
master = { path = "/host/Games", name = "Games" }
slave = { path = "wb.hdf", bootpri = 6 }
# slave = "scratch.hdf" # the bare-string form still worksThe name sets the FFS volume label of a directory mount; AmigaDOS volume
names hold up to 30 characters and cannot contain : or /. It has no
effect on a raw HDF, which carries its own label inside the image.
bootpri (-128..127, default 0) is the de_BootPri written into the
synthesized RDB's partition, which is what the ROM's strap ranks boot
candidates by. Kickstart enters DF0: at priority 5, so the default 0 loses
the tie to a bootable floppy; raise it to 6 to boot the hard disk ahead of
one, or lower it to sort two hardfiles against each other. The sentinel
-128 also clears the partition's PBFB_BOOTABLE flag, so the volume
mounts but is never offered for boot. It has no effect on an image that
carries its own RDB -- those priorities live inside the image, where
HDToolBox put them -- and Copperline logs a warning if you set it on one.
The configuration screen edits bootpri on the Storage tab's Boot
Priority sub-page, one row per drive (see ): a Priority number and a
Bootable box, the cleared box being this -128 sentinel. A drive left at 0 with
no cascade default writes no bootpri key.
The drive responds to ATA IDENTIFY with the Gayle byte order real hardware
uses, so both Kickstart 3.1 variants boot from it. An HDD activity LED
appears in the status bar on IDE machines. On the A4000 profile the same
[ide] section attaches drives to the motherboard IDE interface at
$DD2020 (no Gayle involved; Kickstart's scsi.device drives it the same
way).
CD images (.cue/.iso/.chd) are rejected here: the emulated IDE port
speaks plain ATA, not ATAPI. Attach CD-ROM drives as [scsi] units instead
(see below).
[scsi]
# controller = "a2091" # a2091 (default), a4091, or a3000
rom = "a2091-v6.6.rom" # boot ROM image (a2091/a4091; the a3000 needs none)
# rom_odd = "a2091-odd.rom" # a2091 only: split even/odd EPROM dumps
unit0 = "workbench.hdf" # SCSI IDs 0-6
unit1 = "data.hdf"
unit2 = "game.cue" # a .cue, .iso, or .chd attaches a CD-ROM drive
# unit3..unit6 = ...The [scsi] section attaches a SCSI host adapter with up to seven
drives. controller picks which one:
"a2091"(the default on machines without onboard SCSI): a Commodore A2091 (Commodore DMAC + WD33C93A) as a Zorro II autoconfig board. It works on any machine model (the board needs no Gayle) and has no dependence on the Kickstart IDE driver -- the board's own boot ROM carriesscsi.deviceand autoboots on Kickstart 1.3 and newer, which also sidesteps the stock A600/A1200scsi.deviceonly probing the IDE master.[ide]remains available, and both can be used at once."a4091": a Commodore A4091 (NCR 53C710 SCSI-2) as a Zorro III autoconfig board, for machines with a 32-bit CPU. It needs a raw A4091 EPROM image (e.g. the open-sourcea4091.rom) asrom; it has a single ROM, sorom_odddoes not apply."a3000"(the default on theA3000profile): the A3000's motherboard SCSI -- the Super DMAC at$DD0000driving a WD33C93. It is silicon, not a card, so it needs no boot ROM: Kickstart's ownscsi.devicedrives it and autoboots from an RDB drive. It is only valid on a machine with the Super DMAC (the A3000).
For the A2091, rom must point at an A590/A2091 boot ROM image (version
6.6 or later; 16K/32K, available from the same vendors and dump sets as
Kickstart ROMs). Dumps split into even/odd EPROM halves can be given as
rom (even, U13) plus rom_odd (odd, U12). The ROM is required on the
Zorro boards because the autoboot DiagArea and the scsi.device driver
itself live in it; the autoconfig identity comes from the board (the
A2091 is Commodore product 3, with its DiagArea vector at $2000).
Each unitN accepts everything [ide] paths do: RDB images, bare
partition hardfiles (a synthesized RDB advertises a bootable DHn
partition, named after the SCSI ID), and host directories built into
in-memory FFS volumes -- including the
{ path = "...", name = "...", bootpri = N } table form that overrides a
directory mount's volume name and the synthesized partition's boot
priority. The HDD activity LED covers SCSI traffic too.
A unitN path ending in .cue, .iso, or .chd attaches a SCSI
CD-ROM drive at that ID instead of a hard disk: a read-only removable
SCSI-2 target (INQUIRY device type 5) serving 2048-byte blocks, with the
full READ TOC / READ CD / mode-page surface CD filesystems expect.
Cue/bin and CHD images may mix data and audio tracks; a bare .iso is a
single data track. The drive answers on the host adapter's scsi.device like any
other unit, so mount it the way you would on real hardware: a
DOSDrivers mount entry (or MountList) pointing CDFileSystem --
CacheCDFS, AsimCDFS, and AmiCDROM work the same way -- at the controller's
scsi.device and the drive's unit number.
CD audio plays: the PLAY AUDIO command group streams the disc's audio
tracks into the machine's audio output at 75 sectors per second of
emulated time (as if the drive's analogue output were cabled to the
machine), the sub-channel reports the live playback position, and the
debugger's Audio tab shows the stream on its CD-DA row with the play
state, track, and position. Discs swap at runtime like CDTV/CD32 media:
the status bar's CD load/eject buttons, dropping a .cue/.iso/.chd
on the window, the scheduled --insert-cd-after SECS PATH flag, or the
control protocol's media.cd.insert all eject the current disc, run the
tray for a second of emulated time, and mount the new one with a
medium-change unit attention for the guest's filesystem to notice.
[[filesys]]
path = "/data/amiga/Workbench"
volume = "Workbench" # optional, defaults to the directory name
bootpri = 6 # optional boot priority; default -128 = never boot
[[filesys]]
path = "/data/amiga/downloads"
readonly = true # optional, export the directory write-protectedEach [[filesys]] entry exports a host directory to the guest as an
AmigaDOS volume on its own HOSTFS<n>: device, served live by the
emulator: no disk image is built, and guest reads always see the current
host contents. This differs from giving [ide]/[scsi] a directory
path, which snapshots the tree into an in-memory FFS volume at startup.
Up to 8 mounts.
The volumes are read-write by default: the guest creates, writes, renames,
and deletes the host's files directly, and changes land in the directory
as you would expect. Set readonly = true to export a directory
write-protected instead -- the guest sees a read-only disk and every write
fails with the same "disk is write-protected" error a physical
write-protected disk gives, which is worth setting on anything you would
rather the Amiga could not damage. The launcher's Host Mounts sub-page (under
the Storage tab) exposes the same choice as its Access field.
Amiga file attributes a host filesystem cannot hold -- protection bits
such as script/pure/archive, file comments, and exact datestamps -- are
kept in UAE-style .uaem sidecar files, read when present and written
back when the guest changes them; the sidecars stay hidden from guest
listings, and the delete-protection bit is honoured. Host filenames are
mapped between UTF-8 and the guest's Latin-1 (names with no Latin-1
spelling are hidden, since the guest could neither display nor reopen
them). Host symlinks inside the mount are followed, wherever they point:
the guest has no way to create one, so a symlink is treated as the host
user deliberately grafting a directory into the mount, the same trust
model as the UAE family.
volume sets the AmigaDOS volume name (up to 30 characters, no : or
/). bootpri enters the volume in the boot-device vote (-128..127;
the default -128 means mounted but never booted from): hard-disk boot
partitions typically sit at priority 0 and DF0: at 5, so a bootable
Workbench directory with bootpri = 6 boots ahead of both.
Kickstart 1.3 and newer get the full feature set, booting included: a
bootpri above the competition boots the machine from the host
directory as SYS: under 1.3 exactly as under 3.1 (the service speaks
both the V36 boot-node protocol and V34's own autoboot and handler
startup conventions). Kickstart 1.2 and older lack the expansion-ROM
hook entirely and never see the mounts.
[machine]
profile = "CD32"
[cd]
image = "disc.cue" # BIN/CUE cue sheet (MODE1/2048, MODE1/2352, AUDIO)
insert_delay = 0.0 # emulated seconds after power-on to insert
# nvram = "cd32-nvram.bin" # CD32 save-game EEPROM backing file (default)image takes a BIN/CUE cue sheet, a bare .iso (single data track), or
a .chd -- MAME's compressed CHD CD format (v5, as chdman's createcd
writes: LZMA/Deflate/FLAC-compressed hunks with data and audio tracks).
The disc mounts on the machine's CD controller: Akiko on CD32, the DMAC on
CDTV. insert_delay inserts the disc some emulated seconds after power-on
with the proper media-change notification; some CDTV discs only boot when
inserted after the boot screen appears. CD32 NVRAM
persists to cd32-nvram.bin next to the working directory unless
overridden; without a path the EEPROM is session-only.
[[zorro]]
metadata = "boards/megaram.toml"
[[zorro]]
metadata = "boards/myboard.toml"
# config = { mode = "fast" } # WASM plugin boards: setting overridesEach entry adds a Zorro board described by a TOML metadata file, configured
in file order after the built-in [memory] fast/z3 boards. For a WASM
plugin board, the optional config table overrides individual settings
that the plugin's manifest declares (layered over the manifest's [config]
defaults; the launcher's Zorro tab edits the same values). See
for the metadata format, the plugin ABI, and how autoconfig
assigns addresses.
[a2065]
net = "nat" # or "bridge", "loopback"; "none" for an isolated NIC
# interface = "en0" # required for "bridge"Fits a Commodore A2065 Ethernet board (Am7990 LANCE) on the Zorro chain;
--a2065-net BACKEND is the matching per-run flag, and the launcher's
I/O Ports tab (Ethernet section) has the same picker. net selects the
host network backend:
"nat"-- userspace NAT: the guest gets outbound IPv4 internet through a virtual gateway with no host privileges or setup, identically on Linux, macOS, and Windows. Configure the guest's TCP/IP stack with IP10.0.2.15, netmask255.255.255.0, gateway10.0.2.2, DNS10.0.2.3(or let it BOOTP/DHCP). Outbound only, IPv4 only."bridge"-- attaches complete Ethernet frames directly tointerface, so the Amiga is a separate station on the physical LAN and can accept inbound connections from LAN peers. Usecopperline --list-net-interfacesfor exact identifiers, or--a2065-interface NAME(which implies the bridge backend). Configure the guest by DHCP from the real LAN or with an address appropriate to that LAN. The guest can reach peers and the router; communication with the host's own IP is adapter/OS-dependent and is not guaranteed. Frames keep the Amiga's source MAC, so Wi-Fi is best-effort: many access points reject a second source MAC behind one wireless station. Copperline reports adapter, driver, and permission failures at startup instead of falling back to NAT."loopback"-- echoes transmitted frames back (self-contained, useful for driver bring-up)."none"-- the NIC is fitted but isolated.
Omit the section entirely for no board. Note that host networking is inherently non-deterministic: inbound frames arrive on the host's schedule, not the emulated clock, so a NIC board breaks byte-identical replay and save-state determinism while traffic flows. A save state stores the bridge adapter identifier and must be restored on a host where that adapter can be opened. See for board details, platform bridge setup, and the NAT's limitations.
[hostsocket]
net = "nat" # or "bridge", "loopback"; "none" for a dead wire
# interface = "en0" # required for "bridge"
# dns_server = "10.0.2.3" # only used when resolver = "dns" (see below)
# hostname = "amiga" # gethostname() return value (cosmetic)
# address = "192.168.1.50/24" # interface address; only for "bridge" (see below)
# gateway = "192.168.1.1" # default gateway; only for "bridge" (see below)
# resolver = "dns" # gethostbyname() via dns_server directly instead
# # of the host's own resolver (the default under
# # "nat"/"bridge"); see belowFits the bundled HostSocket board: bsdsocket.library for the guest, backed
by a TCP/IP stack that runs on the host instead of inside the emulated CPU.
Any bsdsocket-consuming application (an Aminet tool, an MQTT client, a game
with an online mode) opens bsdsocket.library and calls
socket()/connect()/send()/recv() exactly as it would against AmiTCP or
Roadshow -- but there is no guest-side stack to install, configure, or boot:
the library autoboots from the board's ROM on Kickstart 1.3 through 3.x and
on the bundled AROS ROM. --hostsocket-net BACKEND is the matching per-run
flag, and the launcher's I/O Ports tab (Ethernet section) has the same
picker.
net selects the same host network backends as the A2065 (see above for
"nat"/"bridge" details and caveats), with one important difference in
what they mean here. The guest never configures an IP address -- the
host-side stack owns addressing -- so under "nat" sockets simply reach the
outside world, and under "loopback" the guest can talk to itself
(127.0.0.1) with fully deterministic behavior, which also makes
"loopback" the right backend for reproducible headless test runs of
socket-using software. gethostbyname() just works under "nat"/"bridge"
with no further configuration (see resolver below for why); under
"loopback" lookups return failure (nothing is listening, and nothing
should be -- see resolver's own note on why loopback never routes through
a real resolver).
address and gateway matter only under "bridge". The default interface
address/gateway (10.0.2.15/24 and 10.0.2.2) are Copperline NAT's own
virtual addresses, hardcoded on the NAT side too -- correct and required
under "nat"/"loopback", but meaningless on a real physical LAN, where
nothing answers ARP for a 10.0.2.2 gateway that doesn't exist there. Set
both to match the LAN interface is bridged to (e.g. address = "192.168.1.50/24", gateway = "192.168.1.1") -- there is no DHCP client,
so the address is always static, picked the same way you would pick one for
any other statically-configured device on that network. Leave both unset
for "nat" and "loopback".
resolver picks how gethostbyname() itself works, independent of the
addressing above. Left unset, it defaults to "host" under "nat"/
"bridge": Copperline's own process resolves the name via the host OS's
resolver on a background thread -- the same mechanism [a2065]'s NAT
backend already uses internally for its own DNS forwarding, now available
directly to this board -- which is why gethostbyname() just works out of
the box under both backends with no dns_server to hand-configure, on
"bridge" in particular where nothing else would know which resolver the
LAN actually offers. Set resolver = "dns" explicitly to opt back into
the board's own DNS query: its own smoltcp stack sends a real DNS request
to dns_server over whichever backend is fitted, which is the one way to
target a specific resolver rather than whatever the host happens to be
configured to use (a corporate/internal-only DNS server on a bridged LAN,
for instance). Explicit resolver = "host" is rejected under "loopback"/
"none" (there is no sane default there either, so those backends simply
get no resolver at all): routing through a real host resolver would
silently defeat loopback's whole reason for existing, byte-identical
deterministic replay. Reverse lookups (gethostbyaddr()) are unaffected by
this setting -- they always use the "dns" path's PTR query against
dns_server.
Do not fit this board and also boot a real guest TCP/IP stack (AmiTCP,
Roadshow, ...) in the same session -- both would add a bsdsocket.library,
and which one an application opens is undefined. Use [a2065] for testing
real stacks and SANA-II drivers; use [hostsocket] for running or testing
the applications above them. The determinism note on [a2065] applies here
too: "nat" and "bridge" traffic arrives on the host's schedule and
breaks byte-identical replay, while "loopback" and "none" stay
deterministic. The board state (open sockets included) rides in save
states, but live TCP peers do not survive a restore on the host side.
Implemented as a bundled WASM plugin board (see ); its
verification record against the external bsdsocktest conformance suite
lives in crates/hostsocket-plugin/docs/bsdsocktest-status.md.
[rtg]
card = "picasso2"
vram = "2M"card is "picasso2", "picasso2plus", "z3660", or "none"; a machine
takes at most one. All three boards give the guest high-resolution,
high-colour screens through Picasso96.
"picasso2" fits a Village Tronic Picasso II with a CL-GD5426 graphics
controller. "picasso2plus" fits the later CL-GD5428 revision, reports its
distinct autoconfig serial number, and wires vertical blank to INT2. Both are
Zorro II boards, so they work with 68000/68010 and 24-bit 68EC020 machines as
well as 32-bit CPUs. vram selects either real board's "1M" or "2M"
memory configuration and defaults to "2M"; it is ignored for other cards.
Install the Picasso96 PicassoII.card driver and its monitor file in the
guest. The board starts on native Amiga pass-through and switches the
Copperline display to RTG only while the guest enables a valid Picasso screen.
"z3660" is a Zorro III board. It comes fitted by default on machines whose
CPU has a 32-bit address bus (the A3000 and A4000) and is unavailable on the
rest; asking for it there is an error, as it is for Zorro III RAM. It needs the
open-source Z3660.card driver installed in the guest (with its monitor in
DEVS:Monitors). With that in place, Z3660 screen modes appear in ScreenMode,
and the window shows the board's output when a screen is opened.
The Z3660 board's stock monitor ships with the DISPLAYCHAIN=NO tooltype, which
models the real hardware's separate RTG monitor and never hands the display
back to the native screen. On a single-window emulator you usually want
DISPLAYCHAIN=YES, so the one window follows whichever screen is active.
[debug]
log_unmapped = "DD0000-DEFFFF"
validate_chipset = true
detect_smc = truelog_unmapped logs every CPU read and write inside the given range that no
device decodes. Reads report the floating bus value they returned, writes
report the value that went nowhere. The value is a hex START-END range whose
end is included (a leading 0x is allowed), or all for the whole address
space.
This is how you find the registers a guest expects and Copperline does not
implement yet. A missing register is usually invisible: a read floats, a write
is dropped, and the guest either sulks or hangs with no diagnostic. Pointing
this at the window a driver probes shows the access pattern directly -- an IDE
presence probe, say, appears as a write of $A0 to the device/head register
followed by a long run of status reads that never come back ready.
A booting Kickstart probes enough empty address space that all produces on
the order of a million lines per boot, so prefer a range once you know roughly
where to look.
validate_chipset arms the custom-register access validator: a running
report of software using the chipset in ways the hardware quietly ignores.
It flags writes to registers the fitted Agnus/Denise does not have, bits a
register does not define, writes to read-only registers and reads of
write-only ones, byte or odd-address access to word registers, access
through an address mirror, and DMA pointers aimed past the chip RAM Agnus
can address. It also covers the engines behind those registers, where
misuse hangs rather than glitches: a blit started while the previous one
is still running (there is no register-file interlock, so the running
blit is drained and the replacement starts from whatever pointer state it
left) or with its DMA switched off (BBUSY is set and the blit stays
pending until BLTEN and DMAEN are enabled), disk DMA armed against a
drive that could not serve it at that moment -- no media, or the motor
still off, the class behind the classic loader dead-spins -- and a
keyboard handshake pulse too narrow to count as one while the MCU was
waiting for it, which costs a key and stalls input until the keyboard
resynchronises after 143 ms. Each finding names the PC (or Copper address) that made the
access and the beam position, is deduplicated by (kind, register, writer)
with a repeat count, and is logged the first time it is seen. It also arms
a per-register last-writer table, which answers "what set BPLCON3, and
from where?" without a bisect. Read both over the control protocol with
chipset.report and custom.writer (see
the control protocol), which can also arm and
disarm the validator live. Off by default; an unarmed machine pays nothing
for it.
detect_smc reports writes that land on memory the CPU has already
executed. Self-modification is legitimate on a 68000 -- decrunchers,
trackers and Copper-list patchers all do it -- but it is also where a
prefetch-related bug hides, since the CPU has already fetched the word
ahead of the one it is executing, and neither a patch applied too late
nor one applied to the wrong address leaves a trace at the moment it
happens. Each report names the written address, the instruction that
wrote it, and the distance between them, calling out a patch close enough
to sit inside the prefetch. An address counts as code once an instruction
there has retired, so an instruction patching its own extension words on
its only execution is not reported; every repeating pattern is caught on
the pass after the first. Read it with smc.report over the control
protocol, which can also arm and disarm the detector live. Off by
default; it costs a 1 MiB execution map while armed.