BytorioGet started

DOCUMENTATION / sdk/README.md

Bytorio SDK 0.2.0

Both players support three frame-rate policies: a custom render cap (60 FPS by default), uncapped rendering, and native display refresh. Simulation remains fixed at 60 Hz in both SDKs.

Two authoring SDKs for the same resource-av-1 console. Luau is the default authoring language; Odin is supported for explicit memory control and performance. The standalone native and browser players share the Odin renderer and cartridge ABI. The formal specification defines behavior and contract.json records wire constants and required exports.

The typed graphics/audio specification and published profile define SDK 0.2.0 / ABI 2. Resources, explicit graphics, PCM audio and synthesis/effects work through both SDKs on native and browser. SDK 0.1.0 / ABI 1 remains an explicit compatibility profile. Existing manifests retain their version; new projects request SDK 0.2.0 and resource-av-1. See profile negotiation and conformance.

SDK 0.3.0 / compact-3d-preview-1 is an explicit experimental opt-in for meshes, cameras, depth and voxel resources in both SDKs. Create ordinary SDK projects, then set that SDK/profile tuple in console.json. mise run build:3d exports the matching native/browser prototypes.

SDK 0.4.0 / textured-3d-preview-1 extends that opt-in with UV meshes and typed materials. Set sdk to "0.4.0" and profile to "textured-3d-preview-1" in console.json. Both SDKs expose gfx3d.textured_mesh, gfx3d.material and gfx3d.draw_textured. CPU rendering is the default; optional CPU supersampling and GPU rendering/residency use the same API. Existing profiles keep their original contracts and defaults.

SDK 0.5.0 / alpha-3d-preview-1 adds depth-tested billboards and alpha cutout. It reuses typed UV meshes/materials on CPU and optional GPU paths. Set both manifest fields explicitly to use it; mise run //examples/billboards:build builds matching examples.

The static glTF/GLB importer generates ready-to-use Luau and Odin model modules, preserving named rigid parts for game-side animation. Use byt import <asset> --output <project>/src/imported; the report identifies the required preview profile, limits and base-color rendering reductions.

SDK 0.6.0 / effects-3d-preview-1 adds premultiplied and additive scene-space effects, with independent depth-test/write controls. Both SDKs use gfx3d.effect_material; CPU rendering remains the default. See examples/effects and examples/kart3d.

Install byt

Install the Bytorio CLI once, then use byt from your terminal. The planned installer script will select the right tools for Windows, macOS or Linux and add byt to your PATH. You will not need a console source checkout to create games.

If you use mise, the planned global installation is:

mise use --global npm:bytorio@latest

The installer script and published package are planned distribution options; they are not available yet. These guides use the installed byt command as the intended workflow. For development from source today, follow the development setup and run the local CLI through the repository's mise task.

Start a game

From the folder where you keep your projects:

byt new my-game
byt check my-game
byt run my-game --web

The command is byt; the package is named bytorio.

For Odin, create with new my-game --language odin. Projects are ordinary folders; they may live outside this repository. The new command refuses existing folders. The current source toolchain supports Windows x64 native development and browser builds from Windows, using the repository's pinned Odin, Wasmtime and Emscripten toolchains. See toolchain setup; Luau executor builds additionally require the Luau CMake/C++ setup. Odin-only SDK projects do not build the Luau executor. For Luau projects, check uses the prebuilt Luau analyzer installed by mise, matching the executor's pinned Luau version. Preparing a fresh toolchain requires network access; subsequent builds reuse it.

New Luau projects include VS Code settings for the standard Luau platform and the new type solver, matching command-line checks. The SDK's read table properties require that solver. For an existing project, merge the settings from the editor template. If diagnostics remain after the settings load, run Luau: Reload Language Server in the command palette.

Command Behavior
new <directory> [--language luau|odin] Create source, manifest, art and editor configuration
check <directory> Check types, assets, ABI and first rendered frames
build <directory> Check and stage standalone native/browser player folders
pack <directory> [--output game.png] [--cover cover.png] Build and export a viewable PNG containing the complete game
run <directory> --web [--port 8080] Build and serve the browser player on localhost
run <directory> --native Build and launch the native player
watch <directory> --web Rebuild after edits; reload the browser to reset the game
watch <directory> --native Rebuild and restart the native player after edits

build/native contains the executable, libraries, licenses and cartridge files. Copy that whole folder to run without Node.js, a compiler or a webview. Both languages launch with player.exe, loading game.png automatically. Share the single build/game.png file with another compatible player. build/web can be served as static files; its configured player loads the same PNG. The browser also accepts local PNGs through Open cartridge or drag-and-drop. See PNG cartridges for the format, custom artwork, runtime components, loading commands and current preview limits.

For assets larger than the code budget, declare content: [{"id": 1, "path": "assets/atlas.rgba"}] in console.json. The indexed content guide describes both SDK read APIs, byte limits and the immutable cartridge index.

Project structure and lifecycle

console.json declares SDK version, title, language, entry path, asset path, optional PNG cover path and initial display. Supported displays are [640,360] and [320,180]. Paths are relative to the project and may not escape it. assets.json contains exactly 16 #RRGGBB palette entries, 1..256 sprites of sixteen 16-character rows, and an optional ASCII bitmap font. Sprite pixels are palette digits 0..f or . for transparency. Font entries have seven five-bit string rows. Extra source metadata, such as a tile map, can be stored in this JSON and in normal game modules.

The package is bytorio; use byt as the local namespace in game code. Luau's entry is a .luau module returning init, update(dt) and draw functions. Use local byt = require("@bytorio"). Strict types and completions come from the real SDK module copied into ignored .bytorio/, with a portable .luaurc alias. The CLI analyzes game sources and their dependencies, then bundles literal relative require("./module") and require("@bytorio") calls. Modules execute once and cache their returned value. Cycles, dynamic requires, shadowing require, external packages and paths escaping the project are unsupported. Strings and comments are preserved. The player requires no filesystem module loader.

When updating source projects, replace require("@console") with require("@bytorio"), rename the local SDK variable to byt, and change Odin imports to byt "sdk:bytorio". Generated Odin files now use bytorio_entry.odin and bytorio_assets/. Luau checks refresh .bytorio/ and the managed bytorio editor alias, preserving other settings and aliases, and ignore the mirror in Git. An old generated editor alias is removed; user-defined aliases are retained. The project manifest remains console.json; cartridge formats, profile markers and exported guest functions are unchanged. Previously built cartridges continue to run without recompilation.

Odin's entry is a package directory with package main, init :: proc(), update :: proc(dt:f64) and draw :: proc(). Import byt "sdk:bytorio". The CLI supplies the collection, generates an atlas package and lifecycle entry point under build/generated/, and compiles the cartridge. Source names bytorio_entry.odin and the bytorio_assets/ package are reserved. The generated atlas can be imported as assets "./bytorio_assets" from the entry package. Game authors do not write Wasm exports or buffer-offset code.

Both SDKs initialize input/time/random state, prepare graphics batches and provide the same console operations. Update always receives 1/60 seconds; input edges are sampled once per tick. Keep simulation in update and presentation in draw. Changing display mode does not reset state. Calling your own init callback to restart a round does not reload the SDK. See the API reference.

Odin retains normal explicit memory management: allocations belong to the game, and temporary allocations need an explicit reset strategy. The SDK itself uses fixed buffers for frame drawing and diagnostics. Avoid returning strings backed by dead stack storage, or allocating text every frame without reclaiming it.

Direction and camera input

The original left, right, up, down, and action buttons retain their bindings and bits. buttons() still returns only those five bits. The current player also delivers independent move_left/right/up/down (WASD or gamepad D-pad), pan_left/right/up/down (arrows), rotate_left/right (Q/E), and reset (R). Odin uses MoveLeft, PanLeft, RotateLeft, and the corresponding enum members; Luau uses the lowercase names with underscores. button, pressed and released support every named button. all_buttons() returns the complete mask. These optional controls require the current player; older SDKs continue to ignore the extra bits. See the canonical values in contract.json.

Input version 2 adds pointer_left, pointer_right, pointer_hover, wheel_up/down, inventory (I), character (C), skills (K), quest (Q), map (Tab), potion_1 through potion_4 (1–4), shift (either Shift), and escape (Escape). Odin names are PointerLeft, PointerRight, PointerHover, WheelUp, WheelDown, Inventory, Character, Skills, Quest, Map, Potion1 through Potion4, Shift, and Escape. Q also retains rotate_left. capabilities().input_version reports 2 without changing cartridge profile ABIs.

pointer_position() returns (hover, x, y) in logical display coordinates, including movement with no mouse button held. pointer() retains its original left-held meaning and returns (false, 0, 0) when inactive. Both discard out-of-display coordinates. wheel() returns +1 for up, -1 for down, or 0; the host coalesces wheel events to the latest direction before the next tick and consumes it once, even when several simulation ticks share a rendered frame. Focus loss clears held controls and pending wheel input.

Native --replay accepts the original whitespace-separated unsigned button masks or versioned v2:mask:leftHeld:x:y tokens. For example, v2:262144:0:80.5:60.75 hovers without pressing a button. Masks are unsigned 32-bit integers; Escape occupies bit 31. Browser worker step requests use the equivalent {buttons, pointer, x, y} input snapshot. Old SDKs ignore the added bits and continue to receive their original pointer semantics.

Browser cartridge cache

Built web players reference game.png?revision=<SHA-256> using the exact cartridge bytes. The browser stores these versioned downloads in CacheStorage; refreshing the same export can load without another cartridge HTTP request. A new cartridge produces a new revision and downloads fresh bytes. HTML must still be served with an appropriate revalidation policy so browsers see new exports.

The cache uses no service worker. It streams the download into browser storage with loading progress, then streams the stored response into the player. It does not tee a large response into an unbounded JavaScript queue. Each transfer retains the ordinary cartridge size limit; successful writes prune older entries to keep two revisions when browser storage operations succeed. Browsers can evict cached data, and unavailable storage or quota errors fall back to network loading. Local files and URLs without an exported revision bypass this cache.

Run mise run validate test:cartridge-cache to check real browser refreshes, revision changes, local files, and storage-failure fallback. Cached bytes are still checked by the ordinary cartridge parser; the URL revision selects an export version and does not replace its format validation.

Verify

Both SDKs also provide a versioned persistent save slot, with a 64 KiB limit, native atomic replacement and browser transaction status.

npm run test:sdk
npm run test:cartridge
mise run validate test:save

The suite builds real projects with both SDKs and verifies native/browser parity, mode switching, clipped and sized sprites, text, input edges, pointer delivery, known random vectors, sound events, reload, static errors and invalid arguments. Retained projects and outputs live in ignored build/sdk-verification/. The RPG and racing examples also use these SDKs; their existing build commands generate the entry points and pack their shared art. Racing replays retain all 2,236 pre-SDK framebuffer/sound pairs, and RPG checks retain the original artwork hashes within its historical 384×216 scene on the current default canvas.