DOCUMENTATION / sdk/resources.md
Typed resources: resource-av-1
The native player and browser Worker support SDK 0.2.0 / ABI 2, resource-av-1, using transport version 4 for typed ownership, software graphics and scheduled PCM/synthesized audio. The published profile and protocol schema define its limits and wire layout. Older players reject its profile-2 PNGs. ABI 1 and internal version-1/2/3 guests remain supported through the compatibility adapter. See negotiation and conformance.
Resources can be created, uploaded, edited and destroyed. Explicit graphics now renders textures and targets with passes, composition and readback. PCM audio adds explicit voices, buses, loops, ramps and bounded streams. Existing sprite, text and sound-cue calls work. Synthesis/effects add typed graphs, instruments, wavetables, scheduled source edits and independent voice/bus DSP state.
SDK operations
Luau uses byt.data, byt.gfx, and byt.audio. Odin imports
data "sdk:data", gfx "sdk:gfx", and audio "sdk:audio".
| Operation | Luau | Odin |
|---|---|---|
| Pixel buffer | data.pixels("rgba8",w,h) or "index8" |
data.pixels(.RGBA8,w,h) or .Index8 |
| PCM buffer | data.pcm(frames,channels) |
data.pcm(frames,channels) |
| Texture | gfx.texture({format="rgba8",width=w,height=h}) |
gfx.texture({.RGBA8,w,h}) |
| Upload | gfx.upload(texture,{x=0,y=0,width=w,height=h},pixels,opts) |
gfx.upload(texture,{0,0,w,h},pixels,opts) |
| Sample | audio.sample({frames=n,channels=2,rate=48000},pcm) |
audio.sample({n,2,48000},pcm) |
| Edit sample | audio.write(sample,{first=0,frames=n},pcm) |
audio.write(sample,{0,n},pcm) |
| Destroy | gfx.destroy(texture), audio.destroy(sample) |
Same operations with distinct handle types |
Upload options specify offset_bytes and stride_bytes; omitted or zero stride is the
pixel buffer's row width. Rectangles use top-left coordinates and positive sizes;
uploads must fit the destination. Buffer format must match texture format. Pixel
bytes are row-major index8, stencil8 attachments or premultiplied RGBA8, in R/G/B/A order. Every RGB
channel must be at most alpha. No texture storage is directly mapped into a game.
PCM is finite interleaved f32, mono or stereo, with a declared source rate from
8,000 to 96,000 Hz. first and frames address source frames, not bytes or scalar
samples. The buffer's channel count must match the sample. Playback, looping and
resampling use the 4c mixer; scheduled live source edits remain 4d. Sample
creation can omit initial PCM in Luau, or use an empty PCM_Buffer in Odin;
storage begins at zero and can be filled with chunked writes.
Luau buffer metadata and handle tokens are frozen; mutate pixels.bytes with
the buffer library and pcm.samples with buffer.writef32 at zero-based byte
offsets. Exported byt.Texture, Sample, PixelBuffer, and PCMBuffer
types support editor checks. Token identity, session and generation are checked
at runtime. Odin exposes []u8 pixel bytes and []f32 samples; it uses distinct
gfx.Texture / audio.Sample types. Release game-owned Odin buffers with
data.free_pixels(&pixels) / data.free_pcm(&pcm) when finished. Luau reclaims
them through GC. Odin callbacks receive the Wasm allocator for ordinary game
allocations; typed buffers retain their originating allocator for release. Guest
allocation remains within the existing memory/heap limits.
Call resource operations inside init/update. Each operation copies supplied data into bounded SDK staging immediately, so a source buffer can be reused before the callback returns. Destruction is explicit; handles from a destroyed resource or previous session fail. Version 2 also retains pipeline/table, row/palette and target/stencil dependencies; destroy dependants before resources they reference.
For complete, type-checked usage, see the equivalent Luau example and Odin example. They upload, mutate and destroy resources, then reuse a slot with a new generation.
Internal transport and ownership
resource-transport.json defines the subset's
version, limits, formats, field layouts, descriptors and configuration exports.
mise run generate generates the Odin, Luau, C++ and browser constants;
mise run generate:check detects stale generated files.
Version 2 adds a session-checked readback mailbox before each update. Its 16-byte
header carries magic, count, byte length and session; up to four 32-byte records
describe copied pixel results. At most 1 MiB of result data is delivered per
update. The mailbox reuses the acknowledged packet arena; SDKs consume/copy it
before recording new commands. Full profile configuration negotiation remains
later work. Version 3 inserts a 448-byte audio status/completion block after the
readback records, moving payload data from byte 144 to byte 592. It carries up to
16 voice completions per update. Input still uses the existing arguments.
Version 4 adds synthesis commands and graph descriptors with the same mailbox.
Before init, the player checks game_resource_protocol() (1–4) and configures a
nonzero fresh session with game_resource_configure(session). The version fixes
the supported operations and limits; a mismatch is rejected. After init and
every update, the host calls game_commit() then game_commit_size(), copies
the packet, validates it in the shared Odin registry, and acknowledges its exact
sequence with game_commit_ack(sequence). Version 2 does the same after every
draw, using a distinct draw phase without advancing the simulation tick. There
is one sealed packet per guest; the SDK refuses arena reuse before acknowledgement.
Draws cannot repeat control/audio events. Legacy guests without these exports
remain supported. Hosts validate version-2 mailbox exports before init and cache
the negotiated version rather than querying a mutable guest getter each frame.
The wire header is the proposal's 64-byte AVP1 header with ABI field 2. Init,
update and draw are phases 0, 1 and 2. Updates require successive ticks; all
packets require successive sequences. Draw uses the most recent update's tick.
Commands begin at byte 64 and use 32-byte records. The payload follows the used
commands immediately. Argument/data offsets are relative to the payload; typed
descriptors contain eight little-endian u32 fields. Graphics destroy, bind and end-pass have no arguments or
data. Reserved fields are zero. Unknown opcodes, flags, bad ranges, unsupported
formats and malformed descriptors fail the entire packet with a command index.
Audio operations use opcodes 32–41 and 32-byte descriptors; audio destruction
also requires that descriptor, filled with zeroes. Absolute times use low/high
u32 words. Sample loops use an additional 8-byte source-frame range. The schema
defines opcode-specific layouts; audio.md defines units and timing.
Synthesis uses public opcodes 42–49, fixed 64-byte typed nodes and a 16-byte graph
header. The separate prepared audio queue translates source creation/destruction
to internal opcodes 50/51; those are rejected in guest resource packets.
The registry simulates metadata and prepares copy-on-write allocations before publication. Failure retains the previous registry, sequence and bytes; temporary allocations are freed. Accepted mutations reclaim replaced storage after this synchronous consumer completes. Later asynchronous consumers will require the retirement tracking specified in the proposal. No guest pointer is retained.
Limits are 1,024 resource slots, 64 MiB runtime resource quota, 16,384 commands and 1 MiB payload per commit. The quota charges live storage, copied packet bytes and pending replacement allocations, including old/new versions simultaneously. Fixed registry/staging capacity and allocator overhead are baseline runtime memory, not claims about RSS. The host input arena and the SDK arena each reserve at most 1.5 MiB, while empty commits transfer only 64 bytes. Large transfers must be chunked. Texture axes are 1..1024; actual buffers still must fit guest limits.
The browser runtime now has an 80 MiB linear-memory ceiling to accommodate the resource quota plus fixed runtime storage and allocation overhead. Guest memory remains capped at 8 MiB and the tracked Luau heap at 4 MiB. The Luau executor's initial memory is 4 MiB for its bounded transport arena. Native and browser use the same registry and charging rules. Faults stop the session; worker errors explicitly release resources and discard the guest.
Verification
Run npm run test:resources on the supported Windows development host. It builds
both SDK fixtures, runs native registry tests and the standalone player, and
compares 121 resource snapshots per language through native and actual browser
Workers. It tests skipped draws, reload, source-buffer reuse, type/generation/
session failures, generation retirement, atomic rollback, copy-on-write budget
exhaustion, malformed packet regions, overflowing ranges, non-finite PCM and
premultiplied-alpha validation. The native registry tests use allocation tracking.
Reports and full traces are written under the ignored build/resource-verification/.
Get started