Web64 logo Web64 Documentation Web64 IDE User Manual
Web64 IDE collage featuring source editing, sprite and map tools, SID tracking, debugging, Commodore 64 media, and the embedded emulator

Web64 IDE User Manual

Version: 2026-09-29

Copyright (c) 2026 Mika Jussila, Siteledger Solutions Oy

License: Web64 IDE License v1.0

Contents

  1. What Web64 IDE is
  2. Web64 principles
  3. The IDE workspace
  4. Keyboard shortcuts
  5. Projects and persistence, including Optional local MCP access
  6. Project templates, including Authoring portable templates
  7. Web64 Cloud
  8. Settings tab
  9. The virtual filesystem
  10. Source editing and compilation
  11. Web64 C compiler and virtual headers
  12. Web64 fixed-point math
  13. Assembler reference
  14. Includes, imports, symbol files, and binary data
  15. Macros
  16. Building, loading, and running programs
  17. Build targets and multi-load projects
  18. KickAssembler Compatibility Mode
  19. Web64-native c64lib compatibility
  20. Disk mastering and project media, including REU and C64U launches
  21. The embedded Web64 runtime
  22. Debugger, including the Cycle / Raster Profiler
  23. Live patching
  24. Character editor
  25. Sprite editor
  26. Block editor
  27. Map editor
  28. SID tracker
  29. SID editor
  30. Generated include files
  31. Snapshots and state
  32. Keyboard, joystick, and gamepad input
  33. Recommended workflows
  34. Troubleshooting
  35. Reference tables
  36. Web64 C and Mixed C/Assembly Projects
  37. Changelog

Dedicated manuals: Web64 C Compiler User Manual, Web64 Game Runtime Libraries, Web64 IDE SID Tracker User Guide, KickAssembler Compatibility Mode

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.

Web64 IDE Code workspace showing the project tree, assembly editor, emulator, diagnostics, and line map
The Code workspace keeps project files, source, runtime output, diagnostics, and source-to-machine mapping visible in one browser tab.

The title-bar groups reuse the asset-editor toolbar colors: purple for project files, green for source files, blue for PRG output/runtime actions, cyan for Web64 Cloud, and olive-yellow for About and documentation. Hover an icon for its command name. Save PRG uses the download icon and remains grouped with Load PRG and Start PRG. The cloud icon opens the lazy Cloud workspace, the information icon opens About, the scale icon opens the Web64 legal documents, and the question-mark icon opens 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. Drive 8 and 9 each have a live LED and track readout (T-- when unknown); hover to see the mounted disk and full state. The LEDs follow VICE drive activity during real disk loads, so long loader phases remain visible even when the C64 display does not change. PRG size and compiler statistics describe the latest completed compiler snapshot; file, line, and asset totals describe the current project model. When Cloud has been opened, a separate sync indicator reports its local, pending, syncing, synced, offline, authentication, conflict, or error state; click it to open Cloud Storage. 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 About button opens an in-IDE dialog with the running IDE version, build date, project/settings format versions, copyright, the 128x128 Web64 IDE logo, the Web64 IDE Facebook group, and direct links to the License, Terms of Service, and Privacy Policy. The Legal button opens docs/web64-ide-license.html in a new tab. Its compact tabs display the canonical docs/WEB64_IDE_LICENSE_v1.0.md, docs/WEB64_TERMS_OF_SERVICE.md, and docs/WEB64_PRIVACY_POLICY.md documents and provide a Markdown link for the selected document. Cloud account creation links directly to the Terms of Service and Privacy Policy. 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.

Keyboard shortcuts

Keyboard shortcuts are shown in toolbar tooltips. Global commands work regardless of the focused IDE pane. Asset-editor commands apply only while their editor is active and do not replace ordinary typing in text fields. On macOS, Command can be used for shortcuts listed with Ctrl.

Global

ShortcutCommand
F5Run the current program. Running switches to the Code tab and expands the emulator panel.
Ctrl+F5Run the selected disk set: build missing/modified targets, open Code and expand the emulator.
Shift+F5Pause or resume the running emulator.
Ctrl+Shift+EShow or hide the emulator panel.

Debugger

These work while the emulator or debugger has focus and no text field or dialog is active. The native runtime must be paused for an advancing command.

ShortcutCommand
F9Step one CPU instruction.
Ctrl+F9Step to the next VIC frame, then the first safe CPU instruction boundary.
F10Step over a call.
Ctrl+F10Run to the selected complete instruction.
Shift+F9Continue or pause.

Source editor

ShortcutCommand
Ctrl+SSave the current source file.
Ctrl+Shift+SSave the current source file with a file picker.
Ctrl+Alt+SSave the project. Add Shift to force a file picker.
Ctrl+FFocus Find.
Ctrl+HFocus Find and Replace.
Ctrl+SpaceRequest autocomplete.
F12Go to definition.
Ctrl+F12Go to implementation.
Alt+F12Show generated assembly when the feature is enabled.
Alt+LeftReturn to the previous source-editor location.
Alt+Shift+FFormat the current document or selection.

Character editor

ShortcutTool or command
Ctrl+Z, Ctrl+Shift+ZUndo or redo the linked tile-family edit.
Insert, Shift+DeleteAdd or remove a character and remap linked blocks.
Alt+Up, Alt+DownMove the character and remap linked blocks.
P, E, L, FPencil, erase, line, or fill.
Ctrl+C, Ctrl+V, Ctrl+DCopy, paste, or duplicate the current character.
X, YFlip horizontally or vertically.
Left, Right, Up, DownShift character pixels in that direction.

Block editor

ShortcutTool or command
Ctrl+Z, Ctrl+Shift+ZUndo or redo the linked tile-family edit.
InsertAdd a block.
Shift+DeleteRemove the selected block and remap linked maps.
Alt+Up, Alt+DownMove the block and remap linked maps.
P, E, FPlace character, clear cell, or fill block.
Ctrl+C, Ctrl+V, Ctrl+DCopy, paste, or duplicate the current block.
X, YFlip horizontally or vertically.
Left, Right, Up, DownShift block cells in that direction.
[, ]Decrease or increase block zoom.

Map editor

ShortcutTool or command
H, P, E, IPan, place block, erase, or pick block.
F, R, O, L, DBucket, rectangle, ellipse, line, or random brush.
V, MSelect a rectangular region or move the current selection.
Ctrl+C, Ctrl+V, Ctrl+D, DeleteCopy, paste, duplicate, or delete the current selection using the enabled planes.
Enter, EscCommit or cancel a floating move/paste.
C, SCapture a stamp or paint the captured stamp.
Shift+FFill the complete map with the selected block.
Shift+RReplace every matching value in the active structure or typed plane.
Shift+OOpen Color RAM analysis and optimization.
HomeFit the map to the viewport.
ZZoom to the selected cell.
[, ]Decrease or increase map zoom.
GToggle the map grid.

Sprite editor

ShortcutTool or command
Ctrl+Z, Ctrl+Shift+ZUndo or redo.
Insert, Ctrl+D, Shift+DeleteAdd, duplicate, or delete a frame.
Ctrl+C, Ctrl+VCopy the frame or selection; paste a Web64 sprite payload or image.
Alt+Left, Alt+RightMove the current frame in the bank.
Alt+1, Alt+2, Alt+3, Alt+4Select multicolor, overlay, composite, or split pair mode.
P, E, F, LPencil, eraser, flood fill, or line.
R, O, S, MRectangle, ellipse, selection, or move selection.
Shift+FFill selected frames.
X, YFlip selected frames horizontally or vertically.
Left, Right, Up, DownNudge selected frames.
Shift+Left, Shift+Right, Shift+Up, Shift+DownWrap selected frames.
I, WInvert or swap pens 1 and 2.
Alt+X, Alt+YReflect the left or top half.
Ctrl+Alt+X, Ctrl+Alt+YTuck horizontally or vertically.
[, ]Decrease or increase canvas zoom.
G, NToggle the grid or onion-skin frames.

SID tracker

Tracker toolbar shortcuts use modifiers so they remain distinct from keyboard note entry. Selection transpose and current-voice commands work while a pattern cell is focused.

ShortcutTool or command
Ctrl+P, Ctrl+Shift+PPlay the selected subtune or stop preview.
Ctrl+Z, Ctrl+Shift+ZUndo or redo.
Alt+ESwitch between reSID realtime and FastSID preview.
Alt+Page Up, Alt+Page DownSelect the previous or next subtune.
Alt+A, Alt+MToggle preview audio or mute.
Ctrl+Alt+Down, Ctrl+Alt+UpDecrease or increase preview volume.
Ctrl+Page Up, Ctrl+Page DownSelect the previous or next pattern.
F2Focus pattern rename.
Alt+PPlay the selected pattern.
Ctrl+N, Ctrl+DCreate or duplicate a pattern.
Alt+Insert, Alt+DeleteAdd or remove the final pattern row.
Alt+Down, Alt+UpDecrease or increase note-entry octave.
Alt+F, Alt+LToggle playback follow or pattern looping.
Ctrl+[, Ctrl+]Transpose the note or selection down or up one semitone.
Ctrl+Alt+M, Ctrl+Alt+SMute or solo the current voice.

SID editor

ShortcutTool or command
P, Shift+PPlay or stop SID preview.
ESwitch between reSID realtime and FastSID preview.
A, MToggle audio or mute.
[, ]Decrease or increase preview volume.
Ctrl+EExport the selected SID file.

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.

The current in-memory virtual filesystem is always the source for compilation, Build and Run. Unsaved (dirty) changes are normal and never require a project save before building. Saving a .web64proj is a separate persistence action.

When you close, reload or navigate away with unsaved source, asset, build-target or disk-layout changes, the IDE requests the browser's standard leave-page confirmation. Pending editor input is included. A successful project save clears the warning for the snapshot saved; edits made while that save is in progress remain protected. Saving only a source file does not save the complete project. The dialog text is controlled by the browser, and browsers may suppress it without prior user interaction or during forced shutdown. It is not a substitute for saving your project. A download fallback offers a project copy but cannot verify that the browser actually wrote it to disk.

Current project format version 4 stores the versioned build target manifest and media version 2 logical-file/disk/disk-set manifest in addition to source, assets, settings, and workspace state. Older projects remain loadable and are normalized in memory; saving writes the current format. Target PRGs, generated D64 bytes, ZIP archives, and runtime disk clones are deliberately not embedded in the project file.

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.

Optional local MCP access

The separately installed web64-mcp-bridge v0.1.0 connects an MCP client to one explicitly approved browser workspace. The compiler, native importers, project VFS and disk mastering still run in Web64 in your browser, not in the bridge or on a remote build server. No Web64 source checkout or local compiler is required. Ordinary IDE visits have no MCP controls, local-bridge discovery or permission prompts; install and configure the optional bridge only if you want this access.

The bridge's README covers Node.js 22+ installation and MCP client setup. Default stdio mode is spawned by the client. Optional Streamable HTTP mode is a loopback-only server requiring separate per-client credentials; it is not a public Web64 endpoint or legacy HTTP+SSE service.

Ask the client for a web64_connection invitation using begin_pairing, then open that single-use URL in the intended IDE tab. Click Connect local bridge, answer the browser's local-network prompt, and separately approve the requested project permissions. Check the browser's address-bar/top-left permission prompt if connection verification appears to wait. A timed-out invitation needs a fresh request; do not disable browser security. The frontend must support the requested capabilities; a Web64 version number alone does not establish compatibility.

Choose project:read for inspection, project:write for editing/local saving, build for read/build without editing, or project:write+build for both. Grants never silently upgrade. The client can inspect public SDK/docs/native schemas, edit/import through native project operations, build selected targets or disks, inspect Build Output and retrieve grant-private artifacts within those permissions. Public knowledge may be cached locally, but its release is validated per logical client session; private project content is not a public knowledge-cache entry.

Unsaved VFS edits remain current and buildable. A build does not save the project, mount a disk, start the emulator or live-patch a running program. Save persists the native .web64proj: MCP can export its native bytes, or save through an already-permitted browser file handle. It cannot open a file picker or claim an export is a completed disk save. Run/F5, Run Disk and all Cloud operations remain your normal IDE actions. The saved project is editable without MCP.

Disconnect revokes access and removes the temporary panel. Reloading or closing the tab requires a new invitation and approval. Invitations expire after five minutes; do not share them or store them in projects. Chrome has full native workflow verification, while Edge has HTTPS pairing verification. Other browsers and OAuth-only MCP clients are not certified by the initial bridge release.

Project templates

Open Project tree > New > New from template... to create a complete project from the bundled template catalog. This green-highlighted entry is a quick starting point: choose a PRG starter, create the project, press F5 to run, change something and press F5 again. Disk-based templates use Run Disk as described in their included guide. No separate emulator, assembler or build-tool installation is needed. Each template is a portable, versioned .web64template JSON artifact. The selected payload and optional preview are loaded on selection; other project payloads are not loaded. The normal ASM, C, and Mixed C/ASM commands in the same menu are shortcuts to the corresponding catalog templates, so all new-project paths use the same instantiation and native validation rules.

The dialog filters by category, language, difficulty, and target type. Search also matches feature tags. Selecting a template displays its architecture summary, first recommended edit, native assets, selected compatibility modules, video-standard applicability, approximate PRG footprint, optional next templates and an actual emulator preview when supplied. Ready to create templates need no answers; Configurable templates show their declared controls. Double-click a ready template or use Create Project. If the current project contains unsaved source or asset work, Web64 offers Save and Create, Create Anyway, or Cancel.

Stock templates are bundled with Web64 and need no account. My Templates belongs to your Web64 Cloud account: Import to My Templates validates a local .web64template and installs it into Cloud, not browser storage. Free accounts can install 5 templates; paid accounts 20, subject to existing storage quotas. The file is only the transport; Cloud is the installation authority. Imported templates cannot replace a stock entry with the same ID. Remove from My Templates removes the installation without changing projects created from it. Export Template downloads the complete selected artifact, including all configurations, native assets and images. Marketplace installation is reserved for future work. See Template Authoring for input definitions, native output projects and the published schema.

Template questions may use bounded text, numbers, checkboxes, choices or multiple selections. Defaults are provided, and invalid answers disable creation with an explanation. Web64 owns the controls and layout. Templates contain no host scripts, custom HTML/CSS or remote resource URLs. After creation, use the normal Save Project, build and run controls; the resulting .web64proj is fully standalone. Template creation never runs the project automatically.

New Project from Template dialog with catalog filters, template list, architecture summary, and creation options
Filter the bundled catalog, inspect the selected architecture and assets, then create a complete browser-local project.

Every template is a project generator, not a shared mutable dependency. Once created, the project owns all generated files. A later Web64 template update never modifies an existing project. The .web64proj stores compact provenance containing the template id, template version, catalog version, and deterministic semantic content hash; provenance does not make the project dependent on the catalog.

Bundled catalog

Official Examples

In Project tree > New > New from template..., choose Collection > Examples to browse complete reference projects from the official Web64 examples repository. Stock and My Templates are recipes that create projects; Examples opens an already-complete native project. Examples need no account, do not use template quota, and never install anything in My Templates.

Select an example to read its authoritative repository README without opening it. Search and the usual filters narrow the collection. Watch Tutorial appears when a video is provided (including Aurora), View on GitHub opens the source project, and optional related documentation appears where available. README HTML/scripts are not executed. If discovery or documentation cannot be reached, use Refresh; Stock templates remain available.

Open Example downloads and validates the .web64proj before changing your workspace. Invalid or missing projects leave the current project untouched. Some older entries carry a validation notice; opening still checks the latest download and never bypasses native validation. Existing unsaved work can be saved first or explicitly replaced.

The opened example is an unsaved working copy, even before you edit anything. Sources, native asset editors, Build Targets, compiler settings and Disk/Media work normally. Use Run / F5 for PRG examples or the normal Run Disk workflow for disk projects. The existing status bar identifies the example until you save your own copy.

Choose Save Project for a local .web64proj, or Cloud > Projects > Convert Current to save it as your own Cloud project. Opening an example alone does neither. Closing/reloading without saving discards that temporary workspace (with the usual unsaved-project warning). A saved copy is independent: later upstream changes or removal cannot modify your project.

Stock template catalog

Catalog revision 11 contains 36 stock templates and 215 advertised option selections. All stock artifacts use portable template format v3. The original Native Cartridge Starter remains the unchanged reference artifact. Catalog revision 6 contained 26 templates; existing projects created from that catalog remain independent of this update.

CategoryTemplates
BeginnerStructured ASM Starter; Scripted Template Starter; Web64-C Stack ABI Starter; Joystick Sprite Controller; Text Application Starter
C / MixedMixed C/ASM Starter
Game / GraphicsTile/Map Game Foundation; Tile2 Map Scrolling; Sprite Multiplexer Overlay; Fixed-Step Game Foundation; Bitmap Graphics Starter; Trajectory Motion Starter; Actor Batch Arena; Animated Sprite Overlay; Double-buffered XOR 3D
ScrollingHorizontal Smooth Scroll ASM; Horizontal Smooth Scroll C + ASM; Vertical Smooth Scroll ASM; Vertical Smooth Scroll C + ASM
Game Runtime: WorldWorld Static Map; World Horizontal Scroller; World Subpixel Motion Scroller; World Vertical Scroller; World Bidirectional Scroller; World Multicolor Platformer; World + Motion and Sprites
DemoCopper Raster Split; Raster Text Demo
AudioW64SID Music + SFX
Disk / CartridgeHardware Loader + Disk Mastering; Native Cartridge Starter; Native Magic Desk + Disk; Managed Cartridge Resources; Persistent Game Data
Testing / SDKSDK + 64spec Tests; Debugger & Profiler Lab

The reauthored scrollers and game foundations use editable moonlit courtyard character, block, map and animated sprite assets in place of the old stick-figure and mathematical demo art. Select only options relevant to a template: a source-level control changes emitted C/ASM, while a structural choice selects a complete saved project with the corresponding compiler, asset, target or media settings. Equivalent output bytes for two selections are possible; both selections still resolve and build independently. The generated project is self-contained and its TEMPLATE.md explains the actual first edit and runtime limits.

The new bitmap, trajectory, actor, text, persistent-data, raster-text and debugger/profiler starters cover common native Web64 workflows without requiring an external generator. The Persistent Game Data project uses Run Disk and an ordinary writable D64; its save/load record is versioned and checked. Disk and cartridge examples retain separate Build Targets and media files. Test targets report a result-block address in Build Targets; run their PRG and inspect that block in Debugger Memory for pass/fail status.

Animated Sprite Overlay is a focused ASM starter: four-frame multicolor hull and four-frame hires cockpit/lighting layers share position but can use independent clocks. Native Sprite Editor assets and their pair descriptor remain editable. A v3-generated sine table moves the pair. Two fixed VIC slots are used; choose the multiplexer starter when you need many logical actors.

Double-buffered XOR 3D defaults to native bitmap runtime lines through web64/bitmap.inc. The optional Direct ASM choice exposes a compact XOR line routine for comparison. Both versions transform cube vertices on the 6510 using 64 signed 8.8 rotation matrices produced by the v3 native matrix generator. Each back bitmap erases its own previous edges, draws the new object, then flips VIC bank at the next lower border. Late rendering repeats the visible frame safely. This is bounded orthographic wireframe drawing, without perspective or hidden-edge removal. The guide records buffer ownership, code memory and the immediate-argument macro/public-window convention for moving endpoints.

Multicolor World selections use multicolor player frames and matching VIC sprite mode. Players advance through four editable poses every eight display ticks. Tile/Map and Tile2 foundations include the sprite bank as a starting asset; their terrain-only demos do not display a player.

Project tree Manage (…) > Download project copy saves a complete native snapshot through the browser download path. Import project copy… reopens a native file using the browser file input. These alternatives complement the toolbar’s handle-based Open/Save workflow and preserve the same project codec and draft collection.

Hardware Loader + Disk Mastering is the starting point for a complete native multi-load disk. It creates an ASM resident program, an uncompressed bank and a Packed data bank, their Build Targets, and a ready-to-master disk set. Open Disk/Media > Build Dependencies > Run Disk. Joystick port 2 selects banks; an IRQ-driven spinner continues during reads and Fire reinstalls the drive loader after a failed attempt. The included DISK-GUIDE.md explains editing/adding banks, memory reservations, Raw versus Runnable disk entries and the explicit SAVE lifecycle. All files and target/media settings are created through the normal project generator and remain editable in the IDE; no external tool or source checkout is required.

Native Cartridge Starter uses the version-3 scriptable template format. Choose ASM or C + ASM hybrid, PAL or NTSC, and Standard 8K, Standard 16K, Magic Desk or EasyFlash. These choices create real language-specific sources, frame pacing, Build Targets and profile-correct ROM layouts. The hybrid keeps the application loop in C and hardware initialization/rendering in ASM. Video selection configures the application's pacing (five PAL or six NTSC frames per update); it does not change the emulator's global video preference.

The starter separates a RAM application at $2000 from its ROM boot target. Standard/Magic Desk use a CBM80 entry; EasyFlash uses an Ultimax reset entry and changes mapping from a RAM trampoline. All profiles copy a maximum 7,936-byte application payload from bank-0 ROML $8100..$9fff into $2000..$3eff. The boot temporarily owns $0200..$02ff and $fb..$fe; IRQs remain masked until the application installs its own policy. Larger cartridge profiles provide additional ROM capacity, but do not automatically enlarge this starter's RAM-copy contract. Run using Run Cartridge, not F5, and read the generated TEMPLATE.md before extending its memory layout.

Native Magic Desk + Disk replaces the promoted c64lib-dependent cartridge starter: an independent RAM-resident cartridge boot reads two ROM banks, while the original multi-load D64 target and loader remain usable. Both cartridge starters are bundled Stock templates, not Examples; the Examples collection continues to open complete projects from the separate web64-examples repository. Neither requires a Cloud account or MCP bridge.

Managed Cartridge Resources is a separate compact v3 example. Its one self-contained project contains a resident C Build Target, an editable palette file and four saved cartridge layouts. Select a layout and Run Cartridge to see the same application fetch its palette through the logical resource API. Cartridge autostart precedes KERNAL screen initialization, so the example explicitly enables VIC text mode, selects screen/charset memory and initializes Color RAM before showing WEB64. The original Native Cartridge Starter is unchanged; this example demonstrates the managed data service described below.

The W64SID Music + SFX template builds its program at $C000 while the generated SID player runs at its declared $1000 load address. Its compact assembly installer embeds and copies exactly the PSID C64 payload. The editable .w64sid contains looping music and a one-shot voice-3 Fire chirp SFX subtune; the driver overlays that voice when the selected joystick port's Fire button is pressed. The default is port 2; port 1 changes the actual CIA read in the generated assembly. The running screen identifies the tracker project and counts Fire-triggered effects, while emulator audio is enabled and unmuted. Editing and saving the tracker asset regenerates the SID payload and include.

The Copper Raster Split template clears the inherited BASIC screen before installing Copper64. COPPER64 ACTIVE identifies the running project and a frame-paced digit beneath it proves that foreground code is still executing independently of the raster IRQ display list. If the digit stops or the display returns to a flashing BASIC cursor, pause and inspect the now-synchronized debugger PC rather than inferring execution state from the raster colors alone.

Templates use current Web64 project architecture. Applicable projects contain editable .chr, .blk, .map, .spr, .w64chr, .w64blk, .w64map, .w64spr, and .w64sid assets plus their generated assembly includes and C asset declarations. Build Targets own artifact policy, Disk/Media owns disk and cartridge layout, and compatibility modules are target scoped. Templates do not require Gradle, Java, native GoatTracker, native Exomizer, or external C64 build tools.

The Game Runtime World-module templates are the recommended starting point for new native Web64 map work. They use first-class .w64* asset wrappers and expose mono/multicolor character mode and camera bounds at creation time. Static projects call the independently linked <web64/world.h> renderer and reproduce the native per-cell Color RAM plane exactly. Moving projects generate a map-specialized src/world-scroll.asm: the VIC displays an immutable screen page while complete neighboring pages are prepared offscreen. Their Map colors (16 rows, PAL) option reads the editable native map color plane and publishes 640 Color RAM cells with the new screen at a character boundary. The reduced playfield starts at map row five so the supplied platformer's ground stays visible; blank lower rows provide publication time. Region color (22 rows) stays the full-height default. Intermediate fine-scroll and idle frames leave the active screen and Color RAM untouched. The bidirectional example maps joystick chords to all eight directions and advances both fine-scroll axes in one step. World remains a module family within Game Runtime, independently linkable like Motion, Collision, Animation, Sprites, and Actors.

The low-level web64_world_shift_* functions are retained for compatibility and explicit offscreen preparation only. Never pass the screen page currently selected by VIC-II. web64_world_scroll_x and web64_world_scroll_y update camera math only; they do not render or synchronize a frame. The application continues to own input, tick order, IRQ policy, and rendering cadence.

Each project contains TEMPLATE.md with its implementation, first useful edit, build/run workflow and relevant memory or runtime limits. The catalog's Next recommended templates links form an optional learning path; they do not restrict what a user may create.

Smooth scrolling foundations

Horizontal and vertical scrolling are separate templates, each with ASM and C + ASM variants. The mixed variants use Web64 Stack ABI v1 and keep gameplay/camera policy in C while assembly owns raster-sensitive scrolling. The projects use native charset, blockset, map, Color RAM/material planes, and player-sprite assets. src/scroll-config.inc exposes the stable configuration boundary.

The default layout reserves a fixed three-row bottom HUD. Character 63 is generated as a reserved blank HUD glyph, and the initialization loop reloads it for every cell so Color RAM writes cannot leak a visible character into the fixed rows. Set WEB64_SCROLL_HUD_ROWS to 0 when a game needs a full-screen scrolling region. Continuous camera movement is capped at two pixels per frame. The renderer expands the canonical block map once, prepares seven rows of the hidden screen per frame, and flips the complete matrix at character boundaries.

The Template dialog exposes a Color RAM option for every horizontal and vertical ASM or C + ASM scroller. Region color (fast) is the default 6,000-cycle PAL path and leaves substantial frame time for gameplay, collision, audio, AI, and sprite work. Per-cell map colors consumes the native map Color RAM plane and has a verified 19,000-cycle boundary-frame ceiling. The selected value is written to src/scroll-config.inc using the named WEB64_SCROLL_COLOR_REGION and WEB64_SCROLL_COLOR_PER_CELL constants, so it remains easy to change and profile after project creation.

The supplied player is deliberately minimal. It demonstrates input, camera requests, sprite-ready asset placement, and extension hooks without imposing gravity, weapons, enemies, inventory, or another genre-specific system. Replace gameplay policy without moving full-map redraws into the C loop.

Authoring portable templates

Advanced users can author .web64template files in a plain-text or JSON editor. There is no template editor or authoring wizard in this release. The template browser imports, previews and instantiates files; it does not edit their recipes. A template is declarative UTF-8 JSON, not a plugin or host script. Web64 owns its form controls, validation and presentation; a file cannot execute host code, fetch dependencies or supply custom HTML/CSS.

Use the complete template-format reference and downloadable v1 JSON Schema bundle, v2 bundle, or v3 bundle alongside this section. The schema bundle includes a complete example object; save that object alone as a .web64template to inspect a minimal valid file. Alternatively, Export Template supplies a complete working catalog example, including all of its configurations. The fragments below explain individual sections and are not standalone importable templates.

For a complete v2 recipe, download the Scripted Template Starter. It contains a saved native project, its virtual include files, source anchors, input definitions, and if, switch and for examples. Export it from the template browser and compare its script blocks with main.asm and TEMPLATE.md.

Start with an IDE-authored project

  1. Develop the intended starting project in Web64. Include its native assets,

compiler/memory settings, Build Targets and Disk/Media layout when applicable. Add TEMPLATE.md explaining the architecture, first useful edit, controls and build/run procedure. Build and run, then Save Project as .web64proj.

  1. Open Project tree > New > New from template..., select a suitable Stock template

and use Export Template. Keep an untouched copy while editing your recipe.

  1. Give the exported file your own manifest ID, name and description. Replace

its output project with the complete JSON object from your saved .web64proj. Do not invent native project/asset internals, copy only loose source files, or link to a project on your computer.

  1. Begin with no questions and one variant whose when is {}. Add bounded

parameters, additional saved-project variants or text substitutions only when the template needs them. Keep metadata and TEMPLATE.md accurate for every supported result. Remove inherited screenshots or performance claims that no longer describe your project.

  1. Validate the JSON and schema, save with the .web64template extension, then

sign into Cloud and choose Import to My Templates. Select the imported entry and create a project to exercise the native validation path.

  1. Check every discrete variant and relevant input boundaries; save/reopen,

edit, build and run the created .web64proj. Check native asset editors and Run Disk if applicable. A schema-valid file is not proof of a working C64 program.

No Web64 source checkout, Node installation or external build system is needed for this workflow. JSON tooling is optional; the IDE remains the authority for project creation, building, native assets and disk mastering.

File structure and version fields

The top-level JSON object uses these fields. Unknown fields reject; comments and trailing commas are not JSON. Empty optional lists can be omitted.

FieldRequiredMeaning
kindYesExactly "web64.ide.template"
versionYesTemplate file-format version 1, 2 or 3
manifestYesIdentity, catalog metadata and compatibility information
parametersNoInput definitions; omitted or [] means no questions
variantsYesOne or more { "when": ..., "project": ... } records
substitutionsNoExplicit literal replacements in authored text files
imagesNoEmbedded logo and/or screenshot PNG records
scriptV2/V3Versioned, bounded text-rendering blocks; v3 adds native table/matrix generation

The following is a minimal manifest fragment for a simple ASM template:

{
  "manifest": {
    "version": 1,
    "id": "my-demo-starter",
    "displayName": "My Demo Starter",
    "description": "A small starting project for a new demonstration.",
    "category": "Demo",
    "language": "ASM",
    "difficulty": "Beginner",
    "architectureSummary": "Assembly owns startup and the frame loop.",
    "projectFormatVersion": 4,
    "templateVersion": 1,
    "files": ["main.asm", "TEMPLATE.md"]
  }
}

All fields shown in this manifest are required. The ID uses 2–64 lowercase letters, digits or hyphens and starts with a letter or digit. Choose a stable ID for your template. files is descriptive: it does not create files, and must include TEMPLATE.md. Actual contents belong to the embedded projects.

Do not confuse the version fields: top-level version: 1, 2 or 3 describes the template format; manifest.version: 1 describes the manifest format; manifest.projectFormatVersion: 4 declares the native project format; and manifest.templateVersion is your positive integer recipe revision. Increase the latter when distributing a revised recipe, not the format versions. Cloud asset/version identity is separate and is never assigned by this JSON.

Category is one of Beginner, Game, Demo, Graphics, Audio, C / Mixed, Disk / Cartridge or Testing / SDK. Language is ASM, C or Mixed; difficulty is Beginner, Intermediate or Advanced. These values are case-sensitive. Optional fields include firstEdit, tags, targetTypes, compilerAbi, sdkModules, compatibilityModules, nativeAssets, palNtsc, memoryAssumptions, estimatedPrgBytes, documentation, recommendedNextTemplates, sourceProvenance and performance; consult the schema for their exact shapes. Record measurements only when verified.

Define the questions

Each parameter requires a unique id, a type, a label and a valid defaultValue. description and required are optional. IDs start with a letter, use only letters/digits and are at most 64 characters; reserved object keys such as constructor and prototype are not allowed. Missing answers use defaults. These definitions are the only input-form authority; do not add a separate creationOptions field to the portable manifest.

TypeRequired type-specific fieldsAnswer
textmaxLength from 1 to 256Single-line string; required: true rejects blank text
numberFinite min and max within ±1 billionJSON number in range, not a quoted number; fractions are permitted unless v2 integer: true is set
booleanNonetrue or false, not 1, 0 or a string
selectchoices with string value and labelExactly one declared value
multiselectSame choices definitionArray of distinct declared values; required: true rejects []

Choices can also have a description. In v1, use a select containing allowed integer strings when generated code needs a small discrete set. In v2, integer: true enforces whole-number answers and enables bounded count loops when min >= 0 and max <= 32. A Boolean's required flag does not require it to be true. For example:

{
  "parameters": [
    {"id":"title","type":"text","label":"Title","defaultValue":"My Demo","maxLength":32,"required":true},
    {"id":"speed","type":"number","label":"Speed","defaultValue":2,"min":1,"max":8},
    {"id":"music","type":"boolean","label":"Include music","defaultValue":true},
    {"id":"video","type":"select","label":"Video standard","defaultValue":"pal","choices":[{"value":"pal","label":"PAL"},{"value":"ntsc","label":"NTSC"}]},
    {"id":"credits","type":"multiselect","label":"Credit sections","defaultValue":[],"choices":[{"value":"code","label":"Code"},{"value":"art","label":"Artwork"}]}
  ]
}

Multiselect answers are normalized into the order in which choices are declared, not the order clicked. Use that same order in variant conditions. Controls do not automatically change code, include a runtime, create assets or add a target; you must describe the effect through variants or substitutions.

Define the output with variants

Each variants[].project holds a complete native .web64proj object, not a filename, URL, stringified JSON or a patch. Preserve the saved project's main, files, native asset records, settings, build and media configuration together. Referenced charsets, blocksets, maps, sprites, music and their native generated bindings must remain a coherent editable project. manifest.files is not a replacement for that VFS. Never depend on publisher-local paths or credentials.

when maps parameter IDs to exact answer values. All listed entries must match; unlisted parameters do not affect that variant. Exactly one variant must match. when: {} matches every answer set; it is not a lower-priority fallback. There is no first-match ordering, implicit merge or executable output function.

For the music and video questions above, author and save four real projects:

Variant whenEmbedded output project
{"music":true,"video":"pal"}PAL project with music
{"music":false,"video":"pal"}PAL project without music
{"music":true,"video":"ntsc"}NTSC project with music
{"music":false,"video":"ntsc"}NTSC project without music

The author supplies the actual PAL/NTSC and music differences in those projects; the labels do not implement them. For a fixed template use one saved project with when: {}. Avoid branching on unbounded text/numeric answers; substitutions are usually the right tool when only text changes.

Apply literal text substitutions

Place tokens such as {{title}} in the embedded project's authored source or documentation. Declare each parameter's destination files explicitly:

{
  "substitutions": [
    {"parameter":"title","files":["main.asm","TEMPLATE.md"]},
    {"parameter":"speed","files":["main.asm"]},
    {"parameter":"credits","files":["TEMPLATE.md"],"separator":", "}
  ]
}

For instance, ; {{title}} in assembly and # {{title}} in TEMPLATE.md become the chosen title. A parameter alone does nothing to the project. The corresponding token must exist in a declared destination. Replacements are single-pass: answer text containing another token is not expanded again. Numbers become decimal text, Booleans true/false, and multiselect values join with the specified separator (a comma by default).

Destinations must exist in every variant and be authored .asm, .s, .inc, .c, .h, .txt or .md text, not a generated include, binary or native asset record. Paths are exact virtual-project paths with forward slashes; no absolute paths or ... Tokens do not rename files, rewrite project settings, modify pixels or add dependencies. Use different complete variants for such changes.

Substitution does not escape C/ASM string literals or evaluate expressions. Use free text in safe comment/documentation positions, or constrained choices for code constants; consider the destination language's comment delimiters too. Do not assume Boolean strings are numeric C64 assembler constants.

Add v2 conditional and repeated source

Use top-level "version": 2 when source needs a checkbox branch, select case or bounded repetition. Add "script": {"version": 1, "blocks": [...]}. Each block names an authored virtual file, an exact full-line comment anchor and an ordered body array of nodes. For assembly, place a line such as ; @web64template setup in the saved project's main.asm, then name that anchor in the block. C headers use /* @web64template setup */; Markdown uses <!-- @web64template setup -->; plain text uses @web64template setup. The anchor must occur exactly once in every variant. Rendering replaces that line; the surrounding source remains normal editable project code.

The operations are text for literal output; if with a typed equals or multiselect contains test and then/optional else bodies; switch on a select or boolean parameter with distinct cases and an optional default; for over an integer count or selected multiselect choices; and emit for a loop variable. A switch must cover all choices unless it has a default. Count loops require a v2 number input with integer: true, min >= 0, max <= 32; indices begin at zero. Multiselect loops use declared choice order. Loops and branches may nest, but there is no expression evaluator or host-code access.

For example, a checkbox can change initialization with this block fragment:

{
  "file": "main.asm",
  "anchor": "; @web64template setup",
  "body": [
    {"op":"if","test":{"parameter":"blueBackground","equals":true},
     "then":[{"op":"text","value":"    lda #$06\n"}],
     "else":[{"op":"text","value":"    lda #$00\n"}]}
  ]
}

The full starter artifact demonstrates switch, counted for, multiselect for.each, if.contains and emit alongside this pattern. It also embeds both PAL and NTSC .inc files in variants[0].project.files. No external file dependency is permitted. Every literal include/import in the selected source must resolve to a virtual file in that project or a bundled Web64 SDK header. A script only renders authored text: use complete native variants when changing virtual file inventories, native assets, settings or build targets. Web64 selects one variant, renders the script, applies literal substitutions, then validates the final native project.

The interpreter caps scripts at 64 blocks, 256 nodes, nesting depth 8, 32 iterations per loop, 4096 executed node visits and 2 MiB of emitted UTF-8. Unknown operations, invalid comparisons, incomplete switch coverage, unbound loop variables, duplicate/missing anchors and missing virtual dependencies reject. Test every meaningful branch and loop boundary through normal create/save/reopen/build/run controls. The v2 reference gives the complete node grammar and a larger JSON fragment.

Generate tables and matrices from answers (format v3)

Templates can use the existing Web64 table/matrix generator during creation. For example, ask for wave amplitude and sample count to produce a sine include, or ask for rotation steps to produce fixed-point matrices. No MCP connection is needed: the IDE uses the same native generator as its insertion dialog.

Set the template envelope to version: 3 and keep script.version: 1. Inside a declared source block, use a node such as:

{
  "op": "generate", "kind": "table",
  "options": {
    "preset": "sine", "name": "wave",
    "count": { "parameter": "sampleCount" },
    "amplitude": { "parameter": "waveAmplitude" },
    "center": 128, "numericType": "uint8", "overflow": "error"
  }
}

Declare sampleCount as a bounded integer number question and waveAmplitude as a bounded number question. The containing block chooses the output file and full-line anchor: .asm/.inc emits ASM, .c/.h emits C. Put C data at file scope. kind: "matrix" uses the native matrix presets. Options accept constants or typed parameter references, never arbitrary formulas or executable scripts.

Generated data becomes ordinary editable project source. Creation checks native numeric rules and the 16 KiB per-generation data limit, then validates the whole project before replacing the workspace. Source emission also counts towards the existing script budget. Save/reopen preserves the table; later edits do not silently regenerate it. V1/V2 templates remain supported unchanged.

See the complete v3 authoring example and option reference and v3 JSON Schema bundle. Test boundary answers and build/run the resulting projects; generation alone does not prove that their tables fit the application's C64 memory layout.

Supply previews and respect the limits

images.logo and images.screenshot each use { "mimeType": "image/png", "data": "...", "alt": "Description" }. Replace ... with raw base64 PNG bytes, without a data: URL prefix. Only static embedded PNG is accepted: no SVG, animation, external URL or custom styling. Each image is limited to 512 KiB decoded PNG file bytes and 2048×2048 pixels. Prefer an actual emulator screenshot of the default configuration; omit an image rather than displaying an unrelated preview.

The current format validation allows up to 16 parameters, 32 choices per choice parameter, 64 variants and 64 substitution records, each naming up to 64 files. Each native project is subject to its 32 MiB validation limit. The local parser's 256 MiB envelope ceiling is an allocation guard, not the Cloud import limit: the normal Import to My Templates workflow currently accepts at most 8 MiB per file, and account storage quotas also apply. Large/gigabyte template packs are not supported by this release's import transport.

Use the JSON Schema correctly

The v1 schema download, v2 schema download, and v3 schema download are bundles, not templates or root schemas. Each contains:

https://web64.nofs.ai/schemas/web64template/1, /2 or /3.

media (media/2) schemas required by its $ref references.

guidance.

In a Draft-07-capable JSON validator, register all referencedSchemas using their $id values, then validate your template object against schema. Do not validate against the entire bundle or import the entire bundle into Web64. A JSON editor can associate extracted schema files externally; do not insert $schema into the template envelope, which rejects unknown fields. Schema IDs identify definitions and are not guaranteed downloadable URLs; resolve them from the supplied bundle instead of fetching guessed endpoints.

Schema validation checks fields, types, required data, enums and structural limits. Web64 additionally checks native project/asset semantics, safe paths and references, parameter/default validity, exact variant selection, substitution destinations and PNG decoding. A JSON Schema pass does not replace importing, creating and testing each output through the IDE. The same schema is available to compatible MCP clients as schemas/web64template/1, /2 or /3; MCP is optional and is not needed to author or install a template.

Install, verify and distribute

External templates require a Cloud account, not a paid subscription. Free accounts can install 5 and paid accounts 20, independently of byte quotas. Local files are transport into My Templates, not browser-local installations. Stock remains account-free, and an imported template cannot overwrite its Stock namesake. Cloud ownership, marketplace listing, price and license-grant records are not fields to invent in the portable envelope. Marketplace installation is not implemented in this release.

Test default answers, every discrete variant and numeric/text boundaries. Check that invalid answers reject, generated bindings still agree with native assets, and saved output projects reopen, remain editable and build/run normally. Creation does not automatically save, build or run. A created project has no live dependency on the original template or its Cloud installation; removing or revising an installation does not rewrite existing projects.

Common authoring failures:

ErrorWhat to check
unsupported format/versionThe root must be a template, not .web64proj or the schema bundle; check kind and version
invalid template schemaRequired metadata, enum spelling, unsupported fields and correct native project object
no project variant matches these answersMissing condition combination or wrong value/type/order
ambiguous project selectionOverlapping conditions, duplicate variants or an unintended unconditional {}
substitution must target an authored text fileThe path must exist in every variant and must not be binary/generated/native-asset data
Cloud upload or quota errorFile size, remaining account storage and installed-template slots are separate limits

Web64 Cloud

SID Tracker also has an optional Cloud tab for reusable private Instruments, Patterns and Subtunes. It uses explicit save/preview/import copies, not linked project dependencies; local music editing remains available offline. Free accounts can store 10 of each category, while current paid entitlements allow 200 instruments, 100 patterns and 50 subtunes, subject to total storage limits. See the SID Tracker guide for dependency remapping, payload limits and deletion/retention semantics.

Web64 Cloud is an optional persistence and private-asset service. The complete local IDE remains available without an account, subscription, or network connection. Cloud code and network access are loaded only after the Cloud workspace is opened; ordinary local startup and editing do not initialize Supabase or a cloud worker.

Open Cloud with the cyan cloud button in the main toolbar or select the Cloud workspace tab. A link such as /ide/?cloud=files can open a specific Cloud section. The workspace has six compact sections: Projects, Assets, Files, Storage, Activity, and Account.

Sign up and sign in

Web64 Cloud supports passwordless email and GitHub authentication. Enter an email address and select Continue with email, then follow the one-time link to return to Web64 IDE, or select Continue with GitHub and authorize the Web64 Cloud sign-in application. Either flow creates a new account or signs in an existing one. Supabase links identities automatically when GitHub supplies the same verified email address; a different provider email identifies a separate cloud account. Authentication remains owned by the main browser thread; provider secrets never enter Web64, and only short-lived access credentials are used for authenticated Cloud requests.

A newly created account receives Web64 Cloud Free with write access, 8 MiB of stored cloud data, 5 cloud projects, and 20 private-library items shared by Assets and Files. Existing non-subscribers receive the same entitlement automatically. Web64 Cloud Paid costs EUR 5.00 per month, tax inclusive, and raises the limits to 512 MiB, 100 projects, 500 library items, and 2 GiB of monthly transfer. Billing activation comes from the signed server webhook, not merely from returning from checkout. Local projects and portable exports do not require a subscription.

Public profiles

The Account section contains an optional public creator profile. A profile remains private until you claim a handle and enable Public profile. Handles use 3-32 lowercase letters, numbers, underscores, or hyphens. A claimed handle is permanent, case-insensitively unique, and becomes the stable profile URL https://web64.nofs.ai/u/<handle>. Choose it carefully.

Display name, bio, website, and location remain editable. Display name falls back to the handle when omitted. The avatar editor accepts PNG, JPEG, or WebP input, provides a square crop and zoom control, and publishes only a static 512x512 WebP result. Original image files and filenames are not uploaded. Profiles without an avatar use initials or the default user icon.

Web64 Cloud Account view with public profile fields, avatar, preview, and plan information
The Account view separates the optional public creator profile from private identity, quota, and subscription details.

Only the handle and explicitly entered profile fields are exposed on a published page. Email addresses, account identifiers, subscription information, projects, and private-library contents are never part of the public profile response. Private, deleted, malformed, and unknown profile URLs all show the same unavailable state. Making a profile private removes its public avatar; republishing uses the fallback avatar until a new image is uploaded.

Handles from effectively deleted accounts remain reserved as handle-only tombstones to prevent impersonation through old profile and future marketplace links. The retained reservation does not expose the former account or email address.

Cloud projects

The Projects section uses a sortable, searchable table with project state, update time, file count, asset count, and storage use. Select a row to inspect its identifier, revision, schema, and timestamps. Double-click a project or use Open to download it. Opening replaces the current IDE project only after the normal unsaved-work safety check. The open project is marked Open in the project list.

Web64 Cloud Projects view with project table, metadata, save, restore, export, and deletion controls
Cloud Projects presents searchable project revisions and the explicit actions that can change or export the selected project.

Use Convert Current to create a Cloud project from the current portable semantic snapshot. Conversion also makes the new Cloud project the active project. Existing local entry IDs are assigned once during conversion and are preserved by the server. Import converts a saved .web64proj into a Cloud project. A selected Cloud project can be renamed, exported as an ordinary .web64proj, given a manual restore point, or moved to recoverable deletion.

When a Cloud project is active, the main title-bar Save Project command and Ctrl+Alt+S save to Web64 Cloud by default. Each save uploads the current portable project snapshot and commits a new revision against the revision that was opened; a conflicting newer cloud revision is reported instead of being overwritten. The selected active project also exposes Save Current in Cloud Projects. Save Project as (Ctrl+Alt+Shift+S) still writes a portable local .web64proj copy and does not change the Cloud save target.

Cloud conversion does not change the local project format or make the Cloud copy the only usable representation. Exported .web64proj files remain portable authority for moving or archiving a project outside Web64 Cloud.

Private asset library

The Assets section presents private assets as a searchable thumbnail grid with an asset-type filter and a detail/version pane. Preview thumbnails are generated from the actual C64-indexed asset data and custom palette when available. Right-click a supported project-tree asset and select Save as Cloud Asset, or use Save Current Asset in Cloud Assets, to create an immutable private version. The toolbar accepts an optional description and comma-separated searchable tags. Asset-native tags, such as sprite-frame tags, are included automatically. A library item can be renamed without changing its immutable versions. Delete moves the item and its version history to recoverable deletion.

Web64 Cloud Assets view with private asset thumbnails, version metadata, dependencies, and import controls
Private assets are browsed visually; the detail pane exposes the selected immutable version, native metadata, dependency pins, and import destination.

A Cloud Map Asset version is an atomic semantic snapshot. It carries the structure bytes, index width, dimensions, cell kind, Color RAM, video-matrix data, material mapping, map metadata, and pinned blockset/charset dependencies. Import recreates the native .map, generated plane binaries, and include binding from that same immutable version, so a map cannot combine one version's structure with another version's colors or gameplay materials.

When a different cloud asset already has the same name, the IDE asks how to proceed. Add New Version appends the snapshot to the existing logical asset. Save Renamed Copy creates a separate item under the entered name. Overwrite Library Item moves the old item to recovery and creates a new logical asset with the old name; it does not rewrite or silently destroy immutable history.

Each immutable version atomically stores its content hash, payload reference, preview, semantic metadata, compatibility data, and exact dependency pins. Sprite versions retain bytes, hires/multicolor mode, C64 multicolor registers and per-frame foreground colors, dimensions, frame names/order/tags, animation sequences, frame timing, expansion flags, overlay relationships, sprite-tile definitions, and custom palette identity. Sprite overlay-pair descriptors bind the exact multicolor and hires layer versions. Blocksets retain character dimensions, labels, mode, colors, custom palette, and referenced charset. Maps retain cells, dimensions, index width, mode, colors, custom palette, referenced blockset, and resolved charset. SID and Web64 SID Tracker assets retain concise title, author, machine, address, subtune, pattern, and instrument summaries. The Version Metadata area under Versions displays the selected immutable version, including C64 color swatches, frame/animation lists, payload size, and pinned dependency identities.

Map and blockset relationships use the same authoritative project fields as the asset editors: a map stores blocksetPath, and a blockset stores charsetPath. The default Bundle dependencies mode saves a self-contained package and pins the exact blockset and charset versions. Reference exact existing reuses matching private versions only when their content hashes and types match. Save only; require pins accepts dependencies only when the project records already identify exact owned cloud versions whose hashes still match. Content-addressed storage is reused behind the scenes, so the same charset used by several maps is stored once even though every map version remains reproducible.

Select an explicit version and destination path, then use Import Snapshot to import the asset and every exact pinned dependency as ordinary project files. Required dependency paths are preserved so a map opens with the intended blockset, charset, mode, colors, and palette. If an imported dependency would replace a different local file, the IDE lists the collisions and requires confirmation before applying the complete package. Cloud metadata cannot override payload bytes or the chosen primary destination path. Imports remain available whenever the signed-in account has read access, including while it is over quota.

Cloud asset imports are copies in the first release. They do not remain silently linked to a changing remote asset. Public marketplace publication, shared ownership, and live pinned asset links remain outside this release.

Cloud Files and project-tree actions

The Files section stores reusable source, header, assembly, include, macro, configuration, and binary files independently of a project. Its compact explorer has a folder tree, a searchable file list, and a detail/version pane. Folders such as include, asm, or libraries/audio can be created, renamed, and moved; moving a folder also moves its descendants and contained Cloud Files. Folder deletion is allowed only when the folder is empty.

The read-only preview below the center file list is enabled by default; use the eye button in the Cloud Files toolbar to show or hide it. The preview follows the selected immutable version and supports C source, headers, assembly, includes, macros, configuration files, and other recognized text formats before import. Drag the horizontal divider to resize the preview; when the divider has keyboard focus, Up and Down resize it in fixed steps. The open state and height are remembered locally. Preview content is fetched only while the panel is open, is bounded to 512 KiB, and binary files remain available for import without being rendered as text.

Use Save Current File to save the selected project file. When its name and folder, or its imported cloud identity, already exists, the IDE asks whether to Create New Version, Overwrite Cloud File, Save Renamed Copy, or Cancel. A new version appends the current local file content to immutable history. Overwrite moves the previous cloud item and its history to recovery before creating a fresh item; it does not mutate an immutable version. Renamed copies receive a suggested unique name within the same cloud folder.

The detail pane's New Version command copies the matching imported project file, its project destination, or the active project file into the next visible version of the selected cloud item. The new revision is selected after saving. Imported cloud provenance remains useful while the remote item exists, but deleting that cloud item does not poison the local copy: its next save creates a new cloud file. Rename or move a cloud item without altering stored versions. Import copies the selected version into the entered project destination as an editable ordinary project file, so paired user libraries such as an assembly implementation and C header can be organized and reused across projects.

Right-click the project root, a project folder, the main source file, or an imported project file to open the project-tree context menu. Files offer Save as Cloud File; recognized Web64 assets also offer Save as Cloud Asset. Folders offer Import Cloud File Here, which opens Cloud Files and uses that project folder as the destination context. The menu also provides Cloud Assets and Cloud Files browsers alongside normal open, rename, remove, and folder commands. Cloud write commands are disabled while signed out or when account access is administratively disabled. Reaching a quota pauses new remote writes until data is removed or the account is upgraded; existing cloud content remains available to browse, import, and export.

Storage and synchronization

The Storage section separates cloud quotas from browser-local durability. It reports cloud bytes, project count, total private-library item count (assets plus files), and monthly transfer against the active entitlement. Free accounts do not have a separate monthly-transfer meter; their 8 MiB stored-data ceiling remains enforced. The durable queue panel reports pending operation count, queued bytes, oldest pending work, and the last error, and provides Sync now and Export current project commands.

Web64 Cloud Storage view showing quotas, transfer, and the durable browser queue
Storage makes remote quotas and the browser-local durable synchronization queue visible as separate authorities.

The status vocabulary is precise:

Current accepted browser state is the editing authority. The durable local queue is the durability authority until acknowledgment. The latest confirmed server revision is the shared Cloud authority. A downloaded .web64proj is the portable authority. Local edits must never overwrite a newer server head merely because they are newer in the current browser session.

Activity, restore points, and account

Activity lists immutable project revisions, checkpoints, restores, and conflict boundaries and can be filtered by project. Restore points are explicit named or manual boundaries; creating one does not mutate the project payload. Deleted projects and account data use recoverable states rather than immediate silent removal.

Account displays the signed-in identity, optional public-profile editor, Free or Paid plan, exact quotas, subscription status, current period, and grace period. Upgrade opens Stripe-hosted checkout. Manage billing appears for accounts with subscription history and opens Stripe-hosted account management. When a paid period or failed-payment grace ends, the account falls back to Free rather than losing Cloud access; data above Free limits remains readable but new writes wait for cleanup or another paid period. Account data can be exported, the session can be signed out, and account deletion can be scheduled with a 30-day recovery period.

Support diagnostics are metadata-only by default. A project/source/asset-content support bundle requires explicit user consent, a reviewed manifest, case binding, restricted audited access, revocation, and 14-day expiry. Siteledger Solutions Oy claims no ownership of user projects or original assets merely because they are stored in Web64 Cloud.

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.

Web64 IDE Settings tab with editor, assistance, diagnostics, emulator, compiler, optimizer, and disk defaults
Settings are grouped by concern so browser preferences, project compiler policy, runtime controls, and disk defaults remain distinguishable.

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.

The v2 Settings surface uses one 28px outer height for every one-line toggle, selector, field, button, scale group, and volume wrapper. Wrapped rows keep an explicit gap, so controls never touch vertically even when the editor bay is narrowed. Ordinary Settings actions and selections use neutral-grey workstation chrome; color is reserved for the selected Settings tab and real warning, status, or domain semantics.

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. Markdown (.md) and plain-text (.txt) documents wrap automatically when opened. The source-editor toolbar wrapping icon and the editor context menu can override wrapping for the current document without changing the saved global preference. 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.

The Disk Mastering Defaults section contains only project-independent preferences used when creating media: the default DOS sector interleave for new disks and the byte used to pad unused generated sectors. These values are stored in the browser rather than in .web64proj. Track 18 is shown as a fixed 1541 BAM/directory fact; it is not exposed as a configurable preference.

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/trajectories/patrol.w64traj
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, .w64traj, .sid, or .w64sid files, Web64 IDE converts them into the matching editable/inspectable asset records where possible and creates generated records for supported asset types. A valid .w64traj owns deterministic .traj and .inc siblings; stale generated trajectory data is rejected by the compiler. 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. Folders use larger monochrome closed/open icons; there are no redundant OPEN or CLOSED labels. 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, .w64traj, .w64sid, or .sid files to open the matching Character, Sprite, Block, Map, Trajectory, SID Tracker, or SID Editor view. Source files retain their direct single-click code-editor behavior.

Drag a virtual file onto a project folder to move it into that folder, or onto the project root to move it back to the root. Web64 rejects moves that would overwrite an existing path and does not treat folders themselves as draggable files. A successful move updates project references to the moved path, including generated-from and linked-asset relationships, without moving sibling files. The target summary above the tree names the active runnable artifact rather than an internal asset category.

Creating virtual files

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

Project tree New menu with project presets, files, folders, and generated asset families
The New menu creates either a complete project preset or an individual virtual file, folder, or editor-backed asset family.

Trajectory assets and Trajectory Editor

.w64traj is the optional rich source of truth for one reusable local path. It is a first-class asset with its own editor, not an extension of .w64map or the Map Editor. The authoritative point list begins at local (0,0); each following point stores a 1–255 PAL-frame duration. Runtime defaults may describe loop, ping-pong, reverse, and X/Y negation. The authoring record may also remember a spatial anchor, optional read-only map backdrop, optional Appearance Preview, grid, and useful viewport framing.

Saving the source deterministically generates name.traj and name.inc. .traj is always raw signed delta_x, signed delta_y, unsigned duration triplets—exactly three bytes per segment. It contains no map, sprite, animation, palette, anchor, world coordinate, viewport, or editor metadata. .inc declares placement-neutral assembly constants and a descriptor macro; it imports no runtime and chooses no address. C sees the generated segment array and Web64TrajectoryPattern descriptor through <assets/generated.h>. Assembly may use the generated .inc and .incbin the .traj, or ignore Web64 declarations and parse the triplets with completely custom code. Runtime starting coordinates always belong to the caller.

The editor's layered HTML5 Canvas viewport provides pan/zoom, grid and snapping, draggable/numeric keyframes, range and box selection, atomic group moves, insertion/deletion, duration editing, a proportional segment strip, scrubbing, diagnostics, and bounded undo/redo through the runtime maximum of 1024 segments. A compatible project map may be selected as an accurately rendered read-only backdrop. It is authoring context, not a level: it never creates runtime placement, camera behavior, or map ownership, and no map is required.

Static, fading-ghost, and realtime preview use an exact integer browser kernel differentially verified against assembled 6502 execution. Frame stepping, reverse, ping-pong, mirroring, and 1–8 phased instances therefore reproduce the runtime's centered Q12.4 DDA rather than floating-point interpolation. Preview speed changes wall-clock cadence only; Web64 is PAL and logical timing remains 50 Hz. Playhead, speed, ghost/phase counts, and temporary traversal variants are session-only. Appearance Preview can render a sprite frame or an existing authored animation, but remains authoring-only visualization and does not generate runtime animation binding.

The import icon accepts Tiled TMX/TMJ polyline objects, including nested layer offsets and object rotation. Fractional transformed geometry is shown in a Canvas preview and must be explicitly quantized. Geometry with a signed-byte segment overflow is diagnosed and never silently subdivided. Missing timing requires an explicit 1–255 default; geometric length is never used to invent timing. web64.durations, web64.loop, web64.pingPong, web64.reverse, web64.negateX, and web64.negateY are the portable custom properties. The export icons create deterministic standalone object-only sidecars and exclude project map/sprite references, asset IDs/paths, anchor, viewport, and editor/session state. See Tiled's TMX reference and TMJ reference.

The workspace follows the Web64 v2 neutral-grey workstation rules: compact icon-first controls with accessible names, shared surface/text/border tokens, and semantic blue/yellow/red accents only where they convey information, focus/warning, or danger. Legacy teal is not used as generic editor chrome.

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, or another explicit project build action flushes the latest draft without moving the caret or losing a trailing empty line. Opening Generated ASM is inspection only; it does not flush or compile a draft. 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, bundled SDK header, and bundled assembly include. 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 SDK headers/includes:

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 Back, 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.

Every successful definition, implementation, include, and generated-source jump records the originating document, selection, and scroll position. Choose Back or press Alt+Left to restore it. The history is editor-local, bounded, and cleared when another project is opened; it does not mutate source or project undo history.

The menu displays the corresponding editor shortcuts:

CommandShortcut
Go to definitionF12
Go to implementationCtrl+F12
Show generated ASMAlt+F12
BackAlt+Left
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' = m00*x + m01*y + 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. For C, C array shape defaults to Multidimensional: multiplication tables emit [ROWS][COLUMNS], matrix sequences emit [STEPS][ROWS][COLUMNS], and identity/custom matrices emit [ROWS][COLUMNS]. Flat (legacy) retains the original one-dimensional declaration. Separate matrix layout emits one one-dimensional table per coefficient. Existing generator API callers that omit the shape option continue to receive flat output. Count/dimension macros, optional static storage, and automatic <stdint.h> or <web64/fixed.h> includes remain available. Assembly output remains linear and unchanged: 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. Structured and flat C forms contain the same row-major bytes. 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, C shape and dimensions, encoding, endpoint policy, and overflow policy. Generated output is ordinary editable source, not a project asset or hidden compilation stage.

Assembly quick reference

In an assembly document, place the caret on an official MOS 6502 instruction mnemonic to show its expanded name, affected flags, addressing modes, opcode bytes, instruction lengths, timings, and conditional timing notes in the Inspector. Mnemonic-like text inside operands, comments, strings, labels, and non-assembly documents is ignored.

The same Inspector provides argument help for macros declared by the bundled Web64 runtime .inc files. Place the caret on a macro invocation or inside its comma-separated argument list to see the owning include, complete invocation signature, ordered argument names, and the current argument position. Commas nested inside parentheses, brackets, or braces do not advance the current argument. The catalog is generated from the actual bundled .macro declarations, so it stays synchronized when runtime helpers are added or their parameter lists change. Macro definitions, comments, strings, operand references, and non-assembly documents do not activate the helper.

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, 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. Source locations survive nested project and bundled-header includes, pragmas, comments, multiline declarations and escaped-newline splicing. Functions, parameters, locals, globals, inline assembly and C diagnostics retain their authored file and line rather than an expanded translation-unit offset. Selecting a C symbol or mapped line opens that original project file or read-only SDK header. Assembly line maps remain byte-exact and C mappings resolve through their generated symbol addresses.

Diagnostic labels are consistent across the Inspector, editor tooltips, Build Targets, Disk/Media and asset/import tools. A warning does not block a valid build, while an error prevents the affected operation. General project or memory-ownership diagnostics have no invented source position and do not place a marker on line 1. Browser capability information is not emitted as a warning in unrelated projects; native disk mastering and browser-side compression require no desktop helper process.

Selection timing and byte size

Highlight a contiguous block in an ASM Source document to show Selection timing above the opcode reference in the Inspector. In Generated ASM, click an instruction row, then Shift-click another to select a range. A fixed-cost selection shows, for example, 42 cycles · 28 bytes; conditional timing shows 42–47 cycles (+0…5). Expand Timing details for branch and indexed-read page-crossing penalties.

Source selections count whole touched lines; ending at the beginning of the next line excludes that line. Multiple/rectangular selections are not combined: select one contiguous range. Generated ASM ranges include intervening rows even when search filters hide them. C-source selections do not receive inferred cycle counts; use Generated ASM instead.

This is a static sum of emitted instructions, each counted once. It does not follow branches, multiply loop iterations or include subroutine bodies. Conditional ranges are conservative instruction-cost envelopes, not average times or guaranteed execution paths. VIC-II stalls, interrupts and self-modifying code are not included. Use the Profiler for observed execution rather than interpreting the sum as elapsed raster time.

Byte size counts assembled output, not source text or compressed/disk size. Mixed selections show code and data separately; only code contributes cycles. Data-only selections still show bytes. Attributed binary imports and emitted padding count; address gaps, PRG headers, virtual reservations, inactive source and macro definitions do not. Macro invocations include their emitted expansion; selecting an include directive does not include another file's contents. Unsupported instruction timing or ambiguous attribution is explicitly marked rather than silently counted as zero.

Results use the current target's existing successful assembly snapshot. Unsaved changes are fine once compiled. Pending, failed or stale assembly hides totals; selection itself never starts a build. With automatic compilation off, use the normal build workflow after editing. The lazy calculation runs in the existing compiler worker; it does not start an emulator, enable profiling or require a bridge.

Build Output

Choose Build Output beside Source and Generated ASM in Code. It is always available, even when the optional Generated ASM view is disabled. The view does not start a build or interrupt the emulator merely because it is opened.

Generated Assembly machine view

Generated Assembly is a read-only projection of built machine instructions. It is available by default; an explicitly saved off setting remains off and can be changed in Settings > Editor. Choose Generated ASM in the Source | Generated ASM | Build Output selector in Code. 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.

During Run Disk, Built component selects an instruction bundle from the launched disk's file and dependency graph. Follow running component switches that selection when a paused PC resolves to another loaded component; deselect it to inspect a particular loader or game output. This built view remains pinned when another target is edited or a newer build is made. Opening it does not compile, alter the running machine, or silently substitute the editor's map. Source navigation uses the loaded source snapshot; if that file has since changed, Web64 shows a read-only excerpt rather than claiming the current editor text is running.

Generated Assembly machine view with addresses, encoded bytes, instructions, source provenance, and active runtime row
The demand-driven machine view aligns addresses and encoded bytes with their originating C or assembly source without creating another editable source file.

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.

When paused, opening or reactivating Generated ASM centers the current execution row where practical, preserving its highlight. Follow PC also centers subsequent execution changes. Manual wheel, touch or navigation-key scrolling disables follow so the view does not fight your inspection. Re-enable Follow PC to resume tracking; switching away and back while paused reveals the current PC again without forcing continuous follow.

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 the browser-native Web64 C compiler. Existing projects that omit compiler-profile fields retain the web64-c-v0.1 dialect and web64-static-v0 ABI byte-for-byte where no bug fix applies. New C projects use the web64-c90 freestanding dialect and web64-stack-v1 ABI. Strict ansi-c90 is also available when Web64 language extensions should be diagnosed. This manual describes all three profiles and their shared C64 lowering, hardware, asset, multi-translation-unit, and mixed C/assembly contracts.

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. Build Targets select the exact C and assembly translation units linked into each PRG. C modules are ordered before assembly modules so generated C labels, compiler-created ABI slots, and handwritten assembly labels share the final assembler symbol namespace.

Web64 C emits a startup label that calls the configured C entry function. A Build Target rooted at main.c does not need a placeholder assembly translation unit. Legacy/single-target C projects may still contain the IDE's root main.asm; leave that file empty unless it intentionally provides assembly support. If a mixed project deliberately supplies its own assembly startup, call the C entry yourself, for example jsr _main for a C function named main, and select only the intended startup module in the target.

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 new-project C configuration is:

{
  "enabled": false,
  "dialect": "web64-c90",
  "abi": "web64-stack-v1",
  "entry": "main.c",
  "includePaths": ["include", "assets"],
  "stdout": "screen",
  "stdin": "keyboard",
  "runtimeProfile": "freestanding",
  "stack": { "size": 512, "address": null },
  "compilerBackend": "web64-native"
}

Persisted projects without dialect or abi fields normalize to web64-c-v0.1 plus web64-static-v0; this is the compatibility profile. web64-c90 enables the C90 translation phases and Web64 extensions. ansi-c90 uses the same backend but reports extensions such as //, _fastcall, _Bool, and inline assembly as errors. web64-stack-v1 supplies recursive automatic frames, variadic layout, and wide parameters/returns. The stack region defaults to the highest safe RAM below I/O and is included in overlap diagnostics and the memory-layout report.

Supported stdout backends are screen, kernal, debug, and none. Supported stdin backends are keyboard, 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.

Compatibility and non-regression contract

The C90 rollout does not route compatibility projects through the stack ABI. A program accepted by web64-c-v0.1 keeps its established semantics, generated bytes, size, cycle ceilings, and helper closure unless a classified compiler bug fix requires a change. Golden assembly/PRG fixtures cover fixed-point operations, arrays, asset access, loops, C/assembly calls, raster kernels, and generated-table consumers. Integer-only and fixed-only projects may not gain floating, stack, recursion, or generic arithmetic helpers.

The executable release gates compare generated output and classify every intentional difference as improved, equivalent, or justified. They also exercise real project classes and fail when code-size, deterministic-cycle, runtime-dependency, or zero-page ownership ceilings regress. C90 code uses the same specialized immediate, direct-index, hardware-register, and fixed-point lowering whenever standard semantics permit it.

Supported C subset

Web64 C is a freestanding implementation with an explicit 6502 data model. Its implemented C90-profile surface includes:

Local names follow lexical C scope and their initializers execute whenever control reaches the declaration. Under web64-stack-v1, automatic objects occupy recursive software-stack frames. Under web64-static-v0, each declaration retains its legacy static function-owned slot and generated functions remain non-reentrant.

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

See 6502 cost model and hot-loop performance for choosing between inexpensive frame work and acceptable setup work.

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.

With the temporary/register optimization pass enabled, eligible byte compound updates at a fixed address plus an unsigned byte index use absolute indexed addressing. This includes |=, &=, ^=, += and -= with foldable constant offsets. Index side effects are evaluated once, typed narrowing and 16-bit address wrapping are preserved, and volatile targets still receive exactly one read and one write. Signed/wide indexes and unproven mutable pointers retain the general path.

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.

With the temporary/register optimization pass enabled, side-effect-free words in fixed storage can be consumed directly instead of being copied to temporary words. This includes non-volatile globals, statics and direct packed-struct fields in comparisons, truth tests, word returns and call arguments. A comparison uses this shortcut only when both operands are safe; calls, pointer dereferences, volatile objects and live stack-frame references retain their conservative evaluation paths. Ordinary call arguments still enter their normal snapshot or caller-owned slot before the next argument is evaluated. Volatile word values read both bytes once in address order, including tests against zero. Explicit narrowing and signed casts retain their typed meaning, and !/!! tests the complete operand even when its result is stored in one byte.

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.

6502 cost model and hot-loop performance

How do I optimize this per-frame C code in Web64? Start with the target and the actual hot path: Web64-C executes on the Commodore 64's MOS 6510, a 6502-family CPU with an approximately 1 MHz budget, not a modern host CPU. Frame/runtime work is cycle constrained. VIC-II display activity and IRQ work share that budget; the full frame is not all available to application code. The emulator host being fast does not make generated C64 instructions free.

For hot-loop optimization, establish which work repeats before changing arithmetic.

The CPU has no native hardware integer multiply, divide or modulo instructions. Repeated non-power-of-two operations such as value % 37, % 12, / 24 or % 10 may therefore be expensive in a hot loop. They are valid C, not errors, and a one-time setup calculation is often entirely reasonable. Do not mechanically remove every division or change a game's ranges merely to replace % 12 with & 15; that changes the result.

Current Web64-C behavior matters more than a generic rule of thumb:

paths, multiplication by zero, one, minus one or powers of two lowers inline. Some other constant multiplies use budget-checked shift/add chains; a constant multiply is not a promise of one instruction or of a helper call.

or masks. Signed division needs truncation-toward-zero bias, and signed remainder retains the dividend's sign. A signed x / 8 is not generally interchangeable with x >> 3, nor is signed remainder generally interchangeable with a mask.

arithmetic helpers. Width, signedness, expression context and optimizer settings affect the output. Do not assume arbitrary constant divisors get a cheap reciprocal-multiply transformation. Inspect the actual Generated ASM.

fixed-point contracts, not integer substitutions.

For repeated bounded transforms, consider a lookup table if its RAM/ROM footprint is justified. An incremental counter or cached result can avoid recomputing the same quotient, digit conversion, layout or address each frame. For example, a counter already constrained to 0..11 can wrap with increment/compare/reset; that is not a general replacement for arbitrary value % 12. Preserve exact semantics, ranges, overflow behavior and side effects before measuring a change.

Separate cold initialization/setup from hot per-frame work. Avoid redrawing unchanged HUD values or rebuilding every screen/color RAM cell each frame; update dirty cells or changed rows, and keep animation updates separate from static layout. Use uint8_t, int8_t, uint16_t and wider types deliberately. Web64's int is 16-bit; byte storage does not prevent C integer promotion. Signedness and intermediate width can change both results and cost.

Expensive initialization may be acceptable, but visible partial construction usually is not. Prepare state before revealing it, keep the old screen visible while safe independent data is prepared, or deliberately blank the display during a transition and restore it only when screen, colors and charset state agree. Choose a strategy compatible with the project's VIC bank, memory ownership and interrupts. An off-screen buffer is optional, not a universal double-buffering requirement; account for the memory and reveal/copy cost. Do not blank or disable interrupts indiscriminately just to hide slow per-frame work.

Measure important paths with Generated ASM and the opt-in Cycle / Raster Profiler. Distinguish observed instruction/stall costs from inferred routine attribution, use the matching build, and compare representative branch paths. Manual cycle counts must include branch/page-cross behavior and display/IRQ interference where relevant. Source-level operation counts alone are not performance measurements. MCP can discover this guidance and inspect generated/build contracts; profiling and emulator execution remain normal human IDE actions, not new MCP controls.

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 charset("encoding")Selects ascii, petscii, screencode, petscii_mixed, or screencode_mixed compile-time encoding for subsequent string literals. ascii is the default and restores ordinary C bytes. No conversion helper is linked.
#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__Native cc65 cdecl remains rejected with c-cdecl-deferred. Web64's independent stack ABI is selected by project configuration, not a cc65 annotation.
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.

Compile-time C64 string encoding

The setting applies to subsequent string literals, including literal call arguments, and can be reset with #pragma charset("ascii"). It changes emitted bytes, not the VIC-II font selection or the bytes received from the keyboard.

EncodingPrintable source mappingIntended use
asciiNo conversion; the defaultOrdinary byte strings and explicitly supplied data
screencodeBoth A-Z and a-z become 1-26; other byte values remain unchangedExisting uppercase-font screen strings; compatibility behavior is retained
petsciia-z become 65-90; other byte values remain unchangedExisting uppercase PETSCII-compatible strings; compatibility behavior is retained
screencode_mixeda-z become 1-26, A-Z become 65-90; full assembler mixed-screen punctuation mappingDirect screen RAM text displayed with the lower/uppercase character ROM font
petscii_mixeda-z become $41-$5a, A-Z become $c1-$da; _ becomes $a4Mixed-case PETSCII strings, matching the assembler's petscii_mixed encoding

The two explicit mixed modes match the assembler for printable source characters. In screencode_mixed, @ is screen byte $00, [, \\, ], ^ are $1b-$1e, and _ is $64. Spaces and digits retain their familiar bytes. The glyph shown for a punctuation byte depends on the selected C64 charset; these modes are byte-oriented C64 mappings, not a Unicode font translator.

Numeric escapes such as \x41, \xff, \101 and \0 are literal target bytes in every mode. Named control escapes such as \n, \r and \t also retain their C byte values (10, 13 and 9). Escaped printable punctuation, including \\, still follows the selected character mapping. High-byte literals have the same representation in arrays and pooled call arguments; they are not expanded to UTF-8.

#pragma charset("screencode_mixed")
const char title[] = "Document Editor";
const char rows[] = "Open\377Save\377Close"; /* application-defined $ff separator */
#pragma charset("ascii")

The generated arrays already contain screen codes, so no startup loop or conversion routine is linked. Only use a separator such as $ff if the receiving routine explicitly implements it. C hex escapes consume all following hex digits: \xffClose includes the C in the escape. The fixed three-digit octal form \377 avoids that ambiguity. In mixed screen codes lowercase j is byte 10, so a screen-text routine cannot also treat byte 10 as an unambiguous line delimiter. Likewise, @ produces byte 0; a normal NUL-terminated C string routine stops there. Use a length-aware renderer when all screen-code values must be displayable.

Quoted character constants such as 'A', 'a' and '\n' are not affected. Unknown encoding names produce explicit errors; the C64 encoding modes also reject source characters beyond their byte-oriented mapping. The directive is #pragma charset, not #pragma charmode.

Adjacent string literals compose into one string in arrays and ordinary or nested call arguments, including literals produced by macros. Each fragment is decoded separately before composition, so "\xff" "Close" emits the exact $ff separator followed by the encoded word; the C cannot become part of the preceding hex escape. Whitespace, comments and source-line breaks may separate fragments without inserting bytes. This permits long messages to remain readable in source without a runtime concatenation step.

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.

The C90 profiles apply trigraph replacement, escaped-newline splicing, comment replacement, conditional preprocessing, macro replacement, stringification, token pasting, and tokenization before the specialized declaration parser. Conditional groups preserve line positions for diagnostics, and bundled headers use include guards so repeated or transitive includes do not duplicate declarations. Host compiler extensions and host filesystem preprocessing are never imported implicitly.

Function-like statement macros can use the usual do { ... } while (0) form. They remain a single controlled statement in if (...) MACRO(...); else ..., while and for bodies. Macro authors must still avoid evaluating an argument more than once when that argument can have side effects; wrapping statements does not itself make repeated argument use safe.

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 stack ABI supports 32-bit arithmetic, arguments, and returns; the compatibility ABI retains its 8/16-bit call boundary.
stddef.h16-bit size_t, signed 16-bit ptrdiff_t, and NULL.
limits.hImplementation limits for the 8-bit char, 16-bit short/int, and 32-bit long data model.
float.hIEEE binary32/binary64 storage characteristics. Constant expressions are folded; nonconstant floating arithmetic currently emits c-floating-runtime-unsupported rather than incorrect integer lowering.
stdarg.hStack-ABI va_list, va_start, va_arg, and va_end lowering over declaration-order argument slots.
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 blocking getchar, 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 isalnum, isalpha, iscntrl, isdigit, isgraph, islower, isprint, ispunct, isspace, isupper, isxdigit, 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.
c64lib/common.hDependency-linked Web64-native memory copy, fill, screen fill, RLE, and optional Exomizer P39 decrunch routines.
c64lib/chipset.hCIA, memory-banking, nine-bit raster, and sprite-position compatibility declarations.
c64lib/bitmap.hOfficial six-byte bitmap tile-configuration layout.
c64lib/vic2.hFamiliar c64lib VIC-II names mapped onto the Web64 c64.h hardware contract.
c64lib/sprites.hSprite register-address and mask helpers plus the chipset positioning routines.
c64lib/text.hCharacter output, hexadecimal output, 40x25 scrolling, 2x2 tile drawing, and the stateful Tile2 runtime.
c64lib/copper64.hFour-byte copper-list entries, the 22 official handler IDs, and adapted start/stop routines.
c64lib/magic-desk.hMagic Desk cartridge target and bank-copy declarations.
c64lib/64spec.hBrowser-native assertions and the machine-readable test-result block.
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.hDependency-linked low-level KERNAL status, channel, load, and save wrappers.
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.
web64/disk.hDependency-linked whole-file and streaming disk I/O, disk markers, and IDE-assisted disk-set requests.
assets/generated.hGenerated C declarations for project assets and media, including deterministic asset descriptors and disk/file/set 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 Web64 SDK tree opens headers as read-only web64://c/include/... documents. It also exposes bundled assembler .inc files as read-only web64://asm/include/... documents in assembly and mixed projects. Both document types retain per-file cursor/scroll state, support navigation and search, and can be copied into include/ when a project-specific editable variant is needed.

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.

The memory helpers use indexed 256-byte page kernels with bounded tails. memset, memcpy, and memmove preserve zero-length behavior and return the original destination; memmove retains forward/backward overlap safety. Each helper links only its own parameter storage and reachable implementation, so memcpy no longer accidentally includes memset. Public declarations and ordinary C calling conventions are unchanged. Native Web64 game libraries retain their separate exact-width _web64_rt windows and direct assembly entry points.

c64lib compatibility from C

c64lib headers are available only when the matching compatibility module is selected for the active Build Target. Dependencies are automatic: Text selects Common and Chipset; copper64 selects Common and Chipset; Magic Desk selects Common and Chipset; and 64spec selects Common and Text. A missing module produces a build diagnostic instead of silently accepting a declaration that cannot link. Merely selecting a module or including one of its headers adds no runtime bytes; a routine is linked only when referenced.

The C-facing routines use the target's canonical Web64 ABI. New C/c64lib targets default to web64-stack-v1 unless the target explicitly selects another ABI. Legacy web64-static-v0 targets keep their existing static parameter slots. Web64 does not introduce a second c64lib calling convention. Handwritten optimized routines that use another register contract require a thin assembly wrapper.

#include <stdint.h>
#include <c64.h>
#include <c64lib/common.h>
#include <c64lib/chipset.h>

void main(void) {
    c64lib_fill_screen((void*)0x0400, 32);
    c64lib_set_vic_bank(C64LIB_BANK_0);
    VIC->border_color = COLOR_BLUE;
    while (1) {
    }
}

The copper64 C list uses immutable four-byte C64libCopperEntry records. Call c64lib_disable_cia_interrupts() before c64lib_copper_start() when CIA interrupt sources would conflict. The V3 dispatcher installs through the KERNAL IRQ vector and implements all 22 handler IDs with direct dispatch. Timing contracts distinguish double-IRQ stabilized handlers, line-sequenced handlers, byte-semantic handlers, and PAL/NTSC raster-transition handlers; full raster bars remain badline-sensitive. C callbacks from a copper JSR entry must go through the assembly web64_c64lib_guarded_c_callback wrapper; arbitrary C runtime calls are not implicitly IRQ-safe.

The complete classification, source pins, migration rules, limitations, and license notices are in Web64-native c64lib compatibility. The machine-readable release contract is c64lib-compatibility-v3.json.

Dependency-linked disk I/O

Include <web64/disk.h> for high-level disk access. Literal names can use WEB64_DISK_NAME("NAME"), which supplies both pointer and byte length without a runtime strlen.

#include <stdint.h>
#include <web64/disk.h>

uint8_t load_level(void) {
    return web64_disk_load(WEB64_DISK_NAME("LEVEL1"), 8, 0x4000);
}

uint8_t save_game(void) {
    return web64_disk_save(WEB64_DISK_NAME("SAVEGAME"), 8, 0x5000, 0x5100);
}

web64_disk_load returns the KERNAL status and records the loaded end address for web64_disk_last_end. web64_disk_save saves [start_address, end_address). web64_disk_require verifies a generated USR marker before a level load. web64_disk_request writes an assisted swap request but does not replace marker verification in portable code.

Streaming I/O uses a caller-chosen logical file and explicit device number. A program can keep its application disk on device 8 while opening document files on device 9; logical file numbers and device numbers are independent. Open the channel, read or write bytes, then close it. web64_file_read stores a byte only after successful channel selection and input. WEB64_DISK_STATUS_EOI can accompany the final valid byte and is not equivalent to device-not-present.

uint8_t value;
uint8_t status = web64_file_open(WEB64_DISK_NAME("TABLE,S,R"), 2, 8, 2);
if (status == WEB64_DISK_OK && web64_disk_last_error() == 0) {
    /* Check this drive's DOS channel 15 before treating OPEN as success. */
    do {
        status = web64_file_read(2, &value);
        if (web64_disk_last_error() != 0) break;
        if (status != WEB64_DISK_OK && status != WEB64_DISK_STATUS_EOI) break;
        /* value is valid here, including on the final EOI byte */
    } while (status == WEB64_DISK_OK);
}
web64_file_close(2);

Native disk errors have three independent domains; check all relevant domains before adopting data or reporting a successful save:

The high-level latch is replaced by load/save/require/file-open/file-read/file-write operations. The direct-CBM latch is replaced by OPEN, CHKIN, CHKOUT, CHRIN, CHROUT, LOAD and SAVE. Each family is independent. READST, last-error accessors, CLOSE and CLRCHN do not erase the prior checked call error; cleanup has no defined KERNAL carry-error result. Read the relevant latch immediately after the operation, before another checked operation in that family. Failed input does not publish an error code as file data. On successful input, READST EOI ($40) still accompanies a valid final byte.

For printer output, check READST after transfer and final cleanup as well as after OPEN; the KERNAL carry-error accessor alone does not report printer sink failures. Emulated output backends retain write, flush and close failures. In KERNAL-trap mode, READST exposes even errors discovered only while closing the host output file, and a new job can recover after the sink is corrected. These failures are not drive DOS error numbers.

Virtual IEC has a remaining limitation: a host sink failure discovered only at CLOSE can leave guest READST zero, and merely changing the output path after a bus transfer failure does not reinitialize the bus. Zero READST therefore does not universally prove that printer output was persisted. A successful channel CLOSE is also not a page-eject command: printer-model controls and finishing/capturing a partially printed page are separate concerns. No browser printer capture/download interface is implied by the KERNAL wrappers.

web64_file_read and web64_file_write select the logical channel on each call and leave it selected. For a measured streaming workload, direct <cbm.h> calls can select once and transfer multiple bytes; check each transfer, then call CLRCHN before returning to keyboard/default-screen I/O. GETIN reads the currently selected input, not necessarily the keyboard. KERNAL IEC transfers are synchronous.

For recoverable document replacement, write a distinct unused temporary file, close it, inspect DOS status and verify its complete contents. Rename the old file to a distinct unused backup, then promote the verified temporary file. Check DOS status after each rename and preserve recoverable names on failure. This is not atomic replacement: power loss can leave the old document under its backup name. Do not silently scratch a user's existing temporary/backup file or use destructive overwrite to bypass a disk-full error. Keep the active document unchanged until a separately staged load has passed format, capacity, content and EOF validation.

Include <cbm.h> for direct KERNAL control when the high-level contract is not suitable. Its wrappers preserve stable C signatures while calling the ROM vectors. The linker emits only referenced wrappers and their parameter slots. Disk development metadata never consumes C64 program memory unless the program includes and calls the corresponding API.

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 fold to direct word materialization. Typed dynamic 8.8 operations by 0, 1, -1, and positive or negative powers of two lower to word copies, negation, or shifts when the transformation preserves the helper contract; dynamic web64_fix8_abs lowers to a sign test plus two's-complement negate and retains the INT16_MIN wrap result. Signed right shifts add an explicit negative-value bias so multiply by a reciprocal power of two and divide by a whole power of two still truncate toward zero. Unsigned division by a reciprocal power of two lowers to a wrapping left shift. A zero identity is used only when the discarded operand has no side effects. The runtime helper is removed only when every surviving lowered call requires it. Fixed specialization no longer reparses source: typed lowering records the decision and final lowered imports drive exact runtime closure.

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 low-level C64 hardware header. It exposes the same hardware through three compatible forms:

All hardware structure members are volatile. A read or write therefore remains an observable hardware access and is not removed or merged by the optimizer.

Memory map and typed memory views

NameValueMeaning
C64_RAM_BASE$0000Start of the 64 KiB CPU address space.
C64_BASIC_START$0801Conventional BASIC-start address used by BASIC-stub PRGs.
C64_SCREEN_RAM$0400Default 40x25 screen character RAM.
C64_COLOR_RAM$D80040x25 color nybble RAM.
C64_CHAR_ROM$D000Character ROM address when the CPU memory configuration maps it in.
C64_SCREEN_WIDTH40Default screen width in character cells.
C64_SCREEN_HEIGHT25Default screen height in character cells.
C64_SCREEN_CELL_COUNT1000Number of default screen/color cells.
C64_IO_BASE$D000Start of the I/O window when I/O is mapped in.
C64_KERNAL_ROM$E000Start of KERNAL ROM when mapped in.

c64_reg8_t is the common unsigned 8-bit register type. The memory structures are:

typedef unsigned char c64_reg8_t;

typedef struct c64_cpu_port_registers {
    volatile c64_reg8_t data_direction;
    volatile c64_reg8_t data;
} c64_cpu_port_registers;

typedef struct c64_screen_ram {
    volatile c64_reg8_t cells[1000];
} c64_screen_ram;

typedef struct c64_color_ram {
    volatile c64_reg8_t cells[1000];
} c64_color_ram;

The corresponding typed aliases are:

NameType/addressUse
C64_CPU_PORTvolatile c64_cpu_port_registers* at $0000data_direction controls the 6510 port direction and data controls memory banking.
C64_SCREENvolatile c64_screen_ram* at C64_SCREEN_RAMFlat access through C64_SCREEN->cells[index].
C64_COLORvolatile c64_color_ram* at C64_COLOR_RAMFlat access through C64_COLOR->cells[index].
C64_SCREEN_ROWSvolatile unsigned char (*)[40]Matrix access through C64_SCREEN_ROWS[row][column].
C64_COLOR_ROWSvolatile unsigned char (*)[40]Matrix access through C64_COLOR_ROWS[row][column].
C64_CHAR_ROM_GLYPHSconst unsigned char (*)[8]Character-row access through C64_CHAR_ROM_GLYPHS[character][row].

The character ROM shares addresses with I/O. Change the 6510 memory configuration before reading C64_CHAR_ROM_GLYPHS, then restore it before accessing VIC-II, SID, CIA, or color RAM.

VIC-II structure

c64_vicii_registers maps $D000-$D02E in hardware register order:

typedef struct c64_vicii_registers {
    volatile c64_reg8_t sprite0_x;
    volatile c64_reg8_t sprite0_y;
    volatile c64_reg8_t sprite1_x;
    volatile c64_reg8_t sprite1_y;
    volatile c64_reg8_t sprite2_x;
    volatile c64_reg8_t sprite2_y;
    volatile c64_reg8_t sprite3_x;
    volatile c64_reg8_t sprite3_y;
    volatile c64_reg8_t sprite4_x;
    volatile c64_reg8_t sprite4_y;
    volatile c64_reg8_t sprite5_x;
    volatile c64_reg8_t sprite5_y;
    volatile c64_reg8_t sprite6_x;
    volatile c64_reg8_t sprite6_y;
    volatile c64_reg8_t sprite7_x;
    volatile c64_reg8_t sprite7_y;
    volatile c64_reg8_t sprite_x_msb;
    volatile c64_reg8_t control1;
    volatile c64_reg8_t raster;
    volatile c64_reg8_t lightpen_x;
    volatile c64_reg8_t lightpen_y;
    volatile c64_reg8_t sprite_enable;
    volatile c64_reg8_t control2;
    volatile c64_reg8_t sprite_y_expand;
    volatile c64_reg8_t memory_setup;
    volatile c64_reg8_t interrupt_status;
    volatile c64_reg8_t interrupt_enable;
    volatile c64_reg8_t sprite_priority;
    volatile c64_reg8_t sprite_multicolor;
    volatile c64_reg8_t sprite_x_expand;
    volatile c64_reg8_t sprite_collision;
    volatile c64_reg8_t data_collision;
    volatile c64_reg8_t border_color;
    volatile c64_reg8_t background_color0;
    volatile c64_reg8_t background_color1;
    volatile c64_reg8_t background_color2;
    volatile c64_reg8_t background_color3;
    volatile c64_reg8_t sprite_multicolor0;
    volatile c64_reg8_t sprite_multicolor1;
    volatile c64_reg8_t sprite0_color;
    volatile c64_reg8_t sprite1_color;
    volatile c64_reg8_t sprite2_color;
    volatile c64_reg8_t sprite3_color;
    volatile c64_reg8_t sprite4_color;
    volatile c64_reg8_t sprite5_color;
    volatile c64_reg8_t sprite6_color;
    volatile c64_reg8_t sprite7_color;
} c64_vicii_registers;

VICII is a volatile c64_vicii_registers* at VIC_BASE ($D000); VIC is its shorter alias. Sprite X/Y members cover sprites 0-7. sprite_x_msb supplies bit 8 for all sprite X positions. control1, control2, and memory_setup control display mode, scrolling, raster bit 8, and VIC memory selection. Collision members are hardware collision latches.

Every VIC-II register also has a numeric address:

Register groupNumeric constants
BaseVIC_BASE
Sprite positionsVIC_SPRITE0_X, VIC_SPRITE0_Y, VIC_SPRITE1_X, VIC_SPRITE1_Y, VIC_SPRITE2_X, VIC_SPRITE2_Y, VIC_SPRITE3_X, VIC_SPRITE3_Y, VIC_SPRITE4_X, VIC_SPRITE4_Y, VIC_SPRITE5_X, VIC_SPRITE5_Y, VIC_SPRITE6_X, VIC_SPRITE6_Y, VIC_SPRITE7_X, VIC_SPRITE7_Y, VIC_SPRITE_X_MSB
Display and rasterVIC_CONTROL1, VIC_RASTER, VIC_LIGHTPEN_X, VIC_LIGHTPEN_Y, VIC_CONTROL2, VIC_MEMORY_SETUP
Sprite controlsVIC_SPRITE_ENABLE, VIC_SPRITE_Y_EXPAND, VIC_SPRITE_PRIORITY, VIC_SPRITE_MULTICOLOR, VIC_SPRITE_X_EXPAND
Interrupt/collisionVIC_INTERRUPT_STATUS, VIC_INTERRUPT_ENABLE, VIC_SPRITE_COLLISION, VIC_DATA_COLLISION
Border/backgroundVIC_BORDER, VIC_BACKGROUND, VIC_BACKGROUND0, VIC_BACKGROUND1, VIC_BACKGROUND2, VIC_BACKGROUND3
Shared sprite colorsVIC_SPRITE_MULTICOLOR0, VIC_SPRITE_MULTICOLOR1
Per-sprite colorsVIC_SPRITE0_COLOR, VIC_SPRITE1_COLOR, VIC_SPRITE2_COLOR, VIC_SPRITE3_COLOR, VIC_SPRITE4_COLOR, VIC_SPRITE5_COLOR, VIC_SPRITE6_COLOR, VIC_SPRITE7_COLOR
Sprite pointersVIC_SPRITE_POINTER_BASE points at the default screen's eight sprite pointer bytes at $07F8.

Convenience aliases preserve familiar naming:

AliasEquivalent
VIC_BORDER_COLORVIC_BORDER
VIC_BACKGROUND_COLORVIC_BACKGROUND
VIC_SPRITE_POINTERSVIC_SPRITE_POINTER_BASE
BORDER_COLORVIC->border_color
BACKGROUND_COLORVIC->background_color0
SPRITE_ENABLEVIC->sprite_enable
SPRITE_MULTICOLORVIC->sprite_multicolor

The 16 VIC-II palette values are:

ValuePrimary nameCompatibility alias
0VIC_COLOR_BLACKCOLOR_BLACK
1VIC_COLOR_WHITECOLOR_WHITE
2VIC_COLOR_REDCOLOR_RED
3VIC_COLOR_CYANCOLOR_CYAN
4VIC_COLOR_PURPLECOLOR_PURPLE
5VIC_COLOR_GREENCOLOR_GREEN
6VIC_COLOR_BLUECOLOR_BLUE
7VIC_COLOR_YELLOWCOLOR_YELLOW
8VIC_COLOR_ORANGECOLOR_ORANGE
9VIC_COLOR_BROWNCOLOR_BROWN
10VIC_COLOR_LIGHT_REDCOLOR_LIGHTRED
11VIC_COLOR_DARK_GRAYCOLOR_GRAY1
12VIC_COLOR_GRAYCOLOR_GRAY2
13VIC_COLOR_LIGHT_GREENCOLOR_LIGHTGREEN
14VIC_COLOR_LIGHT_BLUECOLOR_LIGHTBLUE
15VIC_COLOR_LIGHT_GRAYCOLOR_GRAY3

SID structures

c64_sid_voice_registers describes one seven-register voice and is useful when a routine receives or creates a pointer to a voice base:

typedef struct c64_sid_voice_registers {
    volatile c64_reg8_t freq_lo;
    volatile c64_reg8_t freq_hi;
    volatile c64_reg8_t pulse_lo;
    volatile c64_reg8_t pulse_hi;
    volatile c64_reg8_t control;
    volatile c64_reg8_t attack_decay;
    volatile c64_reg8_t sustain_release;
} c64_sid_voice_registers;

c64_sid_registers maps the complete SID block at $D400:

typedef struct c64_sid_registers {
    volatile c64_reg8_t voice1_freq_lo;
    volatile c64_reg8_t voice1_freq_hi;
    volatile c64_reg8_t voice1_pulse_lo;
    volatile c64_reg8_t voice1_pulse_hi;
    volatile c64_reg8_t voice1_control;
    volatile c64_reg8_t voice1_attack_decay;
    volatile c64_reg8_t voice1_sustain_release;
    volatile c64_reg8_t voice2_freq_lo;
    volatile c64_reg8_t voice2_freq_hi;
    volatile c64_reg8_t voice2_pulse_lo;
    volatile c64_reg8_t voice2_pulse_hi;
    volatile c64_reg8_t voice2_control;
    volatile c64_reg8_t voice2_attack_decay;
    volatile c64_reg8_t voice2_sustain_release;
    volatile c64_reg8_t voice3_freq_lo;
    volatile c64_reg8_t voice3_freq_hi;
    volatile c64_reg8_t voice3_pulse_lo;
    volatile c64_reg8_t voice3_pulse_hi;
    volatile c64_reg8_t voice3_control;
    volatile c64_reg8_t voice3_attack_decay;
    volatile c64_reg8_t voice3_sustain_release;
    volatile c64_reg8_t filter_cutoff_lo;
    volatile c64_reg8_t filter_cutoff_hi;
    volatile c64_reg8_t filter_resonance;
    volatile c64_reg8_t volume_filter_mode;
    volatile c64_reg8_t pot_x;
    volatile c64_reg8_t pot_y;
    volatile c64_reg8_t osc3_random;
    volatile c64_reg8_t env3;
} c64_sid_registers;

SID is a volatile c64_sid_registers* at SID_BASE. The first 21 registers are three identical seven-register voices. filter_cutoff_lo, filter_cutoff_hi, and filter_resonance configure the filter; volume_filter_mode combines master volume and filter mode bits. pot_x, pot_y, osc3_random, and env3 are readback registers.

Numeric SID addresses are:

GroupConstants
BaseSID_BASE
Voice 1SID_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
Voice 2SID_VOICE2_FREQ_LO, SID_VOICE2_FREQ_HI, SID_VOICE2_PULSE_LO, SID_VOICE2_PULSE_HI, SID_VOICE2_CONTROL, SID_VOICE2_ATTACK_DECAY, SID_VOICE2_SUSTAIN_RELEASE
Voice 3SID_VOICE3_FREQ_LO, SID_VOICE3_FREQ_HI, SID_VOICE3_PULSE_LO, SID_VOICE3_PULSE_HI, SID_VOICE3_CONTROL, SID_VOICE3_ATTACK_DECAY, SID_VOICE3_SUSTAIN_RELEASE
Filter/outputSID_FILTER_CUTOFF_LO, SID_FILTER_CUTOFF_HI, SID_FILTER_RESONANCE, SID_VOLUME_FILTER_MODE
ReadbackSID_POT_X, SID_POT_Y, SID_OSC3_RANDOM, SID_ENV3

Control-register bit constants are SID_GATE, SID_SYNC, SID_RING_MOD, SID_TEST, SID_WAVE_TRIANGLE, SID_WAVE_SAW, SID_WAVE_PULSE, and SID_WAVE_NOISE. Combine one or more waveform bits with gate/control bits using |.

CIA, keyboard, and joystick structure

c64_cia_registers maps either CIA at $DC00 or $DD00:

typedef struct c64_cia_registers {
    volatile c64_reg8_t pra;
    volatile c64_reg8_t prb;
    volatile c64_reg8_t ddra;
    volatile c64_reg8_t ddrb;
    volatile c64_reg8_t timer_a_lo;
    volatile c64_reg8_t timer_a_hi;
    volatile c64_reg8_t timer_b_lo;
    volatile c64_reg8_t timer_b_hi;
    volatile c64_reg8_t tod_10ths;
    volatile c64_reg8_t tod_seconds;
    volatile c64_reg8_t tod_minutes;
    volatile c64_reg8_t tod_hours;
    volatile c64_reg8_t serial_data;
    volatile c64_reg8_t interrupt_control;
    volatile c64_reg8_t control_a;
    volatile c64_reg8_t control_b;
} c64_cia_registers;

CIA1 and CIA2 are typed volatile c64_cia_registers* aliases at CIA1_BASE and CIA2_BASE. pra/prb are the two data ports, ddra/ddrb select input or output per bit, timer members form the two 16-bit timers, tod_* is the time-of-day clock, and the final four members handle serial I/O, interrupts, and timer control.

Every CIA register also has a numeric address:

CIA 1CIA 2
CIA1_PRACIA2_PRA
CIA1_PRBCIA2_PRB
CIA1_DDRACIA2_DDRA
CIA1_DDRBCIA2_DDRB
CIA1_TIMER_A_LOCIA2_TIMER_A_LO
CIA1_TIMER_A_HICIA2_TIMER_A_HI
CIA1_TIMER_B_LOCIA2_TIMER_B_LO
CIA1_TIMER_B_HICIA2_TIMER_B_HI
CIA1_TOD_10THSCIA2_TOD_10THS
CIA1_TOD_SECONDSCIA2_TOD_SECONDS
CIA1_TOD_MINUTESCIA2_TOD_MINUTES
CIA1_TOD_HOURSCIA2_TOD_HOURS
CIA1_SERIAL_DATACIA2_SERIAL_DATA
CIA1_INTERRUPT_CONTROLCIA2_INTERRUPT_CONTROL
CIA1_CONTROL_ACIA2_CONTROL_A
CIA1_CONTROL_BCIA2_CONTROL_B

The keyboard matrix aliases are KEYBOARD_COLUMN_PORT, KEYBOARD_ROW_PORT, KEYBOARD_COLUMN_DDR, KEYBOARD_ROW_DDR, and KEYBOARD_ACTIVE_MASK.

JOYPORT_1 and JOYPORT_2 expose the CIA port addresses used by the two joystick ports. The input masks are JOY_UP, JOY_DOWN, JOY_LEFT, JOY_RIGHT, and JOY_FIRE. Joystick bits are active-low: JOY_ACTIVE_LOW is zero and JOY_RELEASED_MASK has all five input bits set.

The direct port-2 predicates JOY2_UP(), JOY2_DOWN(), JOY2_LEFT(), JOY2_RIGHT(), and JOY2_FIRE() read CIA1->pra. The value predicates JOY_UP_P(v), JOY_DOWN_P(v), JOY_LEFT_P(v), JOY_RIGHT_P(v), and JOY_FIRE_P(v) test a previously sampled port byte. A true predicate means pressed.

KERNAL entry points

The header provides numeric entry addresses rather than C wrappers. Call them through a compatible assembly wrapper or a supported zero-parameter indirect call only when the KERNAL ABI matches:

NameAddressPurpose
KERNAL_SCNKEY$FF9FScan the keyboard matrix.
KERNAL_READST$FFB7Read the current I/O status byte.
KERNAL_SETLFS$FFBASet logical file, device, and secondary address.
KERNAL_SETNAM$FFBDSet filename pointer and length.
KERNAL_OPEN$FFC0Open a logical file.
KERNAL_CLOSE$FFC3Close a logical file.
KERNAL_CHKIN$FFC6Select an input channel.
KERNAL_CHKOUT$FFC9Select an output channel.
KERNAL_CLRCHN$FFCCRestore default I/O channels.
KERNAL_CHRIN$FFCFRead a character from the current input channel.
KERNAL_CHROUT$FFD2Write the accumulator to the current output channel.
KERNAL_LOAD$FFD5Load or verify through the configured logical file.
KERNAL_SAVE$FFD8Save a memory range through the configured logical file.
KERNAL_GETIN$FFE4Read one buffered input byte.
KERNAL_PLOT$FFF0Read or set the cursor position according to the carry flag.

The KERNAL routines use register parameters, carry/status results, and shared KERNAL state. Prefer the Web64 SDK or an explicit assembly wrapper for calls that require parameters; a raw C function-pointer cast does not synthesize the KERNAL register ABI.

Web64 helper headers

The helper headers combine compiler-recognized operations, macros, and dependency-selected runtime functions. They are intended for compact C64 projects and provide direct register, KERNAL, SID, disk, graphics, input, timing, and utility interfaces.

#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 the current helper ABI: 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 blocking getchar, puts, and putchar for the tiny runtime, plus restricted compiler-lowered printf for string-literal formats with %d values. getchar waits on C64 KERNAL GETIN, does not echo, and returns the raw unsigned input byte widened to int; live keyboard input therefore does not return EOF. It performs no ASCII, PETSCII, or screen-code conversion. #pragma charset instead rewrites source string literals at compile time before a call such as puts, so it has no effect on bytes received by getchar. Input and output use their configured stdin and stdout backends, and each runtime module is linked only when a surviving call needs it. File I/O declarations and fprintf/sprintf are intentionally absent until they have executable implementations.

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. Prefer the named constants in project code; immediate register values remain useful when matching a datasheet or an existing assembly routine exactly.

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.

Asset filenames normally retain their existing C-safe symbol stem. When linked assets intentionally share a stem, as with a CharPad import containing a charset, blockset, and map, Web64 assigns role-qualified public C prefixes such as _charset, _blockset, and _tilemap. The assembly payload labels remain _chars, _blocks, and _map, so existing generated .inc files and mixed C/assembly projects remain compatible while every alias, size macro, view, and descriptor in assets/generated.h stays unique.

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.

web64/assets.h and assets/generated.h expose the current Asset Model v1 descriptor shapes and generated descriptor constants for source-backed charsets/chars, sprite banks, blocksets, maps, and SID files. Descriptor member reads lower to project metadata constants, while a data-member or data-label reference selects the payload for embedding. These headers define the asset data contract; applying sprites or maps, decompression, overlays, and loader policy remain explicit project/runtime operations unless a documented helper provides them.

A Sprite Editor .spritepair.json record is descriptor-only. The C asset bridge uses the same shared schema validator as the editor, resolves the multicolor base and mono overlay banks, and emits a Web64SpriteOverlayAsset referencing both Web64SpriteAsset descriptors. It never embeds the JSON text. Missing or duplicate layer paths, wrong modes, zero/unequal frame counts, and more than 256 frames are build diagnostics. Referencing pair metadata alone links no runtime; using web64_sprite_render_asset_pair() selects only the direct sprite module, while <web64/sprite-mux.h> selects its independent PAL mux module.

Web64SpriteOverlayBinding records the installed frame-zero VIC pointers; the application remains responsible for copying and 64-byte-aligning both banks in the active VIC bank. Direct pair rendering validates both frame indices and pointer arithmetic atomically. PAL scheduling uses caller-owned 1–24-layer double buffers, 24-byte prepared display-list entries, and 16-bit scheduled raster positions, so line 300 cannot alias line 44. Commit prepares complete owned-slot VIC control snapshots and the IRQ consumes them through patched absolute-indexed low/high windows. Reuse admission reserves ceil((300 + 48 * layers + 63) / 63) PAL lines: seven for one layer and eight for an atomic pair. Alignment-swept fast intervals peak at 283/337 cycles; KERNAL entry plus the demonstrated acknowledgement wrapper yields 336/390-cycle end-to-end paths, still leaving the full 63-cycle safety line. The 49-cycle next-event dispatcher tail is faster than the bundled Copper64 76-cycle normal irqHandlersReturn + fetchNext reference segment. The preserving service keeps A/X/Y intact, while web64_sprite_mux_irq_service_fast() is available when an enclosing dispatcher already owns that register envelope. IRQ service follows the mux-owned armed boundary/event identity instead of treating the delayed current raster as the originating compare. The mux preserves unowned hardware slots, publishes only at the armed line-300 boundary, and leaves vector installation, interrupt acknowledgement/chaining, the game tick, and RTI to application code. The standalone sprite-multiplexer-overlay example verifies motion in every accepted logical entry over 120 consecutive complete PAL frames.

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. Payload labels are flat mutable unsigned char[] declarations, so games can update material maps, collected-object cells, animation data, and other RAM-backed asset bytes directly while retaining compatibility with byte indexing, memcpy, assembly labels, and pointer arithmetic. Historical base names remain C aliases, so existing source using player continues to address player_sprites. End labels and typed Asset Model descriptors remain read-only metadata. The metadata contract retains the historical start/end names while also exposing the physical data/dataEnd labels to build tools.

Assets whose dimensions are known also receive mutable multidimensional views:

AssetTyped viewShape
Charset<data-label>_glyphs[character][8-byte row]
Sprite bank<data-label>_frames[frame][64-byte slot]
Block set<data-label>_grid[block][row][column]
Map<data-label>_rows[row][column]

Each family also has a <data-label>_view alias, a <data-label>_rank constant, and dimension-count constants such as _row_count, _column_count, _frame_count, or _character_count. Map views use uint16_t cells when the map's index width is two bytes; all other listed views use unsigned char. The views contain no copied data and create no C64 storage: they are typed projections over the same original payload label.

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

uint8_t block = level_map_rows[player_y][player_x];
uint8_t character = terrain_blocks_grid[block][tile_y][tile_x];
uint8_t pixels = font_chars_glyphs[character][pixel_row];
uint8_t sprite_data = player_sprites_frames[walk_frame][sprite_byte];

Runtime indexes use Web64-C's multidimensional row-major addressing. Constant indexes fold to direct payload offsets. As with ordinary Web64-C arrays, generated views do not add runtime bounds checks. Use the emitted descriptor and dimension constants when indexes can originate outside trusted game data.

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

#define WEB64_ASSET_KIND_SPRITE_MC 5
extern 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 0
#define player_mutable 1
#define player_sprites_frames ((unsigned char (*)[64])player_sprites)
#define player_sprites_view player_sprites_frames
#define player_sprites_rank 2
#define player_sprites_frame_count 2
#define player_sprites_byte_count 64

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 payload arrays and their typed views are writable in C. The compiler accepts a function call directly in a flat or multidimensional subscript and evaluates each index call once. The array object itself is still not assignable, and generated end labels and descriptor objects remain read-only.

#include <assets/generated.h>

unsigned char *ok = example_blob_data;
uint16_t next_offset(void);
uint8_t next_row(void);
uint8_t next_column(void);

void main(void) {
    ok = example_blob_bytes;  /* supported */
    example_blob[next_offset()] = 1;  /* supported; next_offset runs once */
    level_map_rows[next_row()][next_column()] = 0;
    example_blob = ok;        /* diagnostic: an array object is not assignable */
}

For supported 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. Published SDK records continue to use explicit uint8_t kind, mode, cell-type, flag, and boolean fields so their ABI remains source- and assembler-readable; user records may use the bit-field contract below. Public enum ABI remains 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 current Web64 C 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 game state, control flow, data structures, hardware setup, SDK/runtime calls, and asset-driven logic. Use assembly where exact addressing modes, cycle counts, interrupt entry/exit, custom loaders, established music players, or a deliberately custom register contract matter.

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.

Handwritten native assembly may also call any registered, externally visible Web64 runtime entry by its underscore-prefixed label. Selected assembly translation units participate in the same registry-driven exact-closure analysis as generated C, so the call below links only the sprite-multiplexer module and the dependencies declared for that entry:

example_irq_service:
    jsr _web64_sprite_mux_irq_service_fast
    rts

A declaration in a C header does not link code by itself. The selected assembly reference is the dependency evidence; comments are ignored, and a project assembly file that deliberately defines the same registered entry remains authoritative instead of receiving a duplicate runtime implementation. Entries with _web64_rt argument windows publish their named labels for assembly callers, while zero-argument and deliberately register-oriented entries can be called directly as shown above.

Declaring and passing parameters to assembly routines

Web64 C treats names beginning with asm_ as direct, unmangled assembly labels. 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

Always provide a prototype when an assembly routine accepts arguments or returns a value. Under web64-static-v0, a prototyped ordinary asm_ call uses exact-width deterministic parameter labels: arguments are evaluated left-to-right, staged safely, and written immediately before JSR. Under web64-stack-v1, assembly callees receive the same software-stack frame layout as generated C and must follow the documented frame/cleanup contract; use _fastcall when a small register boundary is preferable. Byte arguments occupy one byte, word/pointer arguments occupy two little-endian bytes, and stack-ABI wide arguments occupy their declared width.

/* game.h */
#include <stdint.h>
void asm_set_sprite_color(uint8_t slot, uint8_t color);
#include "game.h"
void main(void) {
    asm_set_sprite_color(0, 1);
}
; routines.asm
VIC_SPRITE0_COLOR = $d027

asm_set_sprite_color:
    ldx __web64_fn_asm_set_sprite_color_param_slot_0
    lda __web64_fn_asm_set_sprite_color_param_color_1
    sta VIC_SPRITE0_COLOR,x
    rts

The slot naming contract is __web64_fn_<function>_param_<parameter>_<base36-index>. A 16-bit parameter's low byte is at the label and its high byte is at label+1. Ordinary slots are static function-owned storage, so calls are non-reentrant and not interrupt-safe. A byte return value is placed in A; a 16-bit integer or pointer return uses A for the low byte and X for the high byte.

For a smaller direct-register boundary, declare a supported _fastcall shape. One byte argument arrives in A, one word/pointer argument arrives in A/X, and two byte arguments arrive in A then X:

_fastcall void asm_set_sprite_color_fast(uint8_t slot, uint8_t color);
asm_set_sprite_color_fast:
    tay
    txa
    sta $d027,y
    rts

Unsupported _fastcall signatures produce diagnostics. Shared C globals remain appropriate for persistent command blocks, multi-field return state, IRQ-facing mailboxes, and data that must remain visible between calls; external C globals use underscore labels such as _sprite_state. Legacy undeclared zero-argument asm_ calls remain accepted for existing projects, but arguments and return values require a declaration so the compiler can apply the correct ABI.

Web64 C expression and ABI contract

The expression frontend tokenizes, types, and parses expressions before lowering. Supported expressions include integer, floating, character, and string literals; identifiers; groups; casts; direct and indirect declared calls; prefix/postfix increment and decrement; unary +, -, ~, and !; compile-time sizeof; binary arithmetic, shifts, comparisons, bitwise/logical operators; comma and conditional ?:; supported assignments; fixed-size multidimensional indexes; typed pointer dereference; and struct/union member access backed by Web64 aggregate metadata. Floating constants and constant arithmetic are stored as IEEE binary32/binary64. Nonconstant floating conversion, arithmetic, comparison, and truth testing remain explicit errors until the software floating runtime is implemented.

Bit-fields

Web64-C supports named and unnamed bit-fields with 8- or 16-bit integer base types, including uint8_t, int8_t, uint16_t, int16_t, and their supported C integer spellings. Struct layout remains packed and padding-free. Consecutive bit-fields with the same allocation-unit width share that unit from least-significant bit upward in declaration order. A change between 8- and 16-bit base widths begins a new unit, as does a field that does not fit. An unnamed zero-width field ends the current unit. Union bit-fields all begin at bit zero.

typedef struct {
    int8_t *pattern_array;
    uint8_t *timing_array;
    Enemy *enemy_struct;
    Actor *actor_ptr;
    uint16_t index : 10;
    uint16_t negate_x : 1;
    uint16_t negate_y : 1;
    uint16_t flag_3 : 1;
    uint16_t flag_4 : 1;
    uint16_t flag_5 : 1;
    uint16_t flag_6 : 1;
} Glenn;

In the Web64 ABI, the four pointers above occupy eight bytes and the final seven fields share one little-endian 16-bit unit, so sizeof(Glenn) is 10. index occupies bits 0-9 and the flags occupy bits 10-15. This layout is deliberate and assembler-visible; do not assume another C compiler chooses the same implementation-defined packing order.

Dot and arrow access, assignment, compound assignment, and prefix/postfix increment and decrement preserve the other bits in the allocation unit. Signed fields are sign-extended when read, and stored values are truncated to the declared width. Positional aggregate initializers initialize named bit-fields in declaration order and do not consume a value for unnamed fields. A bit-field is not an addressable object: unary & and sizeof on an individual bit-field diagnose. A bit-field write is an inline read-modify-write sequence and is not automatically interrupt-atomic.

Bit-field extraction and insertion are emitted inline and add no runtime dependency. Merely enabling compiler support adds no generated instructions, data, call overhead, or linked bytes to code that does not declare or access bit-fields. Ordinary aggregate members retain their existing direct byte/word loads and stores under both web64-static-v0 and web64-stack-v1.

Scalar 32-bit integer members retain all four bytes in constant global/local aggregate initializers and standalone direct or pointer-member assignments. Supported assignment values are integer constants, scalar integer variables, and matching-width calls (wide call boundaries require web64-stack-v1). Narrow signed values are extended from their captured value without rereading a volatile source; pointer destinations remain stable across a value call that changes the pointer. Other wide member expressions, explicit noninteger wide member initializers, and wide member assignment-as-an-expression produce blocking diagnostics instead of partial writes. This bounded store support does not add general wide aggregate-expression reads, aggregate copies, or runtime aggregate initializer lists; byte/word member behavior and both ABIs are unchanged.

Multidimensional arrays

Web64-C stores fixed-size arrays in standard C row-major order. Every dimension is an integer constant expression. Only the outermost dimension may be omitted, and only when an initializer supplies its size:

uint8_t map[25][40];
uint16_t frames[][2] = {
    {0x1000, 0x1100},
    {0x1200, 0x1300}
};
char labels[][8] = {"PLAYER", "ENEMY"};

Nested braces, brace elision, partial rows, and character-row strings are accepted. Unspecified elements are zero-filled and excess elements diagnose. Global arrays are emitted directly in row-major data. Local arrays use Web64-C's function-owned storage; an initializer is executed again whenever control reaches the declaration.

Each subscript removes one array dimension. In map[row][column], map[row] is a complete 40-byte row and the second subscript selects its byte. sizeof(map), sizeof(map[row]), and sizeof(map[row][column]) therefore produce 1000, 40, and 1 without evaluating index expressions. sizeof(uint16_t[2][3]) produces 12. size_t is the Web64 16-bit unsigned size type.

Arrays decay to pointers to their first element in value contexts, while sizeof and unary & preserve the complete array type. For a multidimensional array, the first element is a row:

uint8_t read_cell(uint8_t rows[][40], uint8_t y, uint8_t x) {
    return rows[y][x];
}

uint8_t (*row)[40] = map + 4;

Array parameters adjust to pointer-to-array parameters. The equivalent explicit declaration is uint8_t (*rows)[40]. Pointer addition and subtraction scale by the complete pointee size, so advancing the pointer above advances 40 bytes. Arrays of structs, runtime-indexed struct elements, and multidimensional array members are supported.

Generated addressing folds constant indexes into direct label+offset operands. Runtime indexes use 16-bit row-major offsets; stride 1 removes scaling, power-of-two strides use shifts, and other constant strides use inline shift/add sequences. Function calls are valid index expressions for reads and writes, including generated asset views, and are evaluated once per subscript while the partial address is preserved. Runtime bounds checks are not inserted. Variable-length arrays and designated initializers are not supported and produce diagnostics. Individual objects must fit the 16-bit Web64 object-size model.

The integer model uses 8-bit _Bool, uint8_t, int8_t, char, and unsigned char; 16-bit uint16_t, int16_t, short, int, unsigned int, size_t, ptrdiff_t, and pointers; and 32-bit long, unsigned long, int32_t, and uint32_t. Plain char is signed. Integer promotions and usual arithmetic conversions use this rank/width model; signed byte identifiers, array elements, aggregate members, and function results are sign-extended before 16-bit arithmetic. Constant 16-bit multiply by 0, 1, -1, or a power of two lowers inline. Unsigned divide/remainder by a power of two use a logical shift or mask; signed divide uses a bias plus arithmetic shift to retain truncation toward zero, and signed remainder retains the dividend's sign. Other multiply/divide/remainder expressions keep the dependency-selected verified helpers. Thirty-two-bit add, subtract, bitwise operations, comparisons, shifts, multiply, divide, and remainder are lowered directly or through dependency-selected long helpers. The compatibility ABI rejects wide call boundaries; the stack ABI accepts wide objects and returns them through a hidden caller-provided pointer.

Function calls are clobber boundaries. Both ABIs evaluate arguments in declaration order. web64-static-v0 retains byte-exact temporary staging into deterministic function-owned parameter labels, including the established six-byte mixed-width (uint16_t, uint8_t, uint16_t, uint8_t) layout. web64-stack-v1 pushes contiguous argument bytes onto a downward software stack and lets the caller reclaim them after return. Its stack pointer is the zero-page pair $02/$03, and its frame pointer is $04/$05. Frames whose maximum local/parameter offset is below 256 use compact LDY #offset plus (frame),Y; larger valid frames use the wide address fallback and produce a performance warning. Caller cleanup can be combined with surrounding stack adjustments without changing callee code.

For variadic functions, argument zero occupies the lowest argument address and later arguments follow at increasing addresses. Default promotions are applied before materialization. va_start points immediately after the final fixed parameter, va_arg reads the requested promoted width and advances the pointer, and va_end has no runtime work. Fixed stack parameters and variadic slots therefore use the same declaration-order layout. The stack region and $02-$18 compiler-owned zero-page range are validated against project declarations and assembled memory ranges.

_fastcall remains available for zero arguments, one word/pointer argument in A/X, or two byte arguments in A then X. Ordinary byte returns use A and word/pointer returns use A/X. Stack-ABI values wider than two bytes use a hidden return pointer. Byte-returning compatibility helpers such as rand, putchar, and puts clear X for older callers. printf supports literal formats with %d arguments; unsupported specifiers and mismatched counts diagnose.

Function results may be a single-level pointer T *, const T * or T const *, where T is a supported scalar, void or a known aggregate typedef. Definitions and prototypes retain the pointee type and const qualification; the pointer itself remains a 16-bit A/X result and may be advanced. Writes through a const result and implicit conversion to a mutable pointer diagnose. Cast a void * result to a concrete pointer type before dereferencing it. Pointer-level qualifiers, volatile/restrict pointees and multiple pointer levels in return declarations remain unsupported and produce located errors. For a returned aggregate pointer, first store it in a named typed local and use local->field; computed call()->field access is not currently supported.

In the size profile, eligible compiler-owned stack-frame accesses may omit unnecessary Y and N/Z flag preservation when control-flow analysis proves those values unused. Both branch paths and loops are checked; unknown calls, opaque assembly and unsupported accesses retain conservative sequences. Each memory access remains in place once, including volatile accesses. This optimization adds no helper calls, RAM or ABI changes and leaves debug, balanced, speed and frame profiles unchanged.

The hidden return pointer is present even for a function with no C parameters. Wide results are delivered to the typed destination before any integer narrowing; assigning a 32-bit return to a byte or word keeps the low 8 or 16 bits, rather than interpreting A/X as a wide return. Direct and indirect calls, local destinations, indexed arrays, nested returns, and discarded wide calls preserve the same caller-cleanup contract. Floating-point storage returns preserve all four or eight representation bytes; this does not add nonconstant floating-point arithmetic or conversions.

Registered runtime argument windows (_web64_rt)

Web64 v2 adds _web64_rt for a bounded set of externally implemented SDK runtime declarations. It is not a general user-function attribute: C definitions, static declarations, variadic declarations, unregistered names, registry-signature mismatches, and combinations with _fastcall are errors. Ordinary user C and both existing C ABIs otherwise remain unchanged.

The native runtime registry owns the ordered argument names and exact byte widths. Every linked _web64_rt entry places one contiguous little-endian window immediately before the unchanged entry label and publishes these assembler symbols:

For example, web64_actor_batch_cull_fast(view, viewport, visible, status) publishes an eight-byte window at _web64_actor_batch_cull_fast__arg_window; its four named pointer arguments occupy offsets 0, 2, 4, and 6, and _web64_actor_batch_cull_fast follows at window address + 8. Handwritten assembly may populate the named labels and JSR _web64_actor_batch_cull_fast without reconstructing an offset convention.

Web64-C evaluates arguments from left to right. When every expression is proven safe for direct placement, it writes each exact byte directly to the named window and calls the runtime entry. If an argument has a C-visible side effect or otherwise cannot be safely placed while the window is incomplete, the compiler retains the ordinary stable materialization path and only publishes the window after every expression has been evaluated. The called kernel sees the same absolute parameter bytes either way.

Argument windows are caller-writable and callee-read-only for the duration of a call. They are mainline, per-function, and non-reentrant: populate the complete window before JSR, and do not call the same entry from an interrupt or nested argument evaluation until the first call has consumed it. A/X/Y/flags and each registry record's published scratch remain caller-save. The runtime still does not allocate game state, copy assets, apply palettes, begin/commit mux frames, or own interrupts.

The initial v2 registry pilot covers actor-batch phase and adapter entries, web64_sprite_mux_submit(), and the four web64_world_shift_*() primitives. Only the called entry's window participates in exact closure; uncalled windows add zero bytes. Both web64-static-v0 and web64-stack-v1 use the same external runtime-window contract. Direct-label performance measurements start at the runtime entry and exclude C argument marshalling; _web64_rt overhead is measured separately.

Function-pointer casts, named function-pointer objects, function-pointer typedefs, and callback parameters retain return/parameter signatures. Constant targets lower to direct absolute JSR; runtime targets patch a call-site operand immediately before JSR $ffff. Under web64-stack-v1, indirect calls materialize typed arguments and hidden wide-return pointers exactly like direct calls. The compatibility ABI keeps its established zero-argument indirect-call restriction. Unresolved identifiers and mismatched argument counts are blocking diagnostics.

Under web64-stack-v1, automatic locals and parameters use recursive frames and direct/mutual recursion is allowed within the configured stack bound. Static locals retain static duration outside those frames. Under web64-static-v0, locals and parameters remain function-owned static storage and recursive call cycles diagnose. The compatibility ABI reserves $02-$09; the stack ABI reserves $02-$18 for stack/frame pointers and pseudo-registers. Both reserve $fb-$fe for argument/pointer scratch. Conflicting __web64_zeropage(address) declarations are rejected and all ownership is exposed by the memory-layout report.

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.

#pragma charset performs compile-time string encoding. Supported #pragma warn, #pragma cc64, and #pragma optimize forms produce stable compatibility or optimization-control diagnostics. 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

Web64-C normalizes persisted and IDE settings into the versioned debug, balanced, size, speed, and frame profiles. debug disables optimization; the other profiles enable their documented pass gates and enforce build/function growth plus optional worst-case-cycle budgets. speed minimizes aggregate executed cycles, while frame requires bounded worst-case critical-path evidence and reports unknown when loops, indirect calls, or other path costs cannot be proven. Every pass reports its gate, proof, cost decision, and whether it transformed output or only analyzed it.

The optimizer uses authoritative logical 6502 records with calibrated byte and best/worst cycle costs. Typed IR models functions, blocks, values, canonical array/pointer shapes, ranges, calls, ordered volatile/hardware effects, unknown pointer aliases, and opaque assembly fences. The IR currently crosses a verified legacy-emitter bridge: scalar/CFG transforms and downstream analyses are retained in reports, while only individually verified typed-lowering/emitter and local backend rewrites mutate generated code. The report therefore keeps outputMutation: false until the typed selector passes the same both-ABI checkpoint.

Implemented emitted-code optimizations include local duplicate load/store/compare cleanup, safe temporary reload removal, jump/dead-record cleanup, ABI-owned zero-page addressing, profitable typed 16-bit constant arithmetic, fixed-point specializations, and constant builtin materialization. Literal strlen, constant signed abs, and constant ASCII ctype predicates/conversions can disappear before runtime composition. Exact public/private/data runtime closure is then rooted only in surviving lowered imports; raw-source scans and the former fixed-point preparse are not dependency authorities. Hardware-register and C volatile accesses remain ordered, opaque inline assembly is a fence, and analysis-only call/frame, placement, loop, and frame candidates are never reported as emitted transformations.

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.
C90 translation and preprocessingImplemented for the compiler profileTrigraphs, line splicing, comment replacement, conditional groups, object/function macros, stringification, token pasting, #undef, and active #error are supported.
Declarations and linkageImplemented for the shipped scalar/aggregate subsetPrototypes, lexical block locals, for initializer declarations, shadowing, parameters, extern, internal-linkage static, enums, and guarded multi-translation-unit headers are supported. Same-scope and 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.
Arrays, decay, and sizeofImplemented for fixed-size arraysMultidimensional globals/locals, positional initializers, inferred outer dimensions, runtime indexes, row pointers, pointer-to-array parameters, array members, and unevaluated sizeof are supported. VLAs and designated initializers diagnose.
Aggregates, bit-fields, member access, and pointer aliasesImplemented for fixed-layout Web64 recordsStructs use declaration-order byte offsets without implicit padding; unions overlay at offset zero. 8/16-bit integer bit-fields use the documented deterministic LSB-first allocation units. Arrays of aggregates and multidimensional aggregate members use the same row-major addressing model.
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 supported scalar, aggregate, and array pointees16-bit pointers preserve pointee shape, pointer-to-array indexes use row strides, and pointer addition/subtraction scales by pointee size. Other broad pointer operations remain limited.
Inline assemblyImplemented with Web64 scoping rulesGCC/ca65 operand constraints and native optimizer behavior are not implemented.
Runtime dependency selectionExact public/private/data closure in optimized profilesFinal lowered imports root the native registry's recursive closure; raw-source evidence is diagnostic-only and cannot add code.
Recursion and reentrancyStack ABI implemented; compatibility ABI diagnosedweb64-stack-v1 uses recursive automatic frames. web64-static-v0 retains function-owned slots and diagnoses call cycles.
Native cc65 objects, libraries, linker configs, and host pathsNot applicableWeb64 is browser-local and lowers to Web64 assembler-compatible source.
32-bit integer ABI and arithmeticImplemented by web64-stack-v1Four-byte arithmetic uses direct lowering and adaptive multiply/divide helpers; wide returns use hidden caller pointers.
Floating pointConstant/storage subsetIEEE binary32/binary64 constants, constant arithmetic, copies, parameters, and hidden-pointer returns are represented; nonconstant arithmetic/conversion/comparison diagnoses until the software runtime is complete.
Interrupt functions, native cc65 cdecl, full hosted libc, flexible arrays, and VLAsDeferred or unsupportedThese remain explicit diagnostics or outside the freestanding profile.

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:

When the emulator display has keyboard focus, F5 and its modified forms go to the C64 keyboard. Click elsewhere in the IDE before using F5 to run a project, Ctrl+F5 to Run Disk, or Shift+F5 to pause/resume.

Build targets and multi-load projects

The Build Targets tab defines independently compiled PRG, Packed data (Exomizer), 64spec test, and legacy Magic Desk CRT outputs inside one .web64proj. Native cartridge bank layouts are configured in Disk/Media over these target outputs and project files; the old CRT target kind remains readable for existing projects. A target has a stable id, display label, kind, root source, explicit translation-unit list, optional entry symbol, origin, output name, target-specific preprocessor defines, and a Web64-C enable switch. C, ASM, and C/ASM hybrid targets use the same compiler and assembler pipeline as the normal Code workspace. Packed data targets add staging, independent-block and capacity settings described below.

Generated target inputs

Use Build Targets > Target outputs as inputs when one target consumes another target's newly built bytes. Add a producer, choose a representation, and assign a project-relative generated VFS path. Payload bytes omit the producer's two-byte PRG load header; Raw PRG includes that header; Packaged artifact uses the final runnable, packed, or cartridge bytes. The consumer can read that path with Kick-compatible LoadBinary("generated/input.bin") or .incbin where the assembler dialect supports it. LoadBinary exposes .size and .getData(index); indexes must be in range. All reads remain inside the project VFS.

Build Target, Build All, and Disk/Media dependency builds compile producers before consumers, including transitive producers. Missing targets, cycles, generated-path conflicts, and failed producers block the build. Generated bytes are ephemeral build inputs, not saved copies of outputs in the .web64proj; reopening and rebuilding reconstructs them from the original sources. Consumer freshness includes changes to producer source and target settings. This graph also works for packed data: the asynchronous build finalizes the producer before binding its packaged artifact to a consumer.

Run Disk builds the selected disk set's outputs and their transitive producers; it does not require unrelated release variants or utility targets. Explicit IDE builds allow up to 30 minutes for a full target graph and up to two minutes for each assembler invocation, so source-heavy multi-output projects can complete on slower computers. Bridge build jobs retain their separate five-minute maximum.

Use the left target navigator to select the active target, Build Target, or Remove it. New Target and Build All remain in the page header. The center groups Identity, Output, Translation Units, Build Defines, C64lib Compatibility and Artifact settings. The right-hand Build summary follows the active target and stays visible while scrolling on wide workbenches; narrower workbenches stack it below the configuration.

The summary distinguishes Not built, Needs rebuild, Building, Ready and Failed artifact states. Project configuration diagnostics are reported separately: having no blocking configuration diagnostics does not mean the target has been built successfully. Select Build Target to populate the artifact size, addresses and build report. Root translation units remain linked automatically; additional selected units and compatibility modules retain their usual linking behavior.

Build Targets tab showing target identity, artifact kind, source root, compatibility mode, library roots, and translation units
Each runnable artifact owns its source root, output policy, optional compatibility settings, and explicit translation-unit set.

Only the active target participates in live compilation. This keeps editor latency independent of the number of secondary loaders, overlays, levels, tests, cartridges, or utility programs in the project. Build Target compiles the selected target in a separate worker. Build All compiles every target. Built artifacts are transient development outputs; source and target declarations are saved, but generated PRG/CRT bytes are rebuilt rather than embedded in the project file.

Inspect complete target-build results in Code > Build Output. Each target keeps its own outcome and diagnostics, including failed targets, independently of the active target's live Inspector. Filters and source links make larger multi-target builds easier to inspect without changing the active target.

Each target uses an explicit multi-select Translation Units listbox. Click a row, or focus it and press Space/Enter, to include or exclude a C or ASM source file. Multiple translation units may be selected for one target; the target root is selected and locked because it is always linked. The list scrolls independently for larger projects. Headers and referenced assets remain available through the project virtual filesystem, but unrelated C or ASM translation units are not silently linked. The entry symbol controls the packaged PRG start address and must resolve to an emitted external symbol. Magic Desk targets require origin $8000, generate .crt, and can require a valid CBM80 autostart header. Test targets require the 64spec result runtime. Missing inputs, incompatible module/target combinations, duplicate target ids, invalid symbols, invalid cartridge origin, and unresolved entry symbols are build errors.

Scope color is consistent with the main toolbar: project-level target controls and badges are purple, disk-level controls and badges are blue, and source/file controls and badges are green.

Old projects are migrated automatically to one main target. A C project uses its configured C entry as the target root; a mixed project retains its C and assembly translation units; a pure ASM project retains its previous main source and start behavior. No project migration step is required.

Typical multi-load layouts include:

Target artifacts become stale when a source dependency, native asset, target setting, define, origin, or entry symbol changes. Disk mastering resolves only target outputs it references. Build Dependencies and Run Disk build missing or stale target outputs for the selected disk set and their transitive producers; unrelated release variants and utility targets are excluded. Current outputs are reused during the open IDE session. Reopening a project clears the in-memory output cache, so the first disk run rebuilds the selected set's dependency graph. Source and native-asset drafts are flushed before this freshness check.

For a Magic Desk target, Run lazily packages the active compile snapshot as a CRT, attaches it through the emulator cartridge API, and power-resets the C64; Save writes the configured .crt. For a 64spec test target, the artifact exposes the address and layout of its seven-byte W6 result block for debugger or automation inspection. PRG targets retain the established load/SYS/direct-run behavior. Cartridge targets do not use live memory patching because the running banked image is not equivalent to writable PRG RAM.

Packed data targets and independent block caches

Select Packed data (Exomizer) as a Build Target's kind to compress an assembled data bank entirely in the browser. Native charset, block, map and SID Tracker sources stay editable. The worker assembles the target, compresses it and verifies a codec round-trip before exposing its disk artifact. Build Dependencies and Run Disk rebuild changed dependencies, including recursively included source and binary assets. Changing compression settings also makes the artifact stale.

The controls are:

Add the output to Disk/Media as a PRG logical file. Packed targets default to Raw data and cannot be used with a SYS loader. Save or Download File writes the packed .prg; Run refuses to execute a packed data target. Run the resident boot program instead. A normal KERNAL LOAD only receives the container—it does not unpack it. The optional Web64 Hardware Loader Runtime supplies native transport, W64X validation, bounded decoding and transactional installation through C and ASM. Merely renaming a PSID, PRG or Exomizer file does not create this format.

W64X version 1 uses little-endian words. After the two-byte PRG staging address:

OffsetMeaning
0–3ASCII W64X
4Format version: 1
5Block count: 1–64
6–7Total container bytes, including its final CRC but excluding the PRG address
8 onwardOne ten-byte record per block: destination, decoded length, packed offset, packed length, decoded CRC16 (five words)
After recordsConsecutive backward Exomizer P39/M255 memory streams; each includes its two-byte destination-end trailer
Last two bytesCRC16/XMODEM of every preceding container byte, initial value 0, polynomial $1021

Packed offsets are relative to the container header, not the PRG file. The decoded CRC covers that block's uncompressed bytes. The fixed M255 profile avoids the known unrestricted long-match boundary in the separate compatibility decoder; do not substitute arbitrary default Exomizer streams. The hardware loader consumes this shared W64X v1 contract and decodes selected blocks into disjoint caller-owned scratch. All selected decoded CRCs must pass before any live destination write. The Common runtime's separate Exomizer memory decoder is not a W64X parser or a disk loader and is not broadened by this API.

For an independent-block cache, author several fixed-sized native records in one data target, enable independent blocks, and reserve enough packed RAM. Receive once with web64_loader_read, retain the container, and select one record with web64_loader_unpack. Its length excludes the two-byte PRG prefix. Reserve disjoint decoded scratch for the selected record and never overwrite cached streams while decoding. The application owns cache policy and its frame clock. The runtime remains synchronous: IRQ-friendly loading does not automatically make mainline-driven title menus responsive, and it exposes no fake polling interface.

KickAssembler Compatibility Mode

Pure-assembly Build Targets can explicitly select KickAssembler 5.25 compatibility as their assembly dialect. Web64 then runs the governed compile-time evaluator and semantic adapters before lowering neutral records to the existing 6502 assembler. The setting is target-local and never inferred from source text. Web64-native assembly remains the default, and C or mixed C/assembly targets remain on the native assembler path.

For a new compatibility project, choose Project tree > New > Kick-compatible assembly project.... The browser-local dialog creates exactly one authoritative Web64 assembly source root and one explicit kick5 PRG Build Target; it does not create a redundant main.asm or launch an external assembler. Project-VFS library roots retain first resolution priority, followed by the bundled Kick c64lib source tree under Web64 SDK > Kick c64lib sources. Bundled sources are read-only; use Copy into project when an editable project-owned override is required.

Compatibility Mode supports the released v5.25 P0 language surface, including functions, variables, mutation, conditionals, loops, brace macros, preprocessing/imports, deterministic data generation, and neutral layout. Project files and binary/text data are read only from the immutable project virtual filesystem. Java/plugins, Gradle or other external processes, arbitrary host/network access, custom writers, Kick v6 preview semantics, unseeded random/shuffle behavior, DTV semantics, and typed SID/graphics loader objects are unsupported or deferred.

Compatibility source follows KickAssembler syntax: use // for line comments and /* ... */ for block comments, while ; is always a statement separator, including inside .for (...) clauses and between data directives. Editor highlighting, Toggle line comment in the context menu, and its keyboard shortcut insert // only for KickAssembler Compatibility Mode. Native Web64 assembly continues to use ; line comments.

The evaluator is bounded and cooperatively cancellable. Its scopes, values, effects, replay/cache state, diagnostics delivery state, and telemetry never enter neutral emission IR or the 6502 backend. Segments and source artifact requests are declarative; Build Targets continue to own output naming and packaging. See KickAssembler Compatibility Mode for the migration steps, complete supported/adapter/deferred/unsupported matrix, semantic differences, c64lib direct-source statement, examples, and release evidence.

Web64-native c64lib compatibility

Web64 provides a curated browser-native compatibility layer for useful public c64lib contracts. It does not bundle KickAssembler, Gradle, Java, native processors, or the original c64lib package graph. Select modules in Build Targets > c64lib Compatibility. Each target stores its own module list, dependencies are resolved automatically, and unused runtime routines are dead-stripped.

The project tree's Web64 SDK group separates C headers and ASM includes into independently collapsible visual folders. Virtual path directories such as web64, c64lib, and assets appear as further collapsible folders without becoming part of, or changing, the underlying path. Selecting web64/c64lib/common.inc, chipset.inc, text.inc, copper64.inc, bitmap.inc, magic-desk-crt.inc, or 64spec.inc opens its exact virtual build source read-only. Assembly .include completion suggests these paths, Ctrl-click and Go to definition open them, and Copy into project creates an editable project copy. Viewing a file does not select its compatibility module; compilation still requires the matching Build Target module and diagnoses a missing selection.

ModuleAssembly includeC headersMain V3 scope
Commonweb64/c64lib/common.incc64lib/common.hByte/word macros, copy/fill routines, RLE, and optional Exomizer 3.1.2 P39 codec/runtime.
Chipsetweb64/c64lib/chipset.incc64lib/chipset.h, vic2.h, sprites.hMOS 6510, CIA, VIC-II, raster, IRQ, banking, video modes, sprites.
Textweb64/c64lib/text.incc64lib/text.hText/hex output, 1x1 scrolling, 2x2 tile drawing, and stateful four-direction Tile2.
copper64web64/c64lib/copper64.incc64lib/copper64.hFour-byte lists, handler IDs 1-22, direct dispatch, and PAL/NTSC timing contracts.
Bitmapweb64/c64lib/bitmap.incc64lib/bitmap.hSix-byte tile-configuration layout; no upstream runtime exists at the pinned revision.
Magic Desk CRTweb64/c64lib/magic-desk-crt.incc64lib/magic-desk.hCBM80 bootstrap, bank loader, CRT target packaging and emulator handoff.
64specweb64/c64lib/64spec.incc64lib/64spec.hAssertions and machine-readable test-target results.

Assembly uses Web64 macro syntax. The includes retain useful original c64lib.* constant names and expose explicit web64_c64lib_* macro names so source authority remains clear.

.include "web64/c64lib/chipset.inc"

start:
    web64_c64lib_disable_cia_interrupts
    web64_c64lib_set_raster 250
    lda #C64LIB_IRQ_RASTER
    sta C64LIB_IRQ_STATUS
loop:
    jmp loop

The compatibility layer participates in the normal Web64 build. C, generated compiler assembly, handwritten assembly, compatibility runtime routines, and assets share one symbol table, memory map, line map, and debugger projection. Selected-but-unused modules produce byte-identical output to a build with no compatibility modules. C routines use the selected Web64 ABI; no alternate c64lib ABI is introduced. Compatibility wrappers remain opt-in boundary adapters: they never replace an existing smaller or faster native Web64 lowering.

Compatibility builds activate declarative zero-page and memory governance. The compiler stack ABI owns $02-$17, compiler/call scratch uses $fb-$fe, and compatibility routines declare temporary use instead of silently reserving addresses. Compatible scratch ranges may overlay only when their lifetimes cannot overlap. Hardcoded user, imported, compiler, KERNAL, IRQ, and compatibility reservations that overlap produce diagnostics. The build result exposes the resolved memory and zero-page layout.

The copper64 dispatcher installs through $0314/$0315, returns through the KERNAL IRQ path, supports official handler IDs 1-22, and stores its list address in generated code rather than persistent zero page. It owns the raster IRQ while active. Handlers 1-7, 9, 11-15, and 18 use double-IRQ stabilization; handlers 16, 17, and 19 are line-sequenced; handlers 8 and 10 are byte-semantic; and handlers 20-22 synchronize to raster transitions. Handler 21 is PAL-specific and 22 is NTSC-specific. Disable conflicting CIA IRQ sources first. web64_c64lib_guarded_c_callback preserves the Web64 stack-ABI and call-scratch zero-page ranges around an approved no-argument C callback; it is pay-for-use and is not a blanket declaration that C code is NMI-safe or raster-cycle-safe.

The Migrate c64lib Project action in the Project Tree Manage menu creates a Web64-native copy and preserves the original files under legacy-c64lib/. It translates recognized include paths and bounded macro calls, infers modules, and writes a migration report. KickAssembler structural DSL, collections, compile-time host loaders, memory-mode irqExit, dynamic copper handler-selection constructs, and arbitrary Gradle command processors remain explicit diagnostics requiring manual migration. Initial inspection never mutates the original project.

Browser-native processor compatibility uses deterministic, cancellable worker operations. RLE, interleave/deinterleave, c64lib Nybbler low/high-plane splitting, packed-nibble helpers, record slicing, RGBA cut/split/extend/flip/resolution reduction, Magic Desk CRT read/write, GTS3-GTS5/GTI3-GTI5 GoatTracker interchange, and lazy Exomizer raw/in-memory P39 processing are implemented directly. Nybbler preserves the upstream option to normalize high nibbles to 0..15 or retain them in bits 4-7. GoatTracker files import through editable W64SID state and remain interchange formats rather than a second tracker project type. CharPad/CTM, SpritePad, Koala, Char/Block/Map, and generated bindings reuse the existing Web64 asset pipeline rather than recreating Gradle tasks. Arbitrary external command processors remain intentionally unsupported. The same canonical asset descriptors generate C declarations and assembly labels, so dimensions, sizes, offsets, modes, and bytes cannot drift between languages.

The Tile2 adapter consumes canonical map and 2x2 block descriptors, including optional per-block or per-cell Color RAM and material planes. Initialization creates row offsets and renders the selected playfield. Directional calls shift Screen and Color RAM and decode only the exposed edge; startRow and endRow support bounded playfields. c64lib_tile2_material_at() resolves map material first and block material second.

Verified examples are available in public/examples:

V3 closes the stateful Tile2 engine, standard GoatTracker song/instrument and gt2reloc option mapping, lazy Exomizer codec/decruncher, and handler-specific Copper PAL/NTSC timing contracts. Arbitrary external command processors remain intentionally unsupported. Web64 does not claim binary compatibility with KickAssembler output or blanket compatibility with every c64lib repository. See Web64-native c64lib compatibility and the generated compatibility manifest for exact authority commits, classifications, limitations, evidence, and migration guidance.

Disk mastering and project media

The Disk/Media tab masters deterministic 35-track 1541 D64 images, groups them into runnable multi-disk sets, validates the bytes, and exports individual images or one deterministic ZIP. Disk mastering is separate from Save PRG: logical disk files may come from project binaries/text or transient build-target outputs.

The complete workflow runs inside Web64 in the browser: create native assets, write source in Code, define Build Targets, place their outputs in Disk/Media, then build and run or download the result. Project paths are virtual paths. No local source repository, external converter, command-line build tool, or manual project-JSON editing is required.

Disk and Media workspace showing a disk set, logical files, disk controls, and the 1541 allocation map
Disk mastering connects logical project or target outputs to concrete D64 placement and exposes the resulting sector allocation before export or run.

The media model has three layers:

  1. media.files[] declares a logical PRG, SEQ, or USR file and its source.
  2. media.disks[] places logical files onto generated D64 images with ordering and allocation constraints.
  3. media.diskSets[] groups disks, selects the boot disk and boot PRG, and enables optional IDE-assisted swaps.

This separation allows one target output or data file to be placed on several disks without duplicating source declarations.

Disk diagnostics are grouped by root cause, not merely by severity. For example, unbuilt target outputs appear in one red [ERROR] summary with a Build Dependencies action and collapsed Show details list. The summary distinguishes the number of affected disk files from distinct unbuilt targets. Unrelated placement/capacity errors and yellow KERNAL zero-page warnings stay separate. Details retain the original messages and disk identities; aggregation does not alter validation or permit an invalid disk to be exported.

D64 writer scope

Generated images use 683 sectors, 256 bytes per sector, and 174848 bytes total. The BAM is at track 18 sector 0. Directory sectors chain from track 18 sector 1 and support up to 144 entries. Track 18 is reserved for BAM/directory growth by default. Writable directory types are PRG, SEQ, and USR. REL side sectors, error-info tails, D71, D81, G64, and protected/nibble formats remain deferred.

The writer supports:

Allocation maps are included as JSON and ASM .inc files in disk-set ZIP exports. They identify directory, reserved, free, and per-file sector chains for fastloader development.

Graphical allocation map

Each generated disk has a 35-track graphical sector map. Every cell is one physical 1541 sector, so the display reflects the actual allocation returned by the D64 writer rather than an estimate. PRG sectors are green, SEQ sectors are yellow/orange, USR files and disk markers are cyan, custom reserved sectors are purple, free sectors are dark, and Track 18 is highlighted as the BAM/directory system track.

Hover a file row to isolate its sectors temporarily, or click the row to keep it selected. The selected file's blocks are outlined on the map and unrelated allocated sectors are dimmed. Hovering a sector reports its exact track/sector, owning file, and position within that file's block chain. Clicking an owned sector selects its file. The Chain field below the map lists the complete ordered sector chain, including non-contiguous and exact-placement layouts.

Creating logical files, disks, and sets

Use New D64 to create a generated disk and the default disk set. Use New Disk Set to define another ordered group. The set controls its member disks, boot disk, boot PRG, drive unit, and assisted-swap behavior.

Add a project file with Add Logical File or a build-target PRG with Add Logical Output, then choose its DOS name and PRG/SEQ/USR type. This first step only declares reusable logical media; it does not allocate sectors on any disk. The Disks column therefore reports not placed until the file is assigned to at least one D64, then reports the number of disks on which it is placed.

For a PRG, also choose its PRG mode in the Logical Files table:

PRG modeBytes placed on diskUse
Runnable (SYS)A runnable program, with a BASIC/SYS loader when neededBoot programs and independent executables
Raw dataThe target's data PRG, without a boot wrapper: assembled payload for normal targets, or staging address plus W64X container for Packed data targetsMusic, graphics, level banks and overlays loaded by a resident program

The mode belongs to the logical file and is saved with the project. New normal PRG logical files default to Runnable (SYS); explicitly select Raw data for data banks. Packed data targets default to Raw data and reject executable SYS wrapping. The directory type stays PRG in both modes. Turning off a SYS wrapper does not strip arbitrary file-format headers: a PSID .sid container must first be assembled as a payload-only target, as described below. Select SEQ or USR only when the program expects that file type and byte layout.

For each destination disk, use the placement selector directly above its allocation map and choose Place File. When the project contains exactly one disk, a newly declared logical file is preselected there, but placement remains an explicit action so multi-disk projects never receive unintended copies. An empty disk displays a reminder above the map. After placement, the disk header reports its placed-file count and the map includes the file's colored sectors. If a target output is unbuilt or stale, choose Build Dependencies to produce its bytes and refresh the allocation.

Directory Up/Down controls determine DOS directory order; put the normal boot PRG first when LOAD"*" compatibility matters. Every generated disk also receives a small final USR marker named from its stable disk id, such as @W64-DISK-2.

The advanced placement controls are intended for custom loaders. Track Order sets a comma-separated preferred track sequence. Reserved Sectors accepts track/sector pairs that ordinary files may not consume. A file can set interleave, alignment, a preferred track range, or an exact comma-separated sector chain. Invalid, conflicting, or too-short exact chains fail mastering explicitly.

A compact version 2 media record resembles:

{
  "build": {
    "version": 1,
    "activeTargetId": "boot",
    "targets": [
      {
        "id": "boot",
        "rootPath": "src/boot.c",
        "inputs": ["src/boot.c", "src/io.asm"],
        "outputName": "boot.prg"
      }
    ]
  },
  "media": {
    "version": 2,
    "files": [
      {
        "id": "boot-prg",
        "dosName": "BOOT",
        "type": "prg",
        "source": { "kind": "target-output", "targetId": "boot" }
      }
    ],
    "disks": [
      {
        "id": "side-a",
        "title": "GAME SIDE A",
        "unit": 8,
        "entries": [{ "fileId": "boot-prg", "order": 0 }]
      }
    ],
    "diskSets": [
      {
        "id": "game",
        "diskIds": ["side-a"],
        "bootDiskId": "side-a",
        "bootFileId": "boot-prg",
        "unit": 8,
        "assisted": true
      }
    ]
  }
}

Project paths are virtual filesystem paths, not host paths. Generated D64 and target bytes are build artifacts and are not stored as source truth in .web64proj. Version 1 media records migrate to logical files, disks, and one default disk set when opened.

Creating a single-disk multi-load project

Multi-load means that the running C64 program requests later files from a mounted disk. It does not require multiple disks. A common layout keeps a small program resident and replaces a separate RAM area with the current level, graphics, music or code overlay.

  1. Plan memory. Keep resident code, stacks, zero-page state, IRQ code and persistent variables separate from replaceable banks. Overlays may reuse an address only when they are never needed simultaneously. VIC bank visibility and ROM/I/O mapping still apply.
  2. Create the resident program in Code and give it a Build Target with the intended C, ASM or mixed translation units. This executable boots and performs subsequent loads.
  3. Create one assembly source per replaceable bank. Use normal .incbin statements to consume native asset bytes and generated includes for sizes and addresses. Native map structure, Color RAM, video-matrix and material exports can form a documented bank layout; the application decides how those planes are installed and interpreted.
  4. Create a PRG target for each bank. Choose its Root source and necessary Translation Units, select Web64 Native Assembler, disable Web64-C target for data-only assembly, and set Origin to the planned destination. Choose its Output file name.
  5. In Disk/Media, create a New D64. Add the resident and bank targets with Add Logical Output. Give them distinct DOS names matching the program's load requests. Choose Runnable (SYS) for the boot file and Raw data for each bank.
  6. Place File for every logical file. Select the set's Boot disk and resident Boot file. Use the same drive unit in the program and disk set. For wildcard booting, place the resident first in directory order.
  7. Choose Run Disk (or Ctrl+F5) to build and boot, or use Build Dependencies first to inspect the result. Test every transition that requests another file, not just the first screen. Running a multi-load PRG without its disk cannot load later banks.
  8. Save the project to retain sources, native assets, target settings and disk layout. Download the mastered D64 or export the disk set separately for a playable image.

Directory order and sector placement do not schedule loads. The application owns the sequence, destinations, transition state and error/retry behavior. Ordinary named-file KERNAL loading does not require fixed sectors; exact placement serves programs with a sector-aware loader.

SID Tracker music as a loadable bank

.w64sid remains editable tracker source. Tracker Save regenerates a .sid PSID container and an .inc file. A generated PSID v2 header is 124 bytes; a raw PRG's load-address prefix is two bytes, so the SID container is 122 bytes larger than the equivalent payload PRG. Renaming .sid to .prg does not remove the header or produce a correctly loadable bank.

For assets/music/song.w64sid, keep the normal filename-derived include symbols. Set Info > Driver address and Music data address to a compatible region in the application's memory plan, then Save. In Code, create a bank source:

.include "assets/music/song.inc"
.incbin song_sid, "assets/music/song.sid", song_sid_c64_data_offset, song_sid_c64_data_size

Create a data-only PRG Build Target for this source, with Origin equal to song_sid_load_address shown in the generated include. Add that target's output as a Raw data PRG logical file and place it on the disk. The assembler selects only the C64 payload and supplies the PRG load address; Disk/Media masters those bytes unchanged. There is no external export or conversion step.

The resident may include the generated constants without embedding the song. In that case define song_sid = song_sid_load_address to resolve the generated end/size expressions. After a successful load, initialize using the generated init address and zero-based subtune index, and call the generated play address at the composition's required frame cadence. Do not assume different compiled songs share entry addresses or lengths. Rebuild resident tables that reference those constants when music changes.

The edit loop is Tracker Save -> Build Dependencies -> Run Disk / Download File / Download D64. The .w64sid, .sid and .inc remain project files; the payload PRG is a rebuilt target output. Do not make a separately exported PRG the canonical disk source: later Tracker saves will not update that unrelated copy.

Build, run, swap, and capture

Build Dependencies and Run Disk compile only missing, stale or previously failed outputs used by the selected set, including their transitive producers. Disk/Media shows diagnostics for the selected set; an unbuilt disk belonging only to another set does not produce errors in that set's banner or prevent its launch. Select the other set to inspect its diagnostics. Pending source/native-asset edits are included. Build failures stop the launch; inspect Build Output for the target and diagnostic. If the project changes during launch, run again to use the latest inputs.

Run Disk is available in Disk/Media, on the main toolbar (disk-with-play icon), and through Ctrl+F5. The toolbar is enabled when a Disk/Media disk image is configured; its target bytes need not already be built. All three entry points preserve the selected disk set, switch to Code and reveal the emulator. F5 continues to run the current PRG and Shift+F5 continues to pause/resume.

After a successful build, Run Disk stages private runtime clones of the complete set, mounts its boot disk, power-resets the C64, and enters LOAD"<boot>",unit,1 followed by RUN. The selected filename uses the same PETSCII bytes as the mastered directory, including case normalization and supported punctuation; it is not replaced with *, so the boot file need not be first in the directory. Choose a Runnable (SYS) PRG as the boot file.

When assisted swaps are enabled, a running program can call web64_disk_request(index) from <web64/disk.h>. The helper writes the request magic and zero-based disk index to the Web64 mailbox at $02fe-$02ff. The IDE polls this two-byte mailbox only during an active assisted disk session, mounts that set's indexed disk, and clears the request. Portable programs should also call web64_disk_require(marker, length, unit) and prompt for the marker manually when no IDE host is present.

Mounted generated disks are writable runtime clones. Emulator writes never mutate the project manifest or generated source image. Capture Runtime flushes the mounted drive, reads the current D64 bytes, reports whether they differ from the staged baseline, and downloads the captured image while keeping it attached.

For an application that writes documents or other user files, import its data .d64 through the normal project-file import. Download Empty D64, available under Imported D64 Images even before importing a disk, supplies a formatted blank image; you can also download the blank D64 here. Select Drive 8 for a single-drive disk swap, or Drive 9 to leave the application disk in drive 8. Enable Drive 9 in Emulator Options when using the second drive, and select the same device in the running application. Mount attaches the selected data image without resetting the C64 or issuing LOAD/RUN; wait until the application has closed its files and the drive is idle before swapping.

After the application saves, Capture Drive 8/9 downloads the disk currently mounted on that device, including the C64's file changes, and leaves it attached. It captures the current runtime disk, which may differ from the imported row's image. Capture changes before mounting a different image or closing the emulator. Saving the Web64 project or downloading the original imported file does not capture these runtime writes. Reimport the captured D64 to use it in a later session.

Download File, in Logical Files, exports the exact bytes from a current mastered disk, including the selected Raw data or Runnable packaging. It is disabled until the file is placed on a valid, non-stale disk. A PRG download has the .prg extension; it is not a renamed .sid source.

Download, in a disk header, exports that generated D64. Export Set ZIP rebuilds the selected set's missing/stale dependencies and exports all its disks plus deterministic allocation-map JSON and ASM include files. Before using a disk's direct Download or Mount action after editing sources, use Build Dependencies to ensure the image is current. Imported .d64 project files are inspect/mount only and are never rewritten by the mastering workspace.

Project Save, Tracker Save and emulator resume are different operations: saving retains editable work, building creates target/disk bytes, and resuming continues the current machine. Rebuilding does not replace an image already mounted in a running machine. Use Run Disk to boot the newly built set, or explicitly mount the rebuilt image when the application is waiting for it.

C and KERNAL disk I/O

<web64/disk.h> provides dependency-linked helpers for whole-file load/save, marker verification, assisted swap requests, and byte-stream open/read/write/close operations. Include it only when needed; unused disk routines and state are absent from the final program.

<cbm.h> provides low-level wrappers for KERNAL READST, SETLFS, SETNAM, OPEN, CLOSE, CHKIN, CHKOUT, CLRCHN, CHRIN, CHROUT, LOAD, and SAVE. The high-level Web64 helpers use the same KERNAL paths and return Web64DiskStatus values. The complete declarations and usage examples are listed in the Web64 C Compiler User Manual.

The generated assets/generated.h media section publishes WEB64_GENERATED_DISK_COUNT, file/set counts, disk indexes, units and marker strings, logical DOS filenames and types, plus each set's boot disk, boot file, and unit. These descriptors are derived from the saved media manifest and stay aligned with disk ordering used by web64_disk_request.

For safe transitions, pause routines or interrupts that read or execute the bank being replaced, load it, check status and the loaded end address, validate its format/version when applicable, then install or call the new content. Never initialize music or jump into an overlay after a failed or truncated load. Whole-file KERNAL loading is not transactional: use separate staging and bounded streaming if incomplete input must not overwrite live data. When streaming a PRG manually, consume its two-byte load address as a header, not payload.

Keep compatible memory configuration and preserve KERNAL workspace during disk operations. A blocking load may pause animation/audio. Smooth cutscenes or music during loading require an explicitly designed loader/IRQ arrangement and measurement; reserving sectors or assisted swaps does not create a fastloader. Assembly callers can use the native convenience macros in Web64 SDK > ASM includes > web64/disk.inc; linked C and ASM helpers use the same runtime contracts.

IRQ-friendly hardware loading from C or ASM

Use <web64/loader.h> or web64/loader.inc when the application explicitly targets the supported 1541-family fast protocol. The ordinary KERNAL wrappers above remain a separate choice; no automatic fallback is installed.

  1. Reserve a page-aligned 256-byte sector buffer, bounded incoming staging, live destination and, for packed data, disjoint decoded workspace. Keep all spans outside code, SID data, sprite data and the C stack.
  2. Install an application-owned IRQ and persistent 16-bit clock. Preserve its registers/scratch, provide RAM and KERNAL-compatible vector paths, then call web64_loader_init(device, sector, ticks).
  3. Use web64_loader_load_raw or web64_loader_load_packed with a typed request, or use read followed by unpack for caller-managed caches. Fast file calls require RAM with I/O visible ($01=$35) and interrupts enabled.
  4. Check the compact status before using the new bank. A failed packed load leaves live destination bytes unchanged, even after a damaged compressed stream passes its outer CRC. Received staging itself is disposable on failure.
  5. Before normal KERNAL SAVE, call web64_loader_shutdown. Reinstall with web64_loader_reinitialize_after_save before the next fast read; after a power-cycle or failed configuration, use fresh init.

The runtime is synchronous, not a background loader. A lightweight IRQ may keep music and presentation running during reads, but SAVE and drive initialization can still stall. Applications own input responsiveness and the bank currently used by the SID player. Raw reading, unpack-only and combined loading exact-link independently; pure ASM needs no C startup. PAL/NTSC true-drive 1541-II emulator coverage does not imply support for arbitrary IEC storage devices.

The Hardware Loader Runtime manual contains the full C request types, ASM windows/macros, errors, ownership map, optional code placement, measured costs and troubleshooting. The full native source/assets → target → optional Packed data → Raw data logical file → D64 chain is creatable inside the IDE; no external compressor or hand-edited project file is required.

Validation and diagnostics

The workspace validates source references, target freshness, ids, DOS names, file types, directory capacity, free-space allocation, exact placement, BAM consistency, directory chains, and runtime attachment eligibility. Typical diagnostics include:

The inspector parses raw bytes independently from writer state. An invalid image cannot be downloaded, mounted, staged, or run.

For common multi-load failures, check these before changing the loader:

Deferred media features

REL side-sector writing, D71/D81/G64 mastering and protected/nibble media remain outside the current writer. The optional hardware loader is a separately selected runtime, not an implicit disk-writer behavior. Web64 never silently replaces a program's loader protocol.

Native cartridge mastering

Cartridge layouts live in Disk/Media because they master release media; Build Targets still owns the compiled programs, packed banks and data contributions. Create a layout, choose Standard 8K, Standard 16K, Magic Desk or EasyFlash, then place target outputs or project files into explicit ROML/ROMH windows and banks. The placement map shows bank, window, offset, size, CRT offset and declared RAM destination. A missing target, overlap, overflow, impossible alignment, invalid boot reservation or stale failed build blocks mastering. Saved .web64proj files keep the layout and VFS, not transient CRT bytes.

Disk Mastering and Cartridge Layouts are separate subtabs within Disk/Media, keeping each medium's actions and diagnostics together. New Cartridge Layout creates the cartridge configuration. Placements use individually collapsible cards in a responsive one-to-four-column grid; each collapsed card still identifies its source, role, bank, ROM window and offset. Expanding or collapsing a card does not change its placement. The top-right × removes a non-boot placement even while collapsed; the boot card has no remove control. Automatic allocation reserves boot first, then uses stable placement-ID order; displayed card order is not a memory-layout control. Drag reordering is not implemented. The bank map and migration review remain available through compact disclosure links.

Build Cartridge compiles changed dependencies and masters the selected CRT. Run Cartridge is in Disk/Media, the main toolbar and Ctrl+Alt+F5; it builds the current image, attaches it through the native VICE cartridge API, power-resets the C64 and reveals the Code emulator. Export CRT downloads only a current successful artifact. The emulator's Advanced options show the attached profile and occupied banks; Eject cartridge detaches it. Starting a PRG or Run Disk detaches the cartridge first. The base emulator also shows attachment identity and an explicit eject action. Neither run path patches a PRG into RAM to simulate a cartridge.

Wrap resident PRG accepts a machine-code target with a declared entry and a load range inside $0200..$7fff. Standard 8K, Standard 16K and Magic Desk use a bank-0 ROML boot with the resident payload in that window. EasyFlash uses a bank-0 ROMH reset boot and requires the same target as a pinned bank-0 ROML PRG payload placement; the reset code moves a short loader into RAM before changing cartridge mode. The bounded copy temporarily uses $fb..$fe, clears them, and enters the program with a fresh stack and IRQs enabled. The boot and payload must fit their assigned windows. This is not a BASIC-file, arbitrary D64 or unknown-loader converter. PRG payload strips the two-byte load address without generating a copy/boot contract; Raw output retains every byte; Packed stream places the verified compressed body for an application-owned unpacker.

The native C/ASM cartridge runtime exposes bank selection, EasyFlash mode selection, ROM read and bounded ROM-to-RAM copy via web64/cartridge.h and web64/cartridge.inc. A copy is at most 256 bytes, within one mapped ROM window, to caller-owned $0200..$7fff RAM; callers chunk longer transfers. The linked service is RAM-resident, restores the selected mapping, and uses $fb..$fe as its documented call-scratch overlay. Interrupt handlers that share those bytes must preserve them. EasyFlash must have an explicit valid mode before read/copy; code that disables its own execution window must run from a RAM trampoline. A cartridge image is immutable here: there is no browser flash programmer or implicit ROM save backend.

The complete C and assembly library contract, runnable mixed-language example, status reference, and per-profile constraints are in Web64 Native Cartridge Runtime.

Managed cartridge resources

Enable Managed resources on a native Cartridge Layout, choose its resident Build Target and a directory address in ordinary RAM, then declare one or more writable transfer-buffer regions. Add a logical resource from a project file or target output. Resource IDs are stable project-local names; their generated numeric values are build-local and should not be stored in save data. cartridge/generated.h and cartridge/generated.inc provide IDs and 32-bit stored sizes to the bound target during Build Cartridge. The Resource map shows each resource's stored size, extent count and generated declarations. The final CRT manifest relates the original source, representation and logical byte offsets to physical bank/window/CRT offsets. Generated files and final directory bytes are projections of the saved layout; edit the layout and its sources, not those projections.

The first version splits a logical resource into extents of at most 8,192 bytes. Standard 8K has one fixed ROML window; Standard 16K has fixed ROML and ROMH; Magic Desk has 64 ROML banks; EasyFlash has 64 shared-bank ROML/ROMH pairs. The boot, application, directory, other placements, fill and alignment also consume capacity. A source too large for the selected profile fails mastering explicitly. A packed-stream resource reports its stored size; copy its stream into caller-owned staging RAM and invoke the normal unpack service separately. There is no transparent decoding or ROM pointer to keep after a bank change.

In C, include <web64/cartridge.h> and <cartridge/generated.h>. In ASM, include web64/cartridge.inc and cartridge/generated.inc. The same resource request works with all four profiles:

web64_cart_resource_request request;
web64_cart_resource_result result;
request.resource = WEB64_CART_RESOURCE_LEVEL_TILES;
request.offset = 0UL;
request.destination = level_buffer;
request.length = sizeof(level_buffer);
uint8_t status = web64_cart_resource_copy(&request, &result);

Call web64_cart_resources_init() after the cartridge's resident boot. web64_cart_resource_read accepts at most 256 bytes; web64_cart_resource_copy accepts a 16-bit length and internally transfers bounded chunks. Both accept a 32-bit resource offset and return a status plus the completed-byte count. A valid zero-length range at or before the end succeeds without touching a destination. An out-of-range request fails rather than truncating; a multi-chunk failure reports the valid copied prefix, not a rollback. A transfer destination must lie entirely within one declared writable region, outside resident code, directory, zero page, stack and active request/result storage. Build Cartridge checks declared RAM ownership and the guest checks each operation. F5 direct PRG start is refused for a managed resident target; use Run Cartridge so the real CRT boot establishes mapping and directory state.

The service executes from resident RAM, masks IRQ for each bounded bank/copy/restore interval and restores the caller's prior IRQ-enable state and cartridge mapping before returning. IRQ/NMI handlers must be bank-invariant during transfers, preserve shared call scratch, and must not call the cartridge API. A 256-byte call can still miss a raster deadline; select shorter reads and measure on the target machine. Raw application writes to $DE00, $DE02 or CPU mapping registers bypass the managed shadow and are outside its safety guarantee. The existing explicit low-level bank/mode API remains available for expert, separately owned code. Managed resources do not bank arbitrary executable functions, virtualize ROM pointers, or program EasyFlash storage.

The packed request is nine little-endian bytes: resource ID at offset 0; uint32_t logical offset at 1–4; 16-bit RAM destination pointer at 5–6; 16-bit count at 7–8. The two-byte result holds the completed count. C can use the declarations directly. Native ASM uses the same labels and the generated web64_rt_call_cart_resources_init, web64_rt_call_cart_resource_read request, result, and web64_rt_call_cart_resource_copy request, result macros from web64/cartridge.inc; status returns in A. Both C ABIs and ASM share the same guest entry points. A/X/Y and flags may change; $fb..$fe are call scratch. The operation is mainline-only and non-reentrant. The directory starts with W64R, version 1, profile and 16-bit resource/extent counts, followed by fixed eight-byte resource and ten-byte extent records. Applications should use generated handles rather than parsing those physical records themselves.

StatusValueMeaning
WEB64_CART_OK0Complete operation; result count equals the request count.
WEB64_CART_PROFILE, BANK, MODE1–3Expert-level profile/bank/mode mismatch.
WEB64_CART_RANGE4Requested source range or bounded-read count is invalid.
WEB64_CART_DESTINATION5Request/result pointer or transfer destination is outside permitted RAM or overlaps live metadata.
WEB64_CART_BUSY6A prior transfer has not completed; ISR calls are still unsupported.
WEB64_CART_NOT_INITIALIZED7Call web64_cart_resources_init from a supported cartridge boot first.
WEB64_CART_RESOURCE8The resource ID is not in this build's directory.
WEB64_CART_DIRECTORY9The copied directory is invalid or an extent cannot satisfy the request.
WEB64_CART_UNSAFE10Reserved for a rejected unsafe mapping state.

Disk-to-cartridge review and legacy projects

An existing disk project can add a separate cartridge boot target and bank layout while preserving its D64 loader, targets and disk set. Expand Disk-to-cartridge review in the layout before building: it lists source/loader changes, placements and writable-file questions. ROM bank reads do not translate IEC loading or drive code. Keep mutable documents, scores or configuration on a data disk in device 8 or 9, and test that save workflow explicitly. A project with no declared SEQ/USR/REL file may still write at runtime, so inspect its code before claiming disk independence.

Imported D64 images offer a collapsed Cartridge feasibility report. A valid directory is not proof of a convertible application: machine-code entry, custom 1541 code, raw-sector access, copy protection, unknown self-modifying loaders and save behavior require source-level review. Invalid images are refused. Web64 does not silently extract PRGs, patch binaries, intercept KERNAL calls or map writes to flash and call the result a cartridge. An eligible standalone resident PRG can use the explicit Standard 8K wrapper; a banked application needs an authored cartridge loader and placement plan.

Legacy magic-desk-crt Build Targets remain readable and buildable. Their c64lib bootstrap/target metadata is not silently reinterpreted as a native layout. The Legacy Magic Desk compatibility review gives a non-destructive route: save the original project, author a separate native boot and targets, review the new layout, save a distinct migrated .web64proj, then reopen and compare both artifacts. If the old source cannot be ported safely, retain the legacy target. The stock Native Magic Desk + Disk starter demonstrates a completed two-bank native path beside a working original D64; it is not an arbitrary binary migration tool.

REU and C64U launches

An REU image is project media, not a separate compiler target. In Disk/Media → REU Images, create a layout, choose its capacity, and optionally place project files or target outputs at declared REU offsets. Build REU Image and Export REU are available for inspection; Build Output reports the capacity, placement map and SHA-256. A project that only writes REU data at runtime can intentionally have an empty layout. Checked C/ASM REU access uses a target-owned RAM-buffer contract, documented in the repository's docs/c64u-reu-sdk.md. Do not infer actual hardware capacity from a presence probe.

In Disk/Media → Launch Profiles, create a launch and select its PRG target, disk set or cartridge, PAL/NTSC mode, and optional REU image. Select it as the Active F5 launch and save the .web64proj to make these relationships portable. F5 and Run in Emulator build changed dependencies and the selected REU image automatically; there is no separate save or REU-build prerequisite. They attach a fresh image and run in the embedded emulator. For projects without an active launch, F5 keeps its existing local run behavior. Cartridge + REU is not a supported launch combination in this candidate. The current REU starter in New from template… has ASM/C+ASM, PAL/NTSC and 512 KiB/16 MiB options; the reu-development example is a small DMA round trip.

Physical C64 Ultimate transfer is a separate, explicit action. Install and start the local web64-device-bridge on a computer that can reach your C64U over a private LAN; its device-only mode needs no MCP client or Cloud account. Open the bridge's one-use invitation in the same Web64 IDE browser, allow the browser's local-network request if shown, then approve inspect + execute in Settings → Devices. Run on C64U stages the current build, checks device settings, and asks for one hardware launch. It never changes F5's destination, never scans your network in the background, and never stores device credentials in the project. The command can replace the currently running program and selected temporary media on the device. Review the selected launch and save anything valuable on the C64U first.

The status distinguishes staged bytes, preflight, transfer, guest handoff and a requested launch. A successful network response does not certify that the application started or that REU contents were read back; the physical-device path awaits community verification. A timeout after device mutation has an unknown outcome: inspect the job and machine before retrying. Cancel before release prevents a new application launch request, but cannot undo bytes already accepted by the device. Disconnect revokes the browser grant. Device 9 is an explicit launch disk binding, not permission to overwrite another disk. The candidate's limited support status and remote-test steps are in the repository's docs/c64u-remote-tester-brief.md.

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, keyboard, keyboardport, gamepads, gamepad1, and gamepad2. Boolean parameters accept true/false and on/off. keyboard accepts numpad, arrows, or off; each port option accepts 1, 2, or (for physical gamepads) off. The legacy joystick=1|2 option remains a shorthand that routes the numpad and first gamepad together.

For example:

https://web64.nofs.ai/?file=https://web64.nofs.ai/greedy-ghost.d64&autorun=on&audio=on&fullscreen=on&keyboard=arrows&keyboardport=2&gamepads=on&gamepad1=2&gamepad2=1

The file URL is parsed independently from the following launch parameters. D64/G64 autorun mounts drive 8 and then feeds the exact LOAD"*",8,1 and RUN sequence; autorun=off only mounts it. PRG autorun retains its normal direct program loader. Requested audio is retained across the media transition instead of being lost when the image attaches. Audio and fullscreen are attempted immediately and remain armed for the first interaction anywhere in the standalone emulator when browser permission policy requires a user gesture. Percent-encode the file value when the nested file URL contains its own query string, fragment, or ampersand. The local Vite development server provides a host-restricted bridge for files served from web64.nofs.ai, whose production same-origin policy otherwise prevents a localhost page from fetching them.

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 in Emulator Options → Storage or the Settings tab. Its current state also appears in the Emulator Options summary. Some C64 programs, including Elite's fastloader, assume only drive 8 exists and can fail if drive 9 is present. Turn Drive 9 off before Run Disk for those programs, then save the .web64proj to keep that choice when the project is reopened. A disk configured for drive 9 requires the drive to be enabled before mounting it.

Debugger

The Debugger tab starts with Execution, a live decode of bytes at the paused C64 CPU's actual PC. The arrow marks the instruction that will run next; a checkpoint badge separately names the instruction that caused the stop, which may have a different address. A selected row has its own outline. The live listing remains usable when source matching is unavailable, ambiguous, modified, or in ROM.

On a wide window, Execution and Variables fill the available debugger height beside each other, with compact Registers and Breakpoints above Variables. Memory stays below them and starts at about 220 pixels tall; drag its divider or focus it and use the arrow/Page/Home/End keys to resize it. Narrow windows stack the panes without changing debugger state. Execution and Variables scroll inside their own panes.

Debugger paused in Elite game code, showing live instructions, original source, breakpoint cause, registers, and memory
The running PAL disk is paused after a guarded Elite breakpoint; the current PC and triggering instruction are shown separately.

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 open-circle address gutter in Execution.
  3. Click a Source or Generated ASM gutter for a source/provenance breakpoint, or Shift-click the Generated ASM gutter for a fixed machine-address breakpoint.

Breakpoint addresses must be whole 16-bit C64 addresses; out-of-range input is rejected rather than wrapped.

For a running disk or directly started PRG, source breakpoints bind to instruction bytes and supported C64 memory mappings from the pinned build. A not-yet-loaded disk component may have a guarded checkpoint; it does not become an unconditional address breakpoint. A source intent without a supported built candidate remains pending. Machine breakpoints deliberately use the entered 16-bit C64 address. The Breakpoints manager shows each intent and its binding state, with enable, disable and remove actions. Competing executable components with indistinguishable bytes remain ambiguous rather than receiving invented source identity. Stored .byte data is not treated as a competing instruction when a unique executable contribution matches. Cartridge-bank and drive-CPU contexts are not yet source-breakpoint contexts.

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. Exposing the Source tab or choosing Open Source in the Inspector centers that highlighted line in the editor. The Inspector's source, Generated ASM, Memory and advanced-mapping actions are compact links.

Conditional execution breakpoints

Select Condition on an existing source or machine breakpoint, enter an expression, and Apply. An empty condition restores the unconditional intent. The Breakpoints list reports conditional, pending, invalid-expression, unresolved-symbol, unsupported-capability, or another explicit binding state. Invalid text remains editable and never installs an unconditional breakpoint. An older emulator without native conditional owners leaves the intent unarmed. Web64's low-level addMonitorBreakpoint(..., { condition }) rejects the formerly ignored option; the IDE installs validated owner-local native programs instead.

Debugger expression v1 is a small, case-sensitive language shared by conditional execution breakpoints and passive expression watches. It accepts unsigned 32-bit decimal (10), hexadecimal ($0A, 0x0A), and binary (0b1010) literals; true and false; parentheses; registers cpu.A, cpu.X, cpu.Y, cpu.SP, cpu.PC, cpu.P; and one-bit flags cpu.C, cpu.Z, cpu.I, cpu.D, cpu.V, cpu.N. cpu.P is the whole processor-status byte. Safe CPU-visible memory reads are mem.u8(cpu, address) and little-endian mem.u16le(cpu, address), where address is itself an expression. A uniquely named numeric label retained by the launched build can be used as a constant; ambiguous or edited-only names remain unresolved. A label is a numeric constant here, not a live C variable read.

Operators bind from tightest to loosest: unary ! and ~; relational <, <=, > and >=; equality == and !=; bitwise &, then ^, then |; logical &&, then ||. Logical operators short-circuit left to right. Use parentheses whenever a bit test participates in a comparison. Zero is false and any nonzero value is true. All numeric comparisons use unsigned 32-bit values; a flag already has value zero or one. Arithmetic (+, -, *, /), shifts, negative literals, casts, pointers, C members, indexing, monitor expressions, and JavaScript are not part of this language. There is no implicit read of a label's address: write mem.u8(cpu, label) to read its current byte.

Examples to enter in a breakpoint's Condition field:

ExpressionStop when the breakpoint instruction is reached and…
cpu.X == 3X is exactly 3.
cpu.C && !cpu.ZCarry is set and zero is clear.
(mem.u8(cpu, $2000) & $80) != 0Bit 7 of the CPU-visible byte at $2000 is set.
mem.u16le(cpu, $C000) >= 1000The bytes at $C000/$C001 form a little-endian value of at least 1000.
cpu.X >= 3 && (mem.u8(cpu, $2000) & $80) != 0A register threshold and memory bit test both hold. The right read is skipped if the first test is false.
mem.u8(cpu, $D012) == 0 && (mem.u8(cpu, $D011) & $80) == 0The visible VIC raster line is zero, including its ninth bit. This samples the raster only when the chosen execution breakpoint is checked.

The $D012/$D011 expression can help investigate code that repeatedly visits a checkpoint near the top of the screen. It is not a cycle-exact frame-start trigger: the CPU may not reach that instruction while raster zero is visible, and another CPU mapping can expose RAM or ROM instead of VIC registers. For a guaranteed next VIC frame transition followed by the first safe CPU instruction boundary, use Step Frame. The expression reads mapped registers through VICE's side-effect-free peek path and does not write or acknowledge an interrupt.

Conditions execute in the native checkpoint hook, with a maximum of 512 source characters, 128 syntax nodes, depth 16, 256 lowered operations, stack depth 32, eight reads, and 16 total read bytes. An invalid, unresolved, unsupported, or over-budget condition stays visible but unarmed; it never silently becomes unconditional. A required read that is unavailable makes that owner false/unavailable; a separate colocated owner may still stop. The accepted checkpoint address can differ from the eventual paused PC and registers: VICE may complete the triggering instruction before the paused state is captured. Execution displays both identities and accepted owners. Conditions are never evaluated by browser-side pause/evaluate/resume. Typed C variable names are not condition operands; use an explicit safe memory read only when its storage address and current CPU mapping are known.

Watches

In Variables, enter a debugger expression and choose Watch Expression, or enter a CPU address and choose Watch Address. Watches are passive: they read only after a coherent pause and never set a memory access checkpoint. A running value is labeled last stop. Each row can be edited (expression text), formatted, disabled or removed. On an ABI-5 runtime, an observed single-byte RAM watch or proven typed scalar watch also has an editable value. A computed expression is read-only. Memory watches show explicit CPU-visible bytes; formats are hex, unsigned/signed 8-, 16- and 32-bit little-endian integers. An expression uses the same v1 grammar as a condition, with unavailable operands displayed as unavailable rather than zero.

Watch on an eligible C global captures a typed scalar binding from the launched direct PRG. This requires proven absolute 1/2/4-byte integer storage, declaration/target identity and an address in that PRG's resident span under the current CPU mapping. It displays a storage observation, not an inferred initialization or source-level lifetime. Disk-loaded component data, overlapping storage, unknown mappings, pointers, arrays, aggregates, locals and parameters do not acquire typed watch authority. Use a raw address watch when that is the intended inspection. Typed C operands in execution conditions are reserved for a later separately validated extension.

The .web64proj saves watch and breakpoint user intents, expression text, format and pinned semantic choices. It does not save values, runtime handles or stop tokens. Projects saved with debugger intents require Web64 2.5.1 or later for lossless resave; older strict project validators can reject the optional debugger section, and older readers can discard it. Opening a project does not run it or trust stale source addresses. Rebuild and launch to bind saved source intents again.

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, variable, memory, PC, and source highlights populate as the runtime bridge returns them.

Instruction controls

Step Instruction uses the native instruction-boundary operation when supported. It can prove completion even when an instruction jumps back to its own PC. Continuing from a checkpoint uses a native step-past and rearm sequence; there is no host timer window in which a tight loop can lose a breakpoint.

Step Over behaves like Step Instruction for ordinary instructions. At a call, it resumes to the decoded return address using temporary monitor breakpoints, preserving existing persistent breakpoints and restoring any temporarily suppressed breakpoint afterward. Both controls refresh registers, variables, memory, current-PC provenance, and source highlighting when execution pauses again.

Step Frame resumes the whole emulated C64 until the VIC-II starts its next frame, then stops at the first safe main-CPU instruction boundary. It advances drives, CIA, SID, loaders and interrupts along with the CPU; it does not execute a fixed number of cycles or stop inside a VIC callback. The Execution header reports the native frame epoch and the actual stopped raster line/cycle. A breakpoint or manual pause that happens first retains its own stop reason. Frame is available only when the paused x64sc runtime advertises native frame stepping.

While observing the emulator, F9 steps one instruction, Ctrl+F9 steps a frame, F10 steps over, Ctrl+F10 runs to the selected complete instruction, and Shift+F9 continues or pauses. These shortcuts leave the current tab and focus in place. They are ignored in text editors and dialogs; key repeat cannot queue multiple commands. C64 F5 remains available to games when the emulator has focus. After an advancing command stops, Web64 presents the latest completed VICE video frame even if its frame number has not changed. A single instruction can leave the visible image unchanged because no newer frame was produced.

Run to Cursor uses the selected complete live instruction row as a temporary guarded stop. It cleans up the temporary checkpoint when that instruction is reached, another stop takes priority, or the run is cancelled. It does not change the saved breakpoint list.

The right Inspector shows compact Instruction Details for the paused or selected row, with source, component, mapping, Generated ASM and memory navigation where available. Show advanced source mapping opens the older searchable, virtualized line-map inspection tool on demand; it is no longer the default debugger workflow.

For a multi-load disk such as Elite, select the PAL disk set, disable Drive 9 if its fastloader requires that, and use Run Disk. Pause after a loader stage or during gameplay. Execution always shows the live bytes; Generated ASM's component selector can inspect the boot file, intermediate contributions and game code from that same launched build. A source breakpoint in a game contribution is guarded until matching bytes execute under a supported C64 mapping. If the editor differs from the loaded source, use the read-only loaded excerpt before rebuilding and restarting the disk. Disk component data without proven residency remains read-only; raw ordinary-RAM bytes can still be edited by address where the current CPU mapping permits it.

Starting a PRG directly also pins its compiled source and Generated ASM to the launched runtime. Later editor changes do not replace that running source map; start the program again to load a new build. The current C64 debugger cannot identify cartridge ROM banks or drive-CPU execution contexts. A cartridge pause still shows live C64 bytes, but source attribution and source breakpoints for those unsupported contexts are unavailable rather than guessed from the editor's selected target.

Registers

The register panel displays:

Flags shows the reported status byte in hexadecimal and binary. The binary digits align with NV1B DIZC from bit 7 to bit 0: negative, overflow, reserved bit 5, break marker, decimal mode, interrupt disable, zero, and carry. The 1 names the reserved position that is set in a status byte pushed to the stack; B distinguishes a BRK/PHP stack image from an IRQ/NMI stack image. Neither bit 5 nor B is an independent persistent CPU flag. The displayed digits are the native debugger's reported byte, not fabricated flag state. Unlike the NES 2A03, the C64 6510 uses D for decimal arithmetic.

Click a register value to edit it while paused on a native ABI-5 runtime. A, X, Y and SP are 8-bit; PC is 16-bit. Flags can change the meaningful C/Z/I/D/V/N bits; B and the unused status bit are not independent editable state. SP is the register value, not its $0100-based effective stack address. Enter an unsigned decimal number or a $/0x hexadecimal bit pattern, then press Enter; Escape cancels. An edit does not execute an instruction. Changing PC cancels temporary step/run-to intent and refreshes the source context while preserving saved breakpoints. If the runtime bridge cannot expose or safely edit a value, the UI disables its editor.

Globals and locals

The Globals panel lists typed C globals from the current compile result. Each row shows the source name and type, current value, storage location, and a Memory action when the variable has an address.

The Locals panel is scoped by the paused PC. Web64 resolves the current machine address through compact debug origins and the hydrated line map, then displays the mapped C function. A parameter or automatic local is readable only when compiler metadata proves a machine-address lifetime and location. Current builds often lack that proof, so these variables show unavailable rather than potentially stale stack/static bytes. Optimized out appears only when compiler metadata explicitly says so. PC is not inside mapped C function code means that the paused address has no trustworthy C-function scope; it is not shown merely because a source breakpoint used a post-instruction stop address. Source breakpoint hits retain their execution address while the register panel continues to report the authoritative current PC.

An eligible direct-PRG integer global can be edited through its value or typed watch. Expand a proven static-duration C struct to inspect signed and unsigned scalar members, including nested by-value members. A function static appears in its owning function under either supported C ABI. Const members and objects remain read-only. A struct pointer can expand one level only when its current two-byte pointer cell identifies one exact, compatible, resident static object. Its pointer cell has its own 16-bit editor if writable; a new numeric address does not by itself prove a struct target. Null, ambiguous, unknown, nonresident, pointer-to-const, automatic, array, union and bitfield cases are not editable. The debugger shows an unavailable reason instead of guessing a target.

Use a variable's Memory action to place its address in the memory viewer. Globals remain visible outside mapped C functions; locals require function/scope provenance at the current execution address.

Memory

The memory panel reads a short range of CPU-visible C64 memory. Enter a start address or symbol and a byte count from 1 to 256, then choose Go. Ranges crossing $FFFF are rejected, never wrapped to $0000. Go pins explicit browsing across later steps; Follow PC returns the viewer to the current instruction after each stop. Memory, Variables and Watches share the captured stop token; resume, step, reset, restore, patch, mapping change or a newer stop invalidates a late read. The safe peek path does not acknowledge CIA I/O reads or trigger access checkpoints. CPU mapping identifies visible bytes, not a physical write destination.

Double-click a byte or select it and press F2 to edit it while paused, if the native runtime proves that address is ordinary CPU-visible RAM. Enter unsigned decimal or $/0x hex, then Enter. Multi-byte typed scalar/member edits use little-endian storage and accept signed decimal in their proven signed range; a hex value is the raw bit pattern. Invalid input, overflow, stale stops, changed pointer cells and read-only mappings are rejected. CPU ports $0000/$0001, ROM, I/O, color RAM, cartridge or unknown/expanded memory and drive CPU memory are not write targets. Every successful edit, even writing an identical value, creates a new stop generation; pending drafts are discarded. Runtime edits are transient and are not written into source, built media or .web64proj.

Editing executable bytes clears affected exact source matching and source-bound breakpoint guards for that mapping through the dependent compiled span. Execution then shows live bytes as modified/unknown rather than claiming the previous instruction boundaries still identify the original source. Rebuild and relaunch to restore the original code and source mapping.

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.

Cycle / Raster Profiler

The Profiler workspace measures where the emulated C64 spends its clocks. It complements the debugger and static opcode timing: the recorded CPU, raster, interrupt and bus-stall data come from execution, not browser refresh timing or an estimate based on the program counter. No guest instrumentation or reserved CIA timer is required. It profiles the C64 CPU, not the drive CPU.

Choose the runtime and capture

  1. Save your IDE project and any important guest data. Open Profiler and

choose Restart with profiling runtime. This is an explicit restart: C64 RAM and mounted media are discarded. Project sources, editor state and undo remain intact. Run the program or disk again through the normal IDE action.

  1. Choose Detailed or Aggregate and a frame count, then Capture.

Start with one frame. Detailed mode records fetched instructions, horizontal intervals and observed branch/page-crossing costs. Aggregate mode retains totals and raster summaries without an instruction trace or those penalties.

  1. A capture can finish while you edit in Code. Hidden profiler views do not

render result controls, issue presentation queries or start more captures. Reopening Profiler presents the completed capture. Stop ends the current request; Clear releases its analysis. Pausing a C64 does not create guest clocks, so an armed capture needs the emulator to resume before it completes.

  1. Choose Restart with normal runtime when finished. The ordinary emulator

uses the separate uninstrumented Wasm. Merely opening Profiler does not fetch or activate the profiling core, and Web64 never runs both cores together.

Capture controls accept 1–16 frames, but detailed storage has a fixed 8 MiB cap. A dense trace can stop before the requested duration. Incomplete and its reason are part of the result; do not treat a partial edge or exhausted capture as a whole-frame measurement. Native summary storage is also capped at 8 MiB; the shared buffer/analysis/transport ownership budget is 32 MiB. Browser and Wasm heaps may retain a stable allocation high-water mark after Clear. Restarting with the normal runtime terminates the profiling worker and releases its heap.

Detailed capture costs extra host CPU. Keep requests short on a slower computer; aggregate captures are the less expensive starting point. Profiling is not enabled automatically by a project. The standalone SID preview uses the normal preview workflow and is not run alongside the profiling C64.

Read the result

The top ledger reconciles elapsed emulated clocks as CPU + stalled. A complete PAL frame is 312 × 63 = 19,656 clocks. Dimensions come from the actual VIC model; a supported NTSC capture is not forced into the PAL grid. The raster strip includes the borders. Click it or use arrows, Home/End and Page Up/Down; numeric line and clock information accompanies the colors. Detailed mode also shows intervals within the selected line, clipped at line/frame boundaries.

Badline, sprite and other bus requests describe measured blocked intervals, not an assumed cost for merely enabling the display. Overlapping requests do not charge the same clock twice. IRQ entry and branch/page penalties already belong to the CPU total; do not add them to it a second time. Busy-wait loops are CPU work even when the application is waiting for its next deadline.

Use Instruction instances, Compiler functions, or Calls / IRQs (inferred) for different views. Detailed costs can cover the selected frame or all captured frames; hits/calls are whole-capture counts. Elapsed min/mean/max describe an entry's per-frame cost, not the latency of one instruction. For a single selected frame these values coincide; aggregate mode has no per-frame row breakdown. Inclusive call costs overlap and must not be added as an exclusive partition. Unusual stack changes can weaken inferred context while leaving the flat cycle ledger exact. Assembly labels are aliases, not automatic function boundaries. Enter a named PC range with an exclusive end address to measure a kernel or waiting loop; select a memory instance when identical CPU addresses refer to different code.

Inspect an entry for fetched instruction bytes, decoded ASM and source identity. Source and Generated ASM navigate only to the matching current build. Unsupported opcode penalties show as unavailable, not a guessed zero.

Source identity, changed code and disk-loaded modules

Captures belong to their exact build and runtime epoch. Editing cannot attach an old capture to new line numbers. Live Patch or a memory/reset change can stop an active capture; after a completed immutable capture, historical measurements remain inspectable but stale source navigation is disabled. Finish the edit, run the new build and capture again when you need current source navigation.

Normal Run PRG records the bytes actually installed. RAM overlays, decompressed modules and copied code need an explicit execution-range registration in the Profiler: select/build the matching target and supply the physical source address, runtime address and byte length after the module is loaded. The next capture checks the registered range against live bytes; labels alone do not prove an overlay's identity. Unmapped ROM, ambiguous banks and unverified code remain address/disassembly-only. Do not register a different build merely because it occupies the same address.

Run Cartridge in the profiling runtime registers the current layout's compiled PRG-payload placements against their target-specific source maps. Standard 8K/16K, Magic Desk and EasyFlash captures carry the observed ROM bank and CPU window, so two banks executing at the same address remain separate. EasyFlash ROMH also distinguishes its $A000 and $E000 views. Source links are enabled only after the capture's arm-time ROM bytes match the compiled placement; Generated ASM selects the matching target, not whichever target was last selected in the editor. Wrapped resident PRGs are verified against RAM after their real ROM bootstrap has run.

A capture with a supported cartridge temporarily owns up to 1 MiB of extra ROM-baseline evidence, released after analysis. This does not allocate memory or add hooks in the normal runtime. Flash commands or an erase already in progress conservatively leave source generation unproven for that capture; unsupported cartridge/expansion mappings, raw data placements and ambiguous execution ranges remain unbound. Start a fresh capture after flash returns to ordinary reads to verify its new contents. Monitor writes and resets invalidate the runtime epoch. No bank is guessed from a CPU address or a layout alone.

Local capture files

Export W64P streams a bounded .w64p file through the browser save picker (Chrome/Edge where this API is available). Import W64P validates its version, size, structure and accounting before showing it. An invalid import clears the previous displayed result rather than leaving an apparently successful import. Files preserve model, native build, completeness, fetched bytes and artifact identity, but do not embed the source document. Source navigation still requires the exact matching build. Captures stay local; nothing is uploaded to a service.

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.

Empty asset editors

Character, Sprite, Block, Map, SID Tracker, and SID Editor use the same centered create-new state when the project does not yet contain the matching asset. The outlined file-plus graphic, short explanation, and neutral create button keep the empty workspace visually consistent. The button still calls the editor's existing creation workflow; the shared presentation does not choose paths, allocate assets, or hide project mutations.

Character editor

The Character Editor is the character-set part of Web64's linked charset, blockset, and map editing system. Editing a character immediately updates linked block and map previews. Reference-changing operations are committed as one family transaction so undo restores both the asset and its dependants.

Use Export CTM9 or Export CTM5 in the Character Editor to create a CharPad project containing only the selected charset and its supported mode, color, and material metadata. Linked Web64 blocksets and maps are deliberately excluded. Because the CTM project format always contains a map section, Web64 writes a neutral 1x1 map referencing character zero and leaves the CharPad tile-system flag disabled.

Character data model

A C64 character is 8 bytes:

A hardware character set is commonly 256 entries, but Web64 can keep up to 4096 editor characters for banked or generated workflows:

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. Existing raw .chr files remain valid; names, colors, materials, mode, palette, and stable dependency IDs are additive .web64proj metadata.

Char editor layout

The Char Editor contains:

Character Editor with charset browser, pixel editor, tile preview, VIC-II mode, colors, metadata, and generated include
The Character Editor keeps the selected glyph, linked VIC-II color semantics, metadata, and generated binding visible together.

Character selection, clipboard and project append

An ordinary grid click selects one character. Shift-click extends a range from the anchor; Ctrl/Command-click toggles individual slots; Ctrl/Command+Shift adds a range. Arrow/Home/End navigation moves focus, Shift extends selection and Ctrl navigation can move focus without changing the selected slots.

Copy/cut/paste process selected characters in ascending grid order, retaining raw eight-byte records and character-local color/material/name/tag metadata. Cut clears bitmap bytes in place and keeps slots and metadata. Paste either replaces exactly the selected number of slots or writes a contiguous span starting at one selected slot. A count mismatch or overflow produces a diagnostic without partial writes. Each operation is undoable; no existing character index is shifted.

Append charset chooses another compatible charset already in the current Project, with all or selected source characters. It leaves the source unchanged, copies to the destination tail and regenerates the native include through the project pipeline. Display mode, coloring method and shared palette interpretation must match. The 4096-character authoring limit is checked before mutation; selection can reduce the copied range. Existing Block/Map indices remain valid because append does not insert or reorder current characters. Undo/redo restores both byte length and metadata.

VIC-II modes

The Mode selector supports Text hires, Text multicolor, Text extended color (ECM), Bitmap hires, and Bitmap multicolor. Hires editing has pixel values 0 and 1:

The exported bytes are standard C64 character bytes.

Multicolor modes have pixel values 0, 1, 2, and 3:

The editor paints two-bit horizontal pairs. Preview pixels are shown double-wide to match C64 multicolor geometry. The Coloring selector records whether foreground values come from the project, block, character, or map cell. Character Color and Material are per-character metadata; material values preserve the complete byte range 0 through 255.

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. Canvas scaling stays on integer boundaries and multicolor pixels retain their correct double width.

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 charset browser is rendered as one canvas so editing does not create a preview DOM node for every character. Adding character 257 automatically promotes linked block references to two-byte indexes without truncating existing values.

Sprite editor

The Sprite Editor is a browser-local C64 hardware sprite workspace. Existing .spr project files remain compatible. Version 2 project metadata adds names, tags, per-frame mode and color, expansion flags, animation clips, tiles, and display palettes without changing the exported sprite bytes.

Append a project sprite bank

Append sprites chooses an existing project sprite asset and copies all or selected frames. Source bytes remain unchanged; existing destination frames keep their IDs and indices. New frame IDs, overlay references and complete included animation/tile groups are remapped. Incomplete groups are not copied or silently shortened. Shared colors/palettes must match. Joined banks are limited to 256 logical frames; larger requests fail before mutation. Undo/redo and generated native assets follow the ordinary project pipeline.

Overlay banks are paired multicolor-base/hires-layer assets. Overlay-to-overlay append updates both layers atomically. Multicolor frames may append into an overlay bank with corresponding empty hires frames. Hires-only to overlay, or overlay to an ordinary hires/multicolor bank, is refused. This compatibility check applies to the bank/layer semantics, not just the filename.

Sprite bytes and metadata

A hardware sprite remains 24x21 pixels, with 63 pixel bytes and one zero padding byte. Web64 therefore stores and exports 64 bytes per frame. Hires frames use values 0 and 1. Multicolor frames use 12 logical columns with values 0 through 3:

Frame metadata belongs to the .web64proj editor record. It never replaces the zero padding byte and does not consume C64 memory. Older projects without this metadata are upgraded in memory with deterministic defaults.

Workspace

The workspace has a virtualized frame browser, a canvas pixel editor, one compact color-coded toolbar, and a tabbed inspector. The frame browser renders only its visible rows, so large banks do not create a DOM preview for every frame. The right inspector always begins with a gridless sprite preview and animation controls; changing inspector tabs does not hide playback.

Sprite Editor with animation frames, pixel canvas, drawing toolbar, preview, and animation sequence
The Sprite Editor presents the bank, current frame, editing tools, hardware colors, and named animation sequence in one workspace.

Toolbar groups provide:

Every toolbar command is displayed directly when the available width permits. On narrower layouts, Web64 measures the toolbar and moves only the commands that do not fit into More. Expanding the workspace restores those command icons automatically. The active drawing tool and active overlay layer remain directly visible.

Right-click, Shift-drag, or the eraser writes transparent value 0. A pointer stroke stays in a local canvas draft, redraws at most once per animation frame, and commits one 64-byte frame update when the pointer is released. It does not update project state or emulator memory for each crossed pixel.

Use Ctrl-click or Command-click to select separate frames and Shift-click to select a range. Frame transforms apply to all selected frames. Draw a rectangular pixel selection with S; releasing a dragged selection automatically activates the four-way-arrow Move tool. Clicking one cell without dragging while the Select tool is active deselects all pixels. Copy a selection with Ctrl+C and paste it at the current selection origin with Ctrl+V. A pasted Web64 selection remains indexed sprite data, becomes the active movable selection, and does not open the image-conversion dialog; images copied from another application still open the conversion preview. Drag inside the active selection to relocate both its pixel contents and selection rectangle within the sprite grid. While the selection remains active, Web64 retains the pixels beneath it: moving it repeatedly or returning it to an earlier position restores each previous destination before placing the selected pixels again. Paste and move are each committed as one undoable edit. Ctrl+Z and Ctrl+Shift+Z undo and redo while focus is in the workspace.

Frame properties and palettes

The Frame inspector controls the stable frame name, hires or multicolor interpretation, frame foreground color, shared colors, and VIC-II X/Y expansion flags. The generated include exposes each frame address, color, and compact mode/expansion flags.

Display Palette can define a project-local 16-color preview profile using RGB, HSL, or YUV values. Reset restores the standard Web64 C64 palette. A display profile changes editor rendering and image matching; hardware output still uses VIC-II color indexes 0 through 15.

Animations and tiles

The Animation tab creates named clips. A new clip initially contains every bank frame in frame-list order. Each sequence entry shows a sprite thumbnail, bank frame number, and duration multiplier. Drag entries to reorder them, use the arrow buttons for precise keyboard-friendly reordering, append the currently selected bank frames, replace the sequence from the selection, or remove an entry without deleting its bank frame. Repeated frame references are allowed, and a clip retains at least one entry.

Each clip stores stable frame IDs, per-step durations, FPS, and loop, ping-pong, or once playback. The permanent inspector preview provides animation selection, previous/next frame, play/pause, and stop controls without grid lines. Preview runs from editor memory and does not take ownership of the emulator. Onion skin shows adjacent frames. Rotation Sequence generates an 8- or 16-step nearest-pixel Z rotation and a matching animation clip.

The Tiles tab groups selected frames into named rectangular frame arrangements for composite objects and animation planning. Animation and tile references follow stable frame IDs when frames are reordered. Deleting a referenced frame removes it from animations and leaves an explicit empty tile cell.

Image and clipboard import

Import accepts PNG, JPEG, WebP, GIF, raw .spr/.bin, load-addressed .prg, SpritePad .spd, and VICE .vsf snapshots. An image pasted from another image editor opens the same import preview.

Import Image as Sprites dialog with mapping mode, sheet cells, colors, transparency, dithering, and destination
Pasted or imported artwork is previewed and mapped through explicit sprite geometry, palette, transparency, and destination choices before project bytes change.

The image mapper runs in a worker and supports:

Colors are matched deterministically in OKLab space. Multicolor mapping samples each horizontal source pair as one representative color. Animated GIF frames are imported as frames with a named animation clip and source delays.

VICE snapshot import reads the active VIC bank, screen pointer table, sprite addresses, colors, multicolor bits, and X/Y expansion state for all eight hardware sprites.

SpritePad interchange

Web64 imports and exports the documented SpritePad 1.8.1 and version-1 SPD layout commonly identified as SpritePad 2.0. Per-frame color, multicolor, and overlay flags are converted to Web64 metadata while the 63 pixel bytes remain exact.

Newer proprietary SpritePad Pro 3.x formats are rejected with an explicit unsupported-version message rather than guessed. Use raw sprite, PNG, or an older SPD export when transferring from an unsupported version.

Exports

Overlay pairs

New Pair creates ordinary _mc.spr and _ol.spr banks plus a .spritepair.json descriptor. Click Multicolor or Overlay in the toolbar to select the edit target. While Overlay is active, its hires pixels remain editable on a transparent canvas with the multicolor sprite visible behind them. Composite previews both at one position, and Split shows both side by side. The frame browser, animation sequence, and permanent preview show the combined MC+OL result.

Add, duplicate, delete, and reorder remain synchronized across both banks. Pair animations are logical MC+OL frame sequences: the editor keeps names, timing, playback, order, and durations synchronized while translating each logical frame index to the stable frame ID stored by each layer. Existing pair descriptors and .spr files require no migration.

Pair includes retain the existing _mc and _ol symbols and convenience aliases. If a layer is missing or frame counts differ, restore the referenced bank or use Raw mode to inspect it. Pairing is an editor abstraction, not a different C64 sprite binary format.

Block editor

The Block Editor creates and edits .blk or .blocks sets made from characters. A blockset pins its charset by stable asset identity and compatible path. Changes to the charset propagate to the block canvas, browser, tile preview, and linked maps.

Block Editor with block browser, character-composition canvas, tile preview, metadata, palette, and generated include
Blocks are assembled from the pinned charset while previews, metadata, colors, materials, and generated bindings remain visible.

Current Block Editor features:

Pencil, erase, fill, flip, shift, and paste operations commit once per completed action. Save Block Set commits the family draft and regenerates the block include record. Removing a character or block never leaves an unrelated stale index: linked references are remapped and references to a removed item resolve explicitly to item zero.

Map editor

Native asset authoring and independent levels

Native maps normally describe the displayed spatial playfield: map cells select characters or blocks that the game draws, and matching dependencies let a person see and edit that same layout. A .w64map is not the default container for arbitrary level data such as timers, score thresholds, spawn scripts or parameter tables. Keep those in suitable source tables or binary data, separate from the visual map. Use the material plane for per-cell gameplay meaning when appropriate; do not disguise unrelated parameters as character/block indexes. Native assets are authoring data: game code or a suitable runtime must still draw the map.

How should I represent ten editable game levels in Web64? Normally use ten independent native map assets sharing the appropriate charset and blockset, not one tall byte matrix merely because concatenation is convenient for runtime code. The same advice applies to multiple editable C64 rooms, screens and stages. A genuinely continuous scrolling world can instead be one large map. Choose by the user's logical authoring model, not by the easiest storage layout to generate.

Keep three different questions separate:

asset references agree with the complete project?

Web64's normal native editors? Validation or a successful build alone does not establish this.

When several representations are valid, prefer the representation that preserves native-editor semantics and normal human authoring unless the user explicitly requests a runtime-optimized/raw representation. This is guidance, not a mutation rule: Web64 does not silently split, link or rewrite a technically valid map.

Asset / owning editorNormal relationshipNative editing and generated contract
.w64chr / Character EditorShared glyph source for blocksets or character-cell mapsEditable pixels, display profile and colors; generated charset data/include and C bindings.
.w64blk / Block EditorPins a charset through charsetPath / charsetAssetIdEditable groups of character indexes and block metadata; generated block data/include and C bindings.
.w64map / Map Editor, cellKind: "block"Pins a blockset through blocksetPath / blocksetAssetId; the blockset pins the charsetEditable layout of block indexes plus optional map planes; generated map data/include and C bindings.
.w64map / Map Editor, cellKind: "character"Pins a charset directly through charsetPath / charsetAssetIdEditable character-index layout; no blockset needed.

Keep stable project paths and matching asset identities consistent wherever the native record repeats them. Share one dependency when rooms use the same visual vocabulary; use different native dependencies when a different visual bank is intentional. For example:

assets/chars/tiles.w64chr
    <- assets/blocks/tiles.w64blk
           <- assets/maps/level01.w64map
           <- assets/maps/level02.w64map
           <- ... level10.w64map

Each arrow means the asset on the right references the asset on the left. With distinct map stems, generated ASM bindings normally include level01_map through level10_map. Read the actual generated includes and contextual assets/generated.h for authoritative names; never hand-maintain generated files. Native serialization includes authoring metadata, not just binary payload bytes.

An intentionally unbound/raw map can be a legitimate index matrix where permitted by its schema and semantic constraints. Without the matching glyph dependencies, however, native visual rendering and tile selection are incomplete; it is not an equivalent human-editable level asset. Do not claim every valid map must have a charset, and do not fabricate glyphs solely to satisfy a supposed mandatory link.

Preserve structure indexes and gameplay meanings when converting hard-coded levels. Compare each original room's bytes with its new map, retain dimensions and order, and preserve optional Color RAM, video-matrix and material planes separately. Each present native map plane has one byte per logical map cell; it is not an excuse to reinterpret structure bytes as decorative data. Runtime packing or banking can combine derived output later without merging independently authored maps. Remove obsolete authored assets through normal project operations once references have been updated; their generated includes belong to the asset owner.

See Character editor, Block editor, Generated include files, and Web64 asset access from C.

The Map Editor creates and edits .map and .w64map files. A map may store block indexes or direct character indexes. It pins the corresponding blockset or charset and displays the fully resolved VIC-II result.

Map Editor with block palette, map canvas, structure plane, overview, dependency selection, and generated include
The Map Editor resolves linked block and character assets while keeping structure, Color RAM, material, and video-matrix planes independently editable.

Current Map Editor features:

Map drawing is viewport bounded: only visible cells and a small overscan area are rendered. A pencil drag or completed shape becomes one undoable operation instead of rewriting the project for every crossed cell.

Use Select to drag a rectangular region. The editor switches to Move selection automatically. Drag the selected region, then press Enter or use the check button to commit; press Escape or use the cancel button to discard a floating move or paste. Ctrl+C, Ctrl+V, Ctrl+D, and Delete operate on the selection. Selection plane checkboxes determine whether structure, Color RAM, material, and video-matrix values participate.

Save Map commits the family draft and regenerates the map include and all present typed-plane files. The structure bytes use the selected index width. Generated plane records use stable sibling names such as .color.bin, .screen.bin, and .material.bin and are directly usable with .incbin.

Color data and optimization

Global VIC colors belong to the charset display profile. Per-character and per-block values belong to their respective metadata arrays. Per-cell values are first-class optional map planes:

In the Material plane, each nonzero material receives a distinct editor color while the rendered graphics remain onion-skinned underneath. The Graphics opacity slider controls that context. Set, Clear, and Replace apply material values to the complete current selection without changing structure or Color RAM.

Color optimization never changes imported data silently. Remove redundant plane is available only when every Color RAM cell can be derived exactly from the pinned blockset/charset. Color reduction shows the retained swatches and exact changed-cell count before Apply; it is destructive, optional, and undoable.

Restore inherited colors previews how many cells differ, then removes the per-cell Color RAM override so the map again uses its block or character foreground colors. Use it to repair an unwanted or legacy zero-filled color plane. The action discards intentional per-cell colors only after an explicit click and remains undoable.

CharPad CTM interchange

Use Add File in the project tree to import .ctm. Web64 supports CTM 5, 6, 7, 8, 8.2, the obsolete CTM8 prototype ordering, and CTM9. CTM8 prototype files are detected automatically. Import creates a linked charset, optional blockset, map, metadata, color data, includes, and plane binaries in one operation.

CharPad's per-project Text Multicolor flag is a global display-mode declaration even when its stored character color is in the low 0–7 range. Web64 imports that global foreground as the equivalent bit-3-set multicolor attribute. Per-character and per-block Color RAM attributes remain byte-exact so mixed hires/multicolor character sets keep their authored interpretation.

The parser validates the signature, version, section markers, counts, dimensions, index ranges, and truncation before adding files. Unsupported versions or malformed/oversized data produce an explicit error and do not partially import a family.

The Character, Block, and Map editors can export CTM9. CTM9 is the full interchange target. Character export stores only the selected charset plus CTM's required neutral 1x1 map; Block export stores the linked charset and a generated block catalog map; Map export stores the selected map and its complete dependency family. CTM5 export is a constrained compatibility option for supported text-mode state and refuses data it cannot represent rather than dropping it. Imported names that cannot be represented by the CTM byte encoding produce a warning.

Map export resolves the selected map's complete dependency chain and stores the map, linked blockset when block cells are used, and linked charset in the CTM. Block and map indexes are widened to CTM's required 16-bit representation during export without changing the project assets. Importing the CTM into an empty project creates the charset, optional blockset, map, typed planes, and generated includes atomically, then opens the imported map in the Map Editor.

Bitmap and Koala conversion

Adding a .kla, .koa, or .png file opens the bitmap conversion dialog instead of treating the image as opaque bytes. The Map Editor's Import Bitmap... button invokes the same authoritative pipeline. Web64 validates the standard 10,003-byte Koala layout and shows every source image before conversion. Koala conversion extracts and deduplicates character cells while preserving the source video-matrix and Color RAM values as typed planes.

Import Koala Painter dialog with source preview, conversion target, character limit, overflow policy, and block derivation
Koala import previews the source and declares the exact native asset family to generate while preserving its screen, Color RAM, and background data.

PNG import accepts arbitrary image dimensions and maps source colors to the Web64 C64 palette. Choose hires cells, 320-wide multicolor display pixels, or 160-wide logical multicolor pixels. Hires conversion selects the best two colors for each 8x8 cell. Multicolor conversion selects a shared background and the closest three additional colors per cell, with optional 2x2 ordered dithering. Pixels outside complete character cells are reported and cropped rather than silently wrapped.

Import PNG Bitmap dialog with VIC-II target, palette mapping, cell and block conversion, preview, and lossy-conversion confirmation
PNG conversion makes VIC-II cell limits, palette reduction, block slicing, cropping, and any lossy choice explicit before import.

Optional block derivation dices the character map into native blocks from 1x1 through 16x16 characters. This supports common 2x2 blocks and freely sized PNG layouts such as 3x3 blocks. A 320x200 or logical 160x200 image produces a 40x25 character map; 2x2 conversion explicitly offers skipping the unmatched top or bottom character row and produces a 20x12 block map. The direct map retains its typed color planes. The block result uses a color-resolved companion charset so per-character screen and Color RAM values remain visible after dicing. For multicolor bitmap sources, Web64 also derives a Color RAM value for every block from the source foreground pixels, using the closest C64 palette color when a block contains multiple foreground colors; blank characters do not bias the result.

If the requested result needs too many unique characters, approximation is never automatic: choose a strategy and confirm the lossy conversion. PNG palette and VIC-II cell reduction also require explicit confirmation. Cancel leaves the project unchanged.

Runtime and generated code

Generated assembly includes expose stable labels, counts, dimensions, index width, plane labels/sizes, byte offsets, and end labels. assets/generated.h declares the raw asset symbols, typed multidimensional row views, and a packed 19-byte Web64MapAsset descriptor. Its map flags identify block cells, Color RAM, material/attribute data, and video-matrix data.

For a two-byte map, the generated row view is a const uint16_t (*)[columns]; one-byte maps use uint8_t. This matches Web64-C multidimensional array indexing and avoids manual row-offset arithmetic. Asset metadata does not consume C64 memory unless the program includes or copies the generated data.

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 v3 compiler measures compact frame-event and interpreter representations, links the smaller valid player, and publishes exact per-voice playback status.

SID Tracker with song list, three-voice pattern editor, playback controls, and graphical instrument envelope and filter editors
The tracker keeps pattern sequencing and exact three-voice playback alongside the selected instrument's sound-shaping controls.

Current SID Tracker features:

Use Import GT to bring a standard GoatTracker song or instrument into editable W64SID state. Review the destination, timing model, SID model, speed multiplier, and preferred backend before committing the import.

GoatTracker import dialog with source summary, destination, timing, SID model, speed multiplier, and backend
GoatTracker interchange is an explicit conversion into editable Web64 tracker state, with playback and export policy selected at import time.

The modular v3 driver supports three voices, PSID export, rests/cuts/releases/ties, all declared v3 effects, SID control flags, filters, and all six instrument tables. RSID export, stereo/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.

With the emulator focused, press F11 to switch to Fit scaling and enter or leave fullscreen. The icon-only fullscreen button in the emulator toolbar performs the same action; entering fullscreen also focuses the canvas. The display scale remains Fit after leaving fullscreen.

Pasting text

Clipboard paste into the focused emulator treats backslashes as literal text, not VICE keyboard commands. Windows CRLF and lone CR line endings become one C64 Return each. Paste into an IDE text field still belongs to that field. The receiving C64 program controls how pasted PETSCII is handled; this does not add Unicode document support or bypass its input rate and capacity limits.

Runtime embedders can request the same behavior with runtime.handlePaste(text, { literal: true }). For compatibility, runtime.handlePaste(text) without that option retains the existing escaped keyboard-buffer syntax, including \xNN byte escapes used for exact PETSCII boot filenames. Input recording, replay and worker transport preserve the literal-mode flag and escape the payload only once at the native boundary.

Keyboard joystick

The Input controls can select Numpad, Arrows + Ctrl, or Keyboard off, and route that keyboard source independently to C64 port 1 or 2. Keyboard joystick remains available while browser Gamepad polling is enabled.

The numeric-keypad profile uses:

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. Click the emulator display before playing. Held directions are released when emulator focus is lost, preventing stuck joystick input.

The compact-laptop profile uses the four cursor-arrow keys for direction and either Control key for fire. Selecting this profile intentionally gives those keys to the joystick while the emulator has focus; choose Keyboard off or Numpad when cursor-key C64 keyboard input is required instead.

Gamepad

Gamepad input can be enabled in the Input group.

Options:

Two connected gamepads can therefore drive separate C64 ports. A gamepad and either keyboard profile can also target different ports. The runtime exposes these routes through configureJoystick() and input status; it does not hide or automatically rewrite them.

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.

Create a multi-disk build

  1. Add the boot, overlay, or chapter sources to the virtual project.
  2. Open Build Targets and create one target for each independently loaded PRG.
  3. Select each target's root, translation units, origin, entry symbol, and output name.
  4. Open Disk/Media, create the required D64 images, and group them in a disk set.
  5. Add each target output as a logical PRG and place it on the intended disk.
  6. Add project data as SEQ or USR logical files, then set directory order and any loader placement constraints.
  7. Select the boot disk and boot PRG.
  8. Use Build Dependencies, then Export Set ZIP for hardware/media testing or Run Disk for the embedded runtime.
  9. In C, verify generated markers with web64_disk_require and optionally request the next IDE-staged disk with web64_disk_request.
  10. Capture a writable runtime disk before powering off when the program has created save data.

See public/examples/multi-disk-targets.web64proj for a C boot target, a second ASM target, two disk sides, generated marker/index constants, and an assisted side-B request.

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

Turn off Drive 9 in Emulator Options → Storage or Settings, then Run Disk again. Some fastloaders expect only drive 8 and fail when another drive is attached. Save the project to retain the setting on reopen.

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
SpeedChoose 1x, 2x, 4x, 8x, or 16x paced emulation; project-saved and separate from Warp
Live PatchPatch compiled changes into running memory
1x/2x/FitDisplay scaling mode
ScanlinesToggle video scanline effect
Fullscreen (toolbar or F11 with emulator focused)Switch to Fit scaling and enter or leave fullscreen
AudioEnable browser audio
MuteSilence audio without changing runtime state
SID qualitySelect SID emulation quality/resource set
Drive soundsEnable or disable VICE drive motor and head audio independently of the drive LEDs
GamepadEnable browser gamepad polling; the numeric-keypad joystick remains available at the same time
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 and Mixed C/Assembly Projects

Web64 IDE supports browser-native C compilation for complete C64 programs and mixed-language projects. 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, build targets, disk media, 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 stored web64-c-v0.1 dialect id is retained for project compatibility. The current Web64 C v1 contract lowers selected C translation units to Web64 assembly and then uses the existing assembler for final PRG bytes, symbols, line maps, memory ranges, diagnostics, Generated ASM provenance, 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 a C-rooted Build Target, put the program entry in main.c and select the required C/ASM translation units; a placeholder main.asm is not required. Legacy projects may keep their root main.asm empty. 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 structs/unions, enums, typed 16-bit pointers, integer expressions, loops, switch, direct and zero-argument function-pointer calls, dependency-selected libc/SDK/disk implementations, inline assembly, C64 register structs, fixed-point helpers, and generated asset/media declarations. Prototyped asm_ routines receive exact-width ordinary parameter slots or supported _fastcall register arguments and return values through A or A/X. Multi-translation-unit builds emit shared runtime storage once and reject duplicate external definitions or conflicting assembler-visible defines.

The remaining 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 */stdio filesystem API, no floating point, and no complete ISO preprocessor. Dependency-linked KERNAL and <web64/disk.h> file/disk I/O are implemented. Host and absolute filesystem paths are rejected; all sources, headers, runtimes, and assets resolve from virtual project records or bundled browser-served records.

Changelog

The release history below is synchronized from the canonical project CHANGELOG.md. The standalone HTML changelog uses the same source and is available directly from the IDE's About dialog.

This file records notable user-facing Web64 IDE changes. Dates use the ISO YYYY-MM-DD format. Work that has not yet been included in a numbered release remains under Unreleased.

Unreleased

2.5.3 - 2026-09-28

Added

Fixed

2.5.2 - 2026-09-26

Added

Fixed

2.5.1 - 2026-09-25

Added

Fixed

2.5.0 - 2026-09-23

Native cartridge mastering joins the existing PRG and disk workflows.

Fixed

Added

Changed

2.4.6 - 2026-09-21

Added

Fixed

2.4.5 - 2026-09-20

Changed

2.4.4 - 2026-09-19

Added

Fixed

2.4.3 - 2026-09-13

Improved

2.4.2 - 2026-09-13

This release introduces cycle/raster profiling for understanding where frame time goes, alongside small workflow improvements.

Added

The normal emulator runtime remains the default; profiling requires an explicit runtime switch. Native cartridge mastering remains outside this release.

2.4.1 - 2026-09-07

This patch records the compiler correctness, size-generation and application I/O improvements verified during the productivity-application campaign. It retains the v2.4.0 hardware-loader and multi-load workflow; the example's own capacity and feature limits are documented with its project, not presented as platform guarantees.

Added

Fixed

Improved

2.4.0 - 2026-09-07

Web64 2.4.0 introduces the public hardware-loader SDK and a fully native multi-load authoring workflow. The 2.3.0 compiler/runtime campaigns and 2.3.1 IDE/SID/Cloud history remain recorded in their original releases below.

Added

Fixed

2.3.1 - 2026-09-05

Web64 IDE 2.3.1 completes the IDE UX, SID Tracker and private SID Cloud library campaign. GoatTracker playback is locked following user comparison with GoatTracker's own exporter; correctness takes precedence over resemblance to the older deployed preview. Cloud deployment and destination testing remain separate rollout steps.

Added

Fixed

Upgrade notes

The prior Web64-C compiler and native runtime campaigns remain documented separately in the 2.3.0 section below. This patch does not refresh the manually maintained Mirror Pulse demo disk.

2.3.0 - 2026-09-05

Web64 IDE 2.3.0 delivers both optimization campaigns completed in this session: the Web64-C compiler campaign and the native runtime optimization campaign. Existing public C/ASM contracts and example content remain compatible; the release also corrects compiler semantic defects, tall material-map addressing, and isolated runtime linkage.

Added

Changed

Fixed

2.2.1 - 2026-09-02

Web64 IDE 2.2.1 is an urgent compatibility patch for the published Mirror Pulse example and its D64 mastering workflow.

Fixed

2.2.0 - 2026-08-29

Web64 IDE 2.2.0 adds the selectively linked Bitmap Drawing Runtime, native assembly helpers across every runtime family, and verified bitmap and trajectory examples. It also adds contextual 6502 opcode reference and editor-wrapping workflows while tightening exact-link discovery and repairing map-overview and continuous-trajectory behavior.

Added

Changed

Fixed

2.1.0 - 2026-08-27

Web64 IDE 2.1.0 adds the compact, cycle-gated Trajectory Pattern Runtime and deterministic C90 bit-fields, expands cloud, standalone-emulator, input, and contributor workflows, and preserves the open, selectively linked C/assembly runtime model established by Web64 v2.

Added

Changed

Fixed

2.0.1 - 2026-08-25

Web64 IDE 2.0.1 completes the _web64_rt convention rollout across the existing native game runtime, repairs typed native-asset address lowering and runtime diagnostic provenance, and ships the manually verified Trike Mania and Egghunt editor/runtime corrections.

Added

Changed

Fixed

2.0.0 - 2026-08-24

Web64 v2 is the largest Web64 IDE release so far: a neutral, adaptive workstation UI; repaired high-performance asset workflows; broader input and navigation; and an open, cycle-measured game runtime that remains fully interoperable with C, Web64 assembly, KickAssembler-style code, and c64lib-compatible projects.

Added

Changed

1.9.2 - 2026-08-23

Changed

Fixed

1.9.1 - 2026-08-23

Changed

Fixed

1.9.0 - 2026-08-22

Added

Changed

Fixed

1.8.0 - 2026-08-22

Added

Fixed

1.7.0 - 2026-08-17

Added

Fixed

1.6.1 - 2026-08-16

Fixed

1.6.0 - 2026-08-16

Added

1.5.2 - 2026-08-16

Added

Changed

Fixed

1.5.1 - 2026-08-16

Fixed

1.5.0 - 2026-08-15

Added

Changed

Security

1.4.1 - 2026-08-14

Added

Changed

Fixed

1.4.0 - 2026-08-14

Added

Changed

Fixed

1.3.0 - 2026-08-13

Added

Changed

Fixed

1.2.1 - 2026-08-10

Fixed

1.2.0 - 2026-08-08

Fixed

Added

Changed

1.1.0 - 2026-08-07

Added

Fixed

1.0.0 - 2026-08-07

Added

Changed

0.8.1 - 2026-08-06

Fixed

Added

0.6.0 - 2026-08-06

Added

Fixed

0.5.0 - 2026-08-05

Added

Changed

Earlier Development Milestones - 2026-07-19 to 2026-08-02

Added

This earlier section summarizes pre-release development rather than assigning version numbers that were not recorded by the repository.