Architecture
The library is one published npm package (declarative-hex-worlds) that internally decomposes into 20 domain sub-packages under src/. Cross-domain imports traverse barrel re-exports only — Biome’s noRestrictedImports rule enforces it.
Sub-package map
Section titled “Sub-package map”| Sub-package | Purpose | Public surface |
|---|---|---|
traits/ |
koota trait declarations (37 traits) | declarative-hex-worlds/traits |
types/ |
Branded primitives + interface definitions | declarative-hex-worlds/types |
coordinates/ |
Hex algebra, projection, grid layout, world↔hex transforms | /coordinates, /grid, /layout, /projection |
manifest/ |
KayKit pack manifest schema + the autogenerated FREE manifest | /manifest/schema, /manifest/free |
ingest/ |
Node-side asset ingest, source-root validation, manifest generation | /ingest |
cli/commands/bootstrap/ |
CLI bootstrap subcommand + programmatic API; downloads + verifies KayKit assets; layout descriptors |
/bootstrap, /bootstrap/upstream-layout |
gameboard/ |
Plan builders, terrain/connectivity placement construction, occupancy-backed navigation, spawn groups, patrol routes | /gameboard, /navigation, /occupancy |
pieces/ |
Custom piece declarations + cross-kit compatibility | /pieces |
rules/ |
Plan + scenario validation, layout fill rules | /rules, /validation, /rule-types |
scenario/ |
Recipe → blueprint → scenario compilation, catalog queries, guide-page data, public treatment construction | /scenario, /recipe, /blueprint, /catalog, /registry |
actors/ |
Actor traits, queries, registration, navigation profiles, targets | /actors |
movement/ |
Movement agents + step execution | /movement |
patrol/ |
Patrol routes + assignment + scripted patrol simulation | /patrol |
quests/ |
Quest entities, objectives, progress, completion | /quests |
commands/ |
Interaction handler presets, command planning | /commands |
selectors/ |
Internal selector helpers (@internal-tagged) |
/selectors |
koota/ |
createWorld + per-tile/per-actor spawn helpers | /koota |
runtime/ |
Runtime facade for snapshots + asset-root resolution | /runtime |
systems/ |
Command dispatch, per-tick movement/patrol/quest orchestration, rules | /systems, /world-rules |
simulation/ |
Scripted scenario simulation engine | /simulation |
interop/ |
Neutral ECS snapshot, external asset compatibility, release-readiness coverage | /interop, /compatibility, /coverage |
react/ |
React bindings: provider, hooks, selectors | /react |
three/ |
three.js bindings: loaders, scene helpers, animation | /three |
cli/ |
The declarative-hex-worlds Node binary |
/cli |
errors/ |
7-class taxonomy: GameboardError + 6 subclasses |
/errors |
Interop and release coverage
Section titled “Interop and release coverage”interop/ has two deliberately separate jobs:
- Runtime interop lives in
interop.tsandcompatibility.ts. These modules normalize live ECS state into neutral snapshots and adapt external assets so games can inspect, serialize, and bridge runtime data. - Release-readiness interop lives in
coverage.ts. It aggregates guide-page coverage, manifest compatibility, required screenshot artifacts, local reference-pack status, and package/CI gate results into the/coveragemaintainer-facing report.
Keeping release coverage beside runtime interop is intentional: the release ledger exists to prove that the public interop promises are backed by docs, assets, visuals, and package checks. Runtime consumers should reach for /interop or /compatibility; maintainers and CI use /coverage to audit release evidence.
ECS layering
Section titled “ECS layering”The runtime is built on koota. koota’s discipline:
- Traits (
traits/) declare the data — pure data, no behavior. - Systems (
systems/) read traits, decide what to do, mutate traits. Pure functions overworld. - Actions (the
*Actionsexports in each domain) are the bundled mutations consumers call. - Selectors / Queries read traits without mutation.
The library follows that discipline strictly. src/traits/ exports every trait declaration in one barrel so cross-package trait identity stays stable (PRD invariant §6: splitting: true + trait identity test E4 pin this).
Build pipeline
Section titled “Build pipeline”tsup builds with splitting: true so each subpath in package.json#exports becomes its own chunk. Trait identity survives across chunks (E4 tests it). sideEffects: false is on so consumers tree-shake aggressively.
The pipeline runs tsc --noEmit for typechecking, then tsup for both JS + DTS bundling. The two-pass approach (instead of tsup --dts only) keeps cycle-detection separate from bundling.
Asset model
Section titled “Asset model”Per PRD §Phase RB, the library is bootstrap-not-bundle:
- The published tarball ships only
assets/free/manifest.json(the metadata). - The actual GLTF tree is fetched at install time by
pnpm exec declarative-hex-worlds bootstrap. - The bootstrap-target lives under
<consumer-out>/addons/kaykit_medieval_hexagon_pack/Assets/gltf/. - A
.bootstrap.jsonintegrity sidecar records per-file SHA256 + library version + source URL.
See the asset bootstrap guide for usage and the bootstrap layout reference for the upstream tree spec.
Source-of-truth
Section titled “Source-of-truth”docs/PRD/1.0.md in the repo holds the rationale for every architectural choice. .agent-state/directive.md holds the work queue. Both are tracked in git; if you see a layout decision that’s confusing, check the PRD for the why.