# Offline Terrain World Compiler

A standalone Python CLI that compiles several **3JPCS viewer-bundle v2 ZIPs**
into an engine-neutral terrain world. It preserves source terrain relief and
authored scenery, with optional common sea-level normalization, adds Hermite transition strips and four-map corner patches,
and writes versioned JSON documents with exact shared boundaries.

3JHV now reads this format through its own adapter: choose **Import compiled-world
folder** in the viewer. This compiler remains independent of 3JPCS and 3JHV,
and does not render or regenerate their source terrain.

## Install and run

Requires Python 3.10+ and NumPy, Pillow and jsonschema. From the project folder,
run this one-time setup:

```sh
cd terrain_world_compiler
python3 compile_world.py --setup
```

The launcher creates `.venv/` inside this project, reuses compatible libraries
already available to your Python, and installs missing dependencies there.
It does not install into system Python. No environment activation or `PYTHONPATH`
setting is needed. Subsequent commands automatically use that environment and
the current compiler source in `src/`.

Installation needs locally available dependency wheels or access to a package
index. Compilation and validation run completely offline after installation.

Export **Viewer bundle (.zip)** from each 3JPCS project. All input maps must have
the same physical width, depth and sample spacing. Elevation encoding ranges
and sea levels may differ. These are final terrain exports, including any
terrain pads; editable project saves and source terrain-editor ZIPs are not
accepted.

Copy [examples/layout.json](examples/layout.json), set its bundle paths and
grid cells, then run:

```sh
python3 compile_world.py
```

In a terminal, it prompts for the layout JSON and a new output folder. Press
Enter to use `examples/layout.json` and `generated/my-world`. Prompt defaults
are relative to the project directory; supplied relative paths are relative to
your current working directory. `~` expands to your home directory. Non-interactive
launches without arguments print help instead of waiting for input.

You can also supply everything on the command line:

```sh
python3 compile_world.py compile --layout examples/layout.json --output generated/my-world
python3 compile_world.py validate --input generated/my-world
python3 compile_world.py --help
```

Paths to ZIPs are relative to the layout file, or absolute. The example ZIPs
are placeholders: supply your own exports. Choose a new output directory for
each compilation; existing files, directories and symlinks are refused.

For development without installing the project:

```sh
PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=src python3 -m world_compiler compile --layout examples/layout.json --output generated/my-world
PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=src python3 -m unittest discover -s tests -v
```

The development commands still require the three dependencies in that Python
environment (use `.venv/bin/python` after launcher setup). The installed
`.venv/bin/terrain-world-compiler` command also remains available. Tests use
small synthetic ZIPs in disposable temporary directories.
They do not open a desktop application or touch user worlds.

## Layout

The layout uses `format: "terrain-world-layout"` and `schema_version: 1` or `2`.
Each entry supplies a unique `id`, ZIP `path`, zero-based `row` and `column`.
IDs start with a letter and contain only letters, digits, underscores and
hyphens. The cells must fill a rectangle starting at `(0, 0)`; single rows,
single columns and a single map work too. Input list order does not affect
output. Rotations, missing cells and mixed sizes/resolutions are unsupported.

`transition_width_m` defaults to **100 m** and must be a whole multiple of the
map sample spacing, with at least two cells across a strip. For example,
50 m spacing requires at least 100 m; 100 m spacing requires at least 200 m.
`steep_slope_degrees` defaults to **45°** and controls report warnings, not
terrain modification.

## Common sea level

Use [examples/common-sea-level.json](examples/common-sea-level.json) as a v2
layout, with your ZIP paths. Its policy is:

```json
"schema_version": 2,
"sea_level_policy": { "mode": "normalize", "target_sea_level_m": 0 }
```

Each map moves vertically by `target - original sea`. A map with sea at 30 m
moves down 30 m for a target of 0 m. Terrain relief and source coastlines stay
relative to water; scenery placement heights follow the shifted surface.
Joins are generated after translation. All patches, including submerged
strips and corners, receive coverage at the target. This fills isolated
below-level depressions too; no ocean-connectivity analysis is performed.

```sh
python3 compile_world.py compile --layout examples/common-sea-level.json --output generated/common-sea-world
python3 compile_world.py validate --input generated/common-sea-world
```

The target must be explicitly supplied and finite. Omit the policy in v2,
or use `"sea_level_policy": { "mode": "preserve" }`, to retain original local
sea levels and dry connectors. Preserve mode accepts no target. V1 layouts
keep their existing v1 output; v2 layouts produce v2 output in either mode.
Existing worlds need no migration. Recompile into a new destination to opt in.

## Compiled package

```text
my-world/
  world.json
  boundaries.json
  compilation-report.json
  terrain/
    map-0-0.json
    strip-0-1.json
    corner-1-1.json
    ...
  scenery/
    northwest.json
    ...
```

Every data file is JSON. `world.json` defines coordinates, physical bounds,
patches, source layout, local water regions and relative file references with
SHA-256 checksums. Terrain arrays contain elevation metres directly: no new
16-bit encoding or quantization occurs. Source heights retain the precision
available in the exported PNG, not the editor's pre-quantized float32 terrain.

Sources include the bundle filename and checksum, manifest checksum, entry
checksums and original terrain metadata. The editor's float32 terrain checksum
is preserved as `terrain_float32_sha256_unverified`; its source array is absent
from the ZIP, so this compiler cannot verify it.

Scenery documents contain translated places, land use, roads, hedgerows,
prefabs, native vegetation and imported vegetation. Entity/asset IDs include
their source namespace; references are updated consistently. Placement heights
are sampled against the compiled terrain using its exported triangle diagonal.
Original rotation, scale and visibility remain intact. Roads are preserved;
the compiler adds no road links or scenery on connector terrain.

Asset resource strings remain external references, resolved by the consuming
engine. A catalogue entry does not prove that its model exists on disk. Missing
catalogue entries receive explicit unresolved records and report warnings.
Imported vegetation species/model keys remain semantic references. References
to imported regions absent from runtime scenery are marked as external rather
than discarded.

## Join behaviour and limits

Strips match source boundary heights and target their finite-difference slopes
using cubic Hermite interpolation. Four-map gaps use tensor-product Hermite
patches, including mixed derivatives. Shared boundary samples have canonical
ownership, so adjacent patches contain exactly equal heights. Canonical normals
are supplied separately for consistent shading, including corner intersections.

The authoritative output is a **sampled triangle surface**. Hermite derivatives
are smooth targets for generation and boundary shading; finite-resolution
triangles do not guarantee continuous geometric slopes. Wider strips or finer
source spacing may improve joins. Steep slopes and Hermite excursions outside
the source boundary elevation range are reported without clipping heights or
altering source maps. The report describes sampled output, not unsampled extrema
of the generating polynomial. Terrain continuity does not imply natural drainage.

V1 and v2 preserve mode retain water bounded to each source footprint at its
own sea level, with dry connectors. V2 normalize mode supplies one common level
and coverage for every patch. The 3JHV adapter renders it as one bounded plane.

JSON-only output can be large. The compiler currently limits each ZIP to
512 MiB compressed and 512 MiB total uncompressed, each source to 16 million
terrain points, and the compiled world to 32 million stored points. Those are
upper bounds, not a memory guarantee: arrays, derivatives, JSON documents and
validation need substantial RAM. Compilation currently holds the world's patch
arrays in memory; it is not a streaming compiler.

Compilation validates the staged package before publishing it. On POSIX systems,
publication atomically renames the stage onto an exclusively reserved empty
destination. A handled failure removes the stage and preserves pre-existing
output. A process killed during compilation may leave a hidden stage directory
or empty reservation for the user to inspect.

## Contract and acceptance

See [docs/FORMAT.md](docs/FORMAT.md) for v1 and [docs/FORMAT_V2.md](docs/FORMAT_V2.md)
for v2 adapter requirements and public interfaces.
The local, self-contained schemas are in
[src/world_compiler/schemas/](src/world_compiler/schemas/). Layouts and packages accept versions 1 and 2; companions must match the package
version. Compiler release 1.1.0 adds v2 without reinterpreting v1 data.

The validator checks schemas, referenced-file hashes, grid placement, sample
dimensions, neighbours, boundary heights/normals, water bounds, scenery
references/placement heights and report statistics. It needs no original ZIPs
or asset files. It establishes package consistency, not the geographic quality
of joins or model availability. SHA-256 detects changes; it does not authenticate
the package's author.

For first use, compile a small two-map layout, inspect the report for slope,
overshoot and asset warnings, and validate the output. Inspect terrain, shading
and local water by selecting that output directory in 3JHV's compiled-world picker.
