CrashBASIC Technical Architecture

How One Codebase Powers Desktop, Mobile, Web, and Multiplayer

CrashBASIC achieves something rare in game development: a single programming language and a single distributable file (.crashcart) that runs natively on Windows, macOS, Linux, iOS, Android, and web browsers — with real-time multiplayer — all while maintaining the performance characteristics of native applications.

Write once. Ship one cart. Run anywhere. That's not a tagline — it's the engineering invariant the rest of the architecture enforces. The same bytes you author on a laptop play unmodified on a phone, in a browser tab, and on the next platform we ship. Game consoles (Nintendo Switch, PlayStation, Xbox) are firmly on the roadmap; porting to a new platform is a renderer/audio backend swap, not a rewrite, because the architecture below was built for it.

This document explains the architectural decisions that make this possible.


The Challenge

Most cross-platform solutions fall into one of two camps:

  1. Write once, run slowly — Interpreted languages or heavy runtimes that sacrifice performance for portability (e.g., Electron, React Native)

  2. Write everywhere — Native SDKs that require maintaining separate codebases for each platform (e.g., Swift for iOS, Kotlin for Android, C++ for desktop)

CrashBASIC takes a third path: a high-performance core written in Rust that compiles to native machine code or WebAssembly from the same source, combined with a protocol-driven architecture that cleanly separates the game logic from platform-specific rendering and audio.


Core Architecture

┌─────────────────────────────────────────────────────────────┐
│                     BASIC Program                           │
│            (Your game code - runs everywhere)               │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    CrashCore (Rust)                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐ │
│  │   Parser    │→ │  Evaluator  │→ │  Runtime Engine     │ │
│  │  (Pest)     │  │  (Tree-Walk)│  │  (Threads, Timers)  │ │
│  └─────────────┘  └─────────────┘  └─────────────────────┘ │
│                                                             │
│  Compiles to: Native (x86, ARM) │ WebAssembly (WASM)       │
└─────────────────────────────────────────────────────────────┘
                              │
              Protocol Buffers (Binary Messages)
                              │
        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
┌───────────────┐     ┌───────────────┐     ┌───────────────┐
│   Graphics    │     │     Audio     │     │   Network     │
│    Engine     │     │    Engine     │     │    Engine     │
├───────────────┤     ├───────────────┤     ├───────────────┤
│ • wgpu/WebGPU │     │ • SDL2_mixer  │     │ • WebSocket   │
│ • Metal       │     │ • Web Audio   │     │ • Server Auth │
│ • Vulkan      │     │ • CoreAudio   │     │ • State Sync  │
│ • DirectX 12  │     │ • AAudio      │     │               │
│ • WebGL2      │     │               │     │               │
└───────────────┘     └───────────────┘     └───────────────┘

Why Rust?

Rust was chosen as the implementation language for several critical reasons:

1. Native-Speed Interpreter, Engine, and GPU Pipeline

Rust compiles directly to machine code with no runtime overhead. There are no garbage collection pauses, no JIT warmup delays, no managed runtime to amortize. The interpreter, renderer, audio mixer, behavior engine, and network stack are all native machine code — nothing beneath the BASIC layer pays the tax most cross-platform engines do.

The BASIC code itself is interpreted — that's the cost of the language being approachable — so a tight per-pixel loop written in BASIC will not match hand-written C++. The architectural answer is offload:

  • Pattern and behavior engines move per-frame sprite logic (animations, AI, transforms, interpolations) into native code via declarative blocks the developer authors once and the engine executes every tick at native speed.
  • GPU sprites move blits, transforms, scaling, rotation, and batching onto the graphics card — sprite work scales with VRAM and shader cores, not interpreter ticks.
  • The built-in shader DSL (see The BASIC-Like Shader Language below) compiles BASIC-flavored shader code to WGSL and runs per-pixel work on the GPU at hardware rates.
  • Memory-buffer batching rasterizes hundreds of primitive calls into a single texture upload, so procedural backgrounds and generated sprites pay one cost at bake time instead of per-frame.

Idiomatic Crash BASIC games push the hot path into these native systems and use BASIC as orchestration. The result is games that play like native-engine games — even though the developer is writing line-numbered BASIC.

2. First-Class WebAssembly Support

The same Rust codebase compiles to WebAssembly (WASM) without modification. This means the browser version of CrashBASIC runs the exact same interpreter as the desktop version—not a reimplementation, not a compatibility layer, but identical code paths.

3. Memory Safety Guarantees

Rust's ownership system prevents entire categories of bugs (buffer overflows, use-after-free, data races) at compile time. This matters for a platform where user-written games will push the runtime in unexpected ways.

4. Cross-Compilation

From a single macOS development machine, we can produce optimized binaries for:

  • Windows (x64)
  • macOS (Intel, Apple Silicon, Universal)
  • Linux (x64, ARM64) — including Steam Deck out of the box
  • iOS (ARM64)
  • Android (ARM64, ARM32, x86_64)
  • Web browsers (WebAssembly)
  • Planned: Nintendo Switch, PlayStation, Xbox — Rust reaches all three (community toolchains for Switch homebrew; C ABI interop with the official C/C++ SDKs on PlayStation and Xbox). The real work is licensing, certification, and writing a console-native renderer/audio backend behind the existing protocol interface. wgpu doesn't ship on Switch or PS5, but the renderer is pluggable by design — any backend that implements the GfxEngine interface drops in.

Protocol-Driven Rendering

CrashBASIC doesn't embed a renderer. Instead, the core interpreter emits rendering commands through a binary protocol, and platform-specific engines consume those commands.

BASIC Code:        LINE (0,0)-(100,100), 15
                          │
                          ▼
CrashCore:         GfxCommand::Line { x1: 0, y1: 0, x2: 100, y2: 100, color: 15 }
                          │
                          ▼
Protocol Buffer:   [binary encoding, ~20 bytes]
                          │
          ┌───────────────┼───────────────┐
          ▼               ▼               ▼
      Desktop         Mobile           Browser
    (wgpu/Metal)    (wgpu/Vulkan)    (wgpu/WebGPU)

This separation provides several advantages:

  • Backend Flexibility: The same game can use Metal on macOS, Vulkan on Linux, DirectX 12 on Windows, and WebGPU in browsers—automatically selecting the best option for each platform.

  • Batch Optimization: Commands are batched per frame and transmitted efficiently, minimizing the overhead of crossing language/runtime boundaries.

  • Hot-Swappable Renderers: During development, users can switch between rendering backends without restarting their game.


The BASIC-Like Shader Language

Most engines push developers off a cliff at the shader boundary: you write your game in one language, but pixel effects mean learning GLSL/HLSL/WGSL with its own type system, syntax, and toolchain. CrashBASIC closes that gap with a built-in shader DSL that reads like the rest of the language.

SHADER "Greyscale"
  USE NUMBER intensity DEFAULT 1.0

  WHEN DRAWING
    c    = IMAGE
    grey = (c.r + c.g + c.b) / 3.0
    out  = MIX(c, RGBA(grey, grey, grey, c.a), intensity)
    SET COLOR = out
  END WHEN
END SHADER

Each SHADER block compiles at registration time to WGSL — wgpu's native shading language — and runs on the GPU like any other fragment shader. Per-pixel performance is identical to a hand-written WGSL shader; the only overhead is the one-time compile.

The DSL exposes the standard shader toolbox in BASIC syntax: USE NUMBER / USE COLOR / USE IMAGE for parameters; built-ins for IMAGE, UV, TIME, TICK, SCREEN, SCREEN_SIZE; SIN, COS, POW, MIX, SMOOTHSTEP, STEP, and CLAMP; VEC2 / VEC3 / VEC4 with .x/.y/.z/.w and .r/.g/.b/.a swizzling; texture sampling via SAMPLE and OWNTEXTURE. Apply with SET SPRITE SHADER for a per-sprite effect, or SET LAYER SHADER for a full-layer post-process.

Shaders compose with the behavior engine for free animation:

BEHAVIOR "dissolve"
  BSHADER "Dissolve" @progress=0 TO 1 DURATION 60
END BEHAVIOR

That's a full one-second dissolve transition — parameter interpolated over 60 behavior ticks, running on the GPU, driven by a behavior block. No glue code, no manual frame loops, no language boundary to cross.

Shaders are one of the main levers developers have for closing the performance gap with hand-written engine code. Anything that wants to run per-pixel — recoloring, transitions, vignettes, dissolves, screen distortions, post-processing — moves off the interpreter and onto the GPU, where it executes at hardware rates regardless of how the BASIC code around it is structured.


Concurrency Model

CrashBASIC programs are not single-threaded scripts running on top of a busy engine — the interpreter itself is multi-threaded. From inside a BASIC program, developers can spawn background threads, schedule timer-driven work, register frame-synchronized callbacks, and fire off promise-backed async tasks that overlap I/O with computation.

Four Concurrency Primitives

Primitive Cadence Returns Best for
CALL THREAD continuous background — long-running fire-and-forget work
CALL TIMER periodic (e.g. every 500 ms) — polling, regen ticks, scheduled events
CALL RENDER every behavior tick (60 Hz) — per-frame logic synced to sprite movement
ASYNC + RESOLVE one-shot worker promise → value parallel I/O, expensive compute the caller waits on

Each runs in its own execution context but shares the program's global state. The runtime's ThreadManager and TimerManager coordinate lifetimes — a BEGIN SCENE block, for instance, automatically tears down every thread, timer, and callback spawned inside it on END SCENE, so per-level concurrency cleans up without manual bookkeeping.

Asynchronous Execution

ASYNC spawns a worker thread, returns a Promise immediately, and lets the caller continue. RESOLVE blocks for the value when the program actually needs it — making parallel I/O a one-line transformation:

p1 = ASYNC FETCH("https://cdn.example.com/leaderboard", "GET")
p2 = ASYNC FETCH("https://cdn.example.com/news",        "GET")
p3 = ASYNC FETCH("https://cdn.example.com/sprite.png",  "GET")

results = RESOLVE(p1, p2, p3)

Three sequential 200 ms fetches take 600 ms wall-clock. The same three under ASYNC take ~200 ms — total is MAX(durations), not SUM. Re-resolving a settled promise returns the cached value, so ASYNC doubles as a lazy-load mechanism.

Native Threads Everywhere It Matters

On Windows, macOS, Linux, iOS, and Android, threads are real OS threads (std::thread), scheduled by the platform. A game with a CALL RENDER loop, four CALL THREAD workers, and a fan-out of ASYNC FETCH calls genuinely distributes across the user's CPU cores.

True Parallelism in the Browser

The browser is normally a single-threaded sandbox, but the WASM build escapes it: every spawned thread becomes a dedicated Web Worker backed by SharedArrayBuffer, giving atomics, mutexes, and channels the same semantics they have on desktop. With cross-origin isolation in place, a CrashBASIC game in Chrome or Firefox uses every core on the user's machine — not just one.

Embeddable on Third-Party Sites (Document-Isolation-Policy)

Cross-origin isolation traditionally requires both the host page and every embedded resource to opt in with Cross-Origin-Embedder-Policy (COEP) and Cross-Origin-Opener-Policy (COOP) headers — fine for a standalone site, but a non-starter for embedding games into third-party blogs, learning platforms, or storefronts whose headers we don't control.

CrashBASIC Arcade solves this with Document-Isolation-Policy (DIP), a newer mechanism (Chromium 137+) that scopes cross-origin isolation to a single document — the game's iframe — independently of the embedding page. The Arcade serves every published, public-rated game from /games/<slug>/embed with the right DIP + Cross-Origin-Resource-Policy (CORP) headers; an embedder just drops in an iframe:

<iframe src="https://arcade.crashbasic.com/games/my-game/embed"
        width="640" height="480" allow="autoplay"></iframe>

The host page needs no header changes, no CORS configuration, no opt-in. The iframe is cross-origin isolated internally, so the multi-threaded WASM game inside it gets full SharedArrayBuffer, atomics, and Web-Worker fan-out while the parent page stays completely unchanged.

On browsers without DIP support yet (Safari, Firefox), the client feature-detects crossOriginIsolated and transparently falls back to a popout — the game opens in a top-level Arcade-hosted tab, which IS cross-origin isolated everywhere. So the embed snippet works as a one-line "drop-in" on any site: full inline play on Chromium today, popout-fallback elsewhere, and inline play everywhere as the rest of the browser ecosystem ships DIP.

Multiplayer at Interpreter Scale

CrashNet applies the same model at a different scale: each connected player gets its own server-side interpreter running concurrently with the host interpreter, on its own thread. A four-player room means five concurrent BASIC interpreters (1 host + 4 player-servers) — each authoritative for its slice of state, all sharing the same NetState. The interpreter's threading model is what makes the multiplayer architecture cost-effective: one process, many concurrent interpreters, no per-player containers or worker pools to manage.


WebAssembly: Native Speed in the Browser

When CrashBASIC runs in a web browser, the entire interpreter executes as WebAssembly—a binary instruction format that runs at near-native speed in all modern browsers.

Key implementation details:

  • Multi-Threaded by Default: The same CALL THREAD / CALL TIMER / ASYNC primitives work in the browser as on desktop — see the Concurrency Model section above for how SharedArrayBuffer + Web Workers make this real, not simulated.

  • Zero JavaScript Interpreter: The BASIC interpreter is 100% Rust→WASM. JavaScript is only used for browser API bindings (WebGPU surface, WebAudio, WebSocket), not for running game logic.

  • Identical Semantics: A program behaves identically whether running as a native application or in a browser tab. There is no "web version" with reduced features.


CrashNet: Server-Authoritative Multiplayer

CrashBASIC includes built-in multiplayer through CrashNet, a server-authoritative networking system designed for security and simplicity.

How It Works

┌─────────────────────────────────────────────────────────────┐
│                    CrashNet Server                          │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Shared Game State                       │   │
│  │   • Sprite positions  • Player data  • Game world   │   │
│  └─────────────────────────────────────────────────────┘   │
│                           │                                 │
│     ┌─────────────────────┼─────────────────────┐          │
│     ▼                     ▼                     ▼          │
│  ┌──────────┐        ┌──────────┐        ┌──────────┐     │
│  │ Player 1 │        │ Player 2 │        │ Player N │     │
│  │  Thread  │        │  Thread  │        │  Thread  │     │
│  │ (BASIC)  │        │ (BASIC)  │        │ (BASIC)  │     │
│  └──────────┘        └──────────┘        └──────────┘     │
└─────────────────────────────────────────────────────────────┘
         │                     │                     │
         ▼                     ▼                     ▼
    ┌─────────┐           ┌─────────┐           ┌─────────┐
    │ Browser │           │ Desktop │           │ Mobile  │
    │ Client  │           │ Client  │           │ Client  │
    └─────────┘           └─────────┘           └─────────┘

Hybrid Server-Authoritative Design

The same .bas runs in three execution contexts at once: a headless host server (slot 0, holds authoritative state), one player-server interpreter per connected client (slots 1+, also server-side, authoritative for that player's actions), and the player's own local CrashPlayer for rendering and local-only effects. Branch on ME.ISHOST / ME.ISSERVER / ME.ISCLIENT to scope code to the right one.

The server broadcasts authoritative sprite state to every connected client; the client renders that broadcast and can also create its own sprites in a separate ID space (32768+) for local visual effects that never replicate. Clients send opted-in input (STREAM KEYBOARD/MOUSE in the CRASHNET block) and fire-and-forget custom signals (EVENT SERVER "Jump"(args), EVENT HOST "Build"(args)) back to the server.

This architecture provides:

  • Cheat Resistance: All authoritative game state lives on the server. Clients can render and pre-emptively animate, but they cannot manipulate health, positions, or scores — the server is the source of truth.

  • Bandwidth Efficiency: Resource loads (textures, fonts, audio) happen on each client locally; nothing crosses the wire. Input streaming is opt-in per category, defaulting to none. Custom client→server actions are explicit fire-and-forget signals rather than continuous state polling.

  • Cross-Platform Play: A player in Chrome, a player on a Mac, and a player on a Windows PC can all join the same game session. The server doesn't care what client they're using. iOS and Android clients will join the party once their multiplayer wiring ships.

  • Simplified Game Code: Developers write multiplayer games almost identically to single-player games. The ME object identifies the current player and which interpreter context is running; the PLAYERS array lists all connected players. The engine handles synchronization.

Tick-Based Synchronization

CrashNet synchronizes state at a configurable tick rate (default: 20 ticks/second). Each tick:

  1. Server collects all player inputs received since last tick
  2. Server advances game state
  3. Server broadcasts state delta to all clients
  4. Clients interpolate between states for smooth rendering

Performance Characteristics

The numbers below come from our in-house test hardware — recent Apple Silicon Macs, mid-range Windows desktops, current-gen iPhones and Android flagships, and Chrome/Firefox on those machines. They're representative, not guarantees: real-world performance depends on the user's device, the game's complexity, and what else is competing for CPU and GPU. Treat them as the floor we design against, not the ceiling every player will hit.

Startup Time

Platform Cold Start Warm Start
Native Desktop < 100ms < 50ms
Mobile < 200ms < 100ms
Browser (WASM) < 300ms < 100ms

Binary Footprint

CrashPlayer is the full runtime — interpreter, wgpu-based renderer, audio mixer, networking, launcher UI — bundled into a single distributable. Sizes from a recent release build:

  • macOS single-arch (Intel or Apple Silicon): ~48 MB
  • macOS universal (both architectures in one binary): ~95 MB
  • WebAssembly bundle (all modules combined): ~36 MB uncompressed, ~18 MB gzipped (brotli is smaller still)

The renderer is the dominant cost — crashgfx alone is most of the size, since wgpu pulls in shader translation, GPU resource management, and platform-surface code for every supported backend. The interpreter and standard library itself are a small fraction (~1.7 MB on WASM), and code-size is tracked across releases.

Frame Timing

On our tested hardware, CrashBASIC sustains 60 FPS with frame times typically under 4 ms for the interpreter portion, leaving ample headroom for rendering. Lower-end devices, deeply complex scenes, or backgrounded thermal throttling will move that number; the 4 ms figure is the interpreter's contribution on a healthy device, not a guarantee of overall frame budget under arbitrary load.


The CrashCart Format

Games are distributed as .crashcart files—encrypted, compressed bundles containing:

  • BASIC source code
  • Sprites and images
  • Audio files
  • Metadata (title, author, icon)

CrashCarts are:

  • Encrypted: Source code is protected using AES-256-GCM
  • Compressed: Assets use Zstandard compression for smaller downloads
  • Signed: Optional license stamps verify authorized distribution
  • Portable: The same .crashcart file runs on any supported platform

CrashPlayer: The Universal Runtime

CrashCarts don't execute themselves — they're played by CrashPlayer, the runtime that ships on every supported platform. The same .crashcart plays in CrashPlayer regardless of where CrashPlayer is running: desktop launcher, iOS App Store app, Android Play Store app, or the WASM build embedded in CrashBASIC Arcade. One runtime, one cart format, every platform.

Developers get two distribution paths from day one:

1. Side-Load into CrashPlayer

Players install CrashPlayer once — as a native app on their platform of choice, or by visiting the Arcade in a browser — and then load any .crashcart into it: from disk, from the Arcade library, from a URL, from a USB drive on the Steam Deck. One launcher, every game. The emulator-and-cart model, applied to a modern engine.

2. Standalone Apps via Mobile Templates

For developers who want to publish their game as a discrete App Store or Play Store title — with their own branding, icon, and listing — the repo ships ready-to-build mobile templates:

  • mobile/ios/GameTemplate/ — Xcode project that bundles a stock CrashPlayer runtime with one game pre-loaded.
  • mobile/android/template/ — Android Studio / Gradle equivalent.

The developer drops their .crashcart into the template, swaps the app name / bundle ID / icon / splash, and ships the resulting app like any other native title. A helper script (mobile/ios/rename-project.sh) handles the boilerplate renaming so you don't have to spelunk through Xcode and Gradle config files.

The two paths compose: a single game can simultaneously appear in the Arcade for anyone with CrashPlayer, and ship as a branded standalone app for players who don't know what CrashPlayer is. Both paths reuse the exact same interpreter, renderer, and audio stack — the standalone app is CrashPlayer wearing a costume.


Platform Support Matrix

Platform Renderer Audio Multiplayer Distribution
Windows 10+ wgpu (DX12/Vulkan) SDL2_mixer ✓ .exe installer
macOS 10.15+ wgpu (Metal) SDL2_mixer ✓ .dmg / .app
Linux wgpu (Vulkan) SDL2_mixer ✓ .deb / .rpm
iOS 13+ wgpu (Metal) SDL2_mixer Coming soon App Store
Android 8+ wgpu (Vulkan/GLES) SDL2_mixer Coming soon Play Store / APK
Web (Modern) wgpu (WebGPU/WebGL2) Web Audio ✓ URL

On the Roadmap

Platform Path Status
Steam Deck Linux build (works today) ✓ Shipping
Nintendo Switch Console-native renderer (NVN) + audio behind the protocol seam Planned
PlayStation 5 Console-native renderer (AGC) + audio behind the protocol seam Planned
Xbox Series X/S wgpu (DX12) — already a supported wgpu backend Planned

The console row is a backend-swap, not a port. wgpu is today's renderer on every shipping platform, but the architecture has never required it: GfxEngine is a trait, not a wgpu wrapper. Switch (NVN) and PlayStation (AGC) don't have wgpu backends and won't get them — they'll get their own native renderers behind the same protocol seam. Xbox's DX12 path is already inside wgpu, so that one's a port at the SDK level rather than a new renderer.

The interpreter, the language, the cart format, and the multiplayer protocol stay completely unchanged across all of these. A game finished today on a laptop becomes a Switch title when the Switch backend ships, without the developer touching their code.


Conclusion

CrashBASIC's architecture represents a deliberate set of tradeoffs:

  • Rust over C++: Slightly longer compile times in exchange for memory safety guarantees and first-class WASM support.

  • Protocol separation over tight integration: A few microseconds of serialization overhead in exchange for complete renderer flexibility.

  • Server-authoritative over peer-to-peer: Higher server costs in exchange for cheat resistance and simplified game code.

  • Tree-walking interpreter over JIT compilation: Predictable performance over peak performance, prioritizing consistency across platforms.

The result is a platform where hobbyists can write a game in an afternoon and have it run — with full multiplayer support — on virtually any device their players own today, and on every console as those backends come online. Portability isn't a feature retrofitted onto CrashBASIC; it's the invariant the whole system was built to preserve.


For technical inquiries: [email protected]

CrashBASIC is developed by Crash Continuum LLC. All trademarks are property of their respective owners.