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
v0.10.0 · 5 review rounds · 135 defects fixed
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.
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
Capability report
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.
Integrity & Security
CyclePack is hardened against corruption, tampering, and malicious payloads across 135 verified test cases.
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
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
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
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
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
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
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.
#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).
borrowed 5000 bytes of meshes/hero.mesh
#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.
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)
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.
[INFO] vfs_pipeline.lua loaded resolved 5000 bytes
// 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.
⚙️ 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"
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.
wrote 4 bytes into a 16777216-byte arena
// 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.
Not run for this page (needs WebGPU).-- 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.
[INFO] packed 8 entries
-- 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.
[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
-- 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).
[INFO] 46304 -> 20004 bytes (2.31x) [INFO] 128 vertices, 438 indices
-- 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.
[INFO] overlaid assets/hero.mesh (5000 bytes)
-- 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.
[INFO] merkle root over 8 files verified
Architecture
Engineered for real-time asset streaming with zero CPU memory duplication.
Virtual filesystem over mounted archives. Stored entries are borrowed directly from memory mappings without copying or thread locks.
Full-archive encryption with AES-256-GCM or ChaCha20-Poly1305. Per-archive keys derived via HKDF-SHA256 with BLAKE3 content digests.
Opens zero network sockets. License tokens verify offline via embedded RSA keys, and no engine feature is locked or throttled without a license.
Piecewise-linear model predicts asset offsets within ±ε, combined with Bloom filters to reject non-existent paths before querying disk.
Full Lua 5.4 runtime with an opt-in sandbox restricting filesystem access and disabling dangerous functions for safe user-generated content.
Packed 16-byte splat records aligned to 64 KiB boundaries. Chunk bounds and frustum culling run directly on GPU vertex shaders.
64 KiB sparse virtual texture page tables, and neural texture compression (INT8 latent grids with fast MLP inference) for high-density materials.
Geometry clustering up to 128 triangles with precomputed error metrics, deterministic generation, and fast continuous LOD selection.
Content-defined chunking ensures game updates ship only changed byte blocks, written atomically with crash-safe transaction staging.
Background thread pool with non-blocking polling tokens and C-ABI callbacks, instrumented with sub-millisecond Prometheus latency histograms.
Native Linux io_uring asynchronous I/O and cross-platform DirectStorage, CXL, and NVMe ZNS emulation models.
Generate layout-identical structs for C++, C#, Go, Python, and Rust, with 33 language drivers over the stable C ABI.
Engines
CyclePack integrates directly into major game engines and custom runtimes. Stored entries return direct memory-mapped pointers with zero buffer copies.
// 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.
// 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+ 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.
// 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.
// 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.
Maintained in integrations/unreal/CyclePack, integrations/unity/com.cyclechain.cyclepack and integrations/godot/CyclePack.
33 language drivers over the stable C ABI.
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
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)
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
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
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
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.
$0
For studios whose revenue plus funding over the last 12 months is under $200,000.
$2,500per title, one time
For titles with a production budget up to $2,000,000.
from$15,000per title, or a studio-wide agreement
For budgets over $2,000,000.
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.
| What you get | Indie | Pro | Enterprise |
|---|---|---|---|
| Licence and support | |||
| Who it is for | Revenue plus funding under $200,000 over the last 12 months | A production budget up to $2,000,000 | Budgets over $2,000,000 |
| Price | $0 | $2,500 per title, one time | From $15,000 per title, or studio-wide |
| Titles | Unlimited | One licence per title | Per title, or a studio-wide agreement |
| Runtime royalty | 0% | 0% | 0% |
| C++ drivers | Not included | Included | Included |
| Source code | Not included | Not included | Each licensed release, not the repository history |
| Support | Community | 48 hours | 8 hours, priority |
| Evaluation | Not needed | Free for 30 days above the Indie threshold; no commercial release | |
| Technology (the same in every tier) | |||
| Containers | Format v3 archives; IoStore-style and CASC-style containers in CyclePack's own layouts (96-bit FIoChunkId). The Unreal plugin serves IoDispatcher from them. | ||
| Meshlets | Clusters of up to 128 triangles and 64 vertices, per-cluster LOD selection on the CPU | ||
| Gaussian splats | Chunk bounds and frustum culling; 16-byte records a GPU storage buffer takes as stored | ||
| Virtual texturing | 64 KiB tiles and a sampler-feedback residency model | ||
| I/O | Kernel I/O rings (io_uring) on Linux, pread/mmap with no ring elsewhere; GPU-initiated BaM planned | ||
| Remote chunks | Remote chunk sync through a fetcher your host provides | ||
| Compression | LZ4, Zstd, Deflate, Gzip, Snappy, Brotli; chunked LZ4 in 64 KiB blocks; GPU GDeflate: planned | ||
| Scheduling | Five priority bands with starvation aging, deadlines, request coalescing and in-flight budgets; the DirectStorage model's queue has four levels | ||
| Scripting | Lua 5.4 with the opt-in sandbox | ||
| Cryptography | AES-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 | ||
| Integrity | Copying 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 ceilings | 256 MiB in Lua and WASM (configurable in WASM), 4 GiB natively | ||
| Negative lookups | Bloom filter in the learned index | ||
| Hardware models | Emulated DirectStorage, CXL, ZNS, computational storage, GPUDirect and RDMA; see Capabilities | ||
FAQ
Answers to common questions about memory streaming, zero-copy architecture, engine integrations, and licensing.
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.
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.
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.
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.
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.
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
Access by request
Indie is free for studios under $200,000 in revenue plus funding over the last 12 months. Studios above that can evaluate free for 30 days before buying a Pro or Enterprise licence.
The server accepted your request. The CyclePack team replies by email; quote the reference below if you write about it.