---
title: 'Commodore 64'
url: 'https://wiki.teletypegames.org/platforms/c64'
markdown: 'https://wiki.teletypegames.org/platforms/c64.md'
lang: en
date: '2026-07-16'
description: 'The Commodore 64 is the classic 8-bit home computer. Our C64 projects are 6502 assembly, assembled by ACME into a .prg file and tested in the VICE emulator (x64sc). ACME: https://sourceforge.net/projects/acme-crossass/ VICE: https://vice-emu.sourceforge.io Tools repository: https://git.teletypegame…'
taxonomy:
  tag:
    - howto
---

# Commodore 64

The Commodore 64 is the classic 8-bit home computer. Our C64 projects are 6502 assembly, assembled by **ACME** into a `.prg` file and tested in the **VICE** emulator (`x64sc`).

**ACME:** https://sourceforge.net/projects/acme-crossass/ **VICE:** https://vice-emu.sourceforge.io **Tools repository**: https://git.teletypegames.org/build/c64-tools **Projects built with this toolchain:** [Teletype Games C64 Demo](https://wiki.teletypegames.org/projects/c64-demo), [Rabbit Scooter — C64](https://wiki.teletypegames.org/projects/rabbitc64), [Blessing of Ra](https://wiki.teletypegames.org/projects/blessingofra)**Sibling platform:** the [Commodore Plus/4](https://wiki.teletypegames.org/platforms/plus4) uses the same ACME toolchain.

---

# Makefile

All `.asm` files live in the project root. `main.asm` is the single entry point and `!src`-includes the rest.

```text
project-root/
├── main.asm          # entry point, includes the others
├── defs.asm
├── common.asm
├── metadata.json     # catalog metadata + version
├── Makefile
└── README.md
```

## Variables

| Variable | Description | Default |
|---|---|---|
| `PROJECT` | Name used in artifacts and the catalog | — |
| `ASM` | Assembler binary | `acme` |
| `EMU` | Emulator binary | `x64sc` |
| `SRC` | Entry point | `main.asm` |
| `TARGET` | Output program | `$(PROJECT).prg` |
| `METADATA` | Metadata file | `metadata.json` |

## metadata.json

```json
{
  "name": "example",
  "title": "Example C64 Game",
  "author": "Teletype Games",
  "desc": "Short description of the game.",
  "site": "https://git.teletypegames.org/games/example",
  "license": "MIT",
  "version": "1.0.0"
}
```

`name` **must match** `PROJECT`. `version` drives the pipeline's version step. The rest goes into the game catalog.

## Targets

```sh
make deps        # brew install acme vice (macOS only)
make build       # assemble main.asm -> <project>.prg, only when an .asm changed
make run         # build, then launch in VICE
make clear       # remove the .prg and CI artifacts
make rebuild     # clear + build
make rebuildrun  # clear + build + run
```

## VS Code

Copy `example-tasks.json` to `.vscode/tasks.json`. It wraps the same targets, with `build` as the default build task (`Cmd+Shift+B`).

## Generated artifacts

| File | Description |
|---|---|
| `<project>.prg` | The assembled program |
| `<project>-<version>.prg` | Versioned release copy (CI) |
| `<project>-<version>.metadata.json` | Versioned metadata (CI) |

## Requirements

Local only: `make`, `acme`, and `x64sc` from VICE for `run`.

Example Makefile: https://git.teletypegames.org/build/c64-tools/src/branch/master/example-makefile.make

---

# Pipeline

The pipeline is not in the game repo. Woodpecker asks the update server, and WarpEngine serves the whole config from `/build/config` (template `lib/warp_engine/ci/woodpecker/platforms/c64/pipeline.yaml.erb`).

The repo needs three things:

```yaml
# .woodpecker.yaml
platform: c64
name: rabbit   # only when the software name differs from the repo name
```

- `metadata.json`
- the `application_token` Woodpecker secret: a WarpEngine ApplicationToken with `update` + `upload` scopes

The builder and the player image come from the update server's CI adapter config (`platforms:` in the WarpEngine initializer). Preview the served pipeline:

```sh
curl "https://teletypegames.org/build/config?platform=c64"
```

## Steps

| Step | What it does |
|---|---|
| 1. version | the engine's shared step (since WarpEngine 1.7): the version from `metadata.json`; on a tag a version tag has to match it; outside the repo's default branch and on a `dev-<branch>` tag `dev-<version>-<branch>`, with no version `dev-<commit>`; write `.version` |
| 2. build | assemble with ACME in the `c64-builder` image, producing `<project>-<version>.prg` and `<project>-<version>.metadata.json` |
| 3. package | **on the tag only:** `commodore-pack c64` in the `commodore-player` image: `<project>-<version>.html.zip` (EmulatorJS page with the self-hosted `vice_x64sc` core), plus `-linux-x64.zip`, `-win-x64.zip` and `-mac-arm64.zip` (yapeSDL with a launcher that sets the machine type and autostarts the `.prg`) |
| 4. upload | **on the tag only:** `POST /build/upload` for the `.prg`, the metadata and the players that were built (all six files when packaging succeeded), `X-Update-Secret` header |
| 5. publish | **on the tag only:** `POST /build/publish?platform=c64` |
| 6. tag | the engine's shared step (since WarpEngine 1.9), **after a push only**: a new version in the source gets its `v<version>` tag, whose pipeline runs the steps marked *on the tag only*; a version tagged already releases nothing; a dev build moves the branch's `dev-<branch>` tag onto the commit, so the newest dev build is published. See *Versions and releases* on the [WarpEngine](https://wiki.teletypegames.org/engines/warp-engine) page |

Raising the version is the release: a push builds and tags, and the tag's pipeline uploads and publishes (WarpEngine 1.9). A push that leaves the version as it is only builds.

## Result

The server reads the uploaded metadata, creates or updates the catalog entry, and registers five assets on the release: the `.prg` as the downloadable cartridge, the EmulatorJS page as the web-playable build (the catalog's "Play in browser" button, served from `/file/<name>-<version>/`), and the three yapeSDL bundles as the Linux, Windows and macOS downloads. Since WarpEngine 0.11 the two Commodore platforms expect the same asset kinds; earlier releases carry only the cartridge until they are rebuilt.

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Platforms](https://wiki.teletypegames.org/platforms.md)
- Previous: [Bevy](https://wiki.teletypegames.org/platforms/bevy.md)
- Next: [DOS](https://wiki.teletypegames.org/platforms/dos.md)
