Cubby Arcade

Raw Markdown โ†— ยท Manifest generator โ†“

Game-owned releases

The Cubby registry lists a stable manifest_url. A game's own repository and HTTPS domain own that release manifest and its files. A game release does not require a Cubby registry edit or a second GitHub release to insert checksums.

File release manifest: schema 2

{
  "schema_version": 2,
  "id": "example-game",
  "version": "1.0.0",
  "runtime": "web",
  "entrypoint": "index.html",
  "artwork": "cover.svg",
  "files": {
    "index.html": {
      "url": "https://example.com/releases/1.0.0/index.html",
      "size": 15,
      "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    },
    "cover.svg": {
      "url": "https://example.com/releases/1.0.0/cover.svg",
      "size": 6,
      "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    }
  }
}

This example uses **illustrative** byte sizes and hashes. Generate actual values from the exact build files before uploading; the manifest is not self-hashed.

One release workflow

  1. Build all offline game files, using local relative references for assets.
  2. Generate the manifest's sizes and hashes directly from that build directory.
  3. Upload those exact files under immutable versioned HTTPS paths.
  4. Verify they are available, then replace the stable manifest **last**.
  5. Publish the GitHub release in the same workflow. No second release, manually entered hash, or Cubby release is needed for subsequent game updates.

The registry's artwork/trailer/screenshots are discovery URLs, separate from installed relative asset paths. Browsing does not follow the release manifest. An adult metadata title is allowed; this does not change the wordless child UI.

Native installation and transition

The desktop installer accepts both the existing schema-1 ZIP release and the new schema-2 file release at the same registry manifest_url. Downloads and updates remain free; the native launcher charges only for game launches.

For file updates it reuses matching active-version files only after checking their actual size/hash and rejecting symlinked sources. Reused bytes are copied, not hard-linked. Missing, changed, or corrupt files download independently. The entire new version is staged before atomic activation, and activation waits until no game is running. Failed or interrupted installation leaves the active version and saves unchanged; a retry may redownload staged files from the failed attempt. Previous versions remain on disk.

On disk, Cubby generates its existing schema-1 digest-only manifest.json so the offline host and save namespaces do not change. A same-version ZIP-to-files migration is accepted when its payload inventory and game metadata are identical. Different bytes/metadata at an existing version are rejected. URL relocation alone is accepted. No version-ordering/downgrade policy is introduced by this contract.

Existing desktop bundles still seed from their ZIP archives without a network dependency. Games publishing schema 2 require an updated native installer; old desktop clients support schema 1 only. Checksums prove byte integrity, not publisher identity; the registry remains a trusted-developer list.

Generate in the game's repository

runtime/file_manifest.py is a standalone Python 3.11+ standard-library tool. Copy that one file into a game's release tooling (for example tools/cubby_manifest.py); it does not import Cubby or need its registry, installed application, ZIP tooling, or Python dependencies.

After building your game's offline output into dist/game/, a single release job can run:

uv run python tools/cubby_manifest.py dist/game \
  --id line-cutter --version "$RELEASE_VERSION" \
  --entrypoint play.html --artwork cover.png \
  --attract trailer.webm \
  --base-url "https://linecutter.david.app/releases/$RELEASE_VERSION/" \
  --output dist/release-manifest.json

Add --screenshot screenshots/example.png for each screenshot. Hashes, sizes, and safely URL-encoded file names come from the exact build output. The output manifest must be **outside** the payload directory; manifest.json is reserved inside a game installation. Existing payload manifests, symlinks, special files, unsafe paths, and oversized releases fail before publication.

Then, in that same CI job, use the game's hosting deploy command to upload dist/game/ to the versioned URL, verify the files, and publish dist/release-manifest.json at the stable store manifest URL **last**. Finally create the GitHub release with that same version/tag (or use a GitHub release as the job trigger). The hosting command is game-specific; this generator does not upload or create a second release. Do not rebuild files between hashing and uploading. Keep previous versioned files available.

The adult runtime.catalog CLI also accepts this schema through existing registry entries. Its update command is an explicit administrator operation: stop that game first. Unlike the native desktop scheduler, the standalone CLI cannot observe whether gameplay is active. Legacy ZIP commands remain available for existing releases; new game repositories do not need to use them.