๐Ÿ“„ Document ID: CP-ENG-WP-2026, revision 3 ๐Ÿ“ฆ Release: v0.10.0 ๐Ÿ›๏ธ Public Overview ๐Ÿ“… Revised October 2026

Virtual File System (VFS) Architecture Whitepaper

A public overview: how CyclePack reads and seals game archives, what sealing protects and what it does not, and what was measured

Target Audience:
Technical directors, engine and tools programmers, and build or pipeline engineers evaluating an asset file system for a game or a real-time application

1. Summary

Bottom Line Up Front (BLUF)

CyclePack is an asset archive and virtual file system for games and real-time applications. It is a Rust library with a C API, beta plugins for Unreal Engine 5, Unity 6 and Godot 4, and an embedded Lua 5.4 runtime for pipeline scripts. A game mounts archives and reads files from them by path.

  • Stored files are read in place. A file stored uncompressed and unsealed comes back as a borrow of the memory-mapped archive, with no copy and no lock on the read path.
  • Everything else is checked. In the CPK3 archives cyclepack-studio packs, a compressed or sealed file is decoded into a buffer and compared with its BLAKE3 digest.
  • Archives can be sealed under your own 32-byte key with AES-256-GCM or ChaCha20-Poly1305. Without the key nobody can list or read the files, and any edit makes the open or the read fail.
  • Sealing is not DRM. A game that opens its own sealed archives ships the key, and whoever extracts it reads everything.
What this page covers

This public overview covers the architecture at block level, the security model and its limits, what was measured, engines, platforms and licensing. The format specifications, key derivation, module internals, the C API reference and the test suites are in the technical reference that comes with SDK access (section 8).

Everything needed to judge the security model is here. Its protection rests on the keys, not on a secret design.

What is hardware here, and what is a software stand-in

DirectStorage, GPUDirect Storage, NVMe ZNS, NVMe computational storage and RDMA over RoCE v2 are models that run on the CPU in this release, labelled Emulated; the library links no DirectStorage, CUDA or RDMA verbs library. Two hardware interfaces are real: Linux io_uring, used by the Rust read backend when the kernel allows it (run so far on one kernel, in a container), and CXL memory that Linux exposes as a DAX device, mapped directly (not yet run on a real device; without one, CXL is emulated too).

Ask the binary rather than this page: cyclepack doctor prints a verdict for every capability on the machine it runs on, and cyclepack_query_capability_backing returns it to C callers (2 Native, 1 Emulated, 0 Unavailable).

0 copies
Stored, Unsealed Entries Read in Place
A borrow of the mapped archive: no read call per access and no lock. Compressed and sealed entries are decoded into a checked copy.
166 exports
C API, Every Export Panic-Guarded
A Rust panic inside the library reaches the host as an error code, not a crash. Section 6 says how this was checked.
7 of 14
Capabilities Native, and the Rest Say So
cyclepack doctor on an Apple-silicon Mac, 2026-10-02. The seven native ones are formats and algorithms implemented in software. Run it on your own machine.
80.2%
Line Coverage
Measured on 2026-09-24, before web mode landed; not re-run. The coverage script enforces a 77% floor.

2. The Problem: Per-File Loading and Open Archives

Most engines start with a loader that opens and reads one file per asset, and an archive format anyone can parse. Four costs follow, and each has an answer in CyclePack:

1
A system call and a copy per asset. Each read enters the kernel and copies from the page cache into a buffer. A mapped archive lets the kernel fault pages in on first touch, and CyclePack hands out a stored entry where it lies.
2
Contention on a shared archive. A loader that serialises reads behind one lock stalls every thread that wants an asset. CyclePack's reads take no lock; only mounting is exclusive.
3
Rebuilds for pipeline changes. When packing, validation or patch rules are compiled into the engine, changing one means a rebuild. CyclePack runs those steps as Lua scripts, which a host can load again without one.
4
Archives anyone can read or edit. A plain archive gives up its paths and contents to whoever has the file. A sealed archive gives up nothing to anyone without the key; section 5 says where that protection stops.
Figure 2.1: Per-File Loader vs. CyclePack Mapped Archive
graph TD subgraph PerFile ["Per-file loader"] App1["`Engine`"] -->|"`open and read per asset`"| Kernel["`Kernel file system`"] Kernel -->|"`copy out of the page cache`"| Copy["`User buffer, a copy`"] end subgraph Mapped ["CyclePack mapped archive"] App2["`Engine`"] -->|"`read by path: a table lookup`"| Vfs["`CyclePack VFS`"] Vfs -->|"`stored, unsealed entry: a borrow, no copy`"| Map["`Memory-mapped archive`"] Map -.->|"`first touch: the kernel reads the page`"| Disk["`Storage device`"] end

3. Architecture at Block Level

The core is Rust. Engines reach it through a C API (one header, a shared or static library), the engine plugins, or the Rust, Node/Deno/Bun and Python packages. The asset pipeline, the part that changes per project, is Lua 5.4 run by the same library.

Figure 3.1: Components
graph LR subgraph Hosts ["Hosts and tools"] Plugins["`Engine plugins: Unreal, Unity, Godot`"] OwnEngine["`Your engine, through the C API`"] RustPy["`Rust crate and Python package`"] NodePkg["`Node, Deno and Bun package`"] Browser["`Browser: WASM archive reader, web-mode decoder`"] Lua["`Lua 5.4 scripts: trusted or sandboxed runtime`"] Tools["`CLI and studio: pack, verify, inspect, doctor`"] end subgraph Library ["CyclePack library, Rust"] Vfs["`VFS: mounts by priority, layered overlays`"] Reader["`Archive reader: memory-mapped, each entry decrypted, decompressed and verified`"] Containers["`Streaming formats: splats, meshlets, neural textures, BVH, virtual-texture residency`"] Updates["`Live updates: generations, rollback, signed manifests`"] Sched["`Scheduler and I/O backends, hardware models labelled Emulated`"] WebMode["`Web mode: sealed JSON containers`"] end Plugins --> Vfs OwnEngine --> Vfs RustPy --> Vfs Lua --> Vfs NodePkg --> Reader Tools --> Reader Browser --> Reader Browser --> WebMode Vfs --> Reader Vfs --> Containers Vfs --> Updates Containers --> Sched

3.1 Mounts and Layers

Archives mount under path prefixes, each with a priority. The highest-priority mount that has a path wins, so a patch, a DLC or a mod overrides the base game without rewriting it; copy-on-write overlays layer changed files over a base archive. An optional mount type looks paths up with a learned index; no lookup time has been measured for it.

3.2 Streaming Containers

The library writes and reads containers for Gaussian splats, meshlet cluster hierarchies, neural textures and prebuilt bounding volume hierarchies, and keeps the tile residency of sparse virtual textures whose tiles are ordinary archive entries. Culling, level-of-detail selection, tile residency and ray queries run on the CPU; uploading, binding and drawing on the GPU are the engine's job. Two limits:

  • Neural textures: the bundled encoder downsamples a texture into the container and trains no decoder, so no compression ratio is claimed. Decoding runs on the CPU.
  • DirectStorage: the DirectStorage-shaped container holds LZ4 blocks decoded on the CPU, which a GPU decompressor cannot read. A native Windows backend is planned and needs Windows CI.

3.3 Scheduling and I/O

A scheduler orders requests in priority bands with deadlines, merges overlapping reads of the same asset, cancels, and applies backpressure. A zone-graph prefetcher adds speculative, cancellable requests; its hit rate has not been measured. This release has no path into GPU memory, so an emulated backend refuses a request meant for GPU memory unless the host allows it, and then the bytes land in host memory.

3.4 Hardware Paths

Every capability reports what serves it on the current machine (Figure 3.2). Linux io_uring is reachable from the Rust API only; the C API and the plugins read through memory maps. CXL memory has three placements: the process's heap, a shared mapping on one host, and a Linux DAX device, the only one reported as Native. None adds coherency across hosts, and nothing makes a write durable on persistent memory.

Figure 3.2: What Backs Each Hardware Path
graph LR Ask["`cyclepack doctor, or cyclepack_query_capability_backing`"] Ask --> Soft["`Formats and algorithms: FastCDC, learned index, SVT, NTC, splat streaming, scheduler, prefetch`"] Soft --> SoftV["`Native: software in this library`"] Ask --> Uring["`Can this process set up an io_uring ring?`"] Uring -->|"`yes, on Linux`"| UringN["`Native, from the Rust API only`"] Uring -->|"`no`"| UringE["`Emulated: pread and mmap`"] Ask --> Dax["`Is there a Linux DAX device this process can map?`"] Dax -->|"`yes`"| DaxN["`Native: mapped directly, not yet run on a real device`"] Dax -->|"`no`"| DaxE["`Emulated: shared or in-process memory`"] Ask --> Models["`DirectStorage, GPUDirect Storage, RDMA, ZNS, computational storage`"] Models --> ModelsE["`Emulated: models that run on the CPU`"]

3.5 Around the Core

  • Live updates: a new generation of the asset tree is published with one pointer swap; readers keep their snapshot, and the host can roll back.
  • Schema code generation: cyclepack codegen turns one schema into packed, layout-identical structs for C++, C#, Python, Go, Rust and Lua.
  • Off-thread reads: a worker pool runs reads off the caller's thread. The Python package awaits them without blocking asyncio; the Node package's reads are synchronous.
  • Telemetry: recorded in process. The host exports it as Prometheus text or OTLP JSON; the library opens no socket.
  • Remote chunks: remote reads go through a range fetcher the host implements. Fetched chunks are cached in memory and on disk, where a BLAKE3 checksum catches local damage; the fetch itself authenticates nothing. A peer-delivery model checks each chunk against its BLAKE3 address before storing it.

4. The Read Path

Figure 4.1 is what the code does when a host asks for a file. No GPU and no storage controller takes part.

Figure 4.1: A Read in This Release
graph LR Find["`Read by path: sanitise it, try mounts by priority, look up the entry`"] Find -->|"`no mount has it`"| NotFound["`Not found`"] Find -->|"`zero-copy read`"| Stored{"`Stored and unsealed?`"} Stored -->|"`yes`"| Borrow["`A borrow of the mapping: no copy, no hash`"] Stored -->|"`no`"| Refuse["`Refused with an error code: use a copying read`"] Find -->|"`copying read`"| Decode["`Decrypt and decompress into a copy, within the declared size`"] Decode --> Check{"`BLAKE3 digest matches?`"} Check -->|"`yes`"| Copy["`A checked copy`"] Check -->|"`no`"| Fail["`An error, and no bytes`"]
  • Locking: in Rust, reads take no lock and many threads can read at once; mounting needs exclusive access. The C API adds no lock of its own, and its contract says one handle is not used from several threads at once unless a function says it may be, which no cyclepack_vfs_* function does: a C host serialises its calls on one handle.
  • Verification: a borrow is not hashed. Run cyclepack verify when you install an archive: it decodes every entry through the copying path and compares each with its digest. Archives written from Lua use the older CPAK container, which records no digests, so neither kind of read checks them.
  • Sealed archives lend nothing zero-copy, with or without the key. Every read of a sealed entry is a checked copy.

5. Security Model and Its Limits

This section is meant to be complete enough to evaluate on its own. Four layers apply (Figure 5.1); none depends on a TPM, a secure enclave or a drive controller.

Figure 5.1: Four Layers, and What They Do Not Cover
graph LR L1["`Layer 1, parsing untrusted archives: sizes and counts bounded before allocation, decompression capped, paths sanitised`"] L2["`Layer 2, sealing: AES-256-GCM or ChaCha20-Poly1305, keys derived per archive, a generation floor`"] L3["`Layer 3, opt-in Lua sandbox: no process, environment or module access, host files only in chosen folders`"] L4["`Layer 4, C API: every export behind a panic guard`"] Out["`Not covered: the key holder, the process's own memory, the CPU time and Lua heap of a sandboxed script`"] L1 --> L2 L2 --> L3 L3 --> L4 L4 -.-> Out

5.1 What Sealing Protects

Hidden Without the Key

Every path, every per-entry size, every digest, and where one entry ends and the next begins.

Bound to the Key

Every header byte, the manifest, and every entry together with the path it is stored under. An entry that is moved, swapped, dropped or copied in from another archive fails.

Visible Without the Key

The magic, version and flags; the cipher and key-derivation identifiers; the archive UUID; the generation; the total payload and manifest sizes. The manifest is padded, so its size gives only a rough entry count. At run time, anyone watching the reader's I/O sees where entries start and end.

The ciphers are AES-256-GCM and ChaCha20-Poly1305. A key is exactly 32 bytes; any other length is refused, never padded or cut. Each archive derives its own keys from yours with HKDF-SHA256, every entry gets a fresh random nonce, and digests inside a sealed archive are keyed, so they cannot be matched across archives. A wrong key fails at open. Anyone can read the generation, but only a keyed open authenticates it. The key is given at open and nowhere else: no call adds a key to an archive that is already open, in any of the C, Rust, Node and WASM interfaces.

Figure 5.2: A Sealed Archive, With and Without the Key
graph LR subgraph Sealed ["A sealed archive"] H["`Header: plaintext, bound to the key`"] E["`Entry frames: encrypted, each bound to its path`"] M["`Manifest: encrypted and padded`"] end H --> NoKey["`Without the key: cipher, UUID, generation, total sizes, a rough entry count, and at run time the I/O pattern`"] M --> Open{"`Keyed open: right key, header and manifest unedited?`"} Open -->|"`no`"| Refused["`Refused at open: wrong key, or an edited header or manifest`"] Open -->|"`yes`"| Floor{"`Generation at or above the floor?`"} Floor -->|"`no`"| Stale["`Refused at open: older than the floor`"] Floor -->|"`yes`"| Reads["`Each read decrypts and authenticates its entry, and an edited entry fails`"] E --> Reads

5.2 Older Archives

Every keyed open takes a minimum generation, and an authenticated archive below it is refused. The library keeps no state between runs, so the floor comes from the host: a constant built into the game, or the highest generation it has opened. A floor of 0 accepts any archive.

5.3 Whom It Does Not Protect Against

  • The key holder. A game that opens its own archives ships the key, and whoever extracts it reads everything. Sealing stops keyless datamining and tampering, on disk, on a CDN or on a mirror. It is not DRM.
  • Anyone who can read the process's memory. Keys and decrypted bytes live in ordinary process memory. CyclePack wipes its own key material when dropped, but not the internal state of third-party crypto libraries, nor copies the compiler leaves on the stack.
  • A substituted archive. Any archive sealed under the same key at or above the floor opens, a DLC archive in place of a base one for instance. Use one key per archive family.
  • A host that ignores a failed mount, or a missing archive. Reads then fall through to the layers below, and the library cannot notice an archive it was never asked to open. Mount an explicit list, and treat a failed keyed mount as fatal.

5.4 What Sealing Costs Today

  • Sealed entries are never lent zero-copy (section 4).
  • Every repack draws fresh nonces, so two sealed releases share no chunks and chunk patches save nothing between them.
  • Chunked entries cannot be sealed yet.
  • The engine plugins mount unsealed archives only. For a sealed one, call the C API's keyed mount, cyclepack_vfs_mount_mmap_with_key, from your own code.
  • Lua and the Python package have no keyed open, and archives written from Lua use the older CPAK container, which has no per-entry digests and cannot be sealed.

5.5 Integrity Without Sealing

Every archive cyclepack-studio packs records a BLAKE3 digest per entry; copying reads check it, borrows do not. In an unsealed archive the digests are plain, so anyone who can rewrite the file can rewrite an entry and its digest together: they detect damage, not a deliberate edit. Authentication needs sealing or, for updates, a signed patch manifest.

5.6 Untrusted Input and the C Boundary

  • Sizes and counts read from a file are bounded before anything is allocated for them.
  • Decompressing an entry stops at the size the archive declares, and at 4 GiB natively or 256 MiB in WASM.
  • Paths are sanitised. Extraction refuses a path that escapes the output directory, and packing does not follow symlinks.
  • All 166 C exports run inside a panic guard, and release builds keep unwinding enabled so it can catch: a Rust panic reaches the host as an error code.

5.7 Scripts

The default Lua runtime is trusted: scripts get the full standard library and host file access, like any program the user runs; the cyclepack CLI runs pack scripts this way. Mods and downloaded pipelines belong in the sandboxed runtime. It removes process control, environment access and module loading, accepts text chunks only (no precompiled bytecode), and confines CyclePack's file bindings to folders the host chooses, re-checking each path after resolving symlinks. In both runtimes, CyclePack's buffers and file reads are capped at 512 MiB and its decompression helpers at 256 MiB. A script's syntax error, nil dereference or stack overflow comes back to the host as an error, and the next script on the same runtime still runs.

The sandbox does not limit CPU time or the Lua heap, and it runs inside the host's process. Run untrusted scripts in a separate process if a runaway script must not stall the host.

5.8 Updates

The library provides the pieces of an update, not an updater:

  • Patches built by content-defined chunking carry only the chunks that changed.
  • Signed patch manifests: an RSA signature (PKCS#1 v1.5 over SHA-256) under a public key the host supplies; the library sets no minimum key size, so supply a key of 2048 bits or more. A received manifest stays unusable until the signature verifies (only its claimed package id can be read, for logging); skipping the check takes a method named for exactly that, so every skip can be found by searching the code.
  • A limit in this release: the signed digest joins text fields and list items without their lengths, so a signature also verifies the same text split differently (a removed object ab listed as a and b). Treat the boundaries between list items as unauthenticated until that is fixed.
  • Atomic file replace: write a temporary file, fsync it, rename it over the target, then sync the directory where the platform allows it.
  • Generations with rollback.

The host downloads the patch, supplies the key and connects the steps; the library downloads nothing. A crash-consistency suite simulates an interruption at each step of a patch and requires the old or the new generation to be active, never a mix.

Figure 5.3: The Update Path, Split Between Library and Host
graph TD Publisher["`Publisher: content-defined chunking, the patch carries only changed chunks`"] subgraph HostSide ["Your host or launcher"] Download["`Downloads the patch and its signed manifest`"] PubKey["`Holds the publisher's public key`"] end subgraph LibSide ["CyclePack library"] Verify["`Signed-manifest check: fields unreadable until the signature verifies`"] Replace["`Atomic file replace: write, fsync, rename`"] Gen["`Generations: publish with one pointer swap, roll back`"] end Publisher --> Download Download --> Verify PubKey --> Verify Verify -->|"`host applies`"| Replace Replace -->|"`host publishes`"| Gen Replace -.->|"`interrupted before publish`"| Old["`The old generation stays active`"]

5.9 Web Mode (Preview)

Web mode is a small sealed container, ChaCha20-Poly1305 over DEFLATE, for the JSON a web page loads. The server derives a bucket key per time slot from a secret that never leaves it; the page fetches the keys it needs and decodes in a WASM decoder. Without the bucket key, a reader sees only the header (key id, generation, payload kind, codec, whether a context is bound) and the payload's length.

  • The secret must be random, 32 bytes or more, not a passphrase: any bucket key lets someone test guesses of the secret offline.
  • Every page that decodes holds the bucket key, so the person using the page, and any script in it, can read the data. Web mode raises the cost of scraping; it is not DRM.
  • Authentication shows only that a key holder wrote the container, and every page is one. Serve containers over HTTPS, and treat a payload as untrusted input even after it authenticates.
  • A context binds a container to its purpose, so one item's data cannot be served as another's, but not to a time: an older container for the same context still opens.
  • The page's decoder caps decoded output at 64 MiB by default and refuses nesting deeper than 512 levels. A hostile payload can still make it hold a few times that limit (at most 281 MiB of WASM memory in the cases measured), which the page does not get back; a page that knows its payload sizes lowers the limits.
  • The same rule holds for the WASM archive reader: a key used inside a page can be read by whoever runs it. Issue short-lived per-session keys after server-side authorisation.
Figure 5.4: Web Mode Trust Boundary
graph TD subgraph Server ["Your server"] Secret["`Secret: never leaves the server`"] Bucket["`Bucket key, derived per time slot`"] Enc["`Encoder: JSON into a sealed container`"] end subgraph Page ["The web page"] Dec["`WASM decoder`"] Json["`JSON for the page's script`"] end Scraper["`A scraper that runs the page`"] Secret --> Bucket Bucket --> Enc Enc -->|"`container over HTTPS`"| Dec Bucket -->|"`key endpoint, callable by anyone`"| Dec Dec --> Json Scraper -.->|"`gets the same key`"| Bucket

5.10 Not Covered in This Release

  • The computational-storage model's "decrypt" step is a repeating-key XOR on the CPU, whatever cipher a command names. It protects nothing; sealing is what protects content.
  • No Miri, Kani, Loom or sanitizer runs.
  • Nine fuzz targets exist; they were not run for this paper.
  • Release artefacts are not signed, and no SBOM or provenance attestation ships.
  • No confidential-computing support.

6. What Was Measured

Every figure names the command behind it. Commands ran on macOS 26.2, arm64 (Apple silicon); "not re-run" marks a figure carried over from an earlier run of this release. The library's tests also run on Linux (arm64, in a local container); Windows is not tested.

Measure Result How it was obtained Notes
Capability backing 7 of 14 native, 7 emulated, 0 unavailable cyclepack doctor 2026-10-02, debug build; the answer differs by host
C exports 166, each behind a panic guard python3 scripts/generate_abi_manifest.py --check for the count and the header; a scan of the C API source for the guards 2026-10-02; no test enforces the guard yet
Line coverage 80.2% (floor 77%) scripts/coverage.sh 2026-09-24, before web mode; not re-run
Patch size 17,553 bytes for a 153,639-byte file cargo run --release --example cross_fastcdc_subfile_patch One changed chunk; re-run 2026-10-02 (debug build), same figure
WASM archive reader 132,833 bytes gzipped (358,792 raw) scripts/build_wasm.sh Budget 250 KiB; gzip -9 of the 2026-10-02 build, which was not rebuilt for this page
Web-mode page decoder 40,152 bytes brotli scripts/build_wasm_web.sh Script 3,286, module 36,866; budget 64 KiB; brotli 11 of the 2026-10-02 build, the same as on 2026-10-01
Web-mode container vs. brotli 11 0.43 to 1.10 of the brotli size node scripts/web_size_bench.mjs Six synthetic fixtures, from a numeric chart (0.43) to a small crossword grid (1.10); 2026-10-01
Converter parity 25 of 25 matched node scripts/studio_parity_test.mjs Native and WASM outputs byte-identical, except a packed archive's random UUID; not re-run
Defects fixed in review 135 over five rounds The changelog Before web mode, which had its own review on 2026-10-01

No latency or throughput figure is published. The benchmark suite times in-memory and page-cached operations, not a disk; run cargo bench --bench vfs_bench on your own machine. When a benchmark reads a real archive from storage, its figure will be published with its command and machine.

Fault-injection tests corrupt and truncate archives and require an error, not a panic; crash-consistency tests are described in section 5.8. Neither simulates device faults such as an NVMe error or a network partition. Every suite, and how to re-run it, is in the technical reference.

7. Engines and Platforms

One library and one C header serve every integration. The three engine plugins are Beta, and all three mount unsealed archives only (section 5.4).

Unreal Engine 5.4+

Loads through Unreal's file layer and I/O dispatcher; reads CyclePack archives, not .pak or .utoc files. Not yet run inside Unreal: only part of its code has been checked to compile, against stand-in headers.

Unity 6

A UPM package that mounts archives and reads files from C#. Tested outside the Editor, on .NET 8 on macOS; Editor and IL2CPP runs are still to come.

Godot 4.3+

A GDExtension that mounts archives and reads files from GDScript. Runs headless on Godot 4.7.2; exported games are not tested yet.

  • C and C++: a shared or static library with one header and a CMake target, for engines without a plugin.
  • Examples: 33 language driver examples call the C API; most of their tests skip when the toolchain is absent. Sixteen engine examples are starting points to copy, not qualified like the plugins; six are incomplete.
  • Packages: the Rust crate; a Node, Deno and Bun package that binds part of the C API and reads synchronously; and a Python package that awaits reads on a worker pool.
  • Browser: the WASM archive reader, built and smoke-tested; web mode, a Preview (section 5.9); and a WebGPU splat preview package that does no depth sorting and was not run for this paper.

For this release the library was tested on macOS (Apple silicon) and on Linux (arm64, in a local container). Not tested: Windows, Intel Macs, and Linux on x86-64 or on a physical machine. There is no console, iOS or Android build.

Figure 7.1: One Library, Its Plugins and Its Platforms
graph LR Core["`CyclePack core, Rust`"] --> Lib["`libcyclepack: shared or static library, one C header`"] Core --> RustCrate["`Rust crate`"] Core --> Wasm["`WASM builds: archive reader, web-mode decoder in Preview`"] subgraph PluginSet ["Engine plugins: Beta, unsealed archives only"] UE["`Unreal Engine 5.4+: not yet run inside Unreal`"] Unity["`Unity 6: tested outside the Editor`"] Godot["`Godot 4.3+: runs headless on 4.7.2`"] end subgraph OverC ["Also over the C API"] Own["`Your engine`"] Drivers["`33 language driver examples`"] EngEx["`16 engine examples: starting points, six incomplete`"] Pkgs["`Node, Deno, Bun and Python packages`"] end subgraph Platforms ["Platforms in this release"] Tested["`Tested: macOS on Apple silicon, Linux arm64 in a container`"] NotTested["`Not tested: Windows, Intel Macs, x86-64 Linux, physical Linux machines`"] NoBuild["`No console, iOS or Android build`"] end Lib --> UE Lib --> Unity Lib --> Godot Lib --> Own Lib --> Drivers Lib --> EngEx Lib --> Pkgs Core -.-> Platforms

8. Licensing and the Technical Reference

The terms below are the ones in the pricing section; where this page and that section differ, that section is right.

  • Tiers: Indie is $0 for studios whose revenue plus funding over the last 12 months is under $200,000. Pro is $2,500 per title, one time, for budgets up to $2,000,000, with the C++ drivers and 12 months of SDK updates. Enterprise starts at $15,000 per title, or studio-wide, for larger budgets, and adds the source code of each licensed release. No tier pays a runtime royalty.
  • Evaluation: free for 30 days above the Indie threshold; no commercial release under it.
  • Support: community on Indie, 48 hours on Pro, 8-hour priority on Enterprise.
  • Enforcement: nothing in this release checks a licence. No command, tool or read path requires one.
  • Offline: the library opens no network socket, and nothing is sent home.
The technical reference, with SDK access

This page stops at the block level. The technical reference that comes with SDK access goes the rest of the way:

  • the CPK3 archive and CPKW web-container specifications, with their layouts and key derivation;
  • the C API reference and the ABI stability policy;
  • how the index, the scheduler and its cost model, the CXL regions and the streaming containers work;
  • the evidence behind section 6: the command for each figure, the fault-injection and fuzz suites, and what has not been run.

Customers read it at Customer docs (sign-in required), signing in with the email address their access was granted to: an emailed code, or Google or GitHub with the same address. To get access, request it with the button below; it comes in the team's email reply.

Evaluate CyclePack on Your Own Assets

Studios above the Indie threshold can evaluate free for 30 days, and Indie studios can apply for the free licence. Run cyclepack doctor on your target machines and cyclepack verify on your archives before you decide.

View Licensing Tiers