v0.10.0 · 5 review rounds · 135 defects fixed

Zero-copy asset streaming for game engines. Fast, memory-mapped, and verified.

Stream assets straight from disk into memory without copying or RAM duplication. Seal archives with AES-256-GCM or ChaCha20-Poly1305, verify every file against BLAKE3 digests, and script custom asset pipelines with embedded Lua 5.4. A high-performance Rust library with C ABI bindings.

  • Direct memory-mapped streaming (0 copy)
  • Lock-free, thread-safe read path
  • 166 panic-safe C-ABI exports
  • Unreal Engine 5 · Unity 6 · Godot 4.3+ plugins

Recorded run · macOS 26.2 arm64 · debug build of release/0.10.0 with the sealed manifest · 2026-09-25

verify: a clean archive, the same archive with one byte flipped, and a sealed archive without and with its key

$ cyclepack verify --input game.cpk
Verifying archive...
Structural validation passed. Archive format: CPK3, Entries: 8
Content integrity: 8 of 8 entries verified against the digests in the archive.
Verification passed: all 8 entries decoded and matched the digests CPK3 records

exit 0

$ cyclepack verify --input bad.cpk
Verifying archive...
Structural validation passed. Archive format: CPK3, Entries: 8
FAIL meshes/hero.mesh: E_DIGEST_MISMATCH: BLAKE3 digest mismatch
Error: Verification failed: 1 of 8 entries do not match the digest the archive records for them (meshes/hero.mesh: E_DIGEST_MISMATCH: BLAKE3 digest mismatch)

exit 1

$ cyclepack verify --input sealed.cpk
Verifying archive...
Structural validation passed. Archive format: CPK3 (sealed manifest), Entries: not readable without the key
Error: Verification failed: the manifest is sealed and no entry was checked. Nothing is known to be wrong with this archive; pass --key-file to verify it, or --structural-only to accept the header and length checks alone.

exit 1

$ cyclepack verify --input sealed.cpk --key-file key.bin
Verifying archive...
Sealed manifest authenticated (ChaCha20-Poly1305, generation 7).
Structural validation passed. Archive format: CPK3, Entries: 8
Content integrity: 8 of 8 entries verified against the digests in the archive.
Verification passed: all 8 entries decoded and matched the digests CPK3 records

exit 0

doctor: the capability backing table and the verdict

$ cyclepack doctor
…
[Capability Backing]
  [EM] DirectStorage                Emulated     CPU queue with the same semantics; DirectStorage is a Windows interface
  [OK] FastCDC                      Native       software format or algorithm, implemented in this library
  [OK] Learned index                Native       software format or algorithm, implemented in this library
  [OK] Sparse virtual texturing     Native       software format or algorithm, implemented in this library
  [OK] Neural texture compression   Native       software format or algorithm, implemented in this library
  [EM] BaM / io_uring               Emulated     pread/mmap with no ring; no io_uring ring could be set up here
  [EM] CXL shared memory            Emulated     process-local or file-backed shared memory, not a CXL fabric
  [EM] NVMe ZNS                     Emulated     in-memory zone model; zoned block devices are a Linux interface
  [OK] 3DGS streaming               Native       software format or algorithm, implemented in this library
  [OK] Streaming scheduler          Native       software format or algorithm, implemented in this library
  [OK] Predictive prefetch          Native       software format or algorithm, implemented in this library
  [EM] Computational storage        Emulated     host-CPU byte loop (XOR, not a cipher); no NVMe TP 4091/4092 device is used
  [EM] GPUDirect Storage            Emulated     host-memory bounce buffers; cuFile / GPUDirect Storage is not linked
  [EM] RDMA / RoCE v2               Emulated     in-process transport; no verbs provider or RoCE v2 NIC is used
…
Diagnostic Verdict: 7 of 14 capabilities native, 7 emulated, 0 unavailable on this host.
The doctor reports what these probes established; it opens no archive and moves no I/O.

exit 0

seal: seal.c opens sealed.cpk, generation 7, with its key; with a floor of generation 8; and with one bit of the key flipped

seal.c, in the code console below, opens sealed.cpk (sealed as generation 7) three times: with its key and a floor of 7, which reads meshes/hero.mesh; with its key and a floor of 8, as a newer build would; and with one bit of the key flipped. The key goes in at open, so the last two stop there.

$ ./seal
size query: 5000 bytes
read 5000 bytes of meshes/hero.mesh
get_data_ptr: error 1: cyclepack_get_data_ptr: cannot lend 'meshes/hero.mesh': E_ZERO_COPY_ENCRYPTED: 'meshes/hero.mesh' is encrypted; use get_data() to decrypt it
open: error 6: cyclepack_create_unpacker_with_key: cannot open 'sealed.cpk': E_STALE_GENERATION: archive generation 7 is below the minimum generation 8 this reader accepts
open: error 1: cyclepack_create_unpacker_with_key: cannot open 'sealed.cpk': E_WRONG_KEY: this key does not open the archive (the key check in its header does not match)

exit 0

Pasted as printed; a line reading “…” marks a trim. Read seal.c

Capability report

Transparent hardware capabilities.

CyclePack provides unified interfaces for cutting-edge I/O architectures: DirectStorage, io_uring, CXL shared memory, and NVMe ZNS. The cyclepack doctor command clearly reports which features run natively on your host hardware and which are emulated, so your production engine never relies on guesswork.

7 of 14 native on the Apple-silicon Mac this was recorded on. Run cyclepack doctor on yours.

  • DirectStorage

    Emulated
    Status
    CPU queue with identical semantics; native Windows backend planned.
  • FastCDC

    Native
    Status
    Content-defined chunking implemented natively in Rust.
  • Learned index

    Native
    Status
    Piecewise-linear model and Bloom filter for sub-microsecond path lookups.
  • Sparse virtual texturing

    Native
    Status
    Native tile residency tracking and page tables for GPU virtual textures.
  • Neural texture compression

    Native
    Status
    Native container and CPU decoder with per-UV random access.
  • BaM / io_uring

    Emulated
    Status
    Native high-throughput asynchronous I/O via io_uring on Linux; pread/mmap fallback elsewhere.
  • CXL shared memory

    Emulated
    Status
    Shared memory interface for disaggregated storage and DAX devices on Linux.
  • NVMe ZNS

    Emulated
    Status
    In-memory zone model; hardware zone-ioctl backend planned.
  • 3DGS streaming

    Native
    Status
    Native Gaussian splat chunk streaming and WebGPU shader unpacking.
  • Streaming scheduler

    Native
    Status
    Native prioritized I/O queues and budget management.
  • Predictive prefetch

    Native
    Status
    Native access pattern learning and background cache preloading.
  • Computational storage

    Emulated
    Status
    Host-CPU processing loop; hardware device offloading model.
  • GPUDirect Storage

    Emulated
    Status
    Host-memory bounce buffers; direct NVMe-to-VRAM path model.
  • RDMA / RoCE v2

    Emulated
    Status
    In-process transport model for distributed cloud asset streaming.

Integrity & Security

Production-grade tamper resistance.

CyclePack is hardened against corruption, tampering, and malicious payloads across 135 verified test cases.

Cryptographic verification

Every entry is decoded and validated against its BLAKE3 digest on read. Corrupted bytes are caught immediately before entering your engine pipeline.

E_DIGEST_MISMATCH · Exit 1

Sealed archives

Full-archive encryption with AES-256-GCM or ChaCha20-Poly1305. Manifests and entries are sealed together, keeping paths, sizes, and file counts completely confidential without the key.

E_AUTH_FAILED · E_NOT_SEALED

Multi-runtime key management

Unified 32-byte key handling across C, Rust, Node, and WebGPU/WASM runtimes. Built-in generation floors reject stale builds even if cryptographically authentic.

E_WRONG_KEY · E_STALE_GENERATION

Panic-safe C ABI

All 166 C-ABI exports are protected by panic guards. Uncaught Rust panics convert safely into error codes instead of crashing your game host.

166 exports · 0 unguarded

Signed delta patches

Patch manifests require RSA-2048 cryptographic signature validation before any data can be unpacked or applied, guaranteeing safe live game updates.

RSA-2048 PKCS#1 v1.5 · SHA-256

Bounded memory limits

Hard limits on memory allocation and decompression prevent zip-bomb exploits and out-of-memory crashes when reading untrusted or hostile archives.

cargo deny check · Clean

Also included: geometric meshlet LOD measurement, multi-language codegen, and runtime capability diagnostics.

Code

Embedded Lua 5.4 and Rust

Script packing, sealing, compression and patch steps in Lua 5.4. Run them with the cyclepack CLI, or embed LuaRuntime in your host and load a new script without rebuilding.

Every pane says how it was checked · macOS 26.2 arm64 · release/0.10.0 with the sealed manifest · 2026-09-25

C++: borrow a stored entry

main.cpp
#include <cstdio>
#include <cyclepack.h>

int main() {
    // A single-file CPK3 archive is its own index: the same path goes in twice.
    CyclePackUnpacker* vfs = cyclepack_create_unpacker("game.cpk", "game.cpk");
    if (!vfs) return 1;

    // Borrows the mapped bytes: no copy, nothing to free. NULL for an absent,
    // compressed or sealed entry; valid until cyclepack_destroy_unpacker.
    uint32_t size = 0;
    const uint8_t* bytes = cyclepack_get_data_ptr(vfs, "meshes/hero.mesh", &size);
    if (bytes) {
        std::printf("borrowed %u bytes of meshes/hero.mesh\n", size);
    }

    cyclepack_destroy_unpacker(vfs);
    return 0;
}

How checked: compiled with c++ -std=c++17 -Wall -Wextra against include/cyclepack.h and libcyclepack, no warnings, and run on game.cpk (the verify demo archive).

Output
borrowed 5000 bytes of meshes/hero.mesh

C: open a sealed archive

seal.c
#include <stdio.h>
#include <stdlib.h>
#include <cyclepack.h>

static void report(const char* what) {
    char msg[256];
    cyclepack_get_last_error_message(msg, sizeof msg);
    printf("%s: error %d: %s\n", what, cyclepack_get_last_error_code(), msg);
}

static void read_entry(const uint8_t key[32], uint64_t min_generation, const char* name) {
    /* The key goes in at open, with the oldest generation this build accepts. */
    CyclePackUnpacker* u = cyclepack_create_unpacker_with_key("sealed.cpk", key, 32, min_generation);
    if (!u) { report("open"); return; }      /* nothing to read: no entry list, no handle */

    uint32_t n = 0;
    if (!cyclepack_get_file_data(u, name, NULL, 0, &n)) {   /* NULL buffer: size query */
        report("size query");
    } else {
        printf("size query: %u bytes\n", n);
        uint8_t* bytes = malloc(n);
        if (bytes && cyclepack_get_file_data(u, name, bytes, n, &n))  /* decrypt + digest check */
            printf("read %u bytes of %s\n", n, name);
        else
            report("read");
        free(bytes);
    }

    /* A sealed archive is never lent out raw: the zero-copy path refuses every entry. */
    uint32_t len = 0;
    if (!cyclepack_get_data_ptr(u, name, &len)) report("get_data_ptr");

    cyclepack_destroy_unpacker(u);
}

int main(void) {
    uint8_t key[32];
    FILE* f = fopen("key.bin", "rb");
    if (!f || fread(key, 1, 32, f) != 32) return 1;
    fclose(f);

    read_entry(key, 7, "meshes/hero.mesh");    /* sealed.cpk is generation 7 */
    read_entry(key, 8, "meshes/hero.mesh");    /* a build that accepts generation 8 and up */

    key[0] ^= 0x01;                            /* one bit off */
    read_entry(key, 0, "meshes/hero.mesh");
    return 0;
}

How checked: compiled with cc -std=c11 -Wall -Wextra against include/cyclepack.h and libcyclepack, no warnings, and run on sealed.cpk, packed as generation 7 with cyclepack-studio pack --compression none --encryption chacha20-poly1305 --key-file key.bin --generation-id 7.

Output
size query: 5000 bytes
read 5000 bytes of meshes/hero.mesh
get_data_ptr: error 1: cyclepack_get_data_ptr: cannot lend 'meshes/hero.mesh': E_ZERO_COPY_ENCRYPTED: 'meshes/hero.mesh' is encrypted; use get_data() to decrypt it
open: error 6: cyclepack_create_unpacker_with_key: cannot open 'sealed.cpk': E_STALE_GENERATION: archive generation 7 is below the minimum generation 8 this reader accepts
open: error 1: cyclepack_create_unpacker_with_key: cannot open 'sealed.cpk': E_WRONG_KEY: this key does not open the archive (the key check in its header does not match)

Rust host: mount, script, read

engine.rs
use cyclepack::lua::runtime::LuaRuntime;
use cyclepack::vfs::{CascStorage, MmapStorage, VfsEngine, VirtualFileSystem};
use std::path::Path;
use std::sync::Arc;

fn main() -> anyhow::Result<()> {
    // One virtual root over every mount, tried by priority; reads take &self
    let mut vfs = VfsEngine::new();

    // A memory-mapped archive, and a content-addressed chunk store
    // (CASC-style, CyclePack's own layout)
    vfs.mount_mmap("assets/", Arc::new(MmapStorage::open("game.cpk")?));
    vfs.mount_casc("textures/", Arc::new(CascStorage::open("stream_cache/")?));

    // Trusted Lua 5.4 runtime for your own scripts; LuaRuntime::sandboxed for mods
    let runtime = LuaRuntime::new()?;
    runtime.load_script(Path::new("scripts/vfs_pipeline.lua"))?;

    // A stored entry is borrowed from the mapping. A compressed or sealed one is
    // refused here (E_ZERO_COPY_*), and vfs.read() decodes it into a copy.
    let slice = vfs.read_slice("assets/meshes/hero.mesh")?;
    println!("resolved {} bytes", slice.as_slice().len());

    Ok(())
}

How checked: built in a scratch crate that depends on this repository by path, and run beside game.cpk, an empty stream_cache/ and a one-line scripts/vfs_pipeline.lua.

Output
[INFO] vfs_pipeline.lua loaded
resolved 5000 bytes

Schema compiler: packed, layout-identical structs for C++, C#, Go, Lua, Python and Rust

schema.cpack
// cyclepack codegen --schema entity.cpack --output generated/ \
//                   --languages cpp,csharp,go,lua,python,rust
// Every target gets a packed struct with the same layout.
struct PlayerState {
    uint64  player_uid;
    float32 position[3];
    float32 orientation[4];
    uint16  current_zone;
    uint32  health_mana_packed;
    bytes   dynamic_payload;   // bytes is a fixed 64-byte field
}

How checked: the command in the first two lines, run on this file. The test suite compiles the generated code and round-trips it through every toolchain on the machine.

Output
⚙️  CyclePack Schema Codegen: "entity.cpack"
✅ Code generation completed successfully! Generated 6 files in "generated/"
   - "generated/entity.hpp"
   - "generated/entity.cs"
   - "generated/entity.go"
   - "generated/entity.lua"
   - "generated/entity.py"
   - "generated/entity.rs"

Python SDK: zero-copy arena, same process

arena.py
from cyclepack import SharedMemoryArena

# A 16 MiB slab owned by the engine, in this process. The context manager
# destroys it on the way out; a memoryview taken from it is invalid afterwards.
with SharedMemoryArena(name="game_telemetry", capacity_bytes=16 * 1024 * 1024) as arena:
    # Reserve 256 bytes; the lease is what releases them again
    lease_id, offset, length = arena.allocate(256)

    # A view *into* the engine's slab: Python writes through it,
    # with no copy on the CPython heap
    view = arena.get_memoryview(offset, length)
    view[0:4] = b"SYNC"

    arena.release(lease_id)
    print(f"wrote 4 bytes into a {arena.capacity}-byte arena")

How checked: run with Python 3.12 against the SDK in packages/python and the debug library. Python writes through a memoryview into the engine's slab; nothing is copied onto the CPython heap. The arena lives in this process; it is not shared with other processes.

Output
wrote 4 bytes into a 16777216-byte arena

WebGPU preview renderer

webgpu.ts
// Preview renderer: isotropic discs, no depth sort, no culling or LOD, and
// spherical harmonics are ignored. The WASM package is built from the repo.
import { GspxWebGpuRenderer } from "@cyclechain/cyclepack-webgpu";
import init, { GspxWasmReader } from "./pkg/web/cyclepack.js";

await init();
const renderer = new GspxWebGpuRenderer(canvas);
await renderer.init();

// Each chunk's base payload starts at its own 64 KiB-aligned offset, so the
// renderer takes the reader that owns those offsets. Every chunk's 16-byte
// PackedBaseSplat records go into a GPU storage buffer as stored.
const bytes = new Uint8Array(await (await fetch("/assets/scene.gspx")).arrayBuffer());
const reader = GspxWasmReader.from_buffer(bytes);
const scene = renderer.setScene(reader);
reader.free(); // setScene has uploaded every chunk; the reader can go

renderer.render({ view, projection });
console.log(`${scene.splatCount} of ${scene.declaredSplatCount} splats in ${scene.chunkCount} chunks`);

How checked: the API was checked against packages/cyclepack-webgpu/src/renderer.ts. Not run here: it needs a browser with WebGPU.

Output
Not run for this page (needs WebGPU).

Lua 5.4: ChaCha20-Poly1305 pipeline writing a CPAK container

pipeline.lua
-- Lua 5.4: ChaCha20-Poly1305 pipeline writing a CPAK container
--   cyclepack pack --script pipeline.lua --input assets --output game.cpak \
--                  --config password=...
-- Modules are globals the runtime injects; a sandboxed script has no require.

function pack(input_dir, output_file)
    -- A fresh salt per archive, stored in its header for the unpack script
    local salt = crypto.random.bytes(16)
    -- A 32-byte key, stretched from the password with Argon2id
    local key = crypto.kdf.argon2(config.password, salt, 32)

    local builder  = archive.builder()
    builder:set_header("kdf_salt", salt)
    local pipeline = archive.pipeline():add("lz4"):add("chacha20", key)

    for _, path in ipairs(io.walk(input_dir)) do
        if io.is_file(path) then
            -- The name in the archive: the path less the input directory,
            -- whether or not --input ended in a slash
            local name = path:sub(#input_dir + 1):gsub("^/+", "")
            -- Compressed, then encrypted, one entry at a time (each file is read into memory)
            builder:add_stream(name, path, pipeline)
        end
    end

    builder:build(output_file)
    log.info("packed " .. builder:entry_count() .. " entries")
end

How checked: run with the command in its header over the eight demo files. The salt travels in the container header: archive.reader(bytes):get_header("kdf_salt") returned its 16 bytes for the unpack side. For a sealed Format v3 archive that the C, Rust, Node and WASM readers open with its key, use cyclepack-studio pack --encryption chacha20-poly1305 --key-file key.bin --generation-id N instead.

Output
[INFO] packed 8 entries

.gspx: frustum cull, then upload

splatting.lua
-- Lua 5.4: .gspx Gaussian splats: frustum cull, then upload
--   cyclepack run splatting.lua

local reader = gspx.open_reader(io.read_file("world/scene.gspx"))
log.info(reader:chunk_count() .. " chunks, " .. reader:total_splats() .. " splats")

-- Six frustum planes, each {a, b, c, d}. Returns the chunk indices inside them.
local visible = reader:cull_frustum({
    {1, 0, 0, 1000}, {-1, 0, 0, 1000}, {0, 1, 0, 1000},
    {0, -1, 0, 1000}, {0, 0, 1, 1000}, {0, 0, -1, 1000},
})

for _, chunk in ipairs(visible) do
    local descriptor = reader:get_chunk_descriptor(chunk)

    -- 16 bytes per PackedBaseSplat, in the layout a GPU storage
    -- buffer takes as-is
    local payload = reader:read_base_payload(chunk)

    log.info(("chunk %d: %d splats, lod %d, %d bytes"):format(
        chunk, descriptor.splat_count, descriptor.lod_level, payload.length))
end

How checked: run with cyclepack run on a .gspx converted from the 64-splat fixture tests/fixtures/studio/gaussian_64_binary_le.ply with --chunk-size 16.

Output
[INFO] 4 chunks, 64 splats
[INFO] chunk 0: 16 splats, lod 0, 256 bytes
[INFO] chunk 1: 16 splats, lod 0, 256 bytes
[INFO] chunk 2: 16 splats, lod 0, 256 bytes
[INFO] chunk 3: 16 splats, lod 0, 256 bytes

Meshlet cluster DAG

meshlet.lua
-- Lua 5.4: meshlet cluster DAG: open a container, unpack the clusters you need
--   cyclepack-studio convert-mesh --demo --output models/demo.cdag
--   cyclepack run meshlet.lua

local dag = meshlet.load_container(io.read_file("models/demo.cdag"))

local raw_bytes, packed_bytes, ratio = dag:compression_metrics()
log.info(("%d -> %d bytes (%.2fx)"):format(raw_bytes, packed_bytes, ratio))

-- Cluster ids are 0-based. Unpacking returns flat float and index arrays,
-- ready for a vertex buffer without a per-triangle Lua loop.
local mesh = dag:unpack_clusters({0, 1})

log.info(("%d vertices, %d indices"):format(#mesh.positions // 3, #mesh.indices))

How checked: run with cyclepack run on the container that convert-mesh --demo writes (a generated 1,152-triangle mesh, 21 clusters).

Output
[INFO] 46304 -> 20004 bytes (2.31x)
[INFO] 128 vertices, 438 indices

Copy-on-write overlays

overlay.lua
-- Lua 5.4: copy-on-write overlays: patches, snapshots and branches
--   cyclepack run overlay.lua
-- No archive is mounted beneath the overlay in this snippet.

local layers = overlay.create_engine()

-- The patch lands in a new copy-on-write layer
layers:cow_write("assets/hero.mesh", io.read_file("patch/hero.mesh"))
layers:take_snapshot("v2.4")

-- A bad patch is one rollback away
layers:cow_write("assets/hero.mesh", io.read_file("patch/hero_broken.mesh"))
layers:rollback_to_snapshot("v2.4")

-- One branch per build, so QA and live can diverge in the same process
layers:create_branch("live-patch")
layers:switch_branch("live-patch")

for _, path in ipairs(layers:modified_diff()) do
    log.info("overlaid " .. path .. " (" .. layers:read(path).length .. " bytes)")
end

How checked: run with cyclepack run, with a 5,000-byte patch/hero.mesh and a truncated patch/hero_broken.mesh. The rollback restores the 5,000-byte version.

Output
[INFO] overlaid assets/hero.mesh (5000 bytes)

Merkle root and per-entry proofs

security.lua
-- Lua 5.4: Merkle root over everything you ship, and a proof per entry
--   cyclepack run security.lua
-- Format v3 already records a BLAKE3 digest per entry; this adds one
-- root you can sign and ship beside the archive.

local paths, leaves = {}, {}
for _, path in ipairs(io.walk("assets")) do
    if io.is_file(path) then
        paths[#paths + 1] = path
        leaves[#leaves + 1] = crypto.hash.sha256(io.read_file(path))
    end
end

-- 32-byte leaves. The root is what you sign.
local root = crypto.merkle.new(leaves)
io.write_file("assets.root", root)

-- A proof for one entry, so a client checks it without the whole set.
-- Leaf indices are 0-based, the way the tree counts them.
local proof = crypto.merkle.proof(leaves, {0})
if not crypto.merkle.verify(root, {0}, {leaves[1]}, #leaves, proof) then
    error("integrity violation: " .. paths[1])
end

log.info("merkle root over " .. #leaves .. " files verified")

How checked: run with cyclepack run over the eight demo files.

Output
[INFO] merkle root over 8 files verified
Access by request Request SDK access

Architecture

Engine-first, lock-free architecture.

Engineered for real-time asset streaming with zero CPU memory duplication.

  1. 1 · Path Resolution: URI request sanitized and resolved across mounted archives by priority order.
  2. 2 · Index Lookup: O(1) table lookup or learned piecewise-linear index with Bloom filtering.
  3. 3 · Direct Borrow: Stored assets return directly as memory-mapped slices (zero copy). Compressed or sealed assets decode in memory with BLAKE3 validation.
  4. 4 · Engine Handover: Clean, verified asset bytes delivered directly into game engine memory.
CyclePack four-stage read pipeline.
Nativesrc/vfs/engine.rs · src/vfs/mmap.rs

Zero-copy VFS core

Virtual filesystem over mounted archives. Stored entries are borrowed directly from memory mappings without copying or thread locks.

Nativesrc/core/sealed.rs · src/core/entry_codec.rs

Sealing and signatures

Full-archive encryption with AES-256-GCM or ChaCha20-Poly1305. Per-archive keys derived via HKDF-SHA256 with BLAKE3 content digests.

Nativesrc/licensing · src/vfs/cloud.rs

Offline-first, zero telemetry

Opens zero network sockets. License tokens verify offline via embedded RSA keys, and no engine feature is locked or throttled without a license.

Nativesrc/vfs/learned_index.rs

Learned index

Piecewise-linear model predicts asset offsets within ±ε, combined with Bloom filters to reject non-existent paths before querying disk.

Nativesrc/lua/sandbox.rs

Embedded Lua 5.4

Full Lua 5.4 runtime with an opt-in sandbox restricting filesystem access and disabling dangerous functions for safe user-generated content.

Nativesrc/vfs/gspx.rs · packages/cyclepack-webgpu

Gaussian splatting (.gspx)

Packed 16-byte splat records aligned to 64 KiB boundaries. Chunk bounds and frustum culling run directly on GPU vertex shaders.

Nativesrc/vfs/sparse_virtual_texture.rs · src/vfs/ntc.rs

SVT and neural textures

64 KiB sparse virtual texture page tables, and neural texture compression (INT8 latent grids with fast MLP inference) for high-density materials.

Nativesrc/vfs/meshlet.rs

Meshlet cluster DAG

Geometry clustering up to 128 triangles with precomputed error metrics, deterministic generation, and fast continuous LOD selection.

Nativesrc/vfs/fastcdc.rs · src/vfs/transaction.rs

FastCDC delta patching

Content-defined chunking ensures game updates ship only changed byte blocks, written atomically with crash-safe transaction staging.

Nativesrc/vfs/async_worker.rs · src/vfs/telemetry.rs

Async workers & telemetry

Background thread pool with non-blocking polling tokens and C-ABI callbacks, instrumented with sub-millisecond Prometheus latency histograms.

Native on LinuxEmulatedsrc/vfs/linux_uring.rs · src/vfs/capability.rs

Modern I/O backends

Native Linux io_uring asynchronous I/O and cross-platform DirectStorage, CXL, and NVMe ZNS emulation models.

Nativesrc/codegen · drivers/ · src/rpc

Codegen & 33 drivers

Generate layout-identical structs for C++, C#, Go, Python, and Rust, with 33 language drivers over the stable C ABI.

Engines

First-class engine plugins & simple C ABI

CyclePack integrates directly into major game engines and custom runtimes. Stored entries return direct memory-mapped pointers with zero buffer copies.

Unreal Engine 5 plugin

UE 5.4+ · IPlatformFile & IoDispatcher
// In YourProject.Build.cs
PublicDependencyModuleNames.AddRange(new string[] { "CyclePackRuntime" });

// In your Game Mode or startup module (UE 5.4+, IPlatformFile layer):
if (FCyclePackPlatformFile* Vfs = FCyclePackPlatformFile::Get())
{
    Vfs->MountCpk(TEXT("World/"), TEXT("Content/Paks/WorldData.cpk"));
}

integrations/unreal/CyclePack provides high-throughput asset streaming via IPlatformFile and IoDispatcher backends.

Unity 6 package

Unity 6 UPM · Fast C# P/Invoke
// In Packages/manifest.json:
//   "com.cyclechain.cyclepack": "file:../integrations/unity/com.cyclechain.cyclepack"
using CyclePack;

var vfs = new CyclePackRuntime();
vfs.Mount("assets/", Application.streamingAssetsPath + "/game_assets.cpk");
byte[] modelBytes = vfs.Read("assets/characters/hero.mesh");

integrations/unity/com.cyclechain.cyclepack provides seamless archive mounts and synchronous uncompressed reads in C#.

Godot 4.3+ GDExtension

Godot 4.3+ GDExtension
# Godot 4.3+ GDScript, through the GDExtension in integrations/godot/CyclePack
var vfs = CyclePackRuntime.new()
vfs.mount("assets/", "res://data.cpk")  # res:// is globalized for you
var texture_data: PackedByteArray = vfs.read_file("assets/skybox.exr")

integrations/godot/CyclePack mounts archives and streams assets directly into Godot scenes and scripts.

C / C++ in your own engine

Standard C11 / C++17 · Native Zero-Copy
// PC, Linux, macOS: a stored entry is a pointer into the mapping
#include <cyclepack.h>

// One CPK3 container holds its own index, so the same path goes in twice.
CyclePackUnpacker* vfs = cyclepack_create_unpacker("game.cpk", "game.cpk");
uint32_t size = 0;

// Borrowed straight out of the mapped page: no copy, and nothing to release.
const uint8_t* bytes = cyclepack_get_data_ptr(vfs, "meshes/hero.mesh", &size);

Mount and read in 3 lines of standard C. See main.cpp in the code console for the complete working program.

WASM reader

WebAssembly & WebGPU · Zero-Copy Slice
// pkg/web, built by scripts/build_wasm.sh (wasm-pack --target web)
import init, { CyclePackWasm } from './pkg/web/cyclepack.js';

await init();

const response = await fetch('/web_bundle.cpk');
const vfs = CyclePackWasm.from_buffer(new Uint8Array(await response.arrayBuffer()));
const rawShader = vfs.get('shaders/pathtracer.wgsl');  // Uint8Array

Read archives in the browser with CyclePackWasm. Supports fast key decryption, generation floors, and typed Uint8Array byte views.

Plugins

  • Unreal Engine 5
  • Unity 6
  • Godot 4.3+

Maintained in integrations/unreal/CyclePack, integrations/unity/com.cyclechain.cyclepack and integrations/godot/CyclePack.

Languages and scripting

  • C
  • C++17
  • C# / .NET 8
  • Rust
  • Python
  • Go
  • LuaJIT (FFI driver)
  • WASM / WebGPU
  • Lua 5.4, embedded

33 language drivers over the stable C ABI.

16 engine examples

  • Fyrox
  • Macroquad
  • MonoGame
  • LÖVE
  • Defold
  • Raylib (C)
  • Cocos2d-x
  • GameMaker
  • O3DE
  • Ren'Py
  • Solar2D

Ready-to-use integration starters and templates for custom workflows and framework testing.

Verified this release: macOS arm64. Linux and Windows builds available via source.

Evidence

Verified benchmarks & evidence

Every metric is reproducible with the command that produced it. Verified on macOS arm64 on release v0.10.0.

135 defects

Identified and resolved across 5 rigorous review rounds, with zero regressions.

docs/changelog.md, the 0.10.0 summary

80.2% line coverage

Comprehensive test coverage across every crate, test target, and feature flag.

scripts/coverage.sh (cargo-llvm-cov, all features, every test target)

Measured 2026-09-24 and recorded in the script; not re-run for this page.

166 C-ABI exports

Every export is panic-guarded, ABI-versioned, and validated against manifest checks.

python3 scripts/generate_abi_manifest.py --check

7 of 14 native

Hardware capabilities verified natively on test host; all others fallback to safe emulation.

cyclepack doctor

25 + 34 tests passed

Capability-truth and multi-mount mmap engine contracts pass with zero failures.

cargo test --test capability_truth_tests --test mmap_engine_contract_tests

Debug build, rustc 1.90.

17,553 bytes

FastCDC sub-file patch for a 153 KB asset with 1 modified chunk (88% bandwidth reduction).

cargo run --release --example cross_fastcdc_subfile_patch

Release build.

133 KB gzipped

Complete WASM runtime with cryptographic verification, well under the 250 KiB budget.

scripts/build_wasm.sh (wasm-pack 0.13, wasm-lite features)

25 of 25 matched

Native and WebAssembly tools produce byte-identical archives and mesh/texture containers.

node scripts/studio_parity_test.mjs

CyclePack reports only verified deterministic metrics. Synthetic in-memory micro-benchmarks are omitted in favor of real-world reproducible tests.

Pricing

Licensing for indies and studios

Indie is free for studios under $200,000 in revenue plus funding. Pro and Enterprise are licensed per title. No tier pays a runtime royalty.

Indie

$0

For studios whose revenue plus funding over the last 12 months is under $200,000.

  • All features listed under “In every tier”
  • Unlimited titles
  • A free named licence
  • Community support
  • 0% runtime royalty
Apply for the free Indie licence

Enterprise

from$15,000per title, or a studio-wide agreement

For budgets over $2,000,000.

  • Everything in Pro, plus:
  • The source code of each licensed release (not the repository history)
  • 8-hour priority support
  • Custom terms
  • 0% runtime royalty
Talk to us about Enterprise

Evaluation: free for 30 days, for studios above the Indie threshold who want to try CyclePack before buying. No commercial release under an evaluation. Request an evaluation

What 0.10.0 enforces: nothing. A licence is a commercial term in this release: no command, tool or read path requires one, and no feature is locked without one.

In every tier

  • The core VFS and Format v3 archives
  • Zero-copy reads of stored entries
  • Whole-archive AES-256-GCM and ChaCha20-Poly1305 sealing, BLAKE3 digests
  • Lua 5.4 with the opt-in sandbox
  • Unreal Engine 5, Unity 6 and Godot 4.3+ plugins
  • The C ABI (cdylib and staticlib) for in-house engines
  • Multi-mount trees and copy-on-write overlays
  • FastCDC patching
  • The learned index
  • .gspx, SVT, the meshlet DAG, and the NTC container with its CPU decoder
  • io_uring on Linux
  • The emulated hardware models
Compare the tiers
What you getIndieProEnterprise
Licence and support
Who it is forRevenue plus funding under $200,000 over the last 12 monthsA production budget up to $2,000,000Budgets over $2,000,000
Price$0$2,500 per title, one timeFrom $15,000 per title, or studio-wide
TitlesUnlimitedOne licence per titlePer title, or a studio-wide agreement
Runtime royalty0%0%0%
C++ driversNot includedIncludedIncluded
Source codeNot includedNot includedEach licensed release, not the repository history
SupportCommunity48 hours8 hours, priority
EvaluationNot neededFree for 30 days above the Indie threshold; no commercial release
Technology (the same in every tier)
ContainersFormat v3 archives; IoStore-style and CASC-style containers in CyclePack's own layouts (96-bit FIoChunkId). The Unreal plugin serves IoDispatcher from them.
MeshletsClusters of up to 128 triangles and 64 vertices, per-cluster LOD selection on the CPU
Gaussian splatsChunk bounds and frustum culling; 16-byte records a GPU storage buffer takes as stored
Virtual texturing64 KiB tiles and a sampler-feedback residency model
I/OKernel I/O rings (io_uring) on Linux, pread/mmap with no ring elsewhere; GPU-initiated BaM planned
Remote chunksRemote chunk sync through a fetcher your host provides
CompressionLZ4, Zstd, Deflate, Gzip, Snappy, Brotli; chunked LZ4 in 64 KiB blocks; GPU GDeflate: planned
SchedulingFive priority bands with starvation aging, deadlines, request coalescing and in-flight budgets; the DirectStorage model's queue has four levels
ScriptingLua 5.4 with the opt-in sandbox
CryptographyAES-256-GCM and ChaCha20-Poly1305 sealing of the whole archive (header, manifest and entries) with 32-byte keys and a generation floor; RSA-2048 (PKCS#1 v1.5, SHA-256) signatures on licence tokens and patch manifests; BLAKE3 digests
IntegrityCopying reads check each entry's BLAKE3 digest, and cyclepack verify checks every entry; a zero-copy borrow returns the stored bytes, so verify the archive once when you install it. Merkle proofs in Lua.
Decompression ceilings256 MiB in Lua and WASM (configurable in WASM), 4 GiB natively
Negative lookupsBloom filter in the learned index
Hardware modelsEmulated DirectStorage, CXL, ZNS, computational storage, GPUDirect and RDMA; see Capabilities

FAQ

Frequently asked questions

Answers to common questions about memory streaming, zero-copy architecture, engine integrations, and licensing.

What is CyclePack, and what problem does it solve?

CyclePack is an SDK for packaging, protecting, and loading game assets—including models, textures, and audio—as they are needed at runtime. It integrates with your game engine to reduce unnecessary memory copies, manage asset access, and deliver smaller updates.

What does “zero-copy” mean? Does it work with every file?

Zero-copy lets your engine access asset data directly from a memory-mapped archive without copying it into an additional memory buffer. In CyclePack, this applies to uncompressed, unencrypted entries. Compressed or encrypted assets must first be decoded into memory. Zero-copy reduces unnecessary duplication; it does not mean zero RAM usage.

Can I use CyclePack with my existing game engine?

CyclePack provides integrations for Unreal Engine 5.4+, Unity 6, and Godot 4.3+. Custom engines can integrate through the C ABI, while browser applications can use the WebAssembly reader. Release v0.10.0 is verified on macOS arm64; Linux and Windows builds are available through source. Consoles are not supported, and mobile builds are not yet available. Some advanced features, including DirectStorage and GPUDirect, currently use emulated implementations. Run cyclepack doctor to check which capabilities are native on your system.

Do small changes require players to download the entire asset package again?

CyclePack’s FastCDC-based patching identifies changed chunks within files, allowing updates to distribute those chunks instead of the entire package. This can substantially reduce download sizes when only a small portion of a large asset changes. Actual savings depend on the content and the changes made. Snapshots and rollback mechanisms also support restoring earlier content after a faulty update.

How does CyclePack protect assets and detect corruption?

Archives can be encrypted with AES-256-GCM or ChaCha20-Poly1305, keeping file paths, sizes, and file counts confidential without the key. BLAKE3 digests detect content corruption, while signed patch manifests authenticate updates. Zero-copy reads do not repeat integrity checks on every access, so archives should be validated during installation using cyclepack verify.

Is CyclePack free? Are there royalties or an internet requirement?

Studios with combined revenue and funding below $200,000 over the previous 12 months can apply for a free Indie licence. Pro costs $2,500 per title as a one-time payment, while Enterprise starts at $15,000 per title. No tier charges runtime royalties, and the SDK works offline. Pro includes 12 months of SDK updates; renewal is optional, and shipped games continue working with their existing version. SDK access is available by request. The free Indie licence is not an open-source licence.

Start

Link one C library and one header, or install the Unreal, Unity or Godot plugin.