# Terrain World v1 consumer contract

All files use UTF-8 JSON. Format strings and `schema_version` identify document
roles; application/compiler versions are separate. Schemas use JSON Schema
2020-12, contain no external references and require no network resolution.

| Schema | Root contract and reusable definitions |
| --- | --- |
| `layout-v1.schema.json` | Compiler input layout |
| `source-v1.schema.json` | Accepted viewer-bundle v2; `$defs` describe metadata, runtime v3, vegetation v1 and catalogue v3 |
| `world-v1.schema.json` | World descriptor; `$defs/terrain`, `$defs/scenery`, `$defs/boundaries`, `$defs/report` describe companion files |

Cross-file and numerical constraints supplement schemas. Use the Python
validator or implement the rules below; validating the root JSON schema alone
does not establish a coherent world.

## Coordinates and patch geometry

World `(0, 0)` is the northwest corner of the first map. X increases east,
Z increases south and Y is elevation; all measurements use metres. Source map
row/column `(r, c)` starts at integer grid coordinate
`(c * (source_cells_x + gap_cells), r * (source_cells_z + gap_cells))`, multiplied
by `spacing_m`. No origin centering, axis reversal or sea-level normalization
is applied.

`world.patches` describes a rectangular patch lattice. Source maps occupy
even/even lattice cells, transition strips even/odd or odd/even, and corner
patches odd/odd. `origin_m` is `[x, z]`; `bounds_m` contains `min_x`, `min_z`,
`max_x`, `max_z`. `source_id` is null for generated patches.

Each terrain document contains `patch_id`, `spacing_m`, `point_count_x`,
`point_count_z`, and `heights_m` as an array of rows. Access height at grid
`(x, z)` through `heights_m[z][x]`. Point position is
`[origin_x + x*spacing, height, origin_z + z*spacing]`.
Adjacent patches duplicate edge vertices in their storage but agree exactly.

Divide each cell along **northwest–southeast**. For heights NW, NE, SW, SE and
fractional cell coordinates `u` east and `v` south, use:

```text
v <= u: height = NW + u*(NE-NW) + v*(SE-NE)
v >  u: height = NW + v*(SW-NW) + u*(SE-SW)
```

Rendering and collision must use these triangles, not bilinear interpolation
or fresh procedural terrain. Bounds include boundary vertices. At a query
exactly on a shared edge, either member gives the same height.

## Shared boundaries

Patch `neighbours` and `boundaries` map `north`, `east`, `south`, `west` to patch
IDs and edge IDs. Missing sides lie on the outer world perimeter. References
must be reciprocal. `world.boundaries` references a document whose `edges`
array assigns one owner and two `{patch_id, side}` members to each shared edge.

Each edge supplies:

- `origin_m`: first sample position `[x, z]`.
- `axis`: `x` for north/south sides, `z` for west/east sides.
- `spacing_m`, `heights_m`, and upward unit `normals` as `[nx, ny, nz]`.

All edge arrays run in increasing global X or Z, irrespective of which member
uses the edge. There is **no reversed edge indexing**. Apply canonical normals
to both members' shared vertices. Intersecting edges have identical endpoint
heights and normals. Compute interior normals as needed, but do not average
or replace the supplied shared normals independently per patch. These normals
represent Hermite/source derivative targets; collision remains triangulated.

Adapters using LOD must retain or explicitly stitch these canonical boundaries.
This format supplies one resolution and no LOD meshes, material selection,
render-frame rebasing or streaming policy.

## Scenery, resources and water

Each `world.sources` entry points to its scenery document. Collections retain
3JPCS semantics and use global coordinates. Native IDs are
`<bundle>:entity:<source-id>`; asset IDs are `<bundle>:asset:<source-id>`.
`source_entity_id` preserves the original native ID. Relationships such as
`frontage_road_id` and native `source_region_id` refer to namespaced entities.

Imported vegetation has stable IDs `<bundle>:imported-vegetation:<source-index>`
and separate `model_reference_id` values `<bundle>:species:<model_or_species>`.
`source_region_scope: "world"` refers to a namespaced region in the same scenery
document. `"external"` uses `<bundle>:external-region:<source-region-id>` to
preserve an origin reference whose geometry was not exported. Imported model
keys do not imply a catalogue asset with the same name.

The per-source `assets` map preserves `source_asset_id`, `namespace`, `category`
and `resource`. `resolution: "external-reference"` means a catalogue entry
exists, while `"unresolved"` means its resource/category are null. Resource
strings belong to the engine's asset-resolution context, not the compiled
package directory. Each source has independent `category_defaults`.

Honour visibility, rotation in degrees and uniform scale. Placement elevations
are already sampled against compiled terrain. Roads/hedgerows remain semantic
polylines; their meshes and terrain-following geometry belong to the adapter.
Do not infer roads between maps from adjacency alone.

Each `water_regions` entry has a source, patch, footprint bounds and `level_m`.
Clip water to that rectangle. A consuming engine decides whether to hide water
beneath terrain. Do not extend a map's water plane across connectors or assume
one global sea level. Different local water levels are deliberately preserved.

## Integrity, reporting and public Python interfaces

References to terrain, scenery, boundaries and report files contain `path`
and SHA-256 of exact file bytes. Paths are relative POSIX paths within the
package, with no absolute paths, traversal or symlinks. Source-entry hashes in
provenance describe the original ZIP members, not compiled-file references.

`compilation-report.json` contains counts, a configured slope-warning threshold,
per-patch elevation extrema, maximum actual triangle slope and `overshoot_m`.
Overshoot compares generated strip samples with its two bordering source edges;
corner samples are compared with its four source corners. It is zero for source
patches. Warnings do not cause compilation failure or modify geometry.

```python
from world_compiler.compiler import compile_world
from world_compiler.validation import validate_world

report = compile_world("layout.json", "new-world-directory")
summary = validate_world("new-world-directory")  # or path to world.json
```

`compile_world` returns the compilation report; `validate_world` returns world
name, source/patch/boundary counts and warnings. Input/contract problems raise
`world_compiler.contracts.CompilerError`; filesystem failures raise `OSError`.
The CLI prints actionable errors to stderr and returns 1, or returns 0 on
success (including a valid compilation with warnings).

Repeat compilation with the same inputs, namespace IDs and settings produces
identical output bytes regardless of input entry order or output-directory
name. Source ZIP filenames/checksums are intentionally part of provenance:
renaming or repacking a ZIP changes provenance even when its terrain is equal.

Publication validates a temporary sibling directory, atomically reserves a
previously absent output path, then renames the complete stage onto that empty
reservation. The directory rename relies on POSIX semantics; Windows is not a
supported publication target in v1. Publication is not a power-loss durability
guarantee. Schema changes require explicit new versions; recompile from retained
source bundles when upgrading an incompatible format.
