Engine plugins · Godot stable

CAVS for Godot 4.

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.

Client

addons/cavs · class_name CavsClient

100% GDScript. Parses the binary CVSP protocol, decompresses with Godot's native zstd, caches in user://. No GDExtension, no native binaries.

Build side

tools/pack_release.sh

Exports the PCK headless and packages it — FastCDC 64 KiB + zstd + optional Ed25519 signature.

Runtime

every export target

Because it is pure GDScript, the client runs on every platform Godot exports to — desktop, mobile and web.

Install

Drop in the addon,
enable the plugin.

Nothing to compile on the client side — no native binaries, no GDExtension.

1 — copy addons/cavs/
Copy the addons/cavs/ folder from game-engine-plugins/godot-plugin/ into your project's root so you get res://addons/cavs/.
2 — enable it
Turn it on in Project → Project Settings → Plugins. (The runtime also works with a plain preload("res://addons/cavs/cavs_client.gd") even if the plugin is not enabled.)
3 — done
That's the whole client side. It runs on every platform Godot exports to.
Publish a release

Package and serve
your builds.

You 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).

bash — publish copy
# build the tools once
$ cargo build --release
# create a signing key so clients can verify releases
$ cavs keygen -o publisher.key
# export the PCK and package it (helper does both)
$ ./tools/pack_release.sh /path/to/your-godot-project pck game_v42 publisher.key
# → releases/game_v42.cavs
# serve the releases (behind TLS in production)
$ cavs-server releases/*.cavs --listen 0.0.0.0:8990 --tls-cert cert.pem --tls-key key.pem

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.

Runtime usage

Fetch and mount
from your game.

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.

CavsClient API

One class,
one loading screen.

MemberDescription
new(url)Client against a cavs-server (http:// or https://)
fetch(asset) → DictionaryBlocking (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) → boolfetch() + 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_msRetries with exponential backoff on network errors and 5xx
request_timeout_msPer-request timeout (default 30 s)
cache_dirPersistent cache (default user://cavs_cache)
ca_cert_pathTrusted PEM for self-signed dev TLS
require_sha256Warn if the manifest carries no per-file digests
Production notes

Verified, cached,
mounted early.

// integrity

Nothing corrupt mounts

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().

// cache

Immutable by content

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.

// mounting

Mount early

Load resource packs before the scenes that depend on them are preloaded — ideally from the boot autoload/scene.