Skip to content

GX on a 6502

The 6502 runs the game. It does not have to push every pixel, decode every asset, or pretend 64 KiB is the size of the world.

This page explains how a game written with GX, d/BASIC's library for 2D games (scenes, sprites, tilemaps, cameras, collision), can run on a 6502, the processor inside the Atari 800, the Commodore 64 and the Apple II. You will see which work the small CPU keeps, which work moves to the rest of the machine, and why a program is no longer limited to 64 KiB. Nothing here asks you to write differently; it explains why the ordinary way works.

GX matters on a 6502 because d/BASIC divides the job differently from a traditional machine-specific BASIC. The small CPU keeps what it is good at: rules, state machines, collision decisions, counters and characterful game logic. Large storage, paging, drawing, image decoding, sound and bulk operations sit behind services supplied by the runtime, the program that runs your compiled code.

This is not a faster 1982 interpreter with a few new commands. It is one portable compiled program, a .dbc file, using the whole of d/OS, the operating system that brings modern software to 8-bit home computers such as the Atari 800, the Commodore 64 and the Apple II.

Alpha: what has been tested

The large-memory and intent design is a promise d/BASIC makes. The complete 121 KB GX engine, all 17 sample programs, and every route onto real 6502 hardware are still alpha work that has not been fully tested. A game that runs on a desktop or in the browser has not yet proved it runs on the hardware; that proof comes only from measurements taken on a real machine.

The old limits and the new promise

Classic BASICs often shared 16 to 64 KiB among the source, the interpreter, variables, strings, screen memory and the operating system. A large game became a puzzle about overlays and bank registers before it became a puzzle about play.

d/BASIC moves those concerns below the language. The right-hand column mentions intents: an intent is a named request such as "fill this rectangle" or "play this tone" that the machine carries out however it can.

Old program concern What d/BASIC does instead
source and machine code had to fit under one 64 KiB ceiling the sections of a compiled program use wide lengths and are given memory from the runtime's pool
code had to select memory banks by hand the runtime pages in whichever code and data are in use
arrays needed near and far pointer variants arrays are bounds-checked values behind handles
art was copied through CPU-visible RAM every frame checked images stay with the display side of the machine
the CPU painted a framebuffer the program says what it means with GFX intents; the display side draws
sound code wrote chip registers in tight loops AUDIO intents describe notes, voices, effects and streams
each machine needed its own source tree one .dbc file meets whatever routes a machine offers

The first 6502 profile aims at a nominal 512 KiB logical workspace, with larger profiles able to follow. That is not a claim that the processor suddenly addresses 512 KiB in one instruction. It means program code and the values it works on are no longer designed around the processor's 16-bit window.

What "memory limits are erased" means

It means the 16 to 64 KiB language ceiling is gone:

  • compiled code, constants and metadata are not stored with a permanent 64 KiB limit on the file;
  • arrays, strings, objects, maps and loaded resources are known by managed handles, never by exposed addresses;
  • the runtime keeps a bounded working set visible to the CPU and pages the rest in as needed;
  • banking is private work inside the runtime, never a d/BASIC type or statement; and
  • when memory runs out, the program gets a named, all-or-nothing "out of capacity" stop rather than corruption.

It does not mean physical capacity and bandwidth disappeared. A 512 KiB machine still has a 512 KiB budget. A badly laid-out hot loop can still page too often. A thousand active entities can still take more turns than ten. d/BASIC removes ceremony and per-machine forks; it does not repeal arithmetic.

How a GX frame is divided

Stage 6502 and program work Runtime and display work
input read the normalized actions or a coherent input snapshot sample the keyboard, joystick, paddle, pointer or controller
update advance the GX arrays, rules, score, AI and collision consequences resume bounded provider work and keep it in order
prepare choose the camera, the visible objects, the animation frames and the effects resolve stored resources and check which capabilities are available
present send a small, ordered stream of intents compose tiles, sprites, text and colour into the final picture
audio send note, effect and music changes synthesize, mix, stream, or fall back to whatever sound route the machine has

The program authors state. The display side of d/OS (which composes the final picture from what programs ask for) authors pixels. A richer accelerator can sit on top of that display side; if it declines or is missing, the system returns to that product's baseline display, never to a hidden software painter on the CPU.

One rectangle instead of 61,440 pixel decisions

A 320 × 192 frame contains 61,440 pixels. Clearing it byte by byte is exactly the kind of work a 6502 should not repeat when the intent is already known.

INTENT GFX.BEGIN_FRAME()
INTENT GFX.FILL_RECT(0, 0, 320, 192, 2081)
INTENT GFX.DRAW_TEXT(12, 12, "READY", 3, -1, 2081)
INTENT GFX.PRESENT()

The game issues four requests that say what it means. The display side chooses how to satisfy them and reports completion only when the frame has really been shown.

The same principle scales:

  • a sprite request names a resource, a position, a frame, visibility and a layer;
  • a tilemap request names a tileset, a map, a camera and the visible region;
  • a surface move names stored content and a new location;
  • a polyline names its points once instead of one operation per pixel; and
  • a presentation hint is advice only, never permission to change what the game means.

Keep art where it is used

The expensive version of a sprite is "copy all its pixels from the 6502 on every frame." The d/BASIC version is "load this checked named resource once, then refer to it."

package asset → validate once → decode/select representation → retain near compositor
                                                     ↑
                                    later frames send identity + geometry

A browser may keep a high-resolution image. A colour display may keep RGB565 or indexed data. A one-bit display may keep a declared monochrome version. All of them represent the same hero and keep its game geometry.

This is also why a large game package does not eat into the 6502's working set just because it contains music, backgrounds and sprite sheets. The resource provider (the part of the host that supplies files and media) owns those bytes once they have been checked; the game keeps a name for the resource, not a raw pointer.

Sound is an intent, not a polling loop

A collision can request an effect in one short operation:

IF GXEntitiesOverlap(Actors(), HERO, COIN) THEN
  Score = Score + 100
  INTENT AUDIO.NOTE_ON(0, 1047, 224, 1, 8)
END IF

The sound provider decides whether that voice becomes a native chip tone, a mixed oscillator, or another available rendering. Recorded sound and music use bounded PCM writes against an explicit media clock. The 6502 does not busy-wait between samples.

Good game audio code sends changes:

  • start or replace a voice;
  • stop a voice;
  • play a named effect;
  • change a music state;
  • offer the next bounded chunk of PCM; or
  • silence everything this program owns when it closes.

It does not resend an unchanged plan of register writes every game tick.

Autonomous presentation removes needless turns

Some game motion is visually meaningful but logically uneventful. A coin can loop its animation, a background can scroll at a fixed parallax factor, and a projectile can travel until it hits a boundary or something else.

The full GX design lets a capable display keep that presentation state and advance it on its own, without a round trip for every intermediate pixel. The 6502 receives the meaningful events (collision, completion, boundary, input) and stays in charge of what they mean for the game.

This split is safe only when the rules name:

  • the initial state and its exact owner;
  • the clock or tick domain;
  • which motion is presentation only;
  • which event hands control back to the program; and
  • how cancellation and route changes preserve state.

If a route cannot keep those promises, GX falls back to short steps written in the program, or declines the optional feature and says so. It never invents invisible physics.

The 121 KB engine is a feature, not an embarrassment

The GX engine source is 121,067 bytes before adaptation. That size is useful in itself:

  • it crosses the old 64 KiB psychological and container boundary;
  • it contains real control flow, arrays, records, BYREF calls, maps, strings and platform seams;
  • it forces the compiler to separate hot per-frame code from cold loading and tooling code; and
  • it proves that installed shared libraries are about real reuse rather than tiny examples.

The 6502 never needs all 121 KB in memory at once. Startup, map loading, device discovery, editor tooling, collision and animation are not all hot at the same time. The compiler lays the code out in coherent regions; the runtime pages cold code; shared delivery keeps one reviewed engine available to many games.

The measurement that proves the whole engine pages correctly on a 6502 has not been taken yet. "It should fit" is a design. A bounded, repeatable run of the engine and the game suite is proof.

Optimize the meaning, not the instruction count

Write operations that expose useful units:

ARRAYFILL Scratch(), 0
ARRAYSWAP FrontMap(), BackMap()
Frame = GXAnimationFrame(FirstFrame, FrameCount, Tick, TicksPerFrame)

ARRAYFILL makes bulk work visible. ARRAYSWAP exchanges two names instead of copying a world. GXAnimationFrame replaces a sleep-driven controller with plain arithmetic. These choices help every runtime and matter most on a 6502.

For game loops:

  1. keep each event or frame turn short and bounded;
  2. batch whole display and bulk-memory ideas;
  3. keep hot entity state compact and contiguous;
  4. avoid rebuilding strings, objects and helpers inside DRAW;
  5. keep loaded resources loaded;
  6. invalidate only when state or presentation actually changed; and
  7. make optional richness depend on what the machine offers.

Route changes do not fork the game

A d/BASIC game never asks IF MAC or IF ATARI. The runtime adapts beneath the same intent:

Capability Richer route Honest simpler route
font high-resolution outline rendering a stable built-in hinted face
colour true colour or rich RGB565 a fixed indexed or monochrome mapping
sprite a stored high-resolution image a declared compact version
audio mixed PCM and voices described by meaning a bounded native tone route, or a clear message that sound is unavailable
input keyboard, controller and pointer whichever normalized local devices exist
animation motion kept and advanced by the display short ticks written in the program

Geometry, collision, score, save data and game decisions stay the same. A feature the game truly needs is declared up front and checked before the game starts. Optional sparkle can be simplified or declined without turning a win into a loss.

A developer-friendly 6502 workflow

  1. Build and play the game in the browser with the same d/BASIC model.
  2. Keep GX, the program's rules and the assets machine-neutral.
  3. Inspect which Services and intents the compiled program declares it needs.
  4. Test the baseline display and every advertised accelerator separately.
  5. Measure hot turns, page misses, provider latency and when frames are really shown.
  6. Record measurements taken on real hardware separately from desktop and emulator runs.

The reward is unusually pleasant: a game can begin as a tiny browser experiment, grow into a packaged desktop application, and arrive on a 6502 without gaining a bank-switching chapter in its source.


Next: Networking →