@velo/crown-base (0.4.0)
Installation
@velo:registry=npm install @velo/crown-base@0.4.0"@velo/crown-base": "0.4.0"About this package
@velo/crown-base
Static Crownfall map contracts for the frontend editor, with no React, Babylon, simulation or
server dependencies. Zod is the only runtime dependency. The package uses NodeNext .js imports
and publishes JavaScript and declarations from dist/. The game retains thin source re-exports;
the package never imports the game. helloWorld remains available for compatibility.
Editor consumer contract
Both the controlled authoring view (#714) and file controls (#715) use the same MapDocument.
It is a branded, validated document with typed terrain arrays. Obtain it from a successful result,
rather than a type assertion. The public entry point exports:
| API | Contract |
|---|---|
MapDocument |
Validated document: format, artifactVersion, schematic, optional assets |
StaticMap, TerrainField, MapAssetReference |
Represented static data, terrain authority and asset reference types |
validateMapDocument(input: unknown): MapDocumentResult |
Validate an in-memory candidate, returning independently owned data |
readMapArtifact(text: string): MapDocumentResult |
Parse JSON and validate every represented field before accepting it |
writeMapArtifact(document: MapDocument): string |
Revalidate edits and serialize to JSON; throws MapArtifactError on invalid data |
MapDocumentResult |
{ ok: true, document } or { ok: false, error } |
MapArtifactError, MapArtifactIssue |
Error with issues: { code, path, message }[]; codes are json or validation |
mapDocumentSchema, staticMapSchema, terrainFieldSchema |
Strict schemas for consumers needing schema composition; use the operations above for ownership and stable feedback |
MAP_ARTIFACT_FORMAT, MAP_ARTIFACT_VERSION |
Current format identifier and external schema version |
The authoring view receives document: MapDocument and reports
onDocumentChange(document: MapDocument). Clone the current document, edit the candidate,
raise candidate.schematic.field.version for any terrain change, validate, then report only a
successful result. Preserve the rest of the document, including unknown-to-the-view asset roles,
site names, instructions and tuning. Do not rebuild a document from only visible controls.
File controls receive the same current document. Open uses await file.text() followed by
readMapArtifact; only a successful result invokes onDocumentChange. Report
result.error.issues as validation feedback and keep the current document on failure. Save uses
writeMapArtifact(currentDocument) and reports any thrown MapArtifactError. These APIs perform
no file access or downloads and have no UI dependencies. Callbacks are a consumer convention,
not UI components exported by this package.
For package consumption, the editor implementation must declare @velo/crown-base as a runtime
dependency and import these names from @velo/crown-base. Its published dependency range must
include the release containing this contract. This change does not add an editor UI or require
consumers to import games/crownfall. In this repository, tests and the narrow game adapters use
relative source imports so no generated dist/ is required for development verification.
Container and references, version 1
Save a UTF-8 JSON manifest, conventionally manifest.crownfall.json. It is the entire static
map document. No archive is required for local editor round trips. Optional companion files live
under assets/ beside the manifest; a portable copy must keep that directory with the manifest.
Opening or saving the manifest does not load, copy, process or verify asset bytes. A manifest
can round-trip references even when companion files are unavailable.
The manifest has exactly format: 'crownfall-map', artifactVersion: 1, schematic and optional
assets. Unknown properties are errors at every object boundary, including private spawns,
trade assignments, ownership, remaining stock, inventories and participation. Future formats
must declare another supported version; version 1 does not silently discard unknown data.
schematic represents the existing supported dimensions (400 × 280m or 600 × 420m), seed,
settings, analytic river/crossing instructions (including optional branches), public clearings,
objectives, gold-node placements with positive initial stock, neutral bridges and outposts,
generator provenance and the terrain field. Forests, quarries and sheep remain battle-start
placements, outside this static contract. Horizontal coordinates and lengths are metres; terrain
heights and decks are whole centimetres. Numeric site IDs are unique across objectives, mines
and outposts. String bridge IDs are unique within neutral bridges.
The field is the sole terrain authority. Saved heights, materials and water are JSON number
arrays in the existing row-major order; water bodies and span cells/decks remain ordinary JSON.
The reader checks number ranges and lengths before constructing typed arrays, then uses the
shared field/content and static-site validators. It never stores derived navigation, rendering
geometry, terrain identities or battle state. JSON whitespace is not preserved, but all represented
values and optional asset references survive an unedited read/write round trip.
An asset entry is one of:
{ id: 'music', kind: 'audio', source: 'custom', path: 'assets/audio/field.ogg' }
{ id: 'bridge', kind: 'model', source: 'engine', key: 'bridge/default' }
kind is audio, model or texture; id is a document-local unique reference name.
A custom path is case-sensitive, relative to the manifest directory, begins with assets/, and
has no empty, . or .. segments, backslashes, URL syntax, query/fragment, percent escapes or
control characters. It is a literal companion path, not a URL to resolve or fetch. Engine keys
are opaque engine-provided identifiers; engine bytes are never packaged. Entries declare optional
references only: this version assigns no runtime asset loading or site/material override behavior.
Asset bytes, media types, existence, size limits, storage and processing remain deferred.
Versions and preview edits
| Value | Meaning |
|---|---|
artifactVersion: 1 |
External JSON schema/container contract, including JSON layer representation |
schematic.schematicVersion: 1 |
Static instructions/site schema shared with existing game schematics |
schematic.generatorVersion |
Generator provenance; must equal field.generatorVersion; zero means authored |
schematic.field.version |
Unsigned 32-bit terrain mutation/cache revision; increment on terrain edits and preserve through save/reopen |
Binary codec header format: 1 |
Existing little-endian network field encoding, unchanged; not the JSON artifact representation |
No version is a battle tick, ownership or stock counter. An edit does not change generator
provenance or rerun the generator. The existing game convertSchematic adapter still requires
field.version === 0 and validates through parseStaticMap; public delivery still requires an
identity version matching the field. The artifact's authoring contract allows nonzero cache
revisions so a preview can invalidate its caches and save/reopen. It does not reset versions or
feed documents into a live battle. Future battle adoption must deliberately construct fresh
initial schematic data and validate it at that boundary; no artifact loader is supplied here.
Small round trip
import {
readMapArtifact,
validateMapDocument,
writeMapArtifact,
type MapDocument,
} from '@velo/crown-base';
export function editAndReopen(current: MapDocument): MapDocument {
const candidate = structuredClone(current);
candidate.schematic.field.heights[0] += 10; // centimetres
candidate.schematic.field.version += 1;
const checked = validateMapDocument(candidate);
if (!checked.ok) {
throw checked.error;
}
const json = writeMapArtifact(checked.document);
const reopened = readMapArtifact(json);
if (!reopened.ok) {
throw reopened.error;
}
return reopened.document;
}
The focused package tests cover authored documents, every represented layer/site/reference,
malformed input, strict static boundaries and cache-version persistence. Game adapter tests add
generated maps at both sizes, classic conversion and an actual preview-rule cache refresh.
Run npx tsx --test tests/crown-base.test.ts games/crownfall/tests/map-artifact.test.ts from the
repository root; tests/crown-base.test.ts also includes the package checks in CI discovery.
Build
From this directory, install the package's development dependency and compile the TypeScript:
npm install
npm run build
The compiler writes JavaScript, source maps, and type declarations to dist/. The package tarball
contains that output and this README.
Forgejo npm registry
The package is published to the Forgejo npm registry owned by velo:
https://git.amhk.dk/api/packages/velo/npm/
The package is public, so installing it does not require a Forgejo access token. Configure npm to
use the Forgejo registry for the @velo scope, either in your project .npmrc:
@velo:registry=https://git.amhk.dk/api/packages/velo/npm/
Then install the library in that project:
npm install @velo/crown-base
Publishing requires a Forgejo access token with package write permission. To publish manually, first build and then publish against the Forgejo registry:
npm run build
npm publish --registry=https://git.amhk.dk/api/packages/velo/npm/
The repository workflow publishes on tags named crown-base-v*. Set the tag version to the
package's version in package.json; Forgejo rejects a version that already exists. The
PACKAGE_REGISTRY_TOKEN Actions secret must belong to an account with package write access.
Dependencies
Dependencies
| ID | Version |
|---|---|
| zod | ^4.6.7 |
Development dependencies
| ID | Version |
|---|---|
| typescript | ^5.9.3 |