@velo/crown-base (0.4.0)

Published 2026-10-15 13:31:11 +02:00 by r59q

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
Details
npm
2026-10-15 13:31:11 +02:00
0
UNLICENSED
28 KiB
Assets (1)
Versions (20) View all
0.20.0 2026-11-04
0.19.0 2026-11-04
0.18.0 2026-11-03
0.17.0 2026-11-03
0.16.0 2026-11-02