Compatibility

Developing software for the Atari ST, TT, and Falcon meant choosing between a variety of incompatible assemblers; there was no single portable assembly specification. Code was written for whichever tool the author owned.

Each assembler made fundamentally different design choices. They diverged on arithmetic operator precedence (left-to-right versus standard algebraic precedence), macro parameter syntax (positional \1 versus named parameters), macro-local label generation (\@, \~), local label scoping (.name, 1234$, @@name, ?name), comment delimiters (*, ;, |), and structure definition directives.

As a result, legacy Atari source archives can be difficult to build. Discovering a historical source release rarely means you can build it out of the box. Usually you must make manual edits to get things working with a modern cross-assembler.

When we built rg-asm, our primary design goal was to eliminate this friction. Rather than inventing another proprietary syntax, rg-asm is built to ingest preserved Atari code exactly as it was originally written.

To measure compatibility against real-world code rather than artificial micro-benchmarks, we test rg-asm against the heritage corpus curated by ggn at ggnkua/Atari_ST_Sources. This collection holds over 17,000 assembly source files, written between 1985 and the present day by game makers, demoscene crews, utility authors, and Atari themselves.

We continually test changes to rg-asm against this corpus to ensure that we maintain compatibility with all these legacy dialects. This page tracks some of the concrete results of those tests.

Atari Assembler Differences

While several cross-assemblers and native tools target the 68000 family, their architectural priorities differ significantly. The table below compares rg-asm with the primary tools used in Atari development: vasm (with the Motorola syntax module), rmac (the modern MADMAC successor), and native Devpac 3 running on an Atari ST/Falcon (under Hatari).

Capability rg-asm vasm (mot) rmac Devpac 3 (ST)
Dialect Ingestion Automatic detection for dialects with a content marker; others are named with --dialect Fixed syntax modules (-mot, -gas) selected at build time MADMAC syntax family only Devpac 1, 2, and 3 syntax only
Operator Precedence Each dialect's own model Shifts and bitwise operators bind tighter than multiplication Left-to-right (LTR) default Same as vasm: shifts and bitwise operators bind tighter than multiplication
DSP56001 Built-in: mixed 68030 + DSP56001 in a single pass No No No (required separate Devpac DSP product)
Linking & Executable Output Direct .PRG, GST, DRI, Pure C, ELF, flat binary Requires external linker (vlink) for complex layouts PRG or object output Direct .PRG or GST object (CLink)
Branch Sizing Control Exact fidelity mode (--no-opt-size) or sizing optimisation Optimisations controlled via -opt-all, -opt-jbra Optimisations via .opt Manual .s/.w sizing by programmer
Instruction Cycle Listing 68000 instruction cycle annotations in the listing (--listing) Address and hex bytes only Address and hex bytes only Listing with addresses and bytes only

Tier 1: Functional Ingestion

Functional ingestion tests whether historical source code compiles cleanly out of the box. The assembler must accept original syntax without manual edits, evaluate macros and dialect-specific expressions as intended, and emit valid executable or linkable binaries.

Tier 2: Binary Equivalence

Binary equivalence is a much stricter bar: does the emitted machine code match the original period binary byte-for-byte?

On the 68000, binary equivalence is vital for cycle-critical routines such as raster splits, border opening, and self-modifying code. If an assembler automatically shortens a 16-bit branch to an 8-bit branch, the altered cycle count breaks hardware timing.

What Binary Equivalence Requires

Matching original period binaries requires three factors to align:

  1. Strict Instruction Sizing (--no-opt-size): Preserves explicit branch and addressing widths written by the author instead of automatically shortening them (e.g. bra.w → bra.s).
  2. Isolating the Executable Payload: Strips volatile metadata (such as timestamps, host paths, or debugger symbols) to compare the true functional payload: TEXT, DATA, BSS size, and GEMDOS relocations.
  3. Self-Contained Code: Fully resolved sources with no missing external symbols.

Dialect Quirks

Assembling legacy Atari source code without modifying files requires addressing six specific architectural hurdles that routinely break standard cross-assemblers:

1. Operator Precedence Divergence

One of the most insidious sources of silent assembly corruption is arithmetic operator precedence. In Pure C and Sozobon, expressions follow standard algebraic/C precedence, where multiplication binds tighter than addition and addition binds tighter than shifts:

val = 1 << 2 + 1 * 3   ; Pure C / Sozobon: evaluates to 32

In Devpac 1, 2, and 3, vasm, Alcyon AS68, Brainstorm, GST ASM, and Lattice C ASM, shifts bind tighter than multiplication, which in turn binds tighter than addition:

val = 1 << 2 + 1 * 3   ; Devpac / vasm / Alcyon / GST / Lattice: evaluates to 7

In Atari Corp MADMAC and rmac, expressions are evaluated strictly from left to right across all binary operators without operator weighting, as documented in Atari's official MADMAC User's Manual ("All binary operators have the same precedence: expressions are evaluated strictly left to right. Thus the expression 1+2*3 evaluates to 9, not 7"):

val = 1 << 2 + 1 * 3   ; MADMAC / rmac: evaluates to 15

If one dialect's precedence is applied to code written for another, table offsets and raster line counts quietly calculate wrong values without generating an error. rg-asm addresses this by assigning specific precedence models per dialect. The shift-binding model (Devpac, vasm, Alcyon, GST, Lattice) and left-to-right model (MADMAC, RMAC) have been empirically verified by executing authentic period assembler binaries under godbox emulation and cross-referencing their official reference manuals. Dialects without an authentic host oracle currently retain an interim C-precedence model pending direct binary verification.

2. Macro Parameter Syntax & Variable Arguments

Different assemblers implemented distinct macro engines:

rg-asm implements a multi-model macro expander capable of evaluating positional substitution, named parameter binding, repetition directives (REPT/ENDR and .rept/.endr), and conditional macro checks (IFMACROD, ^^defined) without syntax rejection.

3. Filesystem Conventions & Path Normalisation

Historical Atari source code was developed on GEMDOS/FAT12 filesystems, which were strictly case-insensitive and used backslashes (\) as directory separators:

    INCLUDE "INCLUDES\SYS_VAR.I"
    INCBIN  "DATA\SPRITES.DAT"

On modern POSIX host systems (Linux and macOS), these paths fail immediately because filesystems are case-sensitive and treat \ as an escape character rather than a path separator. rg-asm incorporates transparent path normalisation:

4. Local Label Scoping Rules

Local labels allow subroutines to reuse common identifiers (such as loop labels). However, each era introduced a different local naming convention:

rg-asm scopes local labels according to the dialect rules of the input file, clearing local symbol tables on global label transitions or section breaks exactly as expected by the original author.

5. Structure Offsets & Layout Directives

To define memory structures, developers used competing directive sets:

rg-asm maintains native support for each layout pattern, allowing structural offsets to evaluate without manual conversion to constant equates.

6. Falcon 030 + Motorola DSP56001 Mixed-Mode Assembly

On the Atari Falcon 030, high-performance routines typically split work between the 16 MHz 68030 host CPU and the 32 MHz Motorola DSP56001 digital signal processor. Historically, assembling Falcon productions required running two completely separate assemblers: an m68k assembler for the host code and Motorola's asm56000 (or Brainstorm's b56) for the DSP microkernel, followed by a conversion script to turn the DSP binary into a .P56 file or inline DC.W data statements.

rg-asm natively supports both instruction sets in a single tool. Developers can switch architectures directly in source or assemble DSP kernels into standalone .LOD, .P56, .DSP, or raw binary formats without auxiliary tooling.

Instruction Widths & Branch Sizing

A common frustration when assembling historical 68000 code with modern compilers is binary drift caused by automated size optimisation.

When optimising for size, modern assemblers automatically:

While beneficial for general application code, automatic branch shortening can be catastrophic in time-critical Atari demoscene or game code. Sync scrolling, border-opening routines and some soundchip effects rely on exact instruction cycle timing. If an instruction is shortened from 12 cycles to 10 cycles (or 8 cycles for an untaken conditional branch), these effects will not work correctly.

Exact Reproduction Mode:

rg-asm includes explicit sizing control. When run with --no-opt-size, rg-asm preserves the instruction widths and addressing modes written in the source code. Without it, the default settings for some dialects may shorten an unsized branch: an unsized backward bsr that fits in the short range is assembled as the two-byte bsr.s with --dialect=devpac3 and with --dialect=vasm, and with --no-opt-size it stays the four-byte word form. Comparing output with an original release binary therefore needs --no-opt-size, and the output is only comparable where the source is otherwise compatible.

Historical Sources

Below are some examples of classic Atari sources. In each case, rg-asm ingests the preserved source and compiles cleanly, while rival cross-assemblers often stumble over period-specific syntax, macro expansions, or local label rules:

Year Group / Author Production Original Dialect Routine / Effect Ingestion Challenge vasm rmac rg-asm
1987 The Exceptions (TEX) Super Neo Demo K-Seka Bottom Border Shifter Sync K-Seka line-based syntax, colon-free digit-only local labels (1, 2), and implicit register list masks ✗ ✗ ✓
1989 The Carebears (TCB) The Cuddly Demos Devpac 2 Fullscreen Distortion & Overscan Devpac local label scoping, macro expansion, and block allocations (blk) ✓ ✗ ✓
1989 The Lost Boys (TLB) The Twist Scroll Devpac 2 Full-Screen Twist Scroll Devpac 2 inline branch displacement sizing (.s/.w) and multi-register equates ✓ ✗ ✓
1990 Automation Automation Packer 2.3 Devpac 2 Automation Packer 2.3 Column-1 instruction formatting, numeric local labels (1$), and unquoted ASCII string operands ✓ ✗ ✓
1991 Altair / VMAX Atomik Packer 3.3b Devpac 2 Atomik Packer 3.3b Dense numeric local labels (1$, 2$), scoped backward branching, and inline data directives ✓ ✗ ✓
1994 Brainstorm Brainstorm Language System Brainstorm Brainstorm String Data Proprietary ASCII, length-prefixed ASCIIL, and zero-terminated ASCIIZ string directives ✗ ✗ ✓
1995 EKO EKO System Devpac 3 3D DSP Matrix & True-Color Pipeline Devpac 3 OPT P=68030 CPU targeting, multi-section organisation, and scale-indexed addressing syntax ((An,Rn*s)) ✓ ✗ ✓
1995 Aggression Motion Devpac 3 Bicubic Bezier Surface Engine Devpac dotted symbols (bezi.vga), \@ macro expansion, and 68030 bitfield instructions (bfffo) ✗ ✗ ✓
1997 Avena Sonolumineszenz Devpac 3 Audio Crossbar & DSP Engine Devpac 3 structured section directives, symbol-relative displacement expressions, and 68030 control register syntax (CACR, VBR) ✓ ✗ ✓
2000 Doug Little (dml) Bad Mood (BM307) RMAC Falcon 030 Doom Engine Mixed 68030 and 68881/68882 FPU mnemonic parsing, RMAC macro argument expansion, and multi-file equate scoping ✗ ✓ ✓
2003 Dead Hackers Society Derealization RMAC 3D & Voxel Demosystem RMAC macro syntax, multi-level INCLUDE trees, section alignment directives, and strict left-to-right expression evaluation ✗ ✓ ✓
2012 Dead Hackers Society Drone RMAC STe Synclock Demosystem RMAC symbol redefinition (SET), nested macro parameter expansion, and case-insensitive filesystem path normalisation ✗ ✓ ✓
2013 Miro Kropáček (MiKRO) / Mystic Bytes mxPlay 2.0 Devpac 3 SNDH Audio Driver Devpac 3 RSSET/RSRESET structure offset blocks, conditional assembly (IFNE/ENDC), and macro parameter shifting (SHIFT) ✗ ✗ ✓
2015 Excellence In Art & ACCA & GGN Xmas 2015 RMAC STe Intro Kernel Modern RMAC optimisation switches (.opt "+O2"), LTR expression rules, and ELF32 relocation emission ✗ ✓ ✓
2022 Aske Simon Christensen (Blueberry) Shrinkler 1.0 vasm Extreme Density 68000 Depacker vasm Motorola syntax extensions, strict branch displacement sizing (.b/.w promotion), and register list alias equates ✓ ✗ ✓
Completion Rate (15 landmark productions across ST, STe, and Falcon) 46% 26% 100%

Dialect Ingestion Comparison

Beyond individual productions, we executed each tool across our test suites covering twelve historical Atari assembler dialects. The table below records build success (without modifying source files), alongside the main issues causing tools to reject the code:

Dialect Family Sources Tested Primary Ingestion Hurdle for Other Assemblers vasm rmac rg-asm
Alcyon / DRI 29 rmac rejects idnt and offset; vasm rejects comline 86% 48% 100%
GST ASM 26 rmac rejects GST header syntax and section layout directives 92% 0% 100%
MADMAC 46 vasm rejects .text, .comm, .cargs, iif, and goto 46% 98% 100%
Seka / Kuma 21 rmac rejects CODE directive; vasm rejects IFB and macro-local scope 67% 0% 100%
Devpac 2 22 rmac rejects unquoted .opt; vasm rejects listing control directives 91% 0% 100%
TurboAss 17 rmac rejects OUTPUT directive; vasm rejects @@ local label syntax 88% 0% 100%
AssemPro 7 vasm and rmac reject REDEF and AssemPro local label scoping 43% 14% 100%
GFA ASM 11 vasm and rmac fail on GFA-specific macro expansion semantics 91% 82% 100%
GenPC / sc68 26 vasm and rmac reject GenPC numeric local labels and memory blocks 27% 12% 100%
Devpac 3 27 rmac rejects unquoted .opt; vasm rejects subttl and nested rept 93% 0% 100%
RMAC 27 vasm rejects .macro definitions and dot-prefixed section directives 0% 100% 100%
vasm 77 rmac rejects vasm macro argument syntax and carg equates 100% 34% 100%
Total Across All Dialects 336 72% 37% 100%

A Single Universal Assembler

Our design goal with rg-asm is to provide a single, universal assembler capable of building any Atari source code—past, present, or future. Instead of juggling fragmented toolchains, emulated environments, or bespoke syntax flags, developers have a tool that is simple to deploy and operate: distributed as a self-contained, native executable with zero external dependencies, supporting all modern host platforms.

Preserving this level of compatibility requires ongoing testing. As new features, optimisations, and bug fixes are delivered, rg-asm continues to be regression-tested against the full heritage corpus to ensure that existing legacy code builds reliably and without regressions.