Asset bootstrap
declarative-hex-worlds is an asset-bootstrapping library, not
an asset-bundled one. The published npm tarball ships:
- The typed runtime, CLI, React + Three.js bindings.
- The FREE-edition
manifest.json(metadata only — asset ids, paths, bounds, taxonomy, file sizes).
The KayKit GLTF binaries themselves (≈30 MB for FREE, more for EXTRA) are
fetched at install time by the CLI bootstrap subcommand, or by calling the
programmatic bootstrapKayKitAssets API directly.
Why bootstrap, not bundle?
- Keeps the npm tarball lean (~1 MB shipped vs 30+ MB if bundled).
- Lets consumers re-use a single asset tree across multiple apps via a
shared
public/assets/models/directory. - Avoids redistributing CC0 binary blobs through npm's CDN.
- Supports the purchased EXTRA edition without forcing every consumer to redistribute the larger archive.
Quick start (FREE edition, from GitHub)
pnpm add declarative-hex-worlds
pnpm exec declarative-hex-worlds bootstrapThe default --out heuristic detects:
public/assets/models/(Vite / Next.js convention) — preferred.assets/models/(fallback).- The current working directory.
You can always pass --out <path> explicitly. After bootstrap, the tree
lives at:
<out>/addons/kaykit_medieval_hexagon_pack/
├── Assets/gltf/
│ ├── buildings/{blue,green,red,yellow,neutral}/...gltf
│ ├── decoration/{nature,props}/...gltf
│ └── tiles/{base,coast,rivers,roads}/...gltf
├── Textures/
│ └── hexagons_medieval.png
└── .bootstrap.jsonDownloadable default packs
Beyond the FREE tile pack, three first-class CC0 packs by Kay Lousberg compose into a full game from defaults — a hex board, playable characters, and enemies. Fetch any of them by id:
--out names the raw-assets root; each pack lands in <out>/<id>/, so
the default-source resolvers below always find it:
pnpm exec declarative-hex-worlds bootstrap --pack medieval-hexagon --out raw-assets
pnpm exec declarative-hex-worlds bootstrap --pack adventurers --out raw-assets
pnpm exec declarative-hex-worlds bootstrap --pack skeletons --out raw-assets
# → raw-assets/{medieval-hexagon,adventurers,skeletons}/| Pack id | Role | Fills |
|---|---|---|
medieval-hexagon | tiles | the terrain board |
adventurers | models | playable characters |
skeletons | models | enemies |
Packs are never tracked in git — keep raw-assets/ gitignored. They download
from their upstream GitHub archives on demand. Programmatically:
import {
bootstrapPack,
resolveDefaultPackKit,
assertPackPresent,
} from 'declarative-hex-worlds/bootstrap';
// Fetch a pack (lands in raw-assets/adventurers/).
await bootstrapPack('adventurers', { rawAssetsRoot: 'raw-assets' });
// Compose defaults from whatever is downloaded.
for (const pack of resolveDefaultPackKit('raw-assets')) {
if (pack.present) console.log(`${pack.id} → ${pack.dir}`);
}
// Or require one, failing clearly (with the fetch command) if it is missing.
const dir = assertPackPresent('skeletons', 'raw-assets');EXTRA edition (purchased)
The EXTRA edition is sold on kaylousberg.itch.io
and ships extra units/ GLTFs plus seasonal hexagons_medieval_{Fall,Summer, Winter}.png textures. Download the zip from itch.io, then:
pnpm exec declarative-hex-worlds bootstrap \
--source zip \
--zip ~/Downloads/KayKit_Medieval_Hexagon_Pack_1.0_EXTRA.zip \
--edition extra \
--out public/assets/modelsThe CLI auto-detects the edition from the zip's layout markers and refuses to overwrite a FREE-shaped target with EXTRA content (and vice-versa).
Reproducible bootstraps
The integrity sidecar (.bootstrap.json) records:
schemaVersion(currently1.0.0).edition(freeorextra).libraryVersion(the library version that performed the bootstrap).sourceUrl(the GitHub tarball URL, orfile://for zip sources).fetchedAt(ISO-8601 timestamp).files[]— sorted list of{path, sha256, bytes}for every mirrored file.
For deterministic CI builds, pass fetchedAt and libraryVersion to the
programmatic API:
import { bootstrapKayKitAssets } from 'declarative-hex-worlds/bootstrap';
await bootstrapKayKitAssets({
source: { kind: 'github', commit: 'main' }, // pin to a specific sha for reproducibility
out: 'public/assets/models',
fetchedAt: '2026-01-01T00:00:00.000Z',
libraryVersion: '1.0.0',
});Verifying an existing bootstrap
pnpm exec declarative-hex-worlds bootstrap --verify --out public/assets/modelsRe-hashes every file recorded in .bootstrap.json and reports any drift
(missing files, size mismatches, hash mismatches). Exits non-zero on drift —
useful as a CI step before deploys.
Wiring the runtime to your bootstrap target
Three ways to tell the loaders where your bootstrap target lives:
// 1. Per-call (most explicit)
import { resolveManifestAssetUrl, freeManifest } from 'declarative-hex-worlds';
const url = resolveManifestAssetUrl(freeManifest.assets[0], {
bootstrapAssetRoot: '/app/public/assets/models',
});
// 2. Process-wide (app boot)
import { setGameboardAssetRoot } from 'declarative-hex-worlds';
setGameboardAssetRoot('/app/public/assets/models');
// 3. Environment variable (Node consumers)
// HEX_WORLDS_ASSET_ROOT=/app/public/assets/modelsResolution priority: explicit setGameboardAssetRoot override →
globalThis.HEX_WORLDS_ASSET_ROOT → process.env.HEX_WORLDS_ASSET_ROOT
→ default public/assets/models.
Troubleshooting
bootstrap destination ... is not empty
The target already contains files. Pass --force to wipe and re-mirror, or
pick a different --out.
zip contains the EXTRA edition but bootstrap was asked for FREE
The zip's layout markers identify it as EXTRA. Add --edition extra (and
make sure you have a license / purchased copy).
failed to download KayKit FREE tarball ... unexpected status 404
GitHub returned a non-2xx response. Common causes:
- Pinned
--commit <sha>no longer exists (force-pushed or deleted). - Temporary GitHub outage. Retry, or pass
--source zipwith a locally downloaded copy.
bootstrap verify FAILED ... hash mismatch
A file on disk no longer matches its recorded sha256. Either a deploy
modified the tree (unlikely — the tree should be read-only after bootstrap)
or disk corruption. Re-run bootstrap --force to restore.
Runtime loads 404 — wrong asset root
The runtime falls back to public/assets/models if no override is configured.
If you bootstrapped to assets/models or elsewhere, set
HEX_WORLDS_ASSET_ROOT or call setGameboardAssetRoot(...) at boot.
Programmatic API
See the bootstrap reference for the full surface,
including the {@link BootstrapKayKitAssetsOptions}, {@link BootstrapResult},
and {@link BootstrapSidecar} types.
