Web64 logo Web64 Documentation Web64 IDE User Manual

Web64 IDE User Manual

Version: 2026-07-27

Copyright (c) 2026 Mika Jussila, Siteledger Solutions Oy

Contents

  1. What Web64 IDE is
  2. Web64 principles
  3. The IDE workspace
  4. Projects and persistence
  5. The virtual filesystem
  6. Settings tab
  7. Source editing and compilation
  8. Web64 C compiler and virtual headers
  9. Web64 fixed-point math
  10. Assembler reference
  11. Includes, imports, symbol files, and binary data
  12. Macros
  13. Building, loading, and running programs
  14. Disk mastering and project media
  15. The embedded Web64 runtime
  16. Debugger
  17. Live patching
  18. Character editor
  19. Sprite editor
  20. Block editor
  21. Map editor
  22. SID tracker
  23. SID editor
  24. Generated include files
  25. Snapshots and state
  26. Keyboard, joystick, and gamepad input
  27. Recommended workflows
  28. Troubleshooting
  29. Reference tables

Dedicated manuals: Web64 C Compiler User Manual, Web64 IDE SID Tracker User Guide

What Web64 IDE is

Web64 IDE is a browser-native Commodore 64 development environment built around the Web64 VICE port. It combines a compact assembly editor, a virtual project filesystem, a 6502 assembler, browser-local graphics asset editors, an embedded C64 runtime, live patching, and debugging tools into one web application.

The IDE is designed for short feedback loops. You can edit assembly source, create or modify character sets and sprite banks, build a PRG, load it into the embedded emulator, and run it without leaving the browser. When live patching is enabled, many code and asset changes can be written into the running emulator memory so the effect can be inspected without restarting the entire program.

Web64 IDE is separate from the root Web64 emulator page. The root emulator is intentionally lightweight and focused on running existing C64 media. The IDE is a development surface with editors, project state, diagnostics, and debug metadata. The IDE can use the same browser runtime, but the IDE should not be treated as part of the core emulator UI.

Web64 principles

Web64 IDE follows a few practical principles:

The IDE workspace

The IDE is organized into a compact multi-pane layout:

The Code tab is split between the editor and the embedded emulator. The split and sidebars are resizable. The emulator panel can be collapsed completely when the editor needs the full vertical work area; the reveal button remains in the source tab header.

The title-bar groups reuse the asset-editor toolbar colors: purple for project files, green for source files, blue for PRG output/runtime actions, and olive-yellow for documentation. Hover an icon for its command name. Save PRG uses the download icon and remains grouped with Load PRG and Start PRG. The scale icon opens the Web64 IDE License and sits immediately left of the question-mark icon for the IDE manual.

The global status bar remains at the bottom of the IDE. Its activity indicator distinguishes idle, compiling, diagnostic checking, running, paused, SID preview, build-error, and emulator-ready states. PRG size and compiler statistics describe the latest completed compiler snapshot; file, line, and asset totals describe the current project model. Hover a field to see its exact meaning and the unrounded PRG byte count.

The tab line is intentionally compact. The current tab groups are:

The License button opens docs/web64-ide-license.html in a new tab; that page displays the canonical docs/WEB64_IDE_LICENSE_v1.0.md license text and links to the Markdown version. The Help / Documentation button opens docs/web64-ide-user-manual.html in a new tab. Dedicated HTML manuals for the C compiler and SID tracker are also available in public/docs, alongside the Markdown manuals and the IDE PDF.

Projects and persistence

Web64 IDE projects are saved as .web64proj files. A project file is JSON with enough information to recreate the browser-local project state.

A project stores:

The project file does not store:

Opening a project

Use the project open button in the top toolbar. If the browser supports the File System Access API, Web64 IDE can open a file handle directly. Otherwise, use the standard file input fallback.

Opening a project replaces the current source, virtual file tree, selected entry label, workspace selection, emulator-panel collapse state, and persisted emulator settings. Current runtime state is cleared from the IDE model. Version 3 projects reopen the stored project-tree file and restore the stored source selection and scroll position after validating that the referenced files still exist. Older projects and projects with stale workspace paths remain supported and use the normal default-source selection with the emulator panel expanded. The complete project state is also promoted immediately to the full-build snapshot, so the byte count, C/ASM symbols, line maps, entry list, autocomplete build symbols, and runtime metadata all belong to the project that was opened rather than the previous project.

Saving a project

Use the project save button. Web64 IDE writes a .web64proj file through the browser file picker when available. If direct file saving is unavailable, it downloads the project file.

Before saving, the IDE flushes pending character and sprite draft edits into the virtual filesystem. Block sets, maps, and .w64sid tracker files also expose explicit save commands because they maintain draft state and derived output records. This is important: the saved project should contain the current bytes or source records visible in the asset editors, not stale data from a previous commit.

Saving source and PRG files

The source save action saves only the currently edited source text. The PRG save action compiles the current source and virtual files, then saves the generated PRG bytes.

Settings tab

The Settings tab is in the Workspace group. It centralizes IDE preferences without making the root Web64 emulator load IDE-only panels or project state.

Every interactive setting and reset action has a descriptive tooltip. Hover the setting label, selector, slider, or button to see what it controls before changing it.

Settings are split by owner:

The Editor section can disable syntax highlighting while keeping the source text visible. Line numbers, wrapping, active-line highlighting, trace-line highlighting, source mode, theme, and the optional Generated Assembly workspace can be adjusted and reset. Enabling Generated Assembly only makes its Code-tab selector available; it does not materialize or format generated instructions until that selector is opened.

The Assistance section controls autocomplete. Disabling automatic suggestions still allows explicit Ctrl+Space invocation when explicit completion remains enabled. Include, member, snippet, and build-symbol suggestions can be toggled independently.

The Build & Diagnostics section controls background compilation, auto diagnostics, compile-on-save, debounce profile, severity filters, stale-result policy, and visible diagnostic density. Source/project imports always replace the structural build snapshot immediately. While editing, a tiny syntax worker checks only the current line after a short delay. A separate disposable semantic worker starts only after the configured idle interval, returns diagnostics only for that edited line, and is terminated immediately if typing resumes. Blurring the editor, saving, running, explicitly refreshing diagnostics, or making a structural project change flushes the current draft and requests a full build in the project compiler worker. If a newer project revision arrives while that worker is busy, the obsolete worker is terminated rather than allowed to queue in front of the current edit. Older project/settings files that contain the previous compileOnImport preference remain accepted. Manual compile/run/save-PRG actions remain available when live compilation is disabled or the background compile snapshot is stale.

The Compiler and Optimizer sections update the browser-native Web64 C configuration. Entry and include paths must be project-relative virtual paths. Native cc65/cl65 executable paths and host include paths are not supported settings.

Use Reset All to restore defaults, Export to download a .web64settings JSON file, and Import to load a settings JSON file through the browser picker.

Use project save when you want to preserve the entire IDE state. Use source save or PRG save when you only want a single artifact.

The virtual filesystem

The virtual filesystem is the core of Web64 IDE project structure. It is a browser-local tree of files that the assembler can resolve by project path.

Virtual files can be:

Paths use forward slashes. For example:

assets/chars/title.chr
assets/sprites/player.spr
assets/blocks/tiles.blk
assets/maps/level1.map
assets/music/song.w64sid
includes/common.asm
symbols/kernal.sym

The virtual path is the path used by .include, .import, and .incbin. It is not required to match an absolute host filesystem location.

Adding files

The Files panel has actions for:

When you add .chr, .ch8, .spr, .blk, .blocks, .map, .w64map, .sid, or .w64sid files, Web64 IDE converts them into the matching editable/inspectable asset records where possible and creates generated include records for supported asset types. Text-like files become source records. Other files become binary records.

Project tree interaction

The Imports tree renders folders and files in depth-first parent-child order. Each folder shows its basename at the appropriate indentation level, and its direct files appear before the next sibling folder. Collapsing a folder hides only that folder's descendants.

A single click selects a file. Selection exposes the Rename and Remove actions without changing editors for editor-backed assets. Double-click .chr/.ch8, .spr, .blk/.blocks, .map/.w64map, .w64sid, or .sid files to open the matching Character, Sprite, Block, Map, SID Tracker, or SID Editor view. Source files retain their direct single-click code-editor behavior.

Creating virtual files

Use New in the file tree. The extension determines the initial type:

Path resolution

When resolving an import path, Web64 IDE tries practical project-local candidates:

If a file cannot be found, the compiler reports an import diagnostic. If the same base name is ambiguous, use a more specific virtual path.

Why a virtual filesystem is needed

Browsers cannot freely read arbitrary host paths. A line such as this should not depend on a real host file outside the project:

.include "C:/Users/Somebody/project/includes/defs.asm"

Instead, add the file into the virtual tree and refer to it by project path:

.include "includes/defs.asm"

This keeps .web64proj portable and makes the project usable on another machine.

Source editing and compilation

The Code tab contains the main source editor. It uses a CodeMirror 6 viewport editor with incremental C/C++ parsing and a lightweight 6502 assembly language mode. Only the visible source window and a small overscan region are represented by line elements, so opening or scrolling a large file does not create one DOM row per source line. Syntax highlighting, line numbers, folding, bracket matching, selection drawing, history, lint markers, and breakpoint gutters are native editor extensions rather than synchronized full-document overlay layers.

Each edit is applied to CodeMirror immediately and remains in an editor-local draft while the user is typing. It does not wait for React project serialization, autocomplete, diagnostics, or compilation. Blurring, saving, running, opening Generated ASM, or another explicit project action flushes the latest draft without moving the caret or losing a trailing empty line. Pasted blocks, autocomplete choices, Find/Replace, formatting, context-menu edits, and generated Table/Matrix/Macro/File output use one minimal document transaction, making the operation responsive and atomic for Undo.

Wheel and scrollbar movement use the editor's native pixel scroll position without line snapping. Virtual line rendering reaches the real first and final document lines, including a trailing empty line. Editor measurement is recalculated after resize, wrapping, and browser zoom changes, so the caret and rendered text retain the same coordinate system.

The editor stores the complete editing session, including history, selection, caret, folding state, horizontal scroll, and vertical scroll, separately for every root source, virtual project source/header, and bundled SDK header. Moving to another source file, Generated ASM, or an asset editor and returning restores the previous file view and Undo history without reusing stale event subscriptions. Saving a project persists the selected tree file and the current text document's view state. Opening another project clears the previous project's in-memory positions before restoring the positions carried by the opened project, so files with the same name cannot inherit unrelated state.

Editor toolbar

The source toolbar is available for editable source/header files and read-only bundled headers:

Toolbar navigation and find selections update the same per-file view state used by ordinary caret movement, so their locations survive a trip to another file or asset editor.

Editor context menu

Right-click the source editor to open commands for the captured caret or selection. The menu provides Go to definition, Go to implementation, Show generated ASM, source and project save commands, insertion tools, clipboard commands, Select all, and Format selection/document. Generated ASM is disabled in the menu until the optional Generated Assembly setting is enabled. Read-only SDK headers disable editing commands but retain navigation, copy, selection, and Generated ASM inspection.

Navigation searches the current draft, project source/header files, and bundled SDK headers only when requested. Go to implementation prefers a C function body or assembly label over a declaration. Go to definition also resolves macros, types, constants, labels, global/local declarations, include lines, and compiler source mappings. A target in another project file or SDK header opens that document at the resolved line.

The menu displays the corresponding editor shortcuts:

CommandShortcut
Go to definitionF12
Go to implementationCtrl+F12
Show generated ASMAlt+F12
Save / Save asCtrl+S / Ctrl+Shift+S
Save Project / Save Project asCtrl+Alt+S / Ctrl+Alt+Shift+S
Cut / Copy / Paste / Select allCtrl+X / Ctrl+C / Ctrl+V / Ctrl+A
Format selection/documentShift+Alt+F

Insert > File reference selects a project path and can insert an automatic reference, quoted path, C #include, assembly .include, or assembly .incbin. Automatic mode chooses a source include for headers and an .incbin reference for binary assets in assembly.

Insert > Macro is language-aware. In C source it emits the object-like #define NAME replacement form supported by Web64-C. In assembly it opens a macro-definition editor with a name, comma-separated parameters, multiline body, and Custom, Poke byte, Set border, and Wait for raster templates. The result uses .macro NAME parameters and .endmacro; parameter references may use the recommended \name form and %%name creates an invocation-local label. If source is selected before opening the context menu, that selection becomes the initial custom macro body and is replaced by the completed definition when inserted.

Insert > Table opens the offline lookup-table generator. It generates sine, cosine, triangle, saw, square, ramp, easing, reciprocal, and multiplication tables. Waveforms expose amplitude, center, phase, cycle count, and the square-wave duty cycle where applicable. Ramp and easing tables expose start/end values and the easing curve. Reciprocal tables expose the input range and numerator. Multiplication tables use independent A and B ranges and dimensions and store their result in row-major order.

Cyclic waveforms sample index / count, excluding the repeated final endpoint. A 256-value full turn therefore maps exactly to web64_angle8 values 0..255; the next value would repeat the first and is not stored. Ramp, easing, reciprocal, multiplication, and other non-cyclic ranges include both configured endpoints. If any actual reciprocal input sample is zero, generation stops with an error that identifies the sample. A range may cross zero when its selected sample count does not land on zero.

Insert > Matrix generates practical 6502 transformation tables rather than an interactive linear-algebra document. Presets include 2D rotation and scale, 2D affine transforms, individual 3D X/Y/Z rotations, combined Euler rotations in all six application orders, perspective and orthographic projection, identity matrices, and custom 2x2 through 4x4 matrices. Generated matrices use row-major storage and column-vector mathematics. For example, the first 2D result is x' = m00x + m01y + tx. XYZ Euler order means apply X, then Y, then Z and therefore emits Rz Ry Rx. Affine generation applies scale, shear, rotation, then translation. Rotation ranges can be cyclic; non-cyclic ranges include their final angle. Right-handed coordinates are the default and 3D/projection presets offer left-handed output.

Both generators support uint8_t, int8_t, web64_fix8_8, and web64_ufix8_8. Web64 8.8 output uses the compiler's exact scale-256 representation and nearest rounding with half ties away from zero. The default overflow policy is Error. Clamp saturates at the selected type's limit and Wrap explicitly keeps the representable storage bits. Generated C is a flat typed constant array, optionally static, with count/dimension macros and an automatically added <stdint.h> or <web64/fixed.h> include when needed. Separate matrix layout emits one array per coefficient. Assembly byte output uses .byte; 8.8 output supports .word, interleaved little-endian .byte pairs, or separate low/high byte planes.

Generators may be opened only at C file scope because they emit declarations. Assembly insertion is unrestricted. Each insertion is limited to 16 KiB of encoded data. The Preview tab uses canvas plots, multiplication heatmaps, matrix values, transformed shapes, and a sample scrubber. Projection recalculation follows React's deferred update path, and complete source text is formatted only when the Output tab is selected or Insert is pressed. Field input therefore remains independent from preview and large-text generation. Insert applies one minimal CodeMirror transaction and can be reverted with one Undo. The inserted metadata comment records generator version, preset, number type, layout, encoding, endpoint policy, and overflow policy. Generated output is ordinary editable source, not a project asset or hidden compilation stage.

The compiler output updates the Inspector:

Compilation is browser-local. No native assembler is started. Incremental highlighting stays inside the viewport editor; immediate syntax checks, idle semantic diagnostics, autocomplete, and full project compilation use separate workers so none of those tasks owns the input path. The syntax worker checks the edited line without compiling a project. After typing becomes idle, a disposable semantic worker may compile the draft but publishes only diagnostics whose path and source line match the edit; typing again terminates it. The project compiler produces complete build snapshots after editor blur, save, explicit refresh, Generated ASM activation, and structural project changes. It implements latest-revision-wins cancellation, so obsolete full builds are terminated instead of queued.

Normal compiler responses contain executable bytes, symbols, diagnostics, build metadata, and a compact source/address map. Large imported/generated source strings and rich per-instruction debugger structures are not copied to the UI after every build. The PRG byte buffer is transferred rather than cloned. Rich instruction maps remain in the worker and are materialized only when Generated ASM is open. Opening a source/project, creating a preset, importing, adding, renaming, removing, or clearing a build file replaces the complete full-build snapshot. Load, Start, and Save PRG flush and compile the exact current draft for that action, so a lagging background result cannot block or replace the requested build. Assembly source and Web64 C source both compile through the browser-local build graph; generated C assembly is handed to the same Web64 assembler pipeline as handwritten assembly.

For a selected C source file, the Inspector projects generated labels back onto the original virtual C file. Header/preprocessor expansion line offsets are removed for navigation, so selecting a C symbol or mapped line opens the corresponding original project path and source line. Assembly line maps remain byte-exact and C mappings resolve through their generated symbol addresses.

Generated Assembly machine view

Generated Assembly is an optional, read-only projection of the current compiler snapshot. Enable Generated Assembly in Settings > Editor, then use Source | Generated ASM in the Code workspace. It is not a project asset, compilation stage, editable assembly document, or second source of truth. C or ASM source remains authoritative and the generated machine bytes remain the executable output.

The feature has a demand-driven lifecycle:

Rows are formatted only for the currently visible virtualized window. The view does not build or render a complete assembly text document. Address, encoded bytes, source location, and instruction columns can be toggled independently. Search runs in the viewer worker. Select rows to copy their formatted representation. Switching back to Source opens the mapped file at the selected generated row's originating line; double-clicking a mapped row or clicking its Source cell performs the same navigation immediately. Opening Generated ASM from a C line uses its exact generated row when available. Non-emitting control-flow continuation, brace, blank, or optimized-away lines resolve to the nearest preceding mapped instruction in the same source file instead of resetting the generated view.

For Web64 C, source provenance follows lowering through generated instructions and final machine addresses. For handwritten ASM, the same projection correlates source lines directly with assembled instructions. The toolbar revision identifies the compile snapshot represented by the rows; a pending newer compilation is marked stale until its replacement projection arrives.

Click an instruction gutter to toggle a source/provenance breakpoint when source mapping exists. That breakpoint is stored by provenance and remapped to the corresponding generated address after live recompilation. If its exact generated instruction no longer exists, it becomes unresolved instead of remaining attached to unrelated code. Shift-click creates an explicit machine breakpoint; it always means the selected 16-bit address and never moves after recompilation.

While execution is paused, the current PC is published through an isolated debugger subscription. The matching generated row is highlighted and revealed without placing per-step PC state or the instruction collection in broad IDE state. Step advances the paused runtime and updates only this execution projection. Source highlighting changes when the PC reaches an instruction with different source provenance.

Autocomplete

The source editor provides browser-local completions for Web64 C and adjacent assembly authoring. Press Ctrl+Space in the editor to open suggestions immediately, or type at least two non-whitespace identifier characters to refresh ordinary identifier suggestions. Struct member completion is requested from the editor transaction on the next paint frame after . or ->; it does not depend on keyboard-layout-specific keyup ordering, and the two-character identifier threshold does not apply after the receiver has already selected a struct.

Completions are built from the current project source, project and imported headers, bundled read-only virtual headers, generated assets/generated.h, C keywords, Web64 runtime helpers, local variables and parameters, struct members, assembly labels, and the latest successful build symbols. Generated and bundled header entries are read-only source truth; project and imported headers come only from the project virtual filesystem.

Member lookup follows declarations rather than a hardcoded hardware-name list. It indexes tagged and typedef structs, qualified typed values and pointers, pointer-cast object macros, and simple macro aliases across the active file and its bundled/project header indexes. For example, SID-> resolves the c64_sid_registers declaration and VIC-> follows the VIC alias to the typed VICII macro. Their menu entries include declarations such as voice1_freq_lo: volatile c64_reg8_t and border_color: volatile c64_reg8_t. Project struct values, qualified pointers, and typed global macros use the same path; pointer declarations are recognized whether the star is adjacent to the type or the variable. Comment removal preserves source length and line breaks, so comments before the caret cannot move member lookup away from the expression being edited. Chained expressions retain their declared member type, so a receiver such as state->child. offers only the members of child when its struct type is known. Variable and member rows show the resolved declaration type followed by provenance instead of the generic variable - local label. Member lists are returned directly as typed completion results and allow up to 64 type-filtered entries, while ordinary suggestions keep the smaller generic limit.

The completion popup keeps a full selected-item tooltip above its scrollable rows. The tooltip wraps the complete symbol or member name and shows its resolved type/signature and provenance, even when the compact list row must use an ellipsis. Up/Down selection updates the tooltip immediately; moving the pointer over a row selects it and displays the same full detail.

Automatic identifier suggestions wait for an idle typing interval and run in a persistent completion worker. Project/header/build context is configured only when that context changes; an ordinary completion request sends the active document, caret, and settings rather than rebuilding every project index on the UI thread. The worker uses a bounded lexical window and per-document caches, while member completion and explicit Ctrl+Space retain the typed scope/struct information needed for accurate results. Scroll, resize, focus, selection-only, composition, and navigation events do not trigger completion indexing. C comment suppression uses the editor's incremental syntax tree on the input path instead of rescanning the entire file.

Automatic and explicit completion remain closed while the caret is inside a C line comment or block comment. Comment markers inside string and character literals do not suppress completion.

Autocomplete does not search host include paths, run native cc65/cl65, use a native language server, or read absolute host filesystem paths. When parsing is incomplete, the editor keeps partial suggestions available instead of treating the file as empty.

Origin

The project origin determines the default load address if the source does not set one explicitly. The default project source begins at $c000.

You can also set an origin in assembly:

* = $c000

or:

.org $c000

Entry point

The IDE looks for likely entry labels such as:

start
entry
_start
main
mainloop
init

You can also select an entry label manually. ASM-only Start uses a BASIC SYS command with the selected start address, preserving the normal C64 call frame for routines that end in RTS. C and C/assembly hybrid projects use the direct program-start bridge and automatically prefer the generated _start entry. _start calls the configured C entry and parks after it returns; auto-start does not jump directly to _main, whose trailing RTS would otherwise return to BASIC.

Web64 C compiler and virtual headers

The standalone compiler manual lives at Web64 C Compiler User Manual. Use that document when you want the compiler and SDK reference without the rest of the IDE manual.

Web64 IDE includes an experimental browser-native C compiler named Web64 C v0.1. It is intended for small C64 programs, hardware register setup, asset-driven game code, and mixed C/assembly projects inside the Web64 virtual filesystem.

The C compiler is not cc65. It does not use cc65 object files, linker scripts, or a native toolchain. Web64 C lowers supported C modules into Web64 assembler-compatible source, then the existing browser-local assembler produces the PRG, symbols, line maps, diagnostics, and memory ranges.

When the optional Generated Assembly workspace is enabled and opened, the IDE exposes that lowering result as structured, read-only instruction rows with encoded bytes, addresses, and original C source provenance. Display formatting and lookup indexes are created lazily for the active compile revision; enabling the setting alone does not add projection work to live C editing. Source breakpoints use stable lowering provenance where available and are remapped after regenerated addresses move. See Generated Assembly machine view for the complete workflow and machine-breakpoint distinction.

C project model

A C project is made from virtual project records, not host paths. Typical records are:

main.c
include/game.h
assets/generated.h
assets/sprites/player.spr
assets/maps/level1.map

The build graph classifies .c files as C modules, .h files as C headers, assembly files as assembly modules, and asset files as binary or generated records. C modules are ordered before assembly modules so generated C labels and handwritten assembly labels share the final assembler symbol namespace.

For a normal C project, keep the root main.asm empty. Web64 C emits its own startup label that calls the configured C entry function. If you deliberately write your own assembly startup instead, call the C entry yourself, for example jsr _main for a C function named main. Do not leave an unrelated default main.asm program in a C project, because it is assembled after the generated C startup and can make the project appear to start in the wrong code.

C labels are emitted with underscore names. A C function named main becomes _main in assembly. Direct calls to normal C functions emit jsr _name; calls to names that already start with asm_ are treated as direct assembly labels.

C configuration

The default C configuration is:

{
  "enabled": false,
  "dialect": "web64-c-v0.1",
  "entry": "main.c",
  "includePaths": ["include", "assets"],
  "stdout": "screen",
  "stdin": "keyboard",
  "runtimeProfile": "tiny",
  "compilerBackend": "web64-native"
}

Supported stdout and stdin backends are screen, kernal, debug, and none. Use none for freestanding routines that do not need the tiny runtime I/O shims. runtimeProfile currently supports tiny and freestanding.

Supported C subset

Web64 C v0.1 is a freestanding C-like subset with an explicit 6502 data model. Its implemented surface includes:

Undeclared identifiers and undeclared custom functions are errors. Calls to the bundled runtime and names beginning with asm_ retain their legacy implicit-call behavior for existing projects. Unsupported statements, types, signatures, and annotations produce diagnostics instead of being silently accepted.

Constant operands and constant conditions

Typed byte expressions with a compile-time constant use specialized lowering for +, -, &, |, and ^. Commutative operations accept the constant on either side and use a 6502 immediate operand; value - constant uses immediate SBC. constant - value uses an order-preserving sequence and may need one byte temporary, but it avoids the generic stack spill. The constant may be a literal, numeric define, or folded constant expression. This lowering also applies when the dynamic operand is a function result, so it does not use the stack or a zero-page temporary merely to hold the constant. Byte identities such as value + 0, value - 0, value | 0, value ^ 0, and value & 0xff retain evaluation of value but omit the redundant arithmetic instruction.

Constant conditions are folded when the typed expression is side-effect-free. while (true) therefore emits the loop body and back edge without loading and testing 1. Calls, volatile accesses, and other observable expressions are still evaluated; for example, while (rand()) retains the call and conditional branch.

Non-literal 16-bit comparisons now materialize and compare both operand bytes for ==, !=, <, <=, >, and >=. Signed ordering biases both high bytes before comparison, while unsigned ordering compares them directly. Eight-bit integer operands receive the target's normal signed int promotion before mixed-width ordering, including int16_t with uint8_t and int8_t with int16_t. Compound word operands such as target > position + 256 carry through the high byte correctly. Byte-only comparisons retain their immediate and single-byte fast paths.

For example:

while (true) {
    *((uint8_t*)VIC_BORDER) = rand() & 0x0f;
}

lowers to the following core sequence. Generated label suffixes vary by source location.

__web64_c_while_...:
    jsr _rand
    and #0xf
    sta 0xd020
    jmp __web64_c_while_...

The immediate-operand and constant-condition rules are part of typed C lowering and do not require enabling an optional optimizer pass.

Switch statements

Web64 C supports compact integer switch dispatch for state machines, menus, and asset-kind routing. The compiler emits a linear compare/branch sequence and generated labels such as __web64_c_case0_... and __web64_c_switchend_...; it does not pull in the cc64 runtime switch helper or cc65 code movement.

uint8_t mode;
uint8_t color;

void main(void) {
    switch (mode) {
        case 0:
            color = 6;
            break;
        case 1:
            color = 14;
            break;
        default:
            color = 1;
            break;
    }
}

Case labels must resolve to numeric constants or defines. Duplicate case values and duplicate default labels are errors. Fallthrough is preserved when a case omits break.

Pragmas, attributes, and calling conventions

Web64 C uses a registry for pragmas and attributes so source annotations either map to a known Web64 behavior or produce a stable diagnostic.

Supported or recognized forms are:

FormCurrent behavior
#pragma web64 ...Recognized as a Web64-owned control surface and reported with c-pragma-web64-registry until a specific behavior-changing control is implemented.
#pragma warn(...) and #pragma cc64 ...Compatibility no-op warnings.
#pragma optimize(...)Recognized as an optimization-control request; project build settings and optimizer trace remain authoritative.
#pragma bss-name, data-name, rodata-name, code-nameRejected with c-unsupported-pragma; Web64 does not execute native cc65 segment/linker behavior.
_fastcallAccepted as a source-level hint for currently supported declarations/functions.
cdecl / __cdecl__Rejected with c-cdecl-deferred; the native cc65 stack ABI is not implemented.
interrupt / __interrupt__Rejected with c-interrupt-deferred until vector and register preservation policy is implemented.
__attribute__((unused))Diagnostic-only recognized annotation.
__attribute__((noreturn))Diagnostic-only control-flow annotation.

Unknown attributes are errors. This is intentional: Web64 C should not suggest that cc65 or hosted C annotation semantics are active unless the browser-local compiler has executable support for them.

Include resolution

C includes are virtual includes. These forms are accepted:

#include <stdint.h>
#include <c64.h>
#include "include/game.h"
#include "assets/generated.h"

The resolver rejects absolute host paths such as C:/..., /home/..., \\server\..., and file: URLs. Put headers into the Web64 virtual filesystem and include them by project-relative path.

Preprocessing is intentionally bounded. Conditional groups preserve line positions for diagnostics, and bundled headers use include guards so repeated or transitive includes do not duplicate declarations. This is not a complete translation-phase implementation: token pasting, stringification, variadic macros, and general host-compiler preprocessing remain outside the current subset.

assets/generated.h is generated from project asset records at compile time. It exposes deterministic C-safe start/end labels, _size constants, and numeric _kind constants. Asset records keep their browser-local generated-record placement metadata; no native linker script or object segment is introduced.

Bundled virtual headers

Web64 C bundles a small read-only SDK. These headers are resolved from web64://c/include/... before project-local include lookup, except assets/generated.h, which is regenerated from the current project asset records during the build.

HeaderIncludes
stdint.hExact 8/16/32-bit aliases, intptr_t/uintptr_t, intmax_t/uintmax_t, least/fast aliases, and supported integer limits. The 32-bit aliases are primarily storage and constant-expression types; the callable ABI is limited to 8/16-bit values.
stddef.h16-bit size_t, signed 16-bit ptrdiff_t, and NULL.
stdbool.hbool mapped to _Bool, true, false, and __bool_true_false_are_defined. Stores to _Bool normalize to exactly zero or one.
string.hExecutable tiny-runtime implementations of memchr, memcmp, memcpy, overlap-safe memmove, memset, strcat, strchr, strcmp, strcpy, strcspn, strlen, strncat, strncmp, strncpy, strpbrk, strrchr, strspn, and strstr.
stdio.hExecutable puts/putchar plus restricted compiler-lowered printf; unsupported file I/O and fprintf/sprintf names are absent from callable headers.
stdlib.hExecutable abort, 16-bit abs, whitespace/sign-aware decimal atoi, byte-valued rand, and seedable srand; RAND_MAX is 0x00ff.
ctype.hExecutable ASCII isdigit, isalnum, isalpha, isspace, islower, isupper, tolower, and toupper.
libc.hAggregate convenience header that includes stddef.h, stdio.h, stdlib.h, string.h, and ctype.h; it contains no CC64 fixed-address declarations.
c64.hC64 memory map constants, typed register structs, pointer aliases, VIC-II, sprite, SID, CIA, keyboard, joyport, color, screen, and KERNAL constants.
conio.hcc65-familiar console/color macro shim over Web64 screen/stdout helpers.
joystick.hcc65-familiar joystick macro shim over the Web64 joyport helper surface.
6502.hCPU memory-access and simple opcode macro shim through Web64 inline assembly.
cbm.hCBM/KERNAL constant shim; native disk driver and object ABI behavior remain out of scope.
joy.hJoystick helper declaration for joy_read().
sprite.hSprite helper declarations for sprite_enable(), sprite_set_pos(), and sprite_set_color().
web64.hSmall Web64 helper declarations for delays, random byte reads, screen clearing/cursor output, text output, byte plotting, and zero-parameter void calls by address.
web64/assets.hWeb64 Asset Model v1 public typedefs, kind/mode/flag macros, immutable descriptor structs, and mutable runtime instance structs.
assets/generated.hGenerated C declarations for project assets plus deterministic _size, _kind, and implemented typed descriptor constants.

The headers are intentionally compact. Every function declared by the bundled standard-library headers above has either an executable dependency-selected runtime implementation or the documented compiler lowering. Including them does not make the full cc65 or hosted C library available. Runtime-owned parameter slots are emitted once for a whole multi-module build, so repeated guarded includes and calls from multiple translation units do not create duplicate labels. The SDK header tree in the IDE opens these files as read-only web64://c/include/... documents.

Standard headers use extern function declarations because the implementation is outside the header. extern does not assign a fixed address. Calling memcpy, for example, selects web64-runtime/string.asm; including string.h without calling a string function adds no string runtime code. libc.h is retained for source compatibility as an aggregate include, but new code may include the specific standard headers it uses.

Web64 fixed-point math

web64/fixed.h provides the browser-native Web64 fixed-point math surface. The public storage types are ordinary integer-backed C types so they can live in globals, structs, arrays, project files, generated assembly, and debugger metadata without a native object ABI.

TypeStorageMeaning
web64_fix8_8signed 16-bit word8 integer bits and 8 fractional bits, scale 256.
web64_ufix8_8unsigned 16-bit wordUnsigned 8.8 values, scale 256.
web64_fix16_16signed 32-bit long16.16 constants, conversions, floor/to-int/fraction, and wrapping add/sub macros only.
web64_ufix16_16unsigned 32-bit longUnsigned 16.16 storage with the same macro-only v1 policy.
web64_angle8unsigned 8-bit byteOne full turn is 256 units: 0=0, 64=90, 128=180, 192=270 degrees.

Common 8.8 constants and conversions are macros. They do not pull fixed runtime modules by themselves.

NameBehavior
WEB64_FIX8_ONE, WEB64_FIX8_HALF, WEB64_FIX8_MIN, WEB64_FIX8_MAXFixed 8.8 constants.
WEB64_FIX8_FROM_INT(value)Convert an integer value to 8.8. Typed 8-bit inputs lower directly to a zero fraction byte and the source integer byte, without a shift loop or helper call.
WEB64_FIX8_TO_INT(value)Convert 8.8 to integer by taking the high byte.
WEB64_FIX8_FRACTION(value)Return the low fractional byte.
WEB64_FIX8_FROM_RATIO(n, d)Compile-time ratio conversion; constant zero divisors report c-divide-by-zero.
web64_fix8_floor, web64_fix8_ceil, web64_fix8_roundMacro-backed integer-style conversions.
web64_fix8_add_wrap, web64_fix8_sub_wrapWrapping 16-bit fixed add/subtract macros.

For a typed uint8_t or int8_t source, WEB64_FIX8_FROM_INT is recognized as byte placement rather than emitted as a shift sequence. The source byte becomes the integer byte and the fraction byte is cleared; the source is loaded first so assigning through overlapping storage remains correct:

    lda source
    sta destination+1
    lda #0
    sta destination

Implementation-backed 8.8 helpers are dependency-selected. Calling one helper pulls only the runtime module family it needs.

Helper familyPublic namesRuntime module
Multiply and interpolationweb64_fix8_mul, web64_fix8_mul_round, web64_ufix8_mul, web64_fix8_lerpweb64-runtime/fixed-mul.asm
Divisionweb64_fix8_div, web64_ufix8_divweb64-runtime/fixed-div.asm
Saturating/core helpersweb64_fix8_add_sat, web64_fix8_sub_sat, web64_fix8_abs, web64_fix8_min, web64_fix8_max, web64_fix8_clampweb64-runtime/fixed-saturate.asm
Trigonometryweb64_sin8, web64_cos8web64-runtime/fixed-trig.asm
Vector/motionweb64_fixvec2_add, web64_fixvec2_sub, web64_fixvec2_scale, web64_motion2d_integrateweb64-runtime/fixed-vector.asm; scale also selects fixed multiply.

The header also provides short 8.8 aliases for these implementation-backed helpers: f8_mul_round, f8_div, uf8_mul, uf8_div, f8_lerp, f8_abs, f8_min, f8_max, f8_clamp, f8_add_sat, f8_sub_sat, f8_sin, f8_cos, v2f8_add, v2f8_sub, v2f8_scale, and m2d_int. These are preprocessor aliases to the web64_* names, so they keep the same ABI, semantics, and runtime dependency selection. The f8/uf8 prefixes are reserved for the current 8.8 tier; future 16.16 helpers can use distinct 16.16 names.

Signed multiply truncates toward zero from the signed widened product. web64_fix8_mul_round rounds nearest half away from zero. Unsigned multiply selects the middle product bytes, equivalent to (a b) >> 8. Division uses a widened numerator equivalent to a 256 / b; dynamic fixed division by zero returns 0, while constant zero divisors in ratio macros are diagnostics. Overflow wraps for ordinary fixed storage and wrapping helpers; use the saturating helpers when clamp-to-range behavior is wanted. web64_fix8_lerp(a, b, amount) uses amount/256, so amount=255 is close to b but not exactly b.

The standard trig table is a deterministic 256-entry signed 8.8 full-wave sine table. web64_sin8(angle) returns the table value. web64_cos8(angle) uses the same table with a 64-unit phase offset. The exact endpoints include web64_sin8(64) == 0x0100, web64_sin8(192) == 0xff00, web64_cos8(0) == 0x0100, and web64_cos8(128) == 0xff00.

The editor's Insert > Table and Insert > Matrix tools import the same canonical quantizer used to build this compiler table. Signed and unsigned 8.8 output therefore uses identical scale-256 words and nearest/half-away-from-zero rounding. A 256-step generated sine table with amplitude 1, center 0, phase 0, and web64_fix8_8 output is bit-for-bit identical to the compiler's web64_sin8 table. Generated cyclic tables exclude the repeated turn endpoint, matching web64_angle8 values 0..255. These tools perform offline source generation only; they do not add runtime helpers, hidden assets, or program-memory metadata.

Fixed 16.16 support is intentionally smaller in v1. Use the WEB64_FIX16_* constants, integer/ratio conversion macros, floor/to-int/fraction macros, and wrapping add/sub macros for storage and constant-scale data. There are no public 16.16 multiply/divide helper names until executable 6502 helpers and differential tests exist.

Use named helpers for fixed multiply and division. Direct fixed-point operators such as web64_sin8(phase) * radius are rejected by the v1 compiler with c-unsupported-fixed-point-operator; write the operation explicitly:

#include <web64/fixed.h>

web64_fix8_8 x = WEB64_FIX8_FROM_INT(120);
web64_fix8_8 velocity = WEB64_FIX8_FROM_RATIO(3, 2);

void step(void) {
    x = web64_fix8_add_wrap(x, velocity);
    *(uint8_t*)0xd000 = WEB64_FIX8_TO_INT(x);
}
#include <web64/fixed.h>

web64_angle8 phase;
web64_fix8_8 radius = WEB64_FIX8_FROM_INT(24);
web64_fix8_8 offset;

void step(void) {
    phase = phase + 2;
    offset = web64_fix8_mul(web64_sin8(phase), radius);
    *(uint8_t*)0xd000 = 160 + WEB64_FIX8_TO_INT(offset);
}

The fixed-point optimizer has a gated specialization pass. Constant helper calls can fold to direct word materialization, and typed dynamic identity or safe power-of-two unsigned cases can lower to word copies or shifts. The runtime helper is removed only when every call to that helper is proven folded or lowered; mixed folded and fallback call sets keep the verified runtime module.

The public examples repository contains source-backed projects for this section: c-fixed-subpixel-scroll, c-fixed-sine-lerp, and c-fixed-motion. These are portable .web64proj records and are compiled by the fixed-point regression harness.

c64.h hardware definitions

c64.h is the main C64 hardware header. It provides numeric constants for pointer-style register access and typed volatile pointer aliases for common chip register blocks.

Common memory and I/O constants include:

C64_RAM_BASE
C64_BASIC_START
C64_SCREEN_RAM
C64_COLOR_RAM
C64_CHAR_ROM
C64_IO_BASE
C64_KERNAL_ROM

Typed aliases include:

c64_reg8_t
c64_cpu_port_registers
c64_screen_ram
c64_color_ram
c64_vicii_registers
c64_sid_registers
C64_CPU_PORT
C64_SCREEN
C64_COLOR
VICII
VIC
SID

VIC-II, sprite, and color constants include:

VIC_BASE
VIC_BORDER
VIC_BACKGROUND
VIC_RASTER
VIC_CONTROL1
VIC_CONTROL2
VIC_MEMORY_SETUP
VIC_SPRITE_ENABLE
VIC_SPRITE_X_MSB
VIC_SPRITE0_X
VIC_SPRITE0_Y
VIC_SPRITE0_COLOR
VIC_SPRITE_POINTER_BASE
VIC_BORDER_COLOR
VIC_BACKGROUND_COLOR
VIC_SPRITE_POINTERS
BORDER_COLOR
BACKGROUND_COLOR
SPRITE_ENABLE
SPRITE_MULTICOLOR
VIC_COLOR_BLACK
VIC_COLOR_WHITE
VIC_COLOR_BLUE
VIC_COLOR_LIGHT_GRAY

SID constants include:

SID_BASE
SID_VOICE1_FREQ_LO
SID_VOICE1_FREQ_HI
SID_VOICE1_PULSE_LO
SID_VOICE1_PULSE_HI
SID_VOICE1_CONTROL
SID_VOICE1_ATTACK_DECAY
SID_VOICE1_SUSTAIN_RELEASE
SID_VOLUME_FILTER_MODE
SID_WAVE_TRIANGLE
SID_WAVE_SAW
SID_WAVE_PULSE
SID_WAVE_NOISE
SID_GATE

CIA, keyboard, and joyport constants include:

CIA1_PRA
CIA1_PRB
CIA1_DDRA
CIA1_DDRB
CIA2_PRA
CIA2_PRB
KEYBOARD_COLUMN_PORT
KEYBOARD_ROW_PORT
KEYBOARD_COLUMN_DDR
KEYBOARD_ROW_DDR
JOYPORT_1
JOYPORT_2
JOY_UP
JOY_DOWN
JOY_LEFT
JOY_RIGHT
JOY_FIRE
JOY_RELEASED_MASK
JOY2_UP()
JOY2_DOWN()
JOY2_LEFT()
JOY2_RIGHT()
JOY2_FIRE()

Common KERNAL entry constants include:

KERNAL_CHROUT
KERNAL_CHRIN
KERNAL_GETIN
KERNAL_SCNKEY
KERNAL_PLOT
KERNAL_LOAD
KERNAL_SAVE

Joystick bits are active-low on the C64. A pressed fire button clears the JOY_FIRE bit in the sampled port value, so test for release with joy & JOY_FIRE and for press with !(joy & JOY_FIRE). The JOY2_*() macros express the same active-low tests for joystick port 2.

Web64 helper headers

The helper headers are compiler-recognized v0.1 intrinsics. They are intended for compact C64 projects and lower to direct register/KERNAL/SID reads and writes.

#include <stdint.h>
#include <joy.h>
#include <sprite.h>
#include <web64.h>

uint8_t joy;
uint8_t rnd;

void main(void) {
    clrscr();
    gotoxy(0, 0);
    cprintf("READY");

    sprite_enable(1);
    sprite_set_pos(0, 300, 100);
    sprite_set_color(0, VIC_COLOR_LIGHT_GRAY);

    joy = joy_read();
    rnd = random8();
    if (!(joy & JOY_FIRE)) {
        plot_pixel(0x0400, 42);
    }

    delay_frames(2);
    delay_ms(40);
}

plot_pixel(address, value) is byte-oriented in v0.1: pass the target bitmap, screen, color, or other memory address explicitly. delay_ms() is frame-based and approximate.

VoidCall(addr) calls a zero-parameter void function at a literal, generated, or runtime 16-bit address:

#include <web64.h>
#include <assets/generated.h>

void main(void) {
    VoidCall(title_play_address);
}

A constant address lowers to one absolute JSR. The address must refer to an initialized routine whose calling convention takes no parameters.

VoidCall is a function-like macro, so every .c translation unit that uses it must include <web64.h> itself; an include in another .c file is not project-global. A missing include produces c-implicit-function-declaration on the call line and blocks the build instead of emitting an unresolved _VoidCall. Repeating the exact macro definition produces a non-blocking c-macro-redefinition-identical warning for backwards compatibility. A different replacement definition produces the blocking c-macro-redefinition error. Warnings remain visible but do not prevent Start; only error diagnostics make a build unrunnable.

C runtime header examples

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

uint8_t lives = 3;
bool running = true;

void main(void) {
    lives--;
}

stdio.h declares puts and putchar for the tiny runtime, plus restricted compiler-lowered printf for string-literal formats with %d values. Calls are routed through the configured stdout backend. File I/O declarations and fprintf/sprintf are intentionally absent until they have executable implementations. Select stdout: none for code that should not pull in output support.

Screen and color RAM example

#include <stdint.h>
#include <c64.h>

void main(void) {
    *(volatile uint8_t *)VIC_BORDER = VIC_COLOR_BLUE;
    *(volatile uint8_t *)VIC_BACKGROUND = VIC_COLOR_BLACK;
    *(volatile uint8_t *)C64_SCREEN_RAM = 1;
    *(volatile uint8_t *)C64_COLOR_RAM = VIC_COLOR_WHITE;
}

The typed register aliases from c64.h can express chip register writes more clearly:

#include <c64.h>

void main(void) {
    VIC->border_color = VIC_COLOR_BLUE;
    VIC->background_color0 = VIC_COLOR_BLACK;
}

Joystick and sprite helper example

#include <stdint.h>
#include <joy.h>
#include <sprite.h>

uint8_t joy;

void main(void) {
    sprite_enable(1);
    sprite_set_pos(0, 80, 100);
    sprite_set_color(0, VIC_COLOR_LIGHT_GRAY);

    joy = joy_read();
    if (!(joy & JOY_FIRE)) {
        BACKGROUND_COLOR = VIC_COLOR_RED;
    }
}

SID register example

#include <stdint.h>
#include <c64.h>

void main(void) {
    *(volatile uint8_t *)SID_VOICE1_FREQ_LO = 0x11;
    *(volatile uint8_t *)SID_VOICE1_FREQ_HI = 0x25;
    *(volatile uint8_t *)SID_VOICE1_ATTACK_DECAY = 0x09;
    *(volatile uint8_t *)SID_VOICE1_SUSTAIN_RELEASE = 0xf0;
    *(volatile uint8_t *)SID_VOLUME_FILTER_MODE = 0x0f;
    *(volatile uint8_t *)SID_VOICE1_CONTROL = 0x21;
}

0x21 is SID_WAVE_SAW | SID_GATE. Use the named constants when writing project code; keep immediate values in examples when you need to stay inside the current v0.1 expression subset.

Web64 asset access from C

assets/generated.h is produced from the virtual project asset records at compile time. It is the supported C-facing contract for browser-local assets: C code can refer to deterministic labels, sizes, asset-kind constants, typed data aliases, read-only flags, and the implemented Asset Model v1 descriptor declarations/constants without hard-coding assembler names by hand.

The generated header makes asset labels visible to the C compiler and final assembler namespace. When generated C actually references an asset data label, a C-only build embeds that payload once on demand. A hybrid build reuses an existing assembly label or .incbin instead of emitting a duplicate. Descriptor- and size-only references do not embed payload bytes. Browser-local asset files remain the canonical project records; generated .inc and .sid outputs remain derived assets where the IDE pipeline creates them. SID Tracker generates one .sid binary and one .inc, not a duplicate .bin.

Slice 17 adds the public web64/assets.h Asset Model v1 header and generated typed descriptor declarations/constants for source-backed charsets/chars, sprite banks, blocksets, maps, and SID files. This currently documents and exposes the byte-exact descriptor shape; runtime helper modules, overlay-pair descriptor completion, malformed asset diagnostics, descriptor backing bytes for every planned case, and browser/emulator conformance closure remain planned until their executable evidence exists.

Supported C asset records are project-local records whose kind is one of asset, binary, blob, sprite, spritebank, spriteanimation, charset, char, tile, block, blockset, tilemap, map, sid, or source. Typical file extensions are .bin, .raw, .dat, .spr, .chr, .ch8, .blk, .blocks, .map, .w64map, and .sid. A .w64sid record is editable composition source, not a runtime C asset. Its generated .sid is selected for assets/generated.h; an older generated .bin remains a fallback only when the project has no generated .sid. Unsupported kinds produce a c-unsupported-asset-kind diagnostic instead of becoming silent zero addresses.

The built-in kind constants are provided by web64/assets.h and re-used by assets/generated.h:

#define WEB64_ASSET_MODEL_VERSION 0x0001
#define WEB64_GENERATED_ASSETS_VERSION 0x0001
#define WEB64_ASSET_ADDRESS_NONE 0x0000
#define WEB64_ASSET_REF_NONE 0x0000
#define WEB64_SPRITE_BLOCK_NONE 0xff
#define WEB64_ASSET_KIND_BINARY 1
#define WEB64_ASSET_KIND_CHAR 2
#define WEB64_ASSET_KIND_CHARSET 3
#define WEB64_ASSET_KIND_SPRITE_MONO 4
#define WEB64_ASSET_KIND_SPRITE_MC 5
#define WEB64_ASSET_KIND_SPRITE_OVERLAY 6
#define WEB64_ASSET_KIND_BLOCK 7
#define WEB64_ASSET_KIND_BLOCKSET 8
#define WEB64_ASSET_KIND_MAP 9
#define WEB64_ASSET_KIND_SID 10
#define WEB64_ASSET_KIND_SOURCE 11

Asset paths are converted into C-safe symbols from the file basename with the extension removed. Non-identifier characters become _, and names that do not start with a C identifier character are prefixed. For example, assets/sprites/player.spr becomes player, while assets/sprites/player ship.spr becomes player_ship. Duplicate basenames that normalize to the same C symbol produce c-duplicate-asset-symbol.

The physical data labels match the names generated by the IDE asset editors: <symbol>_chars for charsets, <symbol>_sprites for sprite banks, <symbol>_blocks for block sets, <symbol>_map for maps, and <symbol>_sid for SID files. Historical base names remain C aliases, so existing source using player continues to address player_sprites. The metadata contract retains the historical start/end names while also exposing the physical data/dataEnd labels to build tools.

For a sprite asset named player.spr, the generated header includes:

#define WEB64_ASSET_KIND_SPRITE_MC 5
extern const unsigned char player_sprites[];
#define player player_sprites
extern const unsigned char player_sprites_end[];
#define player_end player_sprites_end
#define player_size 128
#define player_sprites_size player_size
#define player_kind 5
#define player_data player_sprites
#define player_bytes player_sprites
#define player_readonly 1

Use the editor data label or _data/_bytes aliases when a routine needs an asset pointer. Use _size in initializers, conditions, copy loops, and bounds checks. Use _kind when one C source handles multiple generated assets.

#include <stdint.h>
#include <assets/generated.h>

uint16_t player_size_bytes = player_size;
const unsigned char *player_ptr = player_data;

void main(void) {
    if (player_kind == WEB64_ASSET_KIND_SPRITE_MC) {
        asm_load_player_sprite();
    }
}

A binary/blob asset follows the same rules:

#include <stdint.h>
#include <assets/generated.h>

const unsigned char *blob_ptr = example_blob_data;
uint16_t blob_size = example_blob_size;

void main(void) {
    if (example_blob_size) {
        asm_use_blob();
    }
}

Generated asset symbols are read-only in C. Assigning a pointer variable from an asset is supported; assigning to the asset symbol itself is not.

#include <assets/generated.h>

const unsigned char *ok = example_blob_data;

void main(void) {
    ok = example_blob_bytes;  /* supported */
    example_blob = 1;         /* diagnostic: c-readonly-asset-write */
}

For implemented Slice 17 descriptor families, assets/generated.h also includes <web64/assets.h>, declares a typed <symbol>_asset descriptor, creates a <symbol>_descriptor alias, and emits constants derived from the source asset metadata. Descriptor member reads such as title_asset.load_address lower directly to constants and do not allocate a descriptor block or embed the payload by themselves. Referencing the descriptor's data member, a generated data label, or a _data/_bytes alias selects that payload for on-demand embedding when no project assembly label already supplies it.

#include <stdint.h>
#include <assets/generated.h>

uint16_t font_chars = font_char_count;
uint8_t font_multicolor = (font_mode == 2);

uint16_t player_frames = player_frame_count;
uint8_t player_payload = player_payload_bytes_per_frame;  /* 63 visible bytes */
uint8_t player_record = player_bytes_per_frame;           /* 64 stored bytes */

uint8_t terrain_cell_bytes = terrain_bytes_per_block;
uint16_t level_cells = level1_cell_count;
uint8_t level_index_width = level1_index_width;

void main(void) {
    if (player_frames && level_cells) {
        asm_prepare_assets();
    }
}

The generated descriptor declarations have these public shapes where implemented:

extern const Web64CharsetAsset font_asset;
#define font_descriptor font_asset
#define font_char_count 256
#define font_bytes_per_char 8
#define font_mode 2

extern const Web64SpriteAsset player_asset;
#define player_descriptor player_asset
#define player_frame_count 2
#define player_bytes_per_frame 64
#define player_payload_bytes_per_frame 63

extern const Web64BlockSetAsset terrain_asset;
#define terrain_descriptor terrain_asset
#define terrain_bytes_per_block 6

extern const Web64MapAsset level1_asset;
#define level1_descriptor level1_asset
#define level1_cell_count 240
#define level1_index_width 2

extern const Web64SidAsset title_asset;
extern const unsigned char title_sid[];
#define title_descriptor title_asset
#define title_file_size 2980
#define title_c64_data_offset 124
#define title_c64_data_size 2856
#define title_c64_data (title_sid + 124)
#define title_payload (title_sid + 124)
#define title_payload_size 2856
#define title_load_address 4096
#define title_init_address 4096
#define title_play_address 4160
#define title_song_count 1

web64/assets.h defines descriptor and instance structs such as Web64CharAsset, Web64CharsetAsset, Web64SpriteAsset, Web64SpriteOverlayAsset, Web64BlockAsset, Web64BlockSetAsset, Web64MapAsset, Web64SidAsset, Web64Sprite, Web64SpriteOverlay, and Web64MapView. Web64SidAsset describes the complete PSID file and its C64 payload offset/size, load/init/play addresses, song selection, speed flags, and SID flags. Descriptor references use explicit 16-bit Web64AssetAddress payload-label addresses and Web64AssetRef descriptor-label addresses rather than public generated-label pointer initializers. Zero is reserved as WEB64_ASSET_ADDRESS_NONE/WEB64_ASSET_REF_NONE. Public kind, mode, cell-type, flag, and boolean fields use uint8_t typedefs and macros; C bit-fields and public enum ABI are deferred.

In a hybrid project, assembly may explicitly import the complete SID file under the generated <symbol>_sid label:

title_sid:
    .incbin "assets/music/title.sid"

The explicit import is optional for C-only projects because a C reference to title_payload selects title_sid for on-demand embedding. If the hybrid assembly already defines title_sid, the compiler reuses it.

#include <string.h>
#include <assets/generated.h>

void load_title_music(void) {
    memcpy((void*)title_asset.load_address,
           title_payload,
           title_asset.c64_data_size);
}

title_asset.file_size and title_sid describe the complete PSID file, including its 124-byte header. Copying that pair directly to load_address is incorrect. The generated title_payload pointer skips c64_data_offset, and c64_data_size copies only bytes intended for C64 memory.

After the SID payload has been loaded and initialized, its generated zero-parameter play address can be called directly from C:

(*(void (*)(void))title_play_address)();

Constant targets lower to one absolute JSR; runtime uint16_t targets use a self-modifying call-site operand. An undeclared target such as title_play_address without #include <assets/generated.h> produces c-undeclared-identifier during C lowering instead of becoming a late undefined assembly symbol. Indirect calls with parameters remain diagnosed because an unknown target cannot use Web64's callee-owned ordinary parameter slots.

The same rule applies to copy arguments. A fabricated name such as enemy_sprites_any_symbol_blahblah produces c-undeclared-identifier on the C source line. A rejected call emits no partial argument pushes, so an invalid copy cannot silently corrupt the 6502 hardware stack.

The generated SID .inc can be imported by the assembly side of the same hybrid project. All macros from assets/generated.h are compile-time C metadata and are not re-emitted as assembler definitions; the generated .inc remains the assembly-side definition source. This prevents duplicate symbols when a loaded project contains an older generated .inc, while C expressions still fold the current asset-manifest values. Saving the W64SID asset regenerates both derived files; legacy tracker-generated includes are recognized by their generated-file marker, while genuine manual include overrides remain preserved.

The C-facing <symbol>_size is the complete PSID file size, while the W64SID assembly include retains its established <symbol>_size payload-size meaning. Use the unambiguous <symbol>_file_size and <symbol>_c64_data_size names when code needs to distinguish the complete PSID from its C64 payload.

The kind constants are descriptor categories, not broad payload buckets: char, charset, mono sprite, multicolor sprite, sprite overlay, block, block set, map, SID, source, and binary. Web64MapCellType reserves WEB64_MAP_CELL_CHAR and defines current block maps as WEB64_MAP_CELL_BLOCK. Sprite descriptor mode describes payload encoding; mutable Web64Sprite.flags controls the VIC-II instance bit, and helpers must reject applying an instance mode that does not match the asset payload. Sprite multicolor descriptor fields are defaults/compatibility metadata for the global $d025/$d026 registers, not per-instance storage. Source-backed v1 block payloads are row-major char-index bytes only; color/attribute layers stay absent until an explicit binary layout or separate addresses are implemented.

Use memcpy to copy a referenced payload to its runtime C64 address. Graphics labels use the same names shown by their IDE-generated assembly includes:

#include <string.h>
#include <assets/generated.h>

void main(void) {
    memcpy((void*)0x2800, bumps_chars, bumps_size);
    memcpy((void*)0x3000, c_joy_sprites, c_joy_size);
    memcpy((void*)0x3100, bank_2_sprites, bank_2_size);
    memcpy((void*)0x3400, enemy_sprites, enemy_size);
    memcpy((void*)title_load_address,
           title_payload,
           title_asset.c64_data_size);
}

For explicit fixed placement, compression, loaders, or cycle-sensitive copies, assembly remains available and may define the same physical data label. The generated C projection detects that definition and does not append another copy of the asset.

Generated asset labels participate in the C build graph. The IDE build output view marks assets/generated.h as a read-only generated header and records asset-label dependencies from the header to the backing project asset. Descriptor-only access must not select asset helper runtime modules. Duplicate C-safe symbols, unsupported placement/address policies, stale generated metadata, and unsupported asset kinds are reported as diagnostics when detected.

Inline assembly example

#include <c64.h>

void wait_raster(void) {
    asm("wait:\n    lda $d012\n    cmp #$80\n    bne wait");
}

void irq_guard(void) {
    asm volatile(R"(
    sei
    lda #$00
    sta $d020
    cli
)");
}

Inline assembly is emitted directly into the generated assembly module. Supported forms include asm("..."), asm volatile("..."), __asm__("..."), adjacent C string fragments, and simple R"(...)" raw string literals. GCC-style operand constraints and named operand substitution are not part of the v0.1 subset; use constants, labels, or normal C statements around the assembly block. Keep labels unique if the inline block can be emitted more than once.

Mixed C and assembly

Use C for simple game logic and register setup, and assembly for code that needs exact addressing modes, cycle control, or fuller language support.

void main(void) {
    asm_init_irq();
}

A call to asm_init_irq() emits jsr asm_init_irq, so a project assembly file can define:

asm_init_irq:
    sei
    rts

Use this boundary for raster IRQs, music players, fast sprite movement, decompression, asset copies, and other routines that are better expressed directly in 6502 assembly.

Declaring and passing parameters to assembly routines

Web64 C v0.1 treats calls whose names start with asm_ as direct assembly calls. The C call:

asm_copy_sprite();

emits:

    jsr asm_copy_sprite

Declare the routine in a project header so the C source has a stable name, then define the same unmangled label in an .asm file:

/* game.h */
void asm_copy_sprite(void);
; routines.asm
asm_copy_sprite:
    ; assembly body
    rts

Ordinary asm_ calls do not currently marshal C formal parameters. Do not rely on asm_copy_sprite(slot, color) to place values in registers or on the stack. Pass parameters explicitly through shared globals or through an inline assembly block that sets up registers before a jsr.

For shared globals, declare the parameter storage in C and write it before the call. C globals are visible to assembly with underscore-mangled labels:

#include <stdint.h>
#include "game.h"

uint8_t sprite_slot;
uint8_t sprite_color;

void main(void) {
    sprite_slot = 0;
    sprite_color = 1;
    asm_set_sprite_color();
}
/* game.h */
void asm_set_sprite_color(void);
; routines.asm
VIC_SPRITE0_COLOR = $d027

asm_set_sprite_color:
    ldx _sprite_slot
    lda _sprite_color
    sta VIC_SPRITE0_COLOR,x
    rts

For byte-sized values, this shared-global pattern is the most explicit and debugger-friendly option. For 16-bit values, store low and high bytes in separate globals, or use a uint16_t global only with assembly that intentionally reads both bytes from the emitted label layout. Keep the ownership clear: C writes the parameter fields, the assembly routine reads or updates them, and both sides agree which labels are scratch state versus persistent game state.

When a routine must receive CPU registers directly, use inline assembly for the setup and call:

void main(void) {
    asm("    lda #$06\n"
        "    ldx #$00\n"
        "    jsr asm_set_sprite_color_reg");
}
asm_set_sprite_color_reg:
    sta $d027,x
    rts

Use the register setup form only when the C source and assembly routine are tightly coupled. For reusable routines, prefer named globals because the parameter contract remains visible in the project symbols and debugger.

Web64 C v1 expression and ABI contract

The implemented scalar expression frontend tokenizes and parses supported expressions before lowering. Supported scalar expressions include integer and character literals, identifiers, grouped expressions, casts, declared function calls, zero-parameter calls through casted function pointers, unary +, unary -, bitwise ~, logical !, binary +, -, *, /, %, <<, >>, comparisons, bitwise &, ^, |, logical && and ||, assignment expressions in supported statement contexts, constant-index array access, typed pointer dereference, and struct/union member access backed by Web64 aggregate metadata. The conditional ?: operator is not implemented.

The Web64 C v1 integer model uses 8-bit _Bool, uint8_t, int8_t, char, and unsigned char; 16-bit uint16_t, int16_t, int, unsigned int, size_t, ptrdiff_t, and pointers; and 32-bit long aliases for supported storage and constant-expression use. Plain char is signed. INT_MIN is 0x8000, INT_MAX is 0x7fff, and RAND_MAX is 0x00ff. Byte operands promote to Web64 int for ordinary arithmetic unless an explicit cast or assignment context narrows the final value. Function parameters and return values wider than 16 bits are rejected because no 32-bit call ABI exists yet.

Function calls are clobber boundaries. Ordinary C-to-C calls evaluate arguments left-to-right into byte-exact stack-backed temporaries, then pop them in reverse into the callee's statically allocated parameter slots immediately before the call. Each argument uses the declared parameter width: for example, a (uint16_t, uint8_t, uint16_t, uint8_t) call stages exactly six bytes. Byte arguments converted to word parameters are zero-extended or sign-extended according to the declared source type. Nested calls retain their own arguments and declared return width during this staging. _fastcall accepts zero arguments, one word or pointer argument in A/X, or two byte arguments in A then X; unsupported signatures diagnose. Return values use A for byte results and A/X for word or pointer results. Byte-returning compatibility helpers such as rand, putchar, and puts clear X so older callers that consumed A/X continue to work. printf supports string-literal formats with %d arguments; each value is parsed, typed, fully evaluated, and printed in format order. Unsupported specifiers and mismatched argument counts emit diagnostics.

Casted function-pointer calls support return_type (*)(void). Constant targets lower to a direct absolute JSR; runtime 16-bit targets patch a call-site operand immediately before JSR $ffff. Undeclared targets produce c-undeclared-identifier, and indirect calls with parameters produce c-unsupported-indirect-call-arguments.

Ordinary locals and parameters use static storage, so generated functions and tiny-runtime helpers are non-reentrant and are not interrupt-safe unless a helper explicitly documents otherwise. Direct and mutual recursion within a translation unit are diagnosed. Web64 reserves zero-page $02-$09 for compiler/runtime scratch and $fb-$fe for indirect argument and pointer scratch; __web64_zeropage(address) declarations that overlap those ranges are rejected and the memory-layout report exposes the ownership.

Across translation units, external functions and objects retain underscore-prefixed public labels. static definitions receive deterministic module-private labels. Multiple external definitions and conflicting assembler-visible macro definitions are errors. A configured entry must have external linkage; static main is rejected. A helper-only C build with no external entry remains a warning for compatibility with mixed projects that provide their own assembly startup.

Inline assembly is supported through string forms such as asm("..."), asm volatile("..."), and __asm__("..."). Inline assembly labels share the generated assembler symbol namespace; local labels beginning with @ are scoped by the compiler, duplicate labels diagnose, and collisions with generated C/runtime labels diagnose. Inline assembly is a clobber boundary; preserve values explicitly when crossing it.

Supported #pragma warn, #pragma cc64, and #pragma optimize forms are compatibility no-ops that produce stable warnings. Segment/linker-effect pragmas such as bss-name, data-name, rodata-name, and code-name are unsupported diagnostics because Web64 does not run a native linker script.

Optimizer safety contract

Optimizer pass gates default to off and each enabled pass reports its decision in the optimizer trace. Transformations preserve hardware-register and C volatile accesses. Duplicate load/store/compare removal and register-temporary stack rewrites require local value, flag, and control-flow safety; opaque inline assembly is a fence.

Assembly-only strength reduction and boolean-branch folding are deliberately not performed without typed proof of width, signedness, and live flags. Dead-code analysis tracks references from instructions including jsr and from assembler directives, and it preserves distinct adjacent labels. Fixed-point specialization remains a typed, gated pass: a runtime helper is removed only when every use has been proven replaceable. Cost reports expose compiler/runtime dependencies, estimated bytes/cycles, hardware accesses, and the complete reserved zero-page ownership.

Compiler compatibility appendix

CategoryStatusNotes
Parser-backed scalar arithmetic and bitwise expressionsImplementedCovered by the compiler regression suite and deterministic expression fuzzer.
*, /, %ImplementedLower through dependency-selected arithmetic helper runtime modules. Divide by constant zero and INT_MIN / -1 diagnose.
Casts and integer promotionsImplemented for the shipped scalar subsetExplicit casts preserve width/signedness; unsupported cast shapes diagnose.
Conditional preprocessing and include guardsImplemented for the bounded subsetLine-preserving conditional groups, object/function macros, #undef, and active #error are supported; token pasting, stringification, and variadic macros are not.
Declarations and linkageImplemented for the shipped scalar/aggregate subsetPrototypes, locals, parameters, extern, internal-linkage static, enums, and guarded multi-translation-unit headers are supported; duplicate external definitions diagnose.
Nested calls and _fastcall runtime callsImplementedCall results are spilled across later calls according to the Web64 C ABI contract.
printf %d materializationImplementedString-literal formats with matching %d arguments are supported.
Aggregates, member access, and pointer aliasesImplemented for fixed-layout Web64 recordsStructs use declaration-order byte offsets without implicit padding; unions overlay at offset zero. Runtime aggregate indexes remain diagnosed.
Bundled standard-library declarationsImplementedEvery callable declaration in string.h, stdio.h, stdlib.h, and ctype.h has executable runtime code or documented compiler lowering.
Typed pointersImplemented for byte/word pointees16-bit pointers load and store one or two little-endian bytes according to pointee width. Wider pointees and general pointer arithmetic remain limited.
Inline assemblyImplemented with Web64 scoping rulesGCC/ca65 operand constraints and native optimizer behavior are not implemented.
Runtime dependency selectionImplementedRuntime modules are selected from source evidence, not linked monolithically.
Recursion and reentrancyUnsupported with explicit diagnosticsStatic parameter/local slots make ordinary generated code non-reentrant; same-translation-unit call cycles diagnose.
Native cc65 objects, libraries, linker configs, and host pathsNot applicableWeb64 is browser-local and lowers to Web64 assembler-compatible source.
32-bit call ABI, interrupt functions, native cdecl, full hosted libc, floating point, bit-fields, flexible arrays, and broad C preprocessor behaviorDeferred or unsupportedThese remain explicit diagnostics or out of current scope.

Verified public examples

The governed web64-examples repository is the public example suite for Web64 C v1. c-compiler-conformance/c-compiler-conformance.web64proj prints PASS/FAIL for arithmetic, shifts, bitwise expressions, casts, nested calls, _fastcall runtime calls, and printf argument handling. tutorials/README.md links runnable projects for SDK headers, generated assets/generated.h, C64 register aliases, joystick helpers, sprite helpers, mixed C/ASM symbol behavior, runtime dependency boundaries, and unsupported-syntax diagnostics.

Assembler reference

Web64 IDE includes a browser-local 6502 assembler aimed at practical C64 development.

Labels

Labels use a colon:

start:
    lda #$00
    sta $d020
    rts

Symbols can also be assigned:

BORDER_COLOR = $d020
SCREEN_RAM = $0400

.equ, !equ, and equ style definitions are also accepted:

RASTER .equ $d012

Numbers

Supported number forms include:

$c000       ; hexadecimal
0xc000     ; hexadecimal
49152      ; decimal
%10101010  ; binary
'A'        ; one-character literal

Expressions support common arithmetic and bitwise operations:

<label      ; low byte
>label      ; high byte
value + 1
table + index * 2
(screen + 40),y

Directives

Common data directives:

.byte $01, $02, $03
!byte $04
byte 5
db 6

.word start, $c000
!word $d020
word $0400
dw $0801

.text "HELLO"
!text "WORLD"
.ascii "C64"

.fill 40, $20

Origin directives:

* = $c000
.org $c000
!org $c000
org $c000

Instructions

The assembler supports the official 6502 instruction set and a broad set of undocumented opcodes used in C64 coding. It resolves zero-page and absolute modes based on expression values when possible.

Branch instructions are emitted with signed relative offsets. The line map shows the target address and relative byte value, which is useful when debugging loops and branch range errors.

Example:

wait:
    lda $d012
    cmp #$30
    bne wait

If a branch target is outside the valid range of -128 to +127 bytes, the compiler reports a branch-out-of-range diagnostic.

Includes, imports, symbol files, and binary data

Web64 IDE supports source inclusion, symbol import, and binary inclusion through the virtual filesystem.

Source include

Use .include for source or symbol files:

.include "includes/common.asm"
.include "assets/sprites/player.inc"
#include "includes/constants.inc"

Accepted forms:

.include "path"
!include "path"
include "path"
#include "path"

Source import

Use .import source when you want to be explicit:

.import source "Main-CommonDefines.asm"
.import source "Main-CommonMacros.asm"

Accepted source import keywords include:

.import source "path"
.import asm "path"
.import include "path"

Symbol import

Symbol files are text files that expose addresses or constants. .sym files are treated as symbol files automatically.

.import symbols "build/main.sym"
.import source "../../Out/6502/Main/Main-BaseCode.sym"
.include "assets/chars/logo.inc"

Symbol line formats supported include common assignment and listing styles such as:

label = $c000
label equ $c000
$c000 label
label $c000

Generated include files from graphics assets use simple assignments so they can be consumed by the assembler.

Binary import

Use .import binary to insert a binary file at the current assembly address and bind a label to the start:

* = $3000
.import binary player_sprites, "assets/sprites/player.spr"

The label becomes a symbol:

lda #<player_sprites
ldx #>player_sprites

Incbin

.incbin inserts raw bytes from a virtual binary file.

.incbin "assets/chars/logo.chr"

You can also provide a symbol before the path:

.incbin logo_chars "assets/chars/logo.chr"
.incbin player_sprites, "assets/sprites/player.spr"

Accepted binary directive families include:

.incbin
!incbin
incbin
.bin
!bin
bin
.binary
!binary
binary
.import binary
.import bin

Binary assets under a symbol

The recommended pattern for graphics, music, maps, and lookup tables is:

* = $3000
.import binary player_sprites, "assets/sprites/player.spr"
.include "assets/sprites/player.inc"

* = $3800
.import binary logo_chars, "assets/chars/logo.chr"
.include "assets/chars/logo.inc"

This gives you both the binary data and labels for sizes, counts, frame offsets, character indices, and editor color references.

Macros

The assembler supports simple source macros.

Use Insert > Macro from the source-editor context menu to create these definitions without typing the delimiters manually. The dialog exposes parameters and the multiline body, previews the exact source, and includes common 6502 templates. Selecting existing assembly before opening the dialog captures it as the custom body.

Defining a macro

.macro set_border color
    lda #\color
    sta $d020
.endmacro

Accepted delimiters:

.macro / .endmacro
!macro / !endmacro
macro / endmacro
macro / endm

Invoking a macro

set_border $06

For multiple parameters, separate arguments with commas:

.macro poke address, value
    lda #\value
    sta \address
.endmacro

poke $d020, $02

Parameter substitution

Macro parameters can be referenced in several forms:

\name
{name}
name

The backslash form is the clearest and is recommended because it avoids accidental replacement of normal identifiers.

Local labels in macros

Use %%name for macro-local labels. During expansion, each macro invocation receives a unique prefix.

.macro wait_raster line
%%loop:
    lda $d012
    cmp #\line
    bne %%loop
.endmacro

Macro limits

Macro expansion is limited to prevent runaway recursion. If expansion exceeds the supported depth, the compiler reports a macro-recursion diagnostic.

Building, loading, and running programs

The main PRG actions are:

The current program is loaded directly into C64 memory through the runtime memory bridge when available. This is faster than simulating disk loading and keeps the IDE workflow close to assemble-and-run.

Load PRG

Load PRG first compiles the current source and virtual files, then writes those bytes into C64 memory at that result's load address. It does not automatically start the program. A stale or failing background diagnostic snapshot does not disable the action; only errors from this current action build prevent loading.

Use Load PRG when you want to inspect memory, set breakpoints, or manually start the program later.

Start PRG

Start PRG compiles the current source/project state and resolves the selected entry against that same result. A newly created runtime waits through a minimum initialization interval and requires advancing, fresh nonblack VIC output before any project is started. An already running ASM-only project is power-reset and given the same elapsed-time plus fresh-frame readiness check before the IDE loads the PRG and sends SYS. C and C/assembly hybrid projects use the direct start bridge without this BASIC-oriented reset, preserving the previously working C runtime handoff. Canvas binding, stale frame diagnostics, and a few early advancing frames are not treated as readiness.

ASM-only projects are started with the same semantics as Load PRG followed by:

SYS <entry-address>

The command is sent as text input to BASIC, giving RTS a valid return frame. C and C/assembly hybrid projects prefer the direct program-start bridge because generated C startup parks safely after main returns. If a preferred launch capability is unavailable, the IDE uses the other runtime-supported mode for backwards compatibility.

The new ASM project template changes the border color and then remains in an explicit mainloop. Existing ASM routines that intentionally end in RTS return to the BASIC READY. prompt after completing; that return is normal SYS behavior rather than a crash.

Background diagnostics never gate Start PRG. If the current action build reports errors, the start is stopped with those current diagnostics; otherwise changed source, graphics assets, and project files are loaded before execution.

Power, Pause, Reset

The runtime controls include:

Disk mastering and project media

The Disk/Media tab is the IDE workspace for browser-local D64 disk mastering. It creates project-owned disk records, resolves entries from the virtual filesystem, builds deterministic D64 v1 images, validates the generated bytes, and then exposes valid images for download or mounting in the embedded runtime.

Disk mastering is different from Save PRG. Save PRG exports one compiled program file. Disk mastering creates a 1541 disk image that can contain a boot PRG plus data files, text/SEQ files, generated assets, maps, music payloads, or other project binaries that your program loads through normal C64 file I/O.

D64 v1 scope

Web64 D64 v1 writes standard 35-track 1541 D64 images:

The v1 writer does not shell out to c1541 or native VICE tools. Images are generated in browser-owned Web64 code and validated from raw bytes before they are treated as attachable.

Creating a disk

Use Disk/Media, then New D64. The workspace adds a media.version and media.disks[] record to the .web64proj project state. The disk record has a stable id, title, target drive unit, and an entry list. Drive 8 is the normal default.

A minimal saved project media block has this shape:

{
  "media": {
    "version": 1,
    "disks": [
      {
        "id": "disk-1",
        "title": "disk-1",
        "unit": 8,
        "entries": [
          {
            "name": "LOADER",
            "type": "prg",
            "source": {
              "kind": "project-file",
              "path": "build/loader.prg"
            }
          },
          {
            "name": "LEVEL1",
            "type": "seq",
            "source": {
              "kind": "project-file",
              "path": "data/level1.seq"
            }
          }
        ]
      }
    ]
  }
}

Project file paths are virtual filesystem paths, not host paths. A disk entry must reference source truth that exists in the project tree; generated D64 bytes are build artifacts and are not stored as the source of truth in .web64proj.

Adding entries

Use Attach Source to add an existing project file to the selected disk. The entry name is derived from the source path unless you edit the project record directly. Files ending in .seq or .txt are treated as SEQ by default; other entries default to PRG unless the manifest specifies another supported type.

For PRG entries, the manifest may request the default SYS loader injection behavior with injectSysLoader and may provide sysAddress when the entry needs an explicit start address. These options are normalized at disk or entry level.

Validation and diagnostics

The Disk/Media workspace shows each disk id, target drive, entry count, byte size, free block count, and entry source state. Missing or unsupported sources keep the disk invalid until fixed. Common diagnostics include:

Validation is independent of writer state. The inspector parses the raw D64 bytes, reconstructs directory and allocation data, reports free blocks, and blocks attach/download actions when the image is not valid.

Downloading and mounting

Download D64 is enabled only when the disk validates. It saves the canonical generated image bytes for that disk.

Mount Drive 8 or Mount Drive 9 passes a cloned byte array to the existing runtime media path as disk media. The runtime attaches the image through the same VICE disk boundary used by external D64 files. Downloaded and mounted bytes should match for the same build output unless a future writable-clone policy explicitly says otherwise.

After mounting, test ordinary DOS/KERNAL access from the C64 side. A directory smoke test is:

LOAD"$",8
LIST

Program loaders should use normal C64 filename loading patterns such as SETNAM, SETLFS, and LOAD. Disk mastering does not publish a new Web64 C loader API in v1.

Unsupported and deferred media features

The disabled REL, Error Info, and Loader API controls are deliberate boundaries, not incomplete user steps. D64 v1 does not write REL side-sector files, error-info D64 images, D71/D81/G64 outputs, raw/protected/nibble media, public track-loader APIs, or public multi-disk runtime swap/request APIs. Those features require implementation-backed writer, validator, runtime, and example evidence before the manual can describe them as supported.

The embedded Web64 runtime

The embedded runtime is the Web64 VICE-based C64 instance used by the IDE. It is shown inside the Code tab so you can edit source and inspect the running program in the same workspace.

Collapse emulator panel in the emulator header gives the editor the full center-stack height without pausing, resetting, or unloading the C64. Show emulator panel in the source tab header restores the panel and resynchronizes its video surface. The collapse state is stored in both local layout preferences and the .web64proj workspace record.

The emulator options are grouped in collapsible sections:

Root emulator URL launches

The root Web64 emulator accepts a remote or same-origin D64/PRG URL through ?file=<url>. Optional parameters can be combined with it, including autorun, audio, scanlines, warp, fullscreen, and joystick. Boolean parameters accept true/false and on/off.

For example:

https://web64.nofs.ai/?file=https://web64.nofs.ai/greedy-ghost.d64&audio=on&fullscreen=on

The file URL is parsed independently from the following launch parameters. Media loading and attachment complete before optional audio and fullscreen requests, so a browser autoplay or fullscreen permission restriction cannot prevent the D64 or PRG from loading. Audio or fullscreen may still require the first interaction with the emulator screen because browsers control those permissions. Percent-encode the file value when the nested file URL contains its own query string, fragment, or ampersand.

Display scale

Available scale modes:

Fixed scale modes may need scrolling or clipping if the emulator panel is smaller than the rendered surface. Fit mode is intended for normal responsive use.

Scanlines

Scanlines are controlled through runtime video resources. If scanlines bother a user, disable them in Display.

Fullscreen

Fullscreen applies to the emulator panel. When fullscreen is entered, the emulator canvas is focused so keyboard input goes to the C64 without requiring a second click.

Audio

Audio must be enabled by user action in most browsers because browser audio APIs require a gesture. The IDE exposes:

SID quality options include FastSID and reSID modes.

Drive 9

Drive 9 can be enabled or disabled. Some C64 programs assume only drive 8 exists and can fail if drive 9 is present. Disable drive 9 in Advanced when testing those programs.

Debugger

The Debugger tab provides source-oriented debugging support around the embedded runtime.

Debugger features include:

Breakpoints

Breakpoints can be added in three ways:

  1. Enter an address or symbol in the Debugger tab and click Add Breakpoint.
  2. Click the breakpoint marker in the Inspector line map.
  3. Click a line breakpoint entry in the Debugger line list.
  4. Click a Generated Assembly gutter for a remappable source/provenance breakpoint, or Shift-click it for a fixed machine-address breakpoint.

Breakpoint addresses are normalized to 16-bit C64 addresses.

Source/provenance breakpoints follow the generated instruction identity or source location across live compiler revisions. A source breakpoint that no longer resolves is shown as unresolved and is removed from the runtime without being guessed onto another instruction. Machine breakpoints remain fixed at their entered address. The Debugger list labels the two kinds separately.

When a breakpoint is hit, the runtime pauses. The IDE switches to debugger context, captures register and memory state when the runtime bridge supports it, and highlights the corresponding source line and trace line.

Pausing

Manual pause acts like a debugger pause. The Debugger tab opens and shows a pausing status before emulator pause, register reads, or memory inspection begin, so slow runtime state capture cannot delay the workspace transition. Register, memory, PC, and source highlights populate as the runtime bridge returns them.

The Line Breakpoints list separates its actions. Use the narrow +/B gutter to set or remove a breakpoint. Click the source line and instruction text to open that exact line in the source editor. When Generated Assembly is enabled, every debugger line that maps to generated code has an ASM button. Click ASM to open Generated Assembly with that line's exact machine instruction and address selected. Resolved entries in the main Breakpoints list expose the same ASM action.

Registers

The register panel displays:

The available fields depend on runtime monitor/debug exports. If the runtime bridge cannot expose a value, the UI leaves it blank or shows placeholder state.

Memory

The memory panel reads a short range of C64 memory. Enter a start address or symbol and a byte count from 1 to 256, then refresh.

Example addresses:

$0400
$d020
screen_buffer

Line map

The line map is one of the most useful debugging tools. It shows:

Use it to validate branch offsets, asset import ranges, compiled code size, and generated addresses.

Live patching

Live patching writes compiled changes into the running emulator so you can see changes quickly.

When Live Patch is enabled:

  1. The IDE notices source or asset changes.
  2. The compiler rebuilds after the edit/paste debounce.
  3. If diagnostics are clean, the IDE determines whether the change can be patched.
  4. The runtime pauses briefly.
  5. The relevant memory bytes are written.
  6. The runtime resumes unless it was manually paused.

Asset live patch

Character and sprite edits are handled with a shadow-buffer style workflow:

This is much faster than writing every brush stroke directly to C64 memory.

Full PRG live patch

If an edit changes code or the changed asset cannot be isolated, the IDE can patch the full compiled PRG body into memory.

When live patch waits

Live patch does not write memory when:

Status messages indicate whether bytes were patched, compilation is waiting, or an asset is not imported into the running program.

When to reload instead

Reload the PRG when:

Live patch writes memory bytes, but it does not automatically rerun your initialization logic.

Character editor

The Character Editor is a browser-local C64 character set editor.

Character data model

A C64 character is 8 bytes:

A full character set is commonly:

256 chars * 8 bytes = 2048 bytes

Creating a charset

Use New Charset in the Char Editor. The default path is similar to:

assets/chars/charset.chr

The editor adds the binary asset and its generated include file to the virtual filesystem.

Char editor layout

The Char Editor contains:

Mono mode

Mono mode has pixel values 0 and 1:

The exported bytes are standard C64 character bytes.

Multicolor mode

Multicolor character mode has pixel values 0, 1, 2, and 3:

The editor paints 2-bit horizontal pairs. Preview pixels are shown double-wide to match C64 multicolor geometry.

Editing tools

Available tools:

Mouse drag continues painting, including skipped cells during fast pointer movement. Right mouse or erase tool paints zero. Touch/pointer drawing is supported where the browser provides pointer events.

Pencil, erase, and line strokes are kept in a local character draft while the pointer is down. Visual updates are coalesced to animation frames, and the editor writes the changed 8-byte character to the project and optional live-patch path once when the stroke ends. It does not rewrite project files or C64 memory for every crossed pixel. The 16x16 charset browser is rendered as one canvas so editing does not trigger thousands of preview-element style and layout updates.

Sprite editor

The Sprite Editor is a browser-local C64 hardware sprite editor. Existing .spr projects continue to work unchanged; overlay pairing is an editor abstraction over ordinary sprite assets, not a new sprite binary format.

Sprite data model

A C64 hardware sprite is:

24 x 21 pixels
63 bytes of pixel data
1 padding byte in Web64 IDE sprite banks
64 bytes per exported frame

The padding byte is included because VIC-II sprite pointers address 64-byte sprite slots.

Creating sprite assets

Use New Sprite Bank to create a single ordinary sprite bank. The default path is similar to:

assets/sprites/sprites.spr

Use New Pair to create one logical sprite made from two ordinary sprite banks:

assets/sprites/player_mc.spr
assets/sprites/player_ol.spr
assets/sprites/player.spritepair.json
assets/sprites/player.inc

The _mc bank is the multicolor base layer. The _ol bank is the monochrome overlay layer. The .spritepair.json file stores editor metadata such as logical name, layer paths, overlay preview color, and opacity. The .spr bytes remain standard 64-byte sprite records.

Sprite editor layout

The Sprite Editor contains:

Pair modes

Multicolor mode edits only the _mc base asset. Overlay mode edits only the _ol asset and keeps the multicolor base available as context. Composite mode is read-only and previews the two hardware sprites at the same X/Y position with overlay pixels drawn above base pixels. Split mode is reserved for side-by-side cleanup while preserving the same logical pair selection and synchronized frame index.

Frame add, duplicate, delete, and move operations apply to both layer banks when a logical pair is selected. If pair frame counts diverge, the editor shows repair diagnostics rather than silently treating the layers as a valid synchronized pair.

Mono mode

Mono sprite mode has pixel values 0 and 1:

Rows export as 3 bytes per row, high bit leftmost.

Multicolor mode

Multicolor sprite mode has logical width 12 by height 21. Each logical pixel represents a 2-bit horizontal pair.

Color slots:

The third slot is intentionally exposed as the sprite-specific color because that is the color normally set per hardware sprite.

Pair include output

Pair includes generate deterministic _mc and _ol symbol groups, plus pair convenience labels. For Player, labels include:

player_mc_sprites_size = ...
player_mc_sprite_count = ...
player_mc_frame_0 = player_mc_sprites + 0

player_ol_sprites_size = ...
player_ol_sprite_count = ...
player_ol_frame_0 = player_ol_sprites + 0

player_sprite_count = player_mc_sprite_count
player_mc = player_mc_sprites
player_ol = player_ol_sprites

Existing single-sprite generateGraphicsAssetInclude output remains unchanged.

Animation preview

The animation preview runs from JavaScript editor memory, not from emulator memory. This keeps preview playback responsive while editing. For paired sprites, animation advances both layers with the same frame index.

Controls:

Onion skin shows neighboring frames at low opacity to help create animation continuity. Pair overlay color and opacity are editor preview settings and do not change exported .spr bytes.

Troubleshooting pairs

If a logical pair reports a missing layer, create or restore the referenced _mc.spr or _ol.spr file, or switch to Raw mode to inspect the underlying assets. If frame counts mismatch, use paired frame operations from the logical entry after restoring both layers to a matching count. Manual include files are not the source of truth; regenerate pair includes from the sprite assets and descriptor when needed.

Block editor

The Block Editor creates and edits tile block sets stored as .blk or .blocks files. A block set is a list of fixed-size blocks made from character indices, usually backed by a charset selected from the project.

Current Block Editor features:

Save Block Set commits draft edits back into the virtual filesystem and regenerates the block include record.

Map editor

The Map Editor creates and edits .map and .w64map files. A map stores block indices and references a block set, which in turn can reference a charset.

Current Map Editor features:

Save Map commits draft edits and regenerates the map include record. Use the exported map bytes from assembly and interpret each cell as a block index in your engine.

SID tracker

The standalone tracker manual lives at Web64 IDE SID Tracker User Guide. Use that document when you want the music workflow and generated SID asset rules without the rest of the IDE manual.

The SID Tracker edits browser-local .w64sid tracker files. .w64sid is the editable composition source. The modular v2 compiler produces compact frame-event streams and a small 6502 player with exact playback status.

Current SID Tracker features:

The modular v2 driver supports three voices, PSID export, rests/cuts/releases/ties, all declared v2 effects, SID control flags, filters, and all five instrument tables. RSID export, multi-SID settings, unknown commands, out-of-range values, or invalid limits block generated output with diagnostics.

The generated tracker include exposes load/init/play, driver/music sizes, exact status and audition ABI labels, hashes, <prefix>_file_size, <prefix>_data_offset, <prefix>_c64_data_offset, and <prefix>_c64_data_size. The Web64 assembler accepts optional offset and length expressions after a binary path, so assembly can place only the C64 payload from the valid .sid:

.include "assets/music/title.inc"
* = song_sid_load_address
.incbin song_sid, "assets/music/title.sid", song_sid_c64_data_offset, song_sid_c64_data_size

Existing .incbin and .import binary forms without offset/length retain their previous whole-file behavior.

SID editor

The SID Editor is for imported or generated .sid binaries. It is separate from the .w64sid tracker source editor.

Current SID Editor features:

Use SID Editor when you already have a PSID/RSID-style binary. Use SID Tracker when you want to edit a Web64-native tracker source file.

Generated include files

Graphics, tile, and SID assets can generate include files. These are visible in the project tree as generated virtual files and previewed in the matching asset editor.

Generated includes are meant to be consumed by assembly. Edit the source asset, not the generated include, unless you intentionally want a manual source file. For .w64sid output, a manual include at the generated path is preserved as an override.

Charset include labels

For assets/chars/logo.chr, the label base is logo.

Typical generated labels:

logo_chars_size = $0800
logo_color_0 = 0
logo_background_color = 0
logo_color_1 = 5
logo_multicolor_1 = 5
logo_color_2 = 2
logo_multicolor_2 = 2
logo_color_3 = 1
logo_foreground_color = 1
logo_char_count = $0100
logo_chars_end = logo_chars + logo_chars_size
logo_char_0 = 0
logo_char_1 = 1

Usage:

* = $3000
.import binary logo_chars, "assets/chars/logo.chr"
.include "assets/chars/logo.inc"

Sprite include labels

For assets/sprites/player.spr, the label base is player.

Typical generated labels:

player_sprites_size = $80
player_color_0 = 0
player_background_color = 0
player_color_1 = 6
player_multicolor_1 = 6
player_color_2 = $0c
player_sprite_color = $0c
player_color_3 = $0e
player_multicolor_2 = $0e
player_sprite_count = 2
player_sprites_end = player_sprites + player_sprites_size
player_frame_0 = player_sprites + 0
player_frame_1 = player_sprites + $40

Usage:

* = $3000
.import binary player_sprites, "assets/sprites/player.spr"
.include "assets/sprites/player.inc"

The important sprite color constants are:

Use player_sprite_color for the per-sprite VIC-II color value.

Snapshots and state

The IDE supports runtime state tools through the embedded Web64 runtime.

Quick Save and Quick Load

Quick Save stores a temporary runtime state in browser memory. Quick Load restores it during the current IDE session.

This is useful for short testing loops, but it is not a project persistence mechanism.

Export Snapshot

Export Snapshot saves a VICE snapshot file, usually .vsf, from the current runtime.

Use this when you want a runtime state artifact outside the project file.

Import Snapshot

Import Snapshot loads a .vsf or binary snapshot file into the runtime when supported by the runtime bridge.

Snapshots are emulator state, not source state. They do not replace .web64proj.

Keyboard, joystick, and gamepad input

The embedded emulator receives keyboard and joystick input through the Web64 runtime input bridge.

Keyboard focus

The emulator canvas needs focus for C64 keyboard input. Fullscreen activation focuses the emulator automatically. If input does not work, click the emulator canvas once.

Numeric-keypad joystick

When Gamepad is disabled, both the root Web64 emulator and the IDE emulator use the numeric keypad as a keyboard joystick on the selected joystick port:

The mapping follows the physical numeric-keypad keys and works with Num Lock on or off. It is inactive while focus is in a source editor, text field, or other editable control. Held directions are released when emulator focus is lost, preventing stuck joystick input. Enabling Gamepad returns the numeric keypad to normal keyboard input.

Gamepad

Gamepad input can be enabled in the Input group.

Options:

Most C64 games use joystick port 2, but some use port 1.

Drive and input differences

Some C64 programs are sensitive to attached drives or input port state. If a program behaves differently in the IDE than on the root emulator page:

  1. Confirm the same PRG bytes were loaded.
  2. Confirm the same joystick port.
  3. Disable drive 9.
  4. Reset the runtime and reload.
  5. Test with audio and warp disabled if timing is relevant.

New assembly program

  1. Open /ide.
  2. Write or paste assembly into the main source editor.
  3. Set origin, commonly $c000 for development PRGs.
  4. Confirm diagnostics show Compile ready.
  5. Click Start PRG.
  6. Use the line map to inspect addresses and emitted bytes.
  7. Save the project as .web64proj.

Add a sprite bank

  1. Open Sprite Editor.
  2. Create assets/sprites/player.spr.
  3. Draw frames.
  4. Set multicolor mode and color slots if needed.
  5. In source, add:
* = $3000
.import binary player_sprites, "assets/sprites/player.spr"
.include "assets/sprites/player.inc"
  1. Use player_frame_0, player_sprite_count, and player_sprite_color in code.
  2. Start PRG.
  3. Edit the sprite and observe live patching if the program uses the imported bytes directly.

Add a character set

  1. Open Char Editor.
  2. Create assets/chars/tiles.chr.
  3. Draw characters or tiles.
  4. Use 3x3 preview to test tile continuity.
  5. In source, add:
* = $3800
.import binary tiles_chars, "assets/chars/tiles.chr"
.include "assets/chars/tiles.inc"
  1. Copy the charset to VIC-visible RAM or point VIC bank/screen setup at the imported location as appropriate.

Build a tile map

  1. Create a charset in Char Editor.
  2. Create a block set in Block Editor and select the charset.
  3. Create a map in Map Editor and select the block set.
  4. Save both the block set and the map.
  5. Import/include the generated .blk, .map, and .inc files from assembly.

Create a Web64 SID song

  1. Open SID Tracker.
  2. Create a .w64sid file.
  3. Edit metadata, instruments, pattern rows, per-note volume, and the selected subtune's independent order and playback settings.
  4. Create or duplicate subtunes for title/game music, jingles, and one-shot sound effects. Disable Loop for a one-shot subtune.
  5. Select the intended subtune in the toolbar or Song tab and use Play for runtime playback.
  6. Start the main project normally when you are done previewing; Start performs the required preview-to-program reset automatically.
  7. Save W64SID to regenerate .sid and .inc outputs when validation is clean.
  8. Include the generated .inc and import the payload range from the generated .sid as needed. Initialize the driver with a generated zero-based subtune constant in A.

For title music, game music, and one-shot effects in one asset, the generated player is stored once. The .inc exposes <prefix>_subtune_<n>_stream_size for checking each subtune's contribution; keep one-shot patterns and their Start/End ranges limited to the rows and order positions they use.

To overlay a voice-3 effect on game music, set Purpose to Sound effect and disable Loop. Point voices 1 and 2 at the same sequences used by the game-music subtune, and point voice 3 at the short effect pattern. The compiler inherits the matching music voices and serializes only voice 3. Initializing the SFX index no longer changes the active music index or Playing status.

The generated C64 data offset/size constants provide the exact SID payload copy range. A fixed page count is also valid when its capacity has been checked against the current payload; for example, a 6 KB copy fully covers a 5.6 KB payload. Recheck that bound when the composition grows.

Use split source files

  1. Add virtual files such as:
includes/constants.asm
includes/macros.asm
src/raster.asm
  1. In the main source:
.include "includes/constants.asm"
.include "includes/macros.asm"
.include "src/raster.asm"
  1. Keep paths project-relative.
  2. Save the project when the virtual file tree changes.

Import symbols from another build

  1. Add a .sym file to the virtual tree.
  2. Include it:
.import source "Main-BaseCode.sym"

or:

.include "Main-BaseCode.sym"
  1. Use imported labels in expressions.

Troubleshooting

Pasting a large source block makes the IDE slow

Large paste operations are one editor transaction. Rendering remains viewport-bounded, immediate checks inspect only the edited line, and semantic diagnostics/full compilation wait until the paste becomes idle. If the UI itself remains slow after the paste, verify that the current build is using the default viewport editor rather than an internal legacy-editor development override. Very large generated source, recursive macros, and actual full-project compile time may still delay a completed build, but they should not delay caret movement or additional typing.

The browser page goes black

A black page usually means an uncaught JavaScript exception. Current builds guard compiler failures and should show diagnostics instead. If it happens again:

  1. Reload the IDE.
  2. Reopen the last saved .web64proj.
  3. Paste the code into a small temporary source file first.
  4. Check for unterminated strings, unexpected macro recursion, or very large generated output.

Include file not found

Check:

Binary data appears as garbage

Check:

Live patch says asset changed but display does not update

Possible reasons:

Reload and restart the PRG when in doubt.

Start PRG goes to READY or syntax error

For an ASM-only project started through SYS, reaching READY. after an intentional RTS is normal. Use a main loop instead of RTS when the program should remain active.

Check:

Breakpoint does not hit

Check:

Gamepad does not work in the IDE

Check:

Drive 9 causes problems

Disable Drive 9 in Advanced. Some programs expect only drive 8 and fail when another drive is attached.

Reference tables

Common file extensions

ExtensionTypeNotes
.asm, .sSourceAssembly source
.inc, .txt, .macSourceInclude files, macro files, text
.symSymbols/sourceParsed as symbol source
.chr, .ch8Charset assetEditable in Char Editor
.sprSprite bank assetEditable in Sprite Editor, 64 bytes per frame
.blk, .blocksBlock set assetEditable in Block Editor
.map, .w64mapMap assetEditable in Map Editor
.sidSID binaryInspectable/previewable in SID Editor
.w64sidWeb64 SID trackerSource-truth tracker JSON, editable in SID Tracker
.d64Disk imageGenerated by Disk/Media or attachable as runtime drive media
.bin, .raw, .datBinaryUsable with .incbin or .import binary
.prg, .seq, .scr, .koaBinaryStored as project binary records
.web64proj, .web64project, .jsonProjectFull Web64 IDE project
.vsfSnapshotRuntime/emulator state

Runtime controls

ControlPurpose
Save PRGCompile and save generated PRG bytes
Load PRGLoad the current compiled PRG into the embedded runtime
StartComplete cold-boot or power-reset initialization, load, and run the selected entry point
PausePause or resume runtime without resetting and refresh debug context
PowerDestroy and unload the emulator runtime
ResetPower reset C64 runtime
WarpToggle fast runtime execution
Live PatchPatch compiled changes into running memory
1x/2x/FitDisplay scaling mode
ScanlinesToggle video scanline effect
FullscreenExpand emulator panel
AudioEnable browser audio
MuteSilence audio without changing runtime state
SID qualitySelect SID emulation quality/resource set
GamepadEnable browser gamepad polling; when disabled, use the numeric-keypad joystick
Joystick portChoose port 1 or 2 for gamepad or numeric-keypad input
Swap PortsSwap joystick assignment
Quick Save/LoadTemporary in-memory runtime state
Snapshot import/exportVICE snapshot file exchange
Drive 9Enable or disable second drive

Generated sprite color constants

ConstantMeaning
name_color_0Transparent/background color reference
name_background_colorAlias for color 0
name_color_1Sprite multicolor 1
name_multicolor_1Alias for color 1
name_color_2Sprite-specific color
name_sprite_colorAlias for color 2
name_color_3Sprite multicolor 2
name_multicolor_2Alias for color 3

Generated charset color constants

ConstantMeaning
name_color_0Background/global color
name_background_colorAlias for color 0
name_color_1Multicolor 1
name_multicolor_1Alias for color 1
name_color_2Multicolor 2
name_multicolor_2Alias for color 2
name_color_3Foreground/character color
name_foreground_colorAlias for color 3

Minimal complete example

* = $c000

BORDER = $d020
BG = $d021

.include "assets/sprites/player.inc"

start:
    sei
    lda #0
    sta BORDER
    sta BG

    lda #<player_sprites
    sta $07f8
    lda #player_sprite_color
    sta $d027

main:
    inc BORDER
    jmp main

* = $3000
.import binary player_sprites, "assets/sprites/player.spr"

Save the sprite asset at assets/sprites/player.spr, then save the project. The generated include exposes the color and frame constants, and .import binary inserts the sprite bytes into the PRG.

Web64 C v0.1 and Mixed C/Assembly Projects

Web64 IDE supports an experimental browser-native C path for small C64 programs. C files (.c) and headers (.h) are ordinary virtual project files and are saved inside .web64proj alongside assembly files, generated asset metadata, binary assets, symbols/includes, and emulator settings.

The .web64proj C block is portable and uses only project-relative virtual paths:

{
  "c": {
    "enabled": true,
    "dialect": "web64-c-v0.1",
    "entry": "main.c",
    "includePaths": ["include", "assets"],
    "stdout": "screen",
    "stdin": "keyboard",
    "runtimeProfile": "tiny",
    "compilerBackend": "web64-native"
  }
}

The v0.1 dialect lowers supported C modules to Web64 assembly and then uses the existing Web64 assembler for final PRG bytes, symbols, line maps, memory ranges, diagnostics, emulator launch, and debugger handoff. External C functions and globals use underscore labels such as _main and _player_x; file-scope static definitions use deterministic module-private labels. Hand-written assembly can call or reference external C labels, while C can call direct assembly labels such as asm_plot_pixel().

For ordinary C projects, leave main.asm empty and put the program entry in main.c. Web64 C emits a startup stub that calls the configured C entry. Mixed projects that supply their own assembly startup must explicitly call the C entry, usually jsr _main, before returning or continuing into the game loop.

Current mixed-project behavior includes guarded project/bundled headers, bounded conditional preprocessing, byte/word globals and locals, fixed-layout aggregates, enums, typed 16-bit pointers, scalar expressions, loops and conditionals, direct calls, dependency-selected tiny-library implementations, inline assembly, C64 register aliases, and generated asset declarations. Multi-translation-unit builds emit shared runtime storage once and reject duplicate external definitions or conflicting assembler-visible defines.

The deliberate boundaries are the same as in the compatibility appendix above: no native cc65 object/linker ABI, no 32-bit parameter/return ABI, no recursion with static call slots, no hosted file I/O, no floating point, and no complete ISO preprocessor. Host and absolute filesystem paths are rejected; all sources, headers, runtimes, and assets must resolve from virtual project records or bundled browser-served records.