Deduplicated content delivery for Godot games without changing the engine or the PCK format. The player downloads only the chunks that changed between versions; the pack is reconstructed byte-identically (SHA-256 verified) and mounted with ProjectSettings.load_resource_pack(). Measured with real PCKs exported by Godot 4.7: updates of −67% to −70% versus downloading the full compressed PCK.
100% GDScript. Parses the binary CVSP protocol, decompresses with Godot's native zstd, caches in user://. No GDExtension, no native binaries.
Exports the PCK headless and packages it — FastCDC 64 KiB + zstd + optional Ed25519 signature.
Because it is pure GDScript, the client runs on every platform Godot exports to — desktop, mobile and web.
Nothing to compile on the client side — no native binaries, no GDExtension.
1 — copy addons/cavs/2 — enable it3 — doneYou need the CAVS command-line tools once, to package and serve builds. Build them from the repository root with cargo build --release (produces cavs and cavs-server).
A runnable demo lives in game-engine-plugins/godot-plugin/demo/; demo/run_demo.sh wires the whole flow (export → package → serve → fetch → mount) automatically.
The first fetch installs the pack; later versions download only the chunks that changed. Mount packs as early as possible — ideally in the boot autoload/scene.
# On your loading screen (see demo/boot.gd for the full version with progress): var cavs := CavsClient.new("https://content.mygame.com") cavs.progress.connect(func(done, total, stage): progress_bar.value = done * 100.0 / total, CONNECT_DEFERRED) cavs.fetch_async("game_v42", func(result): if result.ok and cavs.ensure_pack("game_v42"): get_tree().change_scene_to_file("res://levels/level_new.tscn"))
Retries are safe by design: downloaded chunks stay in the content-addressable cache, so a retried fetch() — even after the game was killed mid-download — only fetches what is missing.
| Member | Description |
|---|---|
| new(url) | Client against a cavs-server (http:// or https://) |
| fetch(asset) → Dictionary | Blocking (for threads/tests); returns {ok, error, files, bytes_wire, chunks_inline, refs} |
| fetch_async(asset, on_done) | Internal thread; delivers the result on the main thread |
| ensure_pack(asset) → bool | fetch() + load_resource_pack() of the .pck |
| signal progress(done, total, stage) | Logical bytes for the progress bar; connect with CONNECT_DEFERRED |
| max_retries / retry_base_ms | Retries with exponential backoff on network errors and 5xx |
| request_timeout_ms | Per-request timeout (default 30 s) |
| cache_dir | Persistent cache (default user://cavs_cache) |
| ca_cert_path | Trusted PEM for self-signed dev TLS |
| require_sha256 | Warn if the manifest carries no per-file digests |
The server verifies BLAKE3 + Merkle + Ed25519 on load; the client verifies the per-file SHA-256 the packer embeds in the manifest. A corrupt chunk or tampered pack never reaches load_resource_pack().
Chunks are addressed by content, so the cache is shared across versions and assets — that is where the savings come from. Deleting user://cavs_cache is always safe.
Load resource packs before the scenes that depend on them are preloaded — ideally from the boot autoload/scene.