Crusty Studio Guide

Crusty Studio is a focused desktop studio for building small retro-computer programs with crustyBASIC. It brings source editing, project files, native toolchains, build output, emulator launching, and pixel-and-sound workshops into one place.

This guide explains the complete workflow: how projects are organised, how the editor decides what to build, which tools work with which targets, and how the asset workshops feed data back into your game.

Start here

Create your first project

On the very first launch a short setup wizard appears before anything else. Its first page lists the tools a project needs — crustyBASIC, VASM (and NASM for an x86 target), and an emulator — with links to get them; choose I will install these later to carry on, or Continue once you have them. The second page offers Create a project…. Nothing is searched for until the wizard is answered, which is why a fresh install starts here rather than with an empty editor. After that, a workspace with no project shows a Start a project card with New project…, Open project… and your recent projects; close it with its × and the editor area shows a quiet hint instead, which says whether it has no project or a project with no file open.

  1. Open Project → New.
  2. Enter a project name. The name becomes the project folder name and the default .crusty manifest name.
  3. Choose a computer target, such as Commodore 64, Nintendo Entertainment System, or Apple II. The list is alphabetical by the name each machine is shown under, so Commodore 64 comes before Commodore 128, and Atari 8-bit before Atari 2600. Every machine crustyBASIC defines is listed, and the ones that are not implemented yet are marked — not yet supported (a machine that has built on this installation reads — newly working).
  4. Choose the parent folder. On macOS the default is /Volumes/Data2/Programming when that folder exists; on other machines the default is a Programming folder inside Documents. Once you have created a project this session the dialog opens at the folder that project went into, so a run of projects meant for the same place - several NES games inside one NES folder, for example - does not have to be re-pointed at it every time; closing Crusty Studio returns the dialog to the default. Browse… opens where the field points.
  5. Choose Create Project.

Crusty Studio creates the project folder, a src folder containing main.cbs, and folders for sprites, charsets, tiles, shapes, sound, music, generated, and build. The project is remembered and restored the next time the desktop app opens.

Create an empty project leaves out src/main.cbs. The folders are still created and the manifest records no entry point, so the project starts with no source file at all. That is what you want when the source already exists and is about to be imported, or when the first file should be named by you rather than inherited from the starter. The editor opens on the "no file open" hint, a build with nothing to build says so, and the first .cbs a build finds is adopted as the entry point and recorded in the manifest.

The browser preview can demonstrate the interface and a simulated C64 build, but native file browsing, real compilation, and emulator launching require the installed desktop application.

Understand the workspace

The app is divided into four working areas:

Area What it does
Menu bar Project, file, edit, run, tools, and help commands, with the dark/light switch at the right.
Project Explorer Open source tabs, project assets, target information, and tool status.
Code editor Monaco-based crustyBASIC editor with syntax colours, completion, diagnostics, and tabs.
Output and Problems Build messages, compiler output, warnings, errors, and clickable source locations.
Search Project-wide text search results, one row per matching line, each one clickable.

The window itself remembers its size and place between launches, maximised or not, so it does not have to be resized on every start. A window that was maximised when it closed reopens maximised, and unmaximising it returns the size it had before, not the screen's; a window whose saved place is no longer on a connected screen opens where the platform decides rather than out of reach.

The sidebar and bottom panel are resizable. Drag the vertical divider between the Explorer and editor, or the horizontal divider above Output, to give a panel more room. Both panels scroll internally, so increasing the interface scale does not hide their contents.

The source editor has a two-row header. The top row contains the Project, File, Edit, Run, Tools, and Help menus. The row below keeps the build target, Options, Build, and Build and run controls together, followed by the independent Edit toolbar and the Go to procedure picker. Menu and Options popovers stay attached to their controls and are repositioned to remain visible inside the window. When the window is narrow, the Edit toolbar scrolls independently; its thin scrollbar appears when you hover over that toolbar, so the Target and build controls do not move with it.

The Dark/Light switch sits at the right-hand end of the menu bar, on the same line as the menus, and stays there while the menu bar scrolls. It switches the whole interface between the dark theme and a light one; the moon names the theme you are in, and the choice is remembered, so the studio reopens the way you left it. Both themes use the same highlight colour.

The status bar shows the current build phase, target, encoding, runtime, the editor code size, and the version of the build at the far right. Select the code-size indicator (it reads Code 14px at the default size) to reset the code editor to its default size.

Project Explorer

The Explorer mirrors the project on disk:

My Game/
├── My Game.crusty       project manifest
├── src/
│   ├── main.cbs         main source file
│   └── other.cbi        optional include/source file
├── sprites/             C64, NES, Atari 8-bit, Atari 7800, or Apple II graphics assets
├── charsets/            C64 character sets and Atari 8-bit tile glyphs
├── tiles/               C64 and NES tile-map projects
├── shapes/              machine-neutral shape sets (Portable Shapes)
├── sound/               sound recipes
├── music/               music projects: native songs, SID tunes, imported modules and samples
├── generated/           generated include data
└── build/               build products and intermediate files

Double-click a source file to edit it. Double-click a recognised asset to open its workshop. Right-click an open document or a recognised asset for Edit, Rename, or Delete; a plain project file such as a configuration or listing offers Delete only, and right-clicking a folder offers New File, which creates untitled.cbi in that folder (a path picker confirms where). Renames update the project manifest when the asset is part of the current project.

The .crusty project manifest

The manifest is a small JSON file that records the project identity and asset locations. A typical manifest looks like this:

{
  "format": "crusty-project",
  "version": 1,
  "name": "My Game",
  "target": "c64",
  "targets": ["c64", "nes", "amiga"],
  "main": "src/main.cbs",
  "folder": "/Users/me/CrustyFiles/My Game",
  "assets": {
    "sprites": [],
    "charsets": [],
    "tilemaps": [],
    "shapes": [],
    "sounds": [],
    "music": []
  }
}

main and the paths in assets are relative to the project folder — the folder the .crusty file itself is in. That folder is where the project's files are found, always, so moving the project or changing the Projects folder in Toolchain settings cannot make an existing project look somewhere else; Projects folder only decides where the file dialogs open and where a new project is created.

folder records the folder the project was last opened from. It is written for the app rather than for the build: if a project is moved, a Recent projects entry that still points at the old location follows this record to the project's new home. It is optional, and a manifest without it opens normally.

target is the active machine: the one the editors describe and the one Build and Run use. targets is optional and lists every machine the project builds for — see Multi-machine projects below. A manifest without it means exactly one machine, the active target, which is what every project written before the field existed says.

assets.shapes lists the machine-neutral shape sets in shapes/; see Portable Shapes Editor.

You normally do not need to edit this file by hand: creating, saving, importing, renaming, and deleting assets keeps it in sync.

Multi-machine projects

A project can be built for more than one computer. target stays the machine being worked on and targets is the list of machines the project answers for:

"target": "c64",
"targets": ["c64", "nes", "atari800", "amiga"]

The Target menu in the toolbar leads with the project's own machines, each marked in project unless it is the active one, and offers every other machine under Add a machine. Choosing a machine makes it active and records it in the list, so there is no separate step to add one. The active machine decides which workshop an asset opens in, which per-machine includes a build selects, and what Run launches.

This exists because crustyBASIC's own multi-target examples are written that way: one program whose target hooks live in @INCLUDE "{program_name}/{target}.cbi", with a fallback file for machines that need nothing special. Machine-specific art is kept beside the shared art as its own asset kind, and the per-machine include chooses which one the game loads. Art that is not one machine's format at all goes in a shape set instead, so one copy of it serves every target — see Portable Shapes Editor.

The main.cbs file

src/main.cbs is the default entry source file. It is not a special language dialect; it is simply the source file named by the project manifest and shown first when a project opens. You can create additional .cbs, .cbi, or .bas files and switch between them using editor tabs.

The first lines normally declare the target:

@OPTION TARGET c64

@OPTION TARGET (or the compatible @OPTION SYSTEM) controls the target label, target-aware workshop availability, emulator adapter, and default launch behaviour. The editor reads this directive live, so changing it changes the target card in the Explorer.

You can also select an output format:

@OPTION OUTPUT_TYPE crt

When present, Crusty Studio passes that output type to the compiler. Without it, crustyBASIC chooses its normal default for the selected target.

The crustyBASIC editor

The code editor is powered by Monaco and is aware of the crustyBASIC language.

Editing features

Tabs and editor controls

Use the … button at the right side of the tab strip for tab management:

The tab scrollbar is separate from the code editor scrollbar. The Edit toolbar also has its own horizontal scroll area when the window is too narrow for all of its buttons. Hover over that toolbar to reveal the thin scrollbar and reach actions such as indent, outdent, duplicate line, delete line, and Find.

The lightweight live checker catches common mistakes immediately, such as an untyped DIM declaration. After a short pause, the desktop app also asks crustyBASIC to check the source against the project's build target — not the machine of whichever tab is on screen — and reports the compiler's diagnostics.

Source files and includes

Use .cbs for a normal source file and .cbi for reusable include data or generated declarations. @INCLUDE is handled by crustyBASIC; the Studio does not rewrite your source. Asset workshops can generate include-style data for workflows that need byte arrays in code.

Procedure navigation understands include paths relative to the source file, including nested includes and target-specific paths such as machine/{target}.cbi. This means a declaration opened in an include file can find its calls in main.cbs, and a call in main.cbs can return to the correct declaration in an included file. When more than one target or file provides the same procedure name, choose the intended definition from the chooser.

Build and run

Build

Choose Build in the top toolbar or Run → Build. Crusty Studio:

  1. Saves unsaved source when a native build needs a real path.
  2. Reads @OPTION OUTPUT_TYPE and the current target. The Color Computer is the one family with a forced default: a CoCo build is a cartridge, so the studio passes --set output-type=cart when the source leaves it unset, rather than the compiler's own default.
  3. Invokes crustyBASIC with the target's assembler: VASM 2.x for the retro families, NASM 2.x for the x86 targets (dos16, h120, pcjr, tandy1000, linux64, win64).
  4. Captures standard output and errors in Output.
  5. Searches for the newly produced artifact and records it for Run.
  6. Opens Problems automatically the first time a build fails in a session; after that a failure only adds its message to Output, and Problems stays where you put it.

If Generated assembly is enabled in Settings, the compiler receives --emit-asm and writes an .asm file into an assembly folder beside the source.

The Options control beside Target opens common crustyBASIC build parameters. Optimization, array indexing, and video region use --set; Debug mode, warnings, timings, and the size report add their matching compiler flags. Unchanged options are left out so the compiler keeps its normal defaults. These choices are remembered locally for future builds. The same popover holds Startup file, which picks which src/*.cbs a build compiles when a project has more than one; every other .cbs in the Explorer has a ▶ button that makes it the startup file, and the current one carries a ▶ badge. A manifest whose main names a file that is not there is rewritten to the file that answers to it.

Build and run

Choose Run or Run → Build and run. A successful build is followed by target-aware emulator selection. Starting a run while another emulator started by the studio is still running stops that process before launching the new artifact. Fujisan on macOS is reused through its local service when possible, so the studio cannot terminate it the way it terminates a child process.

Target and emulator matrix

The family is the text before a dot, so c64.demo still uses the C64 adapter. Which machine is built is the active target in the Target menu, which is stored in the project manifest and passed to the compiler as --set target; that beats any @OPTION TARGET in a source, which is why a multi-machine source can select its per-machine hooks with {target} and declare no target of its own. A source directive still decides the target shown on the Explorer's target card, and is what crustyBASIC uses when the source is built by hand without --set target. The editor's own error check compiles for the same active machine, so its inline diagnostics describe what Build and Run will produce.

Target family Adapter Executable names searched Launch behaviour
c64 VICE C64 x64sc, x64 -autostart for normal images; -cartcrt for .crt.
c128 VICE C128 x128 VICE autostart.
plus4 VICE Plus/4 xplus4 VICE autostart.
vic20 VICE VIC-20 xvic VICE autostart.
amiga WinUAE, FS-UAE, Amiberry winuae, winuae64 / fs-uae, fsuae / amiberry Mounts and boots the .adf crustyBASIC writes beside the executable, on the machine the Amiga system names. See WinUAE, FS-UAE, Amiberry and Hatari.
atari800 Atari 8-bit atari800, Fujisan Atari800 launch; Fujisan uses its local TCP service on macOS.
atari5200 Atari 5200 atari800, Fujisan Adds the 5200 mode and cartridge flags for .car/.a52.
atarist Hatari hatari Hatari autostarts the TOS .prg given as an argument. The target itself is not yet supported.
atari2600 Atari 2600 stella Direct artifact launch.
atari7800 Atari 7800 a7800, EMU7800 A7800 (macOS and Linux) is a MAME fork: a7800 a7800 -cart <file>. EMU7800 (Windows) takes -r <file> and reads the machine and cartridge type from the .a78 header.
apple2 Apple II linapple*, applewin, microM8 LinApple gets --autoboot --d1, because a bare path leaves it at the Applesoft prompt with the disk unread; AppleWin and microM8 take the build directly, and microM8 also receives its Apple II profile.
coco MAME, XRoar mame, mame64 / xroar A CoCo build is a cartridge, so MAME (coco -cart) comes first and loads it; XRoar is the alternative, launched directly on the artifact.
zxspectrum MAME, Retro Virtual Machine mame, mame64 / Retro Virtual Machine* MAME takes the .tap apart and puts the code into memory, then enters it; see MAME. Retro Virtual Machine can only be started on the machine.
amstradcpc MAME, Retro Virtual Machine as above MAME cpc6128 -flop1.
msx MAME, Retro Virtual Machine as above MAME hx10 -cart.
colecovision MAME, Retro Virtual Machine as above MAME coleco -cart.
sms MAME, Retro Virtual Machine as above MAME sms -cart.
zx81 MAME mame, mame64 MAME zx81 -cass.
gamegear MAME mame, mame64 MAME gamegear -cart.
gameboy MAME mame, mame64 MAME gameboy -cart.
aquarius MAME mame, mame64 MAME aquarius -cart1.
astrocade MAME mame, mame64 MAME astrocde -cart.
nes Nintendo Entertainment System mesen, fceux, nestopia, tetanes Direct artifact launch.
nes, snes, gameboy, gamegear, sms, colecovision, msx, atari2600 ares ares --system <name> and the build's path; ares runs it. See ares and BizHawk.
nes, snes, gameboy, gamegear, sms, colecovision, msx, atari2600 BizHawk EmuHawk The build's path alone, through EmuHawkMono.sh on Linux. See ares and BizHawk.

The Retro Virtual Machine and LinApple entries match their executable by prefix, because those names carry a version or a frontend suffix (Retro Virtual Machine 2.1, linapple-sdl3); the rest are matched exactly. A family served by both names both, and the emulator you pick in the Target menu decides how Run behaves.

ares and BizHawk appear once for every family they can run a build for, which is why the last two rows name eight of them. A family already served by MAME or Stella keeps that emulator as the one Automatic chooses; ares and BizHawk are there to be picked, or to answer when nothing else is installed. snes had no emulator adapter at all before they arrived.

VICE

VICE is one emulator per machine, so the family picks the executable: x64sc (or x64) for the C64, x128 for the C128, xvic for the VIC-20, xplus4 for the Plus/4. Run hands the build over with -autostart, which loads and runs it the way typing LOAD and RUN would; a .crt cartridge goes over with -cartcrt instead. Nothing else has to be configured, because VICE's own defaults are the machine the target builds for.

The C128 is also given a 1571, because it has no drive of its own by default. Autostarting a PRG means building a temporary disk image and booting from it, and a machine with no drive gives VICE no geometry to build that image from: it stops with AUTOSTART: Error - No idea what disk image format to use, closes without ever opening a window, and looks exactly like a broken emulator. Run therefore passes -drive8type 1571 for the c128 family — the drive a C128 shipped with, and the one VICE's own drag-and-drop picks when a file is dropped on the window. The C64, VIC-20 and Plus/4 already default to a usable drive.

On Linux and BSD, VICE has no ROM images until you supply them. The distribution packages leave them out — they are copyrighted, and Debian's own README.ROMs says so — so an emulator installed from a package can find neither its KERNAL, its BASIC nor its character set. It exits immediately instead of opening a window, which from a studio that started it from a menu looks like nothing happening at all.

The images live one folder per machine under VICE's system-file directory — C64, C128, PLUS4 or VIC20, beside the shared DRIVES and PRINTER folders. VICE prints the folders it searched on every start-up, and Run quotes it:

Main: VICE system file search path: '/home/you/.local/share/vice:/usr/bin:/usr/share/vice'.
Main: VICE system file directory: '/usr/share/vice'.

Use the per-user folder, the first entry, because it needs no root: /home/you/.local/share/vice/C128 for the C128, for example. VICE names each image it wants as it starts — Couldn't load basic ROM `basiclo-318018-04.bin' — and Run prints that, so a partial set can be completed a file at a time. Its own log, holding everything it printed, is at ~/.local/state/vice/vice.log.

The C128 is the awkward one: it wants the whole set before it will start at all. Where the other machines load the images the machine actually uses, the C128's own start-up check looks for every language variant of its KERNAL and character ROM — the international files plus the German, Swiss, Swedish, Finnish, French, Italian and Norwegian ones, eight KERNALs and six character ROMs. Missing the German KERNAL, for instance, stops the English machine with Couldn't load kernal ROM kernal-315078-03.bin'` before any window appears, which is exactly what a wrong emulator would look like. Copy the machine's complete ROM folder rather than the international files alone.

A C64 that runs while another Commodore target does not is this cause rather than a studio one: the C64 folder is populated and the machine's own folder is not. macOS is unaffected, because the VICE builds there carry the images.

WinUAE, FS-UAE, Amiberry and Hatari

The Amiga and the Atari ST are both Motorola 68000 machines, so both assemble with vasmm68k_mot rather than one of the 6502 builds. They are otherwise unrelated, and so are their emulators.

An Amiga Run mounts a disk image, not the executable. crustyBASIC writes two files for every Amiga build: a HUNK .exe, and beside it an 880 KB bootable .adf floppy whose startup-sequence runs the program. The .adf is what Run hands over, because it is the only thing FS-UAE and Amiberry can start — both boot media, and a program given on their command lines would be read as a configuration file — and WinUAE mounts it just as happily. FS-UAE is told the machine the Amiga system named with --amiga-model (amiga.500, amiga.600, amiga.1200) and is given the disk on --floppy-drive-0; Amiberry gets --model and the disk in drive 0 with -G, so it starts emulation instead of opening its own GUI; WinUAE is given the boot disk and left to its own model configuration. A build with no .adf beside it falls back to handing WinUAE the .exe, which it can run directly; FS-UAE boots the machine and Amiberry opens its GUI, each saying plainly that it has nothing to mount.

All three Amiga emulators need Kickstart ROM images, which are copyrighted and are not shipped with any of them. They look in their own ROM folders and say so when one is missing; the studio does not supply or check them.

Hatari autostarts the build. Hatari takes an Atari program as a plain argument — hatari game.prg is its own -d <dir> --auto C:<program> shorthand — so Run hands it the .prg and it starts. Hatari ships EmuTOS, so it boots without a TOS ROM of its own, though a real TOS image is more compatible with some programs. It is detected from its macOS bundle as well as a plain executable.

The Atari ST target is a preview: crustyBASIC 1.6.0 defines atarist.tos and atarist.gem but gives neither a tile backend, so a build stops while reading the target's own metadata. The machine is offered, marked not yet supported, and a failure is reported as what it is rather than in the compiler's internal terms. Hatari is there for when the target lands — and for a program that does not need tiles.

MAME

MAME is the only adapter here that runs a build outright, because it takes the machine, the media and the ROM folder on its command line. Run passes:

Cartridges start on their own, because the cartridge is the machine's boot medium.

A ZX Spectrum tape cannot be started by mounting it, so the program is put into memory instead. Nothing can type LOAD "" at a Spectrum under MAME — the driver has no natural keyboard, so -autoboot_command types nothing at all (with it set, the screen three seconds in is byte-identical to a run without it) — and the driver overrides the cassette's default state to stopped, so a mounted tape does not start either. Its quickload slot rejects a raw build outright. So for zxspectrum, Run takes the built .tap apart, finds the machine-code block and the address it loads at, and writes a short Lua script that MAME runs: it waits for the ROM to finish its RAM test, puts the code at that address, sets RAMTOP the way the tape's CLEAR would, leaves a jr $ for the program to return into, and enters it.

That is what the tape's BASIC loader was doing, done directly. Verified against MAME 0.289 with the studio's own generated script: the program ran and printed to the screen, then returned cleanly into the trampoline.

The ZX-81 works the same way, though its build is not a tape at all. A .p is the machine's RAM: it starts at the system variables with the compiled code already at the address it runs from, so Run writes the image back where it came from and enters the documented entry ($43AB), which holds a stub that calls the program and then parks. The stack is left where the ROM put it — the image's own stack end sits below the display file and would leave a program almost no room. The image is checked for that stub before it is trusted: if crustyBASIC ever moves it, the build is mounted as media instead of being entered blind.

Anything that is not a .tap or .p is mounted on the medium the target names: -cart for a cartridge build, -cart1 for the Aquarius, -flop1 for an Amstrad CPC disk, and -cass for a cassette image. A cartridge is the machine's boot medium, so it starts on its own; a disk or tape that needs a command after mounting is what the studio's own launch_note says.

MAME needs ROM sets

MAME ships no ROMs, and every system listed above needs a BIOS set except the cartridges themselves. When the set is missing it exits immediately — which, from a studio that started it from a GUI, means a window that never appears. Crusty Studio checks before it launches and says so instead:

MAME has no spectrum ROM set, so it would exit without opening a window. Put one in
/Volumes/Data2/Emulators/MAME/roms as spectrum.zip. The first ROM MAME lists for this machine is
spectrum.rom; the rest of `mame spectrum -listroms` are alternative ROMs for other variants of the
same machine, which are not needed and are chosen with -bios.

Do not be alarmed by mame <machine> -listroms. It prints every ROM the driver knows, and for a machine with several variants most of that list belongs to the other variants — the ZX Spectrum lists sixty entries and needs one. So only a set that is completely absent blocks the launch. A set that is present but incomplete is reported as a note and the launch goes ahead, because MAME itself runs a Spectrum with nothing but spectrum.rom in place, warning rather than refusing. Let MAME be the judge of its own ROMs.

The ZX-81 is a special case. MAME's driver carries three ROM revisions as BIOS options and needs only the one selected, but its default is the third — and the ZX80/ZX81 copyright is not Amstrad's, so the permission that covers the Spectrum ROMs does not reach it. The first and second revisions are freely distributable: they are the ROMs Nine Tiles released under the GPL as Open81, byte-identical to the revisions MAME checksums. Run therefore asks for -bios 2nd, the revised ROM with the arithmetic bug fixed, so zx81.zip needs only zx81a.rom in it. Adding zx81.rom as well lets you pick the first revision by hand.

What matters is the set name: one archive per machine, roms/<machine>.zip — or .7z, or the same name unpacked as a folder. If MAME still will not start, it names the ROM it wants. Sets go where the MAME ROM folder in Toolchain settings points, or in the roms folder beside MAME.

Retro Virtual Machine cannot load a program

Retro Virtual Machine is a graphical emulator with almost no command line: its only useful switch is -b <machineid>, which boots a machine, and it accepts no program at all. A file named on the command line is ignored, and handing it a document through the system is refused before the emulator starts. Its only way in with a program is to drop the file on the running window.

So Run does the part the studio can do — it starts the emulator on the machine the target needs, which is otherwise a choice from a list of sixty — and then says in the output panel which built file to drag onto the window. Nothing is wrong with the build when this message appears. For anything Retro Virtual Machine and MAME both cover, pick MAME if you want Run to load the program itself.

Some machines have no adapter yet, and Run ends with No emulator adapter is available for target …. That covers the TRS-80 family, the Coleco Adam, NABU, the Sorcerer, the Tandy 100, MEGA65, Pac-Man (arcade), the x86 targets (dos16, h120, pcjr, tandy1000, linux64, win64) — the Target card names the adapter each is waiting for (Z80 emulator adapter, MAME adapter, DOSBox adapter, Native Linux adapter), which is where the wiring will go. MC-10 is not one of the studio's machines: crustyBASIC has a target id for it, but it is not in the project list.

Several rows above describe a launch for a machine that is still a preview in Which targets actually build — most of the Z80 and console families, for instance. The recipe is what Run will do once the target lands; until then the build stops before the assembler, and the Target menu and New Project mark the machine not yet supported.

ares and BizHawk

ares and BizHawk are multi-system emulators: one installation each, covering far more machines than crustyBASIC can build for today. They are offered for the families whose build is a cartridge — nes, snes, gameboy, gamegear, sms, colecovision, msx and atari2600 — and deliberately not for the Spectrum, ZX-81 or Amstrad CPC, where a build is a tape or a disk that neither emulator can start without someone typing at the machine. MAME already does that job properly.

Both are handed the built file and run it, so there is nothing to drag onto a window and nothing to type.

Run passes ares the file and the system name:

ares --system "Super Famicom" --no-file-prompt /path/to/main.sfc

ares decides what a file is by its extension before it looks at anything else, and two of its own guesses are wrong for a studio build: a .bin is an Atari 2600 to ares before it is a Mega Drive, and a .gb is a Game Boy before it is a Game Boy Color. Naming the system settles it. The spelling is ares' own and is compared exactly — it is the list ares --help prints — and a name ares does not recognise is not an error to it: it quietly falls back to the extension. --no-file-prompt is passed as well, so ares never stops to ask about optional extra media that a Run does not have.

BizHawk is handed the file as its only argument.

BizHawk on Linux is a Mono application. Its EmuHawk.exe is a .NET assembly rather than a Linux program, and the release starts it through the EmuHawkMono.sh beside it. Run uses that launcher when it is there. If the mono runtime is missing it says so instead of starting a script that dies in the background:

BizHawk on Linux is a Mono application, and the mono runtime was not found on PATH. Install Mono —
from the project's own downloads, or from your distribution, where the package is `mono-complete`
on older Debian and Ubuntu releases and `mono-devel` on newer ones — and run again.

On Windows EmuHawk.exe is the program itself and no runtime is needed. ares ships a launcher of its own, ares.sh, beside the Linux binary, which points the library path at the copies of SDL2, Xv and libpng it bundles; Run uses that too when it is there, and the plain binary when it is not.

Some of these systems need a BIOS or firmware file — an FDS BIOS, a ColecoVision BIOS, an MSX BIOS — and neither emulator ships one. Each says so in its own window when one is missing. An NES cartridge, which is what the nes target builds, needs none.

The Target menu contains each target and its detected emulators as child options. A target with emulators is marked with a ›: hover over it, or click it, and its emulators appear in a panel beside the menu. Clicking a target's own row, rather than its emulator panel, chooses that machine — so adding a second machine to a multi-machine project is one click on the row. If exactly one compatible emulator is found, it is selected automatically; when several are available, choose one there. The selection is remembered per target family. Choose Automatic to use the first detected emulator.

Which targets actually build

crustybasic targets lists every target the compiler carries a definition for — fifty-eight of them — which is a much longer list than the systems its own README calls supported. crustyBASIC 1.5.0 implements:

apple2, atari800, atari5200, atari2600, atari7800, c64, c128, plus4, vic20, coco, dos16, win64, linux64, nes, zxspectrum and zx81. crustyBASIC 1.6 added the Amiga (amiga) and defines the Atari ST (atarist) without implementing it, so the Amiga is supported here and the Atari ST is marked preview.

Everything else in the target list is marked preview. A preview target fails while reading its own metadata, before the assembler is reached, so it cannot be built whatever is done to the toolchain or the settings — Crusty Studio says so plainly instead of passing on the compiler's internal message. They are listed because they are what is coming.

That list can only be as current as the crustyBASIC release it was taken from, so it is not the last word. Build for a preview target and succeed, and it is promoted to new for that machine — the badge changes, and the output panel says so:

MSX builds with this compiler, so it is no longer a preview target.

A build that succeeded is better evidence than any list, so this is how a newer crustyBASIC is noticed without the studio being updated first. The promotion is remembered per machine, and an older compiler chosen in Toolchain settings — which fails the same way — takes it back.

New Project lists every machine crustyBASIC defines, annotated with its support level. Additional crustyBASIC target IDs can still be edited in source, but Run needs a matching adapter and emulator.

Configure the toolchain

Open Tools → Settings or select the gear in the Explorer. Settings has two sections.

Appearance

The window's size and place are remembered as you move and resize it — written to the app's own settings folder, never into a project — and put back when the studio is opened again. Nothing has to be saved by hand for that, and the remembered size is ignored if it is smaller than the app's minimum.

Interface size scales the complete UI: text, controls, spacing, menus, and panels. Presets are Default (100%), Comfortable (115%), Large (130%), and Extra large (150%). The preview is immediate; choose Save settings to persist it.

The code editor has an independent zoom. Hold Command or Control while scrolling over the code editor. On macOS, Command is the recommended modifier; ⌘+, ⌘−, and ⌘0 are reliable keyboard fallbacks. On Windows and Linux use the equivalent Control shortcuts.

Theme is switched from the Dark/Light control at the right of the menu bar rather than from here, so it applies at once. The light theme restates every colour in the interface at light-theme weights, including the asset workshops and the code editor's own colour scheme, so nothing is left half-dark when you switch.

Highlight colour changes the accent used across the studio: the active tab, the build and run buttons, badges, selection outlines, editor caret, and the highlighted panels in every asset workshop. Drag the wheel's outer ring to choose a hue and the inner square to set how strong and how bright it is, type a hex value, or pick one of the presets. The change is previewed immediately; Reset returns to the stock amber and Save settings keeps the choice. The default amber is used until a custom colour is saved. The light theme deepens the accent so it stays legible both as text on a pale panel and under white type on a filled button.

Toolchain

Select Check installation to refresh the green, yellow, and missing tool indicators. The same scan can be run from the status bar's Find tools button, which turns into Cancel while it works; the status bar also carries the version and build commit of the running build. The desktop app does not bundle crustyBASIC, VASM, NASM, emulators, ROMs, or games; install those separately and point Settings at them.

On macOS, Gatekeeper can quarantine downloaded compiler helper tools. The tool check and build output explain the exact helper that needs to be reviewed and cleared if this occurs.

Asset workshops

Workshops are opened from Tools or by double-clicking a recognised asset in the Explorer. Each workshop has its own scrollable panels and a Back to code action. In the desktop app, New and Save write into the current project; in browser preview, Save downloads a JSON project file.

Availability comes from the project's machines, not from the active source file alone. An editor is offered once for every machine the project builds for — the targets list in the manifest, with the active machine always included — and the active machine's entry comes first, so a single-machine project sees exactly one entry and a C64/Amiga project sees the editor once per machine. Choosing an entry opens that machine's version of the workshop and seeds a new asset from that machine. A target family is the text before the dot in a target id, so c64.demo still counts as C64-family.

Tools menu entry Enabled for What opens
Sprite Editor C64 family, NES, Atari 8-bit, Atari 7800, Apple II C64 Sprite Workshop for the C64 family, NES Sprite Workshop for NES, Atari Sprite Workshop for the Atari 8-bit and 5200, Atari 7800 Sprite Workshop for the Atari 7800, Apple II Graphics Workshop for Apple II.
Charset/Tile Editor C64 family, Atari 8-bit and 5200 The C64 Charset Editor for the C64 family (standard and multicolour character sets), the Atari Tile Editor for the Atari 8-bit and 5200 (8 × 8 one-bit tile glyphs).
Tilemap Editor C64 family, NES, Atari 7800 C64 tile-map editor for the C64 family, NES tile-map editor for NES, MARIA tile-map editor for the Atari 7800.
Sound Editor C64 family, NES, Atari 7800, Amiga The sound editor, adapting to SID, the NES 2A03, the Atari 7800's TIA, or the Amiga's Paula according to the target.
Music Workshop Every target The portable four-lane tracker, which uses the target-neutral CrustyBasic sound API.
SID Music Workshop C64 family The three-voice SID tracker with chip-model, filter, and PAL/NTSC controls.

A greyed-out entry means the project has no machine that uses that editor; a note under the menu says so and points at Target, where a machine is added. Disabling is only about creating a new asset: opening an existing asset from the Explorer always works, because the saved asset records the platform it was authored for. A NES tile map opened from a C64 project still opens in the NES tile editor. The Sprite Editor covers the C64 family, NES, the Atari 8-bit and 5200, the Atari 7800, and Apple II; the Charset/Tile Editor covers the C64 family and the Atari 8-bit and 5200; the Tilemap Editor covers C64, NES and the Atari 7800; Portable Shapes is always offered, because it is machine-neutral.

Sound follows the same split, and it is worth knowing what each family actually gets. The C64, C128, and Plus/4 use the MOS 6581/8580 SID with three voices, a shared multimode filter, and PAL or NTSC timing; the Sound Editor and SID Music Workshop configure those registers directly. NES uses the 2A03 APU with two pulse channels, a triangle, and noise, which is what the Sound Editor's NES controls map onto. The Amiga's Paula is four DMA voices with a period and a level each and one square waveform, which the Sound Editor's Amiga side and the Music Workshop's Amiga player drive with PAULA.SOUND. Every other target has only the portable Music Workshop and the generic CrustyBasic sound API, resolved by the target's own runtime — there is no chip-specific editor for Atari, Apple II, CoCo, or the other targets.

Nothing here is available in the browser preview beyond drawing and downloading a project file; saving into the project, importing binaries, and exporting includes are desktop-only.

C64 Sprite Workshop

Available for the C64, C128, and Plus/4 targets in the current Tools menu. It edits a VIC-II sprite as one or more animation frames.

NES Sprite Workshop

Available for NES targets. It edits 8 × 8 indexed tiles with two bitplanes.

Atari Sprite Workshop

Available for the Atari 8-bit and Atari 5200 targets. The Atari has no sprite chip: ANTIC and GTIA give it four players and four missiles, and the editor is built around that rather than around the C64 model.

Atari 7800 Sprite Workshop

Available for the Atari 7800 (atari7800) target. It edits MARIA sprites in two forms, chosen when the asset is created:

This is deliberately a separate editor from the Atari Sprite Workshop. The 8-bit machines have no sprite chip and use Player/Missile Graphics: 8 × 16 players, four players plus four missiles, a colour register per object, and PMSIZE/PMHPOS/PMPRIORITY with fifth-player mode. The 7800 has none of those — its shapes are 8 × 8 and its colours come from shared palettes — and the two share a colour-byte encoding and nothing else.

Apple II Graphics Workshop

Available for Apple II targets. It provides Apple II-oriented graphics presets and pixel editing for low-resolution and high-resolution sprite-style assets. The preview is an editing guide; composite colour for hi-res art also depends on the screen X position when the program runs.

Apple II graphics assets use .apple2graphics.json or .apple2lores.json and are stored with other sprite assets in sprites/. Open and Save read and write those project files; there is no image import.

Export CrustyBASIC data writes the asset out as CrustyBASIC source — the graphics bytes and their metadata — into a read-only panel and copies it to the clipboard. It is not a .cbi include and it does not touch src/: paste it into the source file that should own the data.

Tilemap Editor (C64 and NES)

Opened from Tools → Tilemap Editor, or by double-clicking a tile map in the Explorer. The C64 tile editor opens for the C64 family and the NES tile editor opens for NES, but the workflow is the same three layers in both:

  1. Atomic tiles — the smallest graphic the hardware can draw.
  2. Metatiles — a rectangle of atomic tiles assembled into a reusable piece such as a platform, pickup, or wall, carrying the game meaning for that piece.
  3. Map canvas — paint metatiles across a grid that can be larger than one screen, which is what makes a scrolling level out of the same small vocabulary.
C64 NES
Atomic tile An 8 × 8 glyph from the charset you load for the map An 8 × 8 CHR pattern edited in the workshop, with pixel values 0–3 (the two CHR bitplanes, not fixed colours)
Colour Comes from the charset the map refers to Four background sub-palettes you edit from the complete 64-entry PPU colour table; palette slot 0 is the shared universal background colour
Metatile meaning Collision (Empty, Solid, One way, Ladder, Hazard) and Gameplay role (Platform, Wall, Floor, Ceiling, Pickup, Checkpoint, Spawn, Goal, Decoration) An attribute palette of 0–3
Default map size 40 × 25 metatiles, matching one character-mode screen at 1 × 1; editable up to 160 × 100 32 × 30 metatiles, matching one nametable screen at 1 × 1; editable up to 256 × 120
Exported include Metatile arrangements, map values, collision values, and gameplay-role values, as <NAME>_MAP, _COLLISION, _ROLE, _METATILE_OFFSETS, and _METATILE_CELLS CHR pattern bytes, the four background palettes as 16 PPU colour bytes, the map table, attribute bytes, and a generated LOAD_<NAME>_CHR procedure

Metatile sizes come from a fixed menu of 1 × 1, 2 × 1, 1 × 2, 2 × 2, 3 × 2, 2 × 3, and 4 × 2 tiles in both editors. Delete a metatile and every map cell using it is cleared, with no confirmation and no undo. The C64 starter project ships Empty, Solid, and Pickup; the NES one ships Blank, Solid, Edge, and Corner. Paint an Empty or Blank metatile over a cell to clear it, and remember that an untouched cell and an Empty cell both export as metatile 0, so your renderer should treat metatile 0 as nothing.

Three consequences are worth planning for:

Both editors scroll their own canvas with zoom buttons, a Fit action, and a Grid toggle; there is no pan tool and no undo, so save before reshaping a map. Resizing discards any cells that fall outside the new bounds.

Tile maps use .c64tilemap.json or .nestilemap.json and are stored in tiles/. Both editors open the editor's own JSON project; neither imports PNG or binary graphics. The C64 editor (and the Atari 7800 one) also offers Import rows from text, which fills the map from a pasted block of rows so a level authored elsewhere can come in as text; the NES editor has no text import. The C64 charset is chosen from the charsets already in the project. Help → Tilemap Editor Guide covers the step-by-step workflow and the NES attribute-grid behaviour in more depth.

Atari 7800 Tilemap Editor

Opened from Tools → Tilemap Editor in an Atari 7800 project. It is a different editor from the C64 and NES ones because the hardware is different, not a port of them.

What it edits. MARIA 320A indirect ("character") screens: a fixed 40 × 24 area of 8 × 8 one-bit tiles, exactly what crustyBASIC's hardware tile display gives you with TILE_BEGIN 1, 1. A project holds several named screens, so a set of levels is one asset. Tile 0 is the blank tile and is not editable: an undefined slot in the character bank reads as background, which is what makes an empty map cell empty.

Colour is per row, not per tile, and the palettes belong to the project. The chip has eight palettes of three colours each, shared with sprites, and a 320A tile shows entry 2 — the middle colour — of the palette it selects, so a tile's colour belongs in the middle slot; entries 1 and 3 are still real palette state. The table lives in the project manifest (<project>.crusty) under atari7800ColourRam as eight rows of four entries: entry 0 is the one shared background register ($20), and entries 1 to 3 are the bytes SET_PALETTE index, c1, c2, c3 writes. The panel shows the background once, above the eight rows, because the rows cannot disagree about a register the chip holds only one of. The Sprite Workshop edits the same table, so a colour change applies to the whole project and is stamped into every 7800 sprite and tile map asset. A project written before entry 0 was stored carries atari7800Palettes, eight 3-colour rows, which is read and promoted by filling the shared background in; a 7800 asset written before the table moved to the project carries four rows, which are padded out to eight with the defaults and kept. Because a tile cannot carry its own palette into the display list, the row is split into palette runs; the editor computes them from the palettes your tiles ask for, and marks the rows that need more than one run with a colour band above them - a single-palette row has no boundary to show, so it stays clean. Palette 0 is usually the tile and text palette for the rest of the screen, so leave it alone unless you mean to change every tile, text and sprite that uses it. Entry 0 is project state that the panel previews with, but the generated include writes only the eight rows of three colours through SET_PALETTE; it does not write $20, so set the shared background register in the program.

The display-list budget is the constraint to watch. A row has 26 bytes for tile and sprite objects together. A palette run costs 5 bytes and reaches 32 columns, so a plain 40-column row costs 10 bytes and leaves room for four sprites — exactly the machine's SPRITE_MAX_COUNT. Extra runs eat that headroom. The Budget panel reports the busiest row's bytes, the sprite room left on it, and the ROM each screen costs, and the editor flags a row whose runs will not fit. Nothing stops an export, because only you know how many sprites that row will show.

Stamps are reusable rectangles of tiles (up to 4 × 4) painted with one click. They are expanded to plain tile IDs when you paint, so the saved map and the export contain only tile numbers and the game needs no metatile renderer — on the 7800 the map is copied straight to the screen with TILE_BLIT.

Markers place objects in tile coordinates: spawn, goal, enemy, pickup, and checkpoint. They are exported as an x, y, kind table per screen with a CONST per kind, so a game asks for the spawn point instead of agreeing on a magic number. Markers are never written into the map itself.

The export is one .cbi include in src/. It carries the tile pictures as CONST glyphs with TILE_DEFINE calls, the project's eight palettes as data with an INSTALL procedure, one map per screen with its palette runs and markers, and a <SCREEN>_SHOW procedure per screen. Its symbols are named after the include file, as the sprite include's are — lunar-levels-tilemap.cbi gives LUNAR_LEVELS_TILEMAP_INSTALL and LUNAR_LEVELS_TILEMAP_LEVEL_1_SHOW — so two maps exported from one project stay apart. Because one program should have exactly one writer of the palette registers, a game that uses both 7800 includes calls the tile map's INSTALL and never the sprite include's <INCLUDE>_SET_PALETTES. A game needs:

@OPTION TILE_ACTIVE_COUNT 16
@INCLUDE "my-map-tilemap.cbi"

PROC MAIN
    DISPLAY MARIA_320A
    TILE_BEGIN 1, 1
    ATARI7800_TILEMAP_MY_MAP_INSTALL
    ATARI7800_TILEMAP_MY_MAP_LEVEL_1_SHOW
ENDPROC

The include reports the @OPTION TILE_ACTIVE_COUNT the program needs, which is the number of tiles in the bank. Set it yourself: it is a compile option, so the include cannot set it for you.

Limits worth knowing. The tile area is fixed at 40 × 24, so PAL's extra rows are not addressable and the 160 modes are not available — a phase-2 job that needs compiler support. The editor imports its own JSON, not PNG tile sheets, but Import rows from text fills the map from a pasted block of rows. Tile maps use .atari7800tilemap.json and live in tiles/.

Charset/Tile Editor

One tool serves two sides, and the active build target decides which one opens: the Charset Editor on the C64 family, and the Tile Editor on the Atari 8-bit and 5200. Both sides edit 8 × 8 glyphs and both save into charsets/; they differ in what those glyphs mean and how they reach the machine. A glyph is the 8 × 8 shape data, a tile is a game-level ID that pairs a glyph with a colour, a tileset is a collection of glyphs, and a tilemap is the layout of tile IDs.

C64 Charset Editor

Available for the C64, C128, and Plus/4 targets. It edits the computer's character memory as 256 glyphs, each 8 × 8 pixels, and it is a C64-family tool throughout: the palette it writes names VIC-II registers, so the same asset is expected on a C128 or Plus/4 as on a C64. The NES equivalent is the CHR editor inside the NES Tilemap Editor; there is no charset editor for Apple II or the other targets.

Choose Standard or Multicolour before drawing, because the two modes are different shapes:

Switching mode converts all 256 glyphs at once, and the conversion is lossy: standard to multicolour keeps only whether a pixel pair was lit, and multicolour to standard loses which of colours 1, 2, or 3 each cell used. There is no undo, so choose the mode before investing in art.

Import. Open charset accepts a classic C64 .64c binary dump or a saved charset project. A binary's two-byte load address is read and shown, eight bytes per glyph are decoded, and a shorter set is padded to 256 glyphs with blanks. The parser rejects anything under one glyph, data that is not a whole number of eight-byte records, and more than 256 glyphs, each with its own message. A binary is converted into an editable .c64charset.json project rather than being edited in place, and the load address is not kept in the project. The editor's Open dialog decodes a binary in the mode you are currently in, while importing the same file from the Explorer or by dropping it always decodes as standard, so set the mode first if you are importing a multicolour dump. Charset projects require exactly 256 glyphs; a hand-edited file with fewer will be rejected as invalid.

Export. Export charset include writes a .cbi into the project's src/ folder for the glyph range you choose. The defaults are glyphs 64 to 127, and the help beside the fields explains why the range matters: exporting all 256 would blank the letters and digits the ROM font still supplies. The generated file starts with the asset marker, then one packed array for the whole range — CONST <SYM>(<count × 8 - 1>) AS U8, wrapped at sixteen bytes a line so it stays readable and diffable — and <SYM>_INSTALL_CHARSET, which calls CHARSET_COPY_DEFAULT and then CHARSET_INSTALL_RANGE first, count, ADDR <SYM>. Multicolour charsets additionally get the stored palette as <SYM>_COLOR_BACKGROUND, <SYM>_COLOR_1, <SYM>_COLOR_2, and <SYM>_COLOR_CELL constants, a <SYM>_SET_PALETTE routine that writes $D021–$D023, and <SYM>_FILL_CELL_COLOR, which writes the cell colour into colour RAM once for the whole screen (a scrolled display swaps two screen pages that share one colour RAM, so a per-cell colour would be left behind). A standard charset emits no palette block, because its two colours come from the screen registers.

Assets use .c64charset.json and are stored in charsets/. Help → Charset/Tile Editor Guide covers the drawing workflow, the screen palette, the .64c import, and how to choose an export range in more depth.

Atari Tile Editor

Available for the Atari 8-bit and Atari 5200 targets. The same editor opens with the heading Tile Editor and edits tile glyphs rather than a text character set:

Export. Export tile include writes a .cbi into the project's src/ folder. It contains one packed glyph array — eight bytes per glyph, one bit per pixel — and a <ARRAY>_DEFINE_TILES(FIRST_TILE AS U8) procedure that emits TILE_DEFINE FIRST_TILE + n, ADDR <ARRAY> + n*8 for each glyph. There is deliberately no charset-install code and no palette code for Atari: the include only binds glyphs to tile IDs, and the game sets their colours.

Glyph order must match tile-ID order. Glyph n in the asset is the glyph that _DEFINE_TILES binds to FIRST_TILE + n, so keep the pictures in the order the game expects. The Atari side is used with @OPTION TILE_BACKEND TILE_BITMAP and the TILE_DEFINE, TILE_COLOR, and TILE_PLOT calls.

A charset include records the machine it was generated for in its @crusty-asset marker (target=c64 or target=atari8), and an export refuses to replace a generated include that belongs to a different machine.

Assets use .atari8tiles.json and are stored in tiles/; the earlier .atari8charset.json spelling still opens. Help → Charset/Tile Editor Guide covers the tile workflow and the C64 charset side in more depth.

Portable Shapes Editor

Tools → Portable Shapes opens an editor for the one kind of art that is not tied to a machine: a named set of 8 × 8 one-bit pictures, saved as shapes/*.shapes.json. It is offered for every target, because there is nothing machine-specific about it.

Every other art asset is a machine format — a C64 sprite is 63 bytes of a 24 × 21 grid, a NES sprite is a tile plus attribute bytes, an Atari glyph is eight bytes of one bit per pixel — so none of them can be shared by a project that builds for several machines. crustyBASIC has a machine-neutral form underneath those: TILE_DEFINE id, ADDR shape and SPRITE_DATA_8X8 id, ADDR shape take the address of eight plain bytes, and each target's own runtime lays them out for its hardware. A shape set is exactly those bytes, with names.

Export writes a .cbi into the project's src/ folder with one CONST array per shape and nothing else — no install routine, no palette code, no TILE_DEFINE. That is the point of it: the include compiles on every target. The generated file records target=portable in its @crusty-asset marker, so a portable export replaces its own previous output from whichever machine is active and never a machine-specific charset or sprite include.

A machine that wants art the portable set cannot express keeps its own asset kind beside it — sprites/c64-ship.c64sprite.json next to shapes/art.shapes.json, say — and that machine's per-target include chooses which one the game loads. See Multi-machine projects.

Sound Editor

Available for the C64 family, NES, the Atari 7800, and the Amiga. One project is one sound effect, and the editor adapts its controls to the sound hardware of the current target, which the header names: C64 SID for the C64 family, NES APU for NES, Atari 7800 TIA for the 7800, or Amiga Paula for the Amiga. The target is not switchable inside the editor, and opening a sound asset adopts the asset's own target, so a C64 recipe opened from a multi-machine project is still edited as a C64 sound.

The Amiga side is a work in progress and switched off by default. It is behind Amiga editors in Settings → Toolchain; turn that on to use it. A shipped build does not offer it, so an Amiga project never leads you into an editor that is still being built.

Import reads the editor's own JSON project only — .c64sound.json, .nessound.json, or .amigasound.json. There is no .sid, GoatTracker, or raw-binary import here. Fields missing from an older file are filled with defaults, so it loads rather than failing, and an invalid file reports Invalid sound project file.

Assets use .nessound.json for NES, .c64sound.json for C64, and .amigasound.json for the Amiga, and are stored in sound/. Only the JSON forms are editable projects: a dropped .c64sound, .sound, or .sfx file is copied into the folder and listed in the manifest, but it does not open in the editor.

Music Workshop

Available from Tools → Music Workshop for every target, because it is a portable four-lane tracker built around the generic CrustyBasic sound API rather than one machine's sound chip. That is also its limit: only the SFX player and the two chip-specific editors get at real hardware registers.

On an Amiga project the Workshop is a work in progress and switched off by default. The Paula instrument designer, the sample import and the Amiga player inside it are still being finished, so the menu greys the Workshop out for that machine until Amiga editors is turned on in Settings → Toolchain. Every other machine's side of the Workshop is unaffected, and so is creating, building and running an Amiga game.

The portable song can be tested against NES, C64, and other CrustyBasic targets. The separate SID Music Workshop remains the place for full SID register authoring, filter design, and chip-model-specific composition. Help → Music Workshop Guide walks through building a song here, its imports, and what the exported player carries.

SID Music Workshop

Available for the C64, C128, and Plus/4 targets. It is a three-voice tracker designed around the SID's musical workflow, and it is the only editor here that writes real SID register behaviour.

A project is data plus a player; your game still has to include and call the player routine. The companion technical notes in docs/SID_MUSIC_WORKSHOP.md explain the hardware model and player design in more depth, and Help → SID Music Guide is the step-by-step tutorial.

The oscillator waveform picker

The Oscillator waveform grid is the same control in the Sound Editor's C64 patch and in the SID Music Workshop's instrument tab, and it is the part of the interface that raises the most questions. It is not a menu of sixteen unrelated sounds. It is the SID's waveform selector, and each button is one of the sixteen states its four waveform bits can be in, with each bit switching one generator on. The glyphs are shorthand for those four generators:

Button Generator Control bit Register What it does
△ Triangle 4 $10 Soft and hollow, the quietest of the four. The only waveform ring modulation can reach.
╱ Sawtooth 5 $20 Bright and buzzy: every harmonic at once, which is why it bites.
▯ Pulse 6 $40 Hollow and reedy; Pulse width decides which harmonics are missing, and $800 is a square.
▦ Noise 7 $80 A hiss with no pitch. This is the chip's percussion, not a note.
∅ None - $00 Silent: no generator selected, so the gate opens and the envelope shapes nothing.

A + between glyphs means those generators run at the same time, not one after another. The chip combines their outputs — it ANDs them bit by bit — so △ + ╱ is a shape that neither a triangle nor a sawtooth makes on its own, which is where the SID's characteristic hybrid timbres come from. The grid is always in the same order: the four plain waveforms, then the eleven combinations, then ∅ Silent:

△ · ╱ · ▯ · ▦ · △ + ╱ · △ + ▯ · ╱ + ▯ · △ + ╱ + ▯ · △ + ▦ · ╱ + ▦ · ▯ + ▦ · △ + ╱ + ▦ · △ + ▯ + ▦ · ╱ + ▯ + ▦ · △ + ╱ + ▯ + ▦ · ∅ Silent

As control-register bytes, in that same order: $10 $20 $40 $80 $30 $50 $60 $70 $90 $A0 $C0 $B0 $D0 $E0 $F0 $00. Those are the values the Live register view shows while a tune plays, and the values a .sid import reads back into a patch, so a byte in a register dump can be matched to the button that produced it.

Combinations are not a gimmick. Measured over 117 classic tunes and 1.26 million gated voice-frames, 2.3% of gated frames select more than one waveform, 19% of the tunes do it somewhere, and the commonest are △ + ▯ ($50) and ╱ + ▯ ($60). A lead you cannot reproduce with a single waveform is usually a combination.

Four things are worth knowing before you pick:

How generated includes are protected

Every workshop that writes crustyBASIC data writes it into src/ as a .cbi include, and they share one safety rule. A generated include starts with a marker comment such as:

' @crusty-asset kind=tilemap name=platform.cbi target=c64

When you export over an existing .cbi, Crusty Studio reads that marker and decides:

Charset includes also record the machine they were generated for (target=c64 or target=atari8), and an export refuses to replace a generated include that belongs to a different machine.

Two exporters do not write a marker yet, and their output is therefore treated as hand-written on a second export. The NES Tilemap Editor's include and the SID Music Workshop's exact register player (SIDEX_*) both carry a normal header comment but no @crusty-asset line, so exporting over the same file again is refused. Delete the old file or export under a new name; the guard is behaving as designed, it is the marker that is missing.

That rule exists because a generated include once overwrote a hand-written level.cbi. If you want to own a file by hand, use a name you never export to. Refusals are reported in Output rather than inside the workshop, so look there if an export appears to do nothing.

Create a complete song: Neon Patrol

This walkthrough makes a short original tune called Neon Patrol. It uses the same practical ideas associated with classic C64 players—small reusable patches, three-channel counterpoint, rapid arpeggios, and parameter motion—without copying a commercial melody or data.

1. Start with a musical brief

Before touching the tracker, decide what each voice must contribute:

Voice Job Register Rhythmic idea
1 Bass and occasional kick C-2 to A-2 Root on beats, rests between phrases
2 Lead C-4 to A-4 Four-note call and answer
3 Harmony, echo, and percussion C-3 to G-4 Chord stabs on alternate rows

Set the tune to 125 BPM, Speed 6, PAL, and start with the 6581 model. Use a 32-row pattern as one phrase. The important constraint is not to fill every cell: rests make room for transients and make the three lines readable.

2. Create the tune file

  1. Open a C64 project and choose Tools → SID Music Workshop.
  2. The Workshop starts blank. Select New tune when you want to create a new music asset, then keep the default sid-tune.c64music.json name or enter neon-patrol.c64music.json.
  3. In the song strip, set Tune to Neon Patrol, add your name in Author, set BPM to 125, and set Speed to 6.
  4. The tracker is intentionally empty. For a reference arrangement, open a music asset such as neon-patrol-starter.c64music.json from the Explorer.

3. Make the bass patch

Select Instrument 1 and set:

Select Audition patch. If the note is too soft, raise sustain; if it blurs into the next note, lower gate length or release. The pulse motion is the animated part of the patch—keep it subtle on bass so the rhythm remains solid.

4. Make the lead patch

Select Instrument 2 and choose:

Audition the patch again. Vibrato is deliberately delayed by the instrument's vibrato-delay setting, so the start of a note stays clear before the pitch begins to move.

5. Add a percussion patch

Select Instrument 3, choose Noise, and make it short:

The wave program turns the noise transient into a pulse almost immediately. That is a compact way to imply a drum without spending another oscillator or importing a sample.

6. Enter the phrase

In the tracker, each row has a note, instrument number, and optional effect for each of the three voices. The following is a useful first phrase; use the controls to enter the notes and instrument numbers, leaving unlisted cells as rests (···).

Row   Voice 1                 Voice 2                  Voice 3
00    C-2 · 1                 C-4 · 2                  C-3 · 3
04    G-2 · 1                 E-4 · 2                  G-4 · 3
08    A#2 · 1                 G-4 · 2  A47             ··· · 3
0C    G-2 · 1                 E-4 · 2                  C-4 · 3
10    C-2 · 1                 C-4 · 2                  ··· · 3
14    G-2 · 1                 D-4 · 2                  G-4 · 3
18    A#2 · 1                 G-4 · 2  V26             ··· · 3
1C    G-2 · 1                 E-4 · 2                  C-4 · 3

The table shows the accented rows. Fill the rows between them with rests first, then add passing notes only when the phrase needs more motion. A47 means a rapid root/major-third/perfect-fifth arpeggio on the lead; V26 adds a modest vibrato override to one longer note.

7. Use the third voice as a time-share

Voice 3 does not need to play harmony continuously. Put the chord stabs on rows 00, 04, 0C, 14, 1C, then use short noise snaps on rows 08, 18, and 1E. This is the time-sharing principle: the ear hears a chord, then a drum, while the chip still has only three voices at any instant.

If the arrangement feels crowded, delete notes before changing instruments. Silence is an arrangement tool, not an unfinished cell.

8. Add a second pattern and arrange the song

Choose + Pattern, rename it Neon Patrol B, and duplicate the phrase's bass and lead. Make three changes:

  1. Move the lead's final answer up one octave.
  2. Add D-2 and A-2 passing notes to the bass on rows 06 and 16.
  3. Replace two harmony stabs with noise snaps so the second phrase lifts without adding a fourth voice.

The new pattern is appended to Song order automatically. The current UI gives one order entry per pattern you create or duplicate; to repeat an earlier pattern, duplicate it or edit the order array in the saved JSON project. Turn Loop on while composing; turn it off when you want the generated player to stop and call SID.ALL_OFF at the end.

9. Shape the shared SID filter

Open the SID chip tab. Start with Low-pass, cutoff around 1180, resonance 7, and master volume 13–15. Only instruments with Route through filter participate in the filtered signal. Raise cutoff for a brighter chorus; lower it for an intro or a darker bass.

Switch between MOS 6581 and MOS 8580 while the song plays. The 6581 preview is warmer and more nonlinear; the 8580 is cleaner and more predictable. If the melody only works at one exact cutoff, simplify it—the physical chips vary.

10. Preview, refine, and save

Choose Play song and listen for three things: can you follow the bass pulse, does the lead answer itself, and does the third voice add colour without masking the melody? Use Audition patch while a cell is selected to fix an instrument without replaying the whole tune.

Save the tune to the project's music/ folder. The .c64music.json file stores notes, pattern order, instruments, effects, filter settings, chip model, clock, and loop state. Saving also adds the relative asset path to the .crusty manifest.

11. Export the player include

Choose Export .cbi. The generated include contains the flattened song arrays and a small player:

@INCLUDE "neon-patrol.cbi"

PROC MAIN
    SIDM_INIT
    WHILE 1 = 1
        SIDM_TICK
        DELAY 1
    ENDWHILE
ENDPROC

Call SIDM_INIT once. Call SIDM_TICK at the target's display refresh cadence—50 Hz for PAL or 60 Hz for NTSC, as noted in the generated file. The include writes SID.FREQ, SID.ADSR, SID.CTRL, SID.PULSE_WIDTH, gate, filter, and master-volume values. Your game loop decides when to tick the player and when to pause it for menus, sound effects, or gameplay events.

12. Make a final listening pass

Play the song in both chip models, then test it beside the game's sound effects. Leave space in the arrangement for priority sounds. A good game cue has a recognisable contour after one listen, a strong rhythm even with the lead muted, and a safe ending when its loop is disabled.

Why this method works

The SID is three oscillators plus a shared filter, but a player can rewrite those registers every refresh. Classic composers used that time dimension as part of the instrument: a patch could change waveform, pitch, pulse width, envelope, or filter after the note began. The Workshop exposes those decisions directly so you can design the player behaviour and the note data together.

For further reading, compare the Workshop's controls with the Commodore 64 Programmer's Reference Guide sound chapter, Rob Hubbard's April 1986 Electronics & Music Maker interview, and Martin Galway's released Wizball music source. These references explain the hardware and historical player techniques; Neon Patrol and the Workshop's starter arrangement are original.

How assets connect to code

Saving an asset updates the manifest so it appears in the Explorer. The compiler does not automatically draw every asset just because it exists: source code must load, include, or reference the generated data according to the target's crustyBASIC APIs.

One C64 compatibility path is built in for the Space Invaders asset set. When the expected C64 sprite and charset files exist, a build can generate generated/space-invaders-3-assets.cbi containing byte arrays for those assets. Other projects can copy the same idea with their own include files and code.

How generated symbols are named

The one rule worth following: the machine belongs in the asset's extension and in the export's symbol prefix, so the file name itself should name the thing, not the machine. sprites/c64-ship.c64sprite.json says C64 three times over — in the extension, in the generated @crusty-asset … target=c64 marker, and in the C64_SPRITE_ prefix — and exports C64_SPRITE_C64_SHIP. Called sprites/ship.c64sprite.json, it exports C64_SPRITE_SHIP, which is what the game's code wants to read.

Two machines needing different art for the same thing are simply two files, and they cannot collide because both the extension and the prefix differ: ship.c64sprite.json gives C64_SPRITE_SHIP and ship.atari8sprite.json gives ATARI_SPRITE_SHIP.

Each editor derives the symbol from a different place, which is why a name that looks fine in one is doubled in another:

Asset Symbol comes from Example
C64 sprite the asset file name ship.c64sprite.json → C64_SPRITE_SHIP
Atari 8-bit sprite the asset file name ship.atari8sprite.json → ATARI_SPRITE_SHIP
Atari 7800 sprite the asset file name (its palette loader uses the include's name instead) ship.atari7800sprite.json → ATARI7800_SPRITE_SHIP
Charset / Atari tiles the export file name art.c64charset.json exported as art.cbi → C64_CHARSET_ART
Tile map the tile map project's own name field —
Portable shapes the asset file name plus each shape's name art.shapes.json holding SHIP_A → SHAPES_ART_SHIP_A
SID music always SIDM_*; the file name appears only in the header comment —

Because the charset and tile symbols come from the export file name rather than the asset name, a charset asset is worth exporting under a name that matches it — charsets/art.c64charset.json to src/art.cbi — or the symbols stop describing the asset they came from. Sprite includes have their own rule: the name must end in -sprites.cbi.

A new asset's default name carries no machine prefix — sprite.c64sprite.json, sprite.nessprite.json, sprite.atari8sprite.json, sprite.atari7800sprite.json — because the machine is already in the extension, in the generated @crusty-asset marker, and in the exporter's own symbol prefix. Naming a file c64-sprite.c64sprite.json would export C64_SPRITE_C64_SPRITE.

Renaming an asset changes the symbols it exports, so rename it before the code uses the include, or update the code in the same pass. Re-exporting after a rename writes a new include; the old one is left alone, because it is a different generated file.

Import, drop, and file management

The Project menu remembers the ten most recently opened or saved projects. Choose one from Recent projects to reopen it; dirty files are still protected by the save-before-switching dialog. Rename project… changes the project's display name, which is stored in the manifest and shown in the Explorer and the recent-projects list; it does not rename the project folder or the .crusty file. The project title in the Explorer can also be double-clicked to rename it.

The magnifier in the Explorer heading searches the whole project; see the crustyBASIC editor for the results workflow.

In the desktop app, choose File → Import into project or drop files onto the editor. Source files and .cbi includes go into src/ and open as tabs; a source file that is already inside the project is moved there after a confirmation naming both paths, because the build only reads source under src/. Recognised assets are copied into the appropriate project folder and opened in their workshop. Other files are copied to the project root.

A module or sample dropped on the Music Workshop is imported, not copied. While the Music Workshop is on screen, dropping a .mod (or, for an Amiga project, a WAV or IFF 8SVX sample) hands the bytes to the Workshop, which owns the parse: the file is stored in music/ and the song on screen is replaced by the imported one. A second module dropped over the same window replaces the song again, and a module that cannot be read is reported above the editor with the reason instead of leaving the old song silently in place. Dropping a module does not need a project open first.

A music file dropped on the SID Music Workshop is imported instead of copied. While that Workshop is the one on screen, dropping a .mid, .midi, .sng or .sid file anywhere on it runs the matching converter and opens the result as a new tune, the same as Import in its header. The drop cue appears while the file is over the Workshop, and says which formats it takes. Everywhere else a dropped file keeps the routing in the table below — a .sng dropped on the editor is still copied into music/ as an asset, and .mid and .sid are not project assets at all.

Dropping a project file is not an import. A .crusty is the project itself, so dropping one anywhere on the window closes what is open and opens that project — the same thing Project → Open does (and File → Open file… also opens a .crusty as a project), and it is the shortest way to move between projects. The drop overlay names the project it is about to open while the file is over the window. If files are dropped alongside the project they are left where they are, and Output reports how many were ignored; import them afterwards if you want them in the new project. A .crusty chosen through File → Import into project is still imported rather than opened, because that command asked for an import.

Neither route gets past unsaved edits: the switch asks about them first, as described below.

Recognised source extensions are .cbs, .cbi, and .bas. Everything else is routed by extension into the matching project folder and opened in its workshop where there is one:

Family Extensions accepted Destination
C64 sprites .c64sprite.json, .c64sprite, .sprite.json, .sprite sprites/
NES sprites .nessprite.json, .nessprite sprites/
Atari 8-bit sprites .atari8sprite.json, .atari8sprite sprites/
Atari 7800 sprites .atari7800sprite.json, .atari7800sprite sprites/
Apple II graphics .apple2graphics.json, .apple2graphics, .apple2lores.json, .apple2lores sprites/
C64 charsets .64c, .c64charset.json, .c64charset, .charset.json, .charset charsets/
Atari 8-bit tile glyphs .atari8tiles.json, .atari8tiles (earlier .atari8charset.json, .atari8charset) tiles/
Tile maps .c64tilemap.json, .c64tilemap, .nestilemap.json, .nestilemap, .tilemap.json, .tilemap tiles/
Portable shapes .shapes.json, .shapes shapes/
Sound .c64sound.json, .c64sound, .nessound.json, .amigasound.json, .sound, .sfx sound/
Music .crustymusic.json, .c64music.json, .c64music, .music, .mod, .sng, .ftm, .fmt music/
Samples (Music Workshop) .wav, .8svx, .svx music/
Source .cbs, .cbi, .bas src/
Anything else project root

An imported file from elsewhere is copied to the destination folder under its own name. An asset that is already inside the project keeps its path, but a .cbs or .cbi is different: project source belongs under src/, so a source file imported from elsewhere in the project folder is moved there after a confirmation naming both paths, and an open tab follows it. Sprite, charset, and tile-map imports then open in their workshop, so you can carry on editing straight away. Two kinds of file are converted on the way in: a C64 .64c charset binary becomes an editable .c64charset.json project with its load address read and its glyphs padded to 256, and a module or sample dropped on the Music Workshop is read and opened rather than merely copied (see above). Other music files are copied as they are and listed in the manifest; the GoatTracker .sng conversion is a separate step performed from inside the SID Music Workshop. Only a .cbs can become a project's entry point, so an imported .cbi is never recorded as main.

Folder pickers remember where they were last used, so importing a second file from the same folder opens the picker there rather than at the projects root. A save inside a project is the exception: it stays within the project's own folder, because a source file written elsewhere would be outside the tree the build reads.

When switching projects, the app asks whether dirty source files should be saved first. The files are listed one per row, each with its own tick box and all of them ticked, because the answer is often neither "save everything" nor "save nothing": a demonstration you mean to keep goes in beside an experiment you do not. The button says how many will be written, files left unticked are discarded when the switch goes ahead, and Cancel abandons the switch entirely — nothing is written and the open project stays exactly as it is. A file that has never been saved has no path to show, so its row says so, and ticking it asks where to put it; cancelling that file chooser cancels the switch too. Project → Open, Recent projects, dropping a .crusty and Project → New all ask the same question in the same way. The Explorer rescans every project subfolder, so files added externally appear automatically; source files added under folders such as src/includes/ are available as editor tabs. If an open file changes outside the app, the editor offers a choice to reload the disk version or keep the current in-app version, and the tab is marked with a blue ↻ while the choice is outstanding. Files that are not open are compared against the project as it was when it was opened, so an external edit to a text file such as crustybasic.config.toml or an .asm listing is marked in the Explorer too; opening or reloading the file clears the mark. The app's own saves, exports and builds never count as external changes, and hidden files such as the compiler's transient check sources are ignored.

Keyboard shortcuts and zoom

Action macOS Windows/Linux
New file ⌘N Ctrl+N
Open file ⌘O Ctrl+O
Save file ⌘S Ctrl+S
Close tab ⌘W Ctrl+W
Undo ⌘Z Ctrl+Z
Redo ⇧⌘Z Ctrl+Shift+Z
Find ⌘F Ctrl+F
Settings ⌘, Ctrl+,
Build ⌘B Ctrl+B
Build and run ⌘R Ctrl+R
Format casing ⌘⇧U or ⌘⇧P Ctrl+Shift+U or Ctrl+Shift+P
Go to declaration or usages Hold ⌘ and click, or F12 Hold Ctrl and click, or F12
Increase code size ⌘+ or ⌘= Ctrl+Plus or Ctrl+=
Decrease code size ⌘− Ctrl+−
Reset code size ⌘0 Ctrl+0

You can also hold Command or Control and scroll over the code editor to zoom code without changing the rest of the interface. The status bar's code-size button (for example Code 14px) shows the current editor size and resets it when clicked.

Help inside the app

Help → Crusty Studio Guide opens this guide in an editor tab. Four companion guides cover one workshop each in depth:

Headings in each guide are clickable through the table of contents, and the help tab has its own scroll area. Help → Crusty Basic Help opens the language documentation shipped beside the configured compiler. Use the Studio Guide for app workflow and the Crusty Basic reference for language syntax and runtime APIs.

Help → About Crusty Studio shows the version, the build date and commit the running copy was made from, the versions of the external tools it can drive, and the emulators it knows about — the quickest way to tell which build you are actually running and why an emulator is not being found.

Troubleshooting

Build says crustyBASIC is missing

Open Tools → Settings → Toolchain, choose the compiler executable, and run Check installation. If the compiler is installed beside the project tools, leave the field empty and let the automatic search try common locations and PATH.

Build says VASM is missing or too old

Install the VASM CPU builds the targets need and point VASM folder at the folder holding them. The VASM page has the 6502 build under Last release binaries — take the build for your operating system from there — and this site publishes the 6502, Z80 and 6809 builds for macOS and Linux, and the 6502 and Z80 ones for Windows, in Getting VASM. A ZX Spectrum, MSX or Game Boy build needs vasmz80_oldstyle; a C64, NES or Atari build needs vasm6502_oldstyle; a Color Computer build needs vasm6809_oldstyle, which has no Windows download because the author publishes none, so on Windows build it from the source archive. An Amiga or Atari ST build needs vasmm68k_mot, which is the Motorola-syntax module rather than one of the oldstyle CPU builds, so it is built from the same source archive with CPU=m68k SYNTAX=mot; it is not published as a download here. Put them in tools/assemblers/vasm/<platform>/ beside the compiler, or in any folder of your own, and select that folder as VASM folder; keep the names they arrive with, because the downloads carry the platform they were built for (vasm6502_oldstyle_linux-x86_64, vasm6502_oldstyle_macos-universal, vasm6502_oldstyle_windows-x64.exe) and only this platform's file is accepted. If only one CPU build is present, Run reports the missing one by name. It rejects versions below the supported 2.x line.

Build says NASM is missing or too old

Only the x86 targets need NASM: dos16, h120, pcjr, tandy1000, linux64 and win64. Install NASM 2.x from nasm.us, or with brew install nasm on macOS or sudo apt install nasm on Debian and Ubuntu, then select the executable as NASM assembler. The other targets build with VASM alone, so a missing NASM does not stop a C64, NES or Atari build.

Run cannot find an emulator

Add the folder containing the emulator in Emulator folders, not only a shortcut or unrelated ROM folder. Nested folders are searched. Confirm that the target directive matches the emulator you installed; for example, C64 uses VICE while NES uses Mesen, FCEUX, or Nestopia.

Run started Retro Virtual Machine but the game is not loaded

That is expected. Retro Virtual Machine takes no program on its command line, so Run can only start it on the right machine. Drag the built file shown in the output panel onto the emulator window to load it. See Retro Virtual Machine cannot load a program. If MAME is installed for the same target, choosing it in the Target menu makes Run load the program itself.

Run says MAME cannot load the ROM set

MAME ships no ROMs and needs a BIOS set for every system. The message names the set, the folder it looked in, and the files that belong inside it. Put the set there and run again; the check is repeated, so a set added after the message is picked up without restarting. If the sets live somewhere else, point MAME ROM folder in Toolchain settings at that folder.

Run says VICE cannot load the ROM images

VICE's packages on Linux and BSD are built without the copyrighted ROM images, so a machine whose images were never supplied cannot start. The message names the folder VICE searched and says which machine's subfolder to use; put the images there — /home/you/.local/share/vice/C128 for the C128, for example — and run again. VICE names each image it still wants, and Run prints that, so a partial set can be completed a file at a time — except on the C128, whose start-up check requires every language variant of its KERNAL and character ROM, so it needs the machine's whole ROM folder. A C64 that runs while the C128, VIC-20 or Plus/4 does not is the same cause: the C64 folder is populated and the other machine's is not. The VICE builds for macOS carry the images, which is why it works there. VICE's own log is at ~/.local/state/vice/vice.log.

Run says VICE exited immediately and no emulator window appeared

The output panel carries VICE's own explanation now, and the two it gives are worth telling apart. Couldn't load ... ROM, or Machine initialization failed, is a missing ROM set — see Run says VICE cannot load the ROM images. AUTOSTART: Error - No idea what disk image format to use is a machine with no drive: autostarting a PRG builds a temporary disk image, and the drive type is what tells VICE which format to build. Run supplies -drive8type 1571 for the c128 family, whose emulator defaults to no drive, so this only appears if that choice is overridden from outside the studio — by a drive type in VICE's own settings, or a -drive8type of your own.

The target card is wrong

Check the first @OPTION TARGET or @OPTION SYSTEM line in the active source. The value is case-insensitive, and the part before a dot determines the family. Save the file if you want the project manifest and future sessions to retain the intended target.

A workshop is unavailable

The Tools menu offers each editor for the machines the project builds for, not only for the machine currently being built, so a multi-machine project can open the C64 Sprite Workshop while the Amiga is active. When more than one machine in the project has the same editor — several of them have a sprite editor — the entry opens a submenu naming them, and the machine currently being built is listed first. When only one machine has it, the entry opens it directly and says which machine in brackets if that is not the active one. An entry is greyed out only when no machine in the project uses that editor at all; adding a machine in the Target menu brings it back.

Which machine each editor is for: C64 Sprite, Sound (SID), and SID Music are for the C64, C128, and Plus/4 targets; NES Sprite and Sound (2A03) are for NES; the Sound Editor also serves the Atari 7800 (TIA) and the Amiga (Paula); the Charset/Tile Editor serves the C64 family and the Atari 8-bit and 5200 (the machine chosen picks which side of it opens), and the Tilemap Editor covers C64, NES and the Atari 7800; Atari Sprite is for the Atari 8-bit and 5200; Atari 7800 Sprite is for the Atari 7800; Apple II Graphics is for Apple II; the Music Workshop and the Portable Shapes Editor are machine-neutral and are offered for every target.

Opening an existing asset from the Explorer always works too, because a saved asset records the platform it was authored for — the file's own extension decides which editor opens it, whatever machine is active.

An export refuses to overwrite my file

That is the generated-include check working. Crusty Studio only replaces a .cbi that carries the @crusty-asset marker line with a matching kind; anything else is treated as hand-written source and left alone. Export to a different name, or delete the file after checking its contents. The refusal message appears in Output, not in the workshop.

An editor has no undo

The C64 Sprite Workshop is the only asset editor with an undo history. The Tilemap Editor, the Charset/Tile Editor (both the C64 charset side and the Atari tile side), the Portable Shapes Editor, the sound and music editors do not, and neither do the NES, Atari, Atari 7800, or Apple II graphics editors. Saving before a large change is the practical safety net, and a workshop auto-saves an asset that was opened from the project. Deleting a metatile clears the map cells that used it, resizing a tile map discards what falls outside the new bounds, and switching a charset between standard and multicolour converts every glyph.

Text is still too small

Use Tools → Settings → Appearance for the complete interface. For code only, hold Command or Control while scrolling over the editor, or use the code-size keyboard shortcuts. Larger panels scroll internally, and the sidebar and Output panel can be resized.

Procedure navigation finds nothing

Confirm that the declaration and the calling source are part of the same project and that the include path is written relative to the including file. Target-specific include patterns are supported; if several target files define the same procedure, Crusty Studio may show a chooser. Navigation removes the declaration itself and duplicate path representations, but it does not treat an unrelated source file containing the same text as a valid reference.

macOS blocks a compiler helper

The tool check reports when Gatekeeper has quarantined a helper. Review that download and follow the exact command shown in the diagnostic to clear the quarantine attribute, then run Check installation again.

The browser preview behaves differently

Browser preview is intentionally safe and limited: it uses simulated tool checks and a simulated C64 build, keeps files in browser memory or downloads workshop assets, and cannot launch native emulators. Use the desktop app for the complete filesystem and toolchain workflow.

A practical project recipe

For a small retro game, this sequence works well:

  1. Create a project with the target you intend to ship.
  2. Keep src/main.cbs as the entry point and split reusable routines into additional source or include files.
  3. Configure crustyBASIC, VASM (and NASM for an x86 target), and the emulator before designing too much content.
  4. Create graphics, charset, sound, and music assets in the target-specific workshops.
  5. Save assets into the project so the manifest tracks them.
  6. Copy or include the generated byte data in code, then make the game explicitly draw or play it.
  7. Build often, read Problems first, and use Output for the full compiler command and messages.
  8. Use Build and run for the target emulator, and let the next run replace the emulator process the studio started before changing emulator settings or switching projects.

The Studio is the organiser and feedback loop around your game. crustyBASIC remains the language and runtime, the assembler turns the compiler's output into machine code, and the emulator provides the target machine's final test.