RIFT64 // REMOTE_GATEWAY

DEVELOPER SDK

Modern C# SDK for Hosted C64 Game & App Engineering

🖥️ V1 Protocol Beta
🏎️ SwiftLink 38400+ Baud
💾 Legacy C64/C128 & Replicas

The RiftServe64 C# SDK

Writing software for a C64 has historically required deep knowledge of 6502 assembly, cross-compilers, and custom disk filing formats. The RIFT64 SDK abstracts the hardware communication layer entirely.

Our high-performance `RiftServe64` C# SDK allows any developer with modern object-oriented skills to spin up an interactive backend, listen to incoming serial requests, and stream rendering buffers instantly.

1. Spinning up a Socket Server

In just a few lines of code, you can start a RIFT64 listener and wait for connections from original hardware or emulators:

using RiftServe64.Sdk.Protocol;
using RiftServe64.Sdk.Networking;

// Create an async TCP server binding to port 8000
var server = SocketServer.GetOrCreate("0.0.0.0", 8000);
server.ClientConnected += async connection =>
{
    // Initialize the RIFT64 protocol protocol codec
    var client = new Rift64ProtocolClient(connection);
    
    await client.ClearScreenAsync();
    await client.SetColorsAsync(Rift64Color.Black, Rift64Color.Cyan);
    await client.WriteAtAsync(10, 5, "RIFT64 NATIVE C# LISTENER");
};
await server.StartAsync();

2. Low-level RAM Manipulation

Need to upload custom sprites, fonts, or assembly code blocks? Use raw byte streams to write directly to C64 RAM, then point a hardware sprite at that block:

// Define a custom 64-byte sprite block (24x21 pixels + padding)
byte[] spritePattern = new byte[64];
spritePattern[10] = 0xAA; // draw a retro stripe

// Upload the sprite pattern into Bank 0 at $3400 (sprite-pointer 0x0D = $0D * 64)
await client.StoreMemoryAsync(0x3400, spritePattern);

// Configure and enable a single hardware sprite
await client.SetSpriteAsync(
    spriteId: 0,
    x: 140,
    y: 120,
    color: Rift64Color.LightGreen,
    pointer: 0x0D,            // 0x0D * 64 = $0340 offset within the active VIC bank
    bank: VicBank.Bank0,
    enabled: true);

// Or batch-configure several sprites in one socket write:
await client.SetSpriteConfigAsync(new[]
{
    new Rift64SpriteConfig(0, 140, 120, (byte)Rift64Color.LightGreen, 0x0D, VicBank.Bank0, true),
    new Rift64SpriteConfig(1, 200, 120, (byte)Rift64Color.Cyan,       0x0E, VicBank.Bank0, true),
});

3. A Modern Developer's Guide to 1982 Hardware (FAQ)

If you are a modern web, mobile, or enterprise developer, you are probably used to unlimited memory, layout engines, and HTML canvases. Designing for the Commodore 64 introduces unique, exciting hardware constraints. Here are the most common questions modern developers ask when getting started with RIFT64:

Q: How do I position visual elements without an HTML DOM or pixel coordinates?

A: The C64 screen operates on a grid-based Screen RAM matrix (40 columns by 25 rows). You draw background tiles, maps, or letters by writing the corresponding character code (0-255) into the target column and row cell of the grid. For pixel-precise, smooth-moving characters (like players or bullets), you bypass Screen RAM and configure the C64's 8 hardware-driven Sprites, which accept absolute (X, Y) pixel coordinates from your server!

Q: Why can't I just stream a raw 320x200 JPEG or PNG photo to the C64?

A: The C64 has no concept of a modern framebuffer. To show complex imagery, you must switch the VIC-II chip into Multicolor Bitmap Mode. Even then, you are constrained to 160x200 resolution with a maximum of 4 colors per 8x8 block, chosen from the fixed 16-color palette. You must pre-process, dither, and index your image into a 16-color C64-compatible bitmap on your server side, and then stream the raw pixel data and parallel color matrices over the wire!

Q: I made a 60 FPS game loop on my server, but the game is lagging and crashing. Why?

A: High-speed serial links (like SwiftLink) max out at 38,400 bits per second (about 3.8KB per second). A single full redraw of the C64 screen (1000 character bytes + 1000 color bytes) is 2KB. At 38.4k, sending a full screen takes over half a second! You should never redraw the entire screen every frame. Instead, only stream the character cells that have actually changed (dirty-region tracking), or use built-in regional copy commands like scrolling (G) or block windowing (W) to keep your data stream light and lightning fast.

Q: How do I handle popup overlays or pause menus without corrupting my background?

A: Instead of keeping track of the background cells on your server and manually redrawing them when a popup is dismissed, use RIFT64's built-in off-screen backup buffers! Send the S0 command to instantly backup the active screen & color RAM on the C64, render your popup window, and then send R0 when the user unpauses. The C64 client executes an instantaneous, native 6502 block-copy, restoring your background perfectly without any serial bandwidth overhead!

Q: What is a sprite and how do I design one?

A: A C64 sprite is a 24x21 pixel grid, packed as exactly 63 bytes (with 1 trailing padding byte = 64 bytes total). You design your sprite patterns on your PC, upload the 64-byte block directly to C64 RAM using StoreMemoryAsync, and set the active sprite pointer register to look at that memory offset. You can then move, expand, and color the sprite instantly using simple C# methods (and since the VIC-II has no native hardware mirroring registers, you can flip them on-the-fly by simply swapping the pointer byte to a pre-mirrored memory block!).

Complete SDK API Reference

This directory lists every single public method and asynchronous API available inside the Rift64ProtocolClient class of the RiftServe64.Sdk namespace, categorized by system tasks:

1. Client Connection & Handshaking

Task<Rift64ClientIdentity> IdentifyClientAsync()
System

Queries the connected client's capabilities, parses the returned feature string, and returns a verified identity profile.

Task<string> QueryCapabilitiesAsync()
System

Sends a raw capabilities query command (?) and waits for a response.

Task<char?> ReadKeyAsync(TimeSpan timeout)
I/O

Performs an asynchronous, non-blocking read to capture a single keypress character from the C64's ring buffer.

Task<string> ReadLineAsync(TimeSpan timeout)
I/O

Performs an asynchronous, non-blocking read to capture a Carriage-Return-terminated string from the C64 input stream.

ValueTask DisposeAsync()
Lifecycle

Safely closes socket connections and releases system resources associated with the client session.

2. Cursor, Screen & Typography

Task ClearScreenAsync()
Draw

Clears the C64 screen character matrix to spaces (`$20`), resets the hardware cursor to (0,0), and hides the cursor.

Task SetCursorAsync(byte x, byte y)
Position

Issues the P command to move the text-writing cursor to column x (0-39) and row y (0-24). Out-of-range values are clamped by the C64 client.

Task SetColorsAsync(Rift64Color borderColor, Rift64Color textColor)
Colors

Issues the K command. The first nibble is written to both the border ($D020) and background ($D021) registers; the second nibble becomes the default foreground colour used by subsequent text writes.

Task WriteTextAsync(string text)
Stream

Streams standard ASCII/PETSCII text at the current cursor offset using the ! command.

Task WriteColoredTextAsync(byte color, string text) Task WriteColoredTextAsync(Rift64Color color, string text)
Stream

Writes a coloured run of text via the T command. The colour byte is sent first (low nibble = palette index 0-15) followed by a $00-terminated string. Convenience overloads accept a Rift64Color enum.

Task WriteLengthTextAsync(string text)
Checked

Writes a length-prefixed text block using the L command, verified and safe for binary-containing strings.

Task WriteAtAsync(byte x, byte y, string text, Rift64Color color)
Convenience

Moves the cursor to absolute coordinate (x, y), sets the color, and writes the specified text in a single efficient stream.

Task EraseLineAsync(byte count)
Erase

Fills exactly count cells starting at the current cursor with spaces (`$20`), then restores the cursor position.

Task SetCursorVisibilityAsync(bool visible)
UI

Toggles whether the C64 KERNAL actively flashes the hardware cursor. Highly recommended to hide the cursor during game loops.

3. Windowing, Borders & Block Painting

Task DrawWindowAsync(byte width, byte height, string text)
Batch

Batch-writes a 2D rectangular character block to the screen using the W command.

Task DrawColoredWindowAsync(Rift64Color color, byte width, byte height, string text)
Batch

Renders a rectangular character block in the chosen foreground color using the V command.

Task<bool?> DrawWindowCheckedAsync(byte width, byte height, string text, TimeSpan timeout)
Checked

Sends a rectangular block verified via an 8-bit additive checksum. The C64 client stages the packet in MemoryStoreBuffer before committing.

Task DrawWindowRawAsync(byte width, byte height, byte[] screenCodes)
Raw

Writes raw, un-translated screen codes directly to a rectangular screen block, bypassing standard character conversion tables.

Task DrawBorderAsync(byte width, byte height, Rift64BorderGlyphs layout)
UI

Instructs the C64 client to outline a rectangle of size WWHH from the current cursor position using customized border and line character sets.

Task FillColorBlockAsync(byte x, byte y, byte width, byte height, Rift64Color color) Task FillColorBlockAsync(byte x, byte y, byte width, byte height, ReadOnlyMemory<byte> colors)
Paint

Issues the Q command to paint a rectangular region of colour RAM ($D800) without altering the underlying screen-character codes. The first overload fills with a single colour; the second streams a row-major byte array, one colour per cell.

Task<bool?> DrawMetatileAsync(byte mode, ushort mapAddr, byte mapW, byte mapH, byte metaHi, ushort targetAddr, byte stride, byte winW, byte winH, byte x, byte y, byte offX, byte offY, byte fillChar = 32, byte colorMode = 0, ushort colorTgtAddr = 0xD800, ushort colorSrcAddr = 0, byte colorFill = 14, TimeSpan timeout = default)
Advanced

Renders a window of a tile-id map into screen and (optionally) colour RAM via the D command. mode selects 1 = raw 1×1 chars, 2 = 2×2 metatiles, 3 = 3×3 metatiles. offX/offY provide sub-tile scroll offsets for smooth scrolling, and colorMode picks 0 = NONE, 1 = FILL, 2 = MAP, 3 = PERCELL (mode-2 only; per-child-cell colour bank) for the colour-RAM pass. Validated with an ACK.

Task<bool?> DrawMetatileAsync(byte mode, ushort mapAddr, byte mapW, byte mapH, byte metaHi, ushort targetAddr, byte stride, byte winW, byte winH, byte x, byte y, byte offX, byte offY, MetatileColorMode color, ushort colorSrcAddr = 0, byte fillChar = 32, ushort colorTgtAddr = 0xD800, byte colorFill = 14, TimeSpan timeout = default)
Advanced

Strongly-typed overload of DrawMetatileAsync that takes a MetatileColorMode enum (None, Fill, Map, MapPerCell) instead of a raw byte. Use this to make the colour-RAM pass selection self-documenting at the call site.

static byte[] BuildMode2PerCellColorBank(IReadOnlyList<(byte TopLeft, byte TopRight, byte BottomLeft, byte BottomRight)> perTilePalette, byte defaultColor = 14)
Advanced

Builds the 1 KB page-aligned colour bank consumed by mode-2 PERCELL rendering. The returned buffer is laid out as four parallel 256-byte slot pages (TL, TR, BL, BR) indexed by tile id — upload it to a page-aligned address with StoreMemoryLargeCheckedAsync and pass that address as colorSrcAddr with color: MetatileColorMode.MapPerCell.

4. Hardware Sprite Manipulation

Task SetSpriteConfigAsync(IReadOnlyList<Rift64SpriteConfig> sprites)
Sprites

Pushes a contiguous batch of Y sprite-config records (id, X, Y, color, pointer, VIC bank, enabled) in one socket write — ideal for initialising all 8 hardware sprites at once.

Task SetSpriteAsync(byte spriteId, int x, byte y, Rift64Color color, byte pointer, VicBank bank = VicBank.Bank0, bool enabled = true)
Sprites

Sends a single Y packet to fully configure one VIC-II hardware sprite (0-7). Automatically handles the X-MSB bit in $D010 when X > 255 and routes the active VIC bank.

Task SetSpritePositionsAsync(IReadOnlyDictionary<byte, (int X, byte Y)> positions)
Performance

An ultra-fast bulk positioning method. Packs multiple active sprite coordinates into a single compact stream, bypassing unnecessary pointer or color writes during heavy game loops.

Task SetSpriteMulticolorAsync(byte spriteId, int x, byte y, Rift64Color color, byte pointer, VicBank bank, bool enabled, bool multicolor, bool expandX, bool expandY, bool priority, Rift64Color sharedColor0, Rift64Color sharedColor1)
Sprites

Sends the U packet, which combines a full single-sprite configuration with the VIC-II multicolor flags ($D01C), expand-X/Y, sprite-foreground priority, and the two shared multicolor registers $D025 / $D026.

5. Memory, Buffer & State Management

Task StoreMemoryAsync(ushort address, ReadOnlyMemory<byte> data)
RAM

Copies raw byte payloads directly to C64 RAM starting at address using the M command. Excellent for injecting fonts, code, or sprite matrices.

Task<bool?> StoreMemoryCheckedAsync(ushort address, ReadOnlyMemory<byte> data, TimeSpan timeout)
RAM

Copies raw binary bytes to RAM, validated using an additive 8-bit checksum. The client returns success/failure.

Task<bool> StoreMemoryLargeCheckedAsync(ushort address, byte[] data)
Bulk

An advanced SDK paging subsystem. Automatically breaks large binary structures into verified 256-byte chunks, uploads them using checked Z writes, and handles automatic retry loops.

Task SaveScreenBufferAsync(byte bufferIndex)
State

Copies the entire active 1000-character screen and 1000-byte color RAM grids instantly into an off-screen backup slot (0 or 1).

Task RestoreScreenBufferAsync(byte bufferIndex)
State

Performs an instantaneous, native 6502 block-copy of the screen and color RAM back from backup slot (0 or 1) to active display memory.

Task ScrollRegionAsync(byte x, byte y, byte width, byte height, Rift64ScrollDirection dir)
Hardware

Instructs the client to execute high-speed multi-directional subregion scrolling of size WWHH from coordinate XXYY on Screen and Color RAM.

Task StartCustomRestoreAsync(byte frameCount, byte jiffyDelay, byte paletteMode)
Interrupts

Issues the O command to drive the client's custom restore/transition routine, returning from raster-split or bitmap modes back to the standard text terminal. frameCount and jiffyDelay control the transition pacing; paletteMode selects the colour fade table.

Task<bool?> StopCustomRestoreAsync(TimeSpan timeout)
Interrupts

Cancels an in-progress custom-restore transition before its frame count completes. Returns true if the client ACKs the cancel.

Task<bool?> SendFrameAsync(byte command, ReadOnlyMemory<byte> payload, TimeSpan timeout)
Low-Level

Escape hatch for sending an arbitrary opcode + payload and waiting for an ACK/NAK. Use this when prototyping new protocol commands before a typed wrapper exists.

5b. VIC-II-Aware Upload Helpers

These helpers wrap StoreMemoryLargeCheckedAsync with VIC-II memory-layout validation. They take a VicBank and a typed slot enum, validate the buffer size, route around the character-ROM shadow at $1000-$1FFF in banks 0/2, and chunk the upload into checked 256-byte frames. They are the recommended path for uploading sprite, charset, bitmap, and screen-RAM data.

Task<bool> UploadSpriteAsync(VicBank bank, byte pointer, ReadOnlyMemory<byte> data)
Sprites

Uploads a 63- or 64-byte sprite frame to slot pointer within bank (effective address BankBase + pointer*64).

Task<bool> UploadCharsetAsync(VicBank bank, VicCharsetSlot slot, ReadOnlyMemory<byte> data)
Charset

Uploads a 2 KB character set into the chosen charset slot. Throws if the destination is hidden by the character-ROM shadow.

Task<bool> UploadBitmapAsync(VicBank bank, VicBitmapSlot slot, ReadOnlyMemory<byte> data)
Bitmap

Uploads an 8000- or 8192-byte multicolor/hi-res bitmap into the chosen bitmap slot of a VIC bank.

Task<bool> UploadScreenRamAsync(VicBank bank, VicScreenSlot slot, ReadOnlyMemory<byte> data)
Screen

Uploads 1000 (or 1024 padded) screen-character bytes to the chosen screen slot. Pair with SetCharsetBankAsync/ApplyVicLayoutAsync to switch the live screen pointer.

Task<bool> UploadColorRamAsync(ReadOnlyMemory<byte> data)
Colour

Uploads 1000 colour-RAM bytes to the fixed VIC colour-RAM region at $D800. Only the low nibble of each byte is used.

Task<bool> WriteSpritePointersAsync(VicBank bank, VicScreenSlot screenSlot, ReadOnlyMemory<byte> pointers)
Sprites

Writes 1-8 sprite-pointer bytes into the screen-slot tail ($3F8-$3FF) so the VIC-II picks up uploaded sprite frames.

Task<bool?> ApplyVicLayoutAsync(VicLayout layout, Rift64Color border, Rift64Color background, TimeSpan timeout, bool multicolor = false, byte d011 = 0x1B, byte d016 = 0x08)
Display

Activates a VicLayout in one round-trip: programs the bank bits, $D018, and (optionally) bitmap/multicolor flags via the I command. Use this after uploading screen / charset / bitmap data to switch the C64 to the new view.

Task<bool?> SetCharsetBankAsync(byte bankBits, byte d018, TimeSpan timeout)
Display

Issues the F command to swap the VIC-II bank (low 2 bits of CIA2 $DD00) and update $D018 — a fast way to switch character sets or screen-RAM locations.

Task<bool?> SetDisplayModeAsync(byte modeFlags, byte d011, byte d016, byte d018, byte borderColor, byte bgColor, TimeSpan timeout)
Display

Issues the I command to atomically reprogram $D011 / $D016 / $D018, the border/background colours, and the bitmap/multicolor flags — the typed overload above is preferred for typical use.

6. SID Audio & Graphics Interrupts

Task<bool> LoadSidModuleAsync(ushort address, ReadOnlyMemory<byte> moduleBytes)
Audio

Uploads a MiniPlayer2 (.bin) music module to a page-aligned address using checked Z frames and then issues an A5 bind so the resident SID engine starts using it. Returns true only if every chunk and the final bind ACKed.

Task<bool?> SendAudioCommandAsync(char subcmd, ReadOnlyMemory<byte> args, TimeSpan timeout)
Audio

Low-level wrapper around the A command. subcmd is a digit selecting the music-module operation (stop / start / pause / resume / tempo / bind / volume / state); args are emitted as ASCII hex bytes after the digit. Higher-level helpers exist (StartAudioAsync, StopAudioAsync, PauseAudioAsync, ResumeAudioAsync, SetAudioTempoAsync, SetAudioVolumeAsync, BindAudioModuleAsync, QueryAudioStateAsync).

Task<bool?> SetRasterSplitAsync(bool enable, byte splitLine, byte topD011, byte topD016, byte topD018, byte botD011, byte botD016, byte botD018, TimeSpan timeout)
Interrupts

Configures the client's stable raster IRQ. Programs the desired $D011 / $D016 / $D018 values for both halves of the screen so you can mix, for example, a multicolor bitmap on top with a text terminal underneath.

7. Upstream Telemetry Streams

Task StartTelemetryAsync(byte divider, TelemetryChannels channels)
Telemetry

Enables periodic upstream telemetry packets. divider selects how many frames pass between sends; channels is a bit mask choosing which sources are sampled (joystick port 1, joystick port 2, sprite-sprite collisions, sprite-background collisions). Each packet is a fixed 8-byte frame ($7E $55 seq joy1 joy2 spr_spr spr_bg cksum).

Task StopTelemetryAsync()
Telemetry

Disables telemetry streaming, freeing up serial bandwidth.

Task RequestOneShotTelemetryAsync()
Telemetry

Requests a single immediate telemetry packet without enabling periodic streaming. Useful for polling-style sampling on demand.

Task<TelemetrySession> StartTelemetrySessionAsync(byte divider, TelemetryChannels channels)
Telemetry

Recommended high-level entry point. Starts telemetry and returns an IAsyncDisposable session that parses incoming bytes into typed Rift64TelemetryFrames. Iterate session.Frames(ct) in your game loop, or hook FrameReceived; disposing stops telemetry and drains the wire.

Task<char?> PauseForKeyAsync(TimeSpan? timeout = null)
I/O

Convenience wrapper around ReadKeyAsync with a default 1-minute timeout. Returns the pressed key, or null if the timeout elapses.