# Polygon County — Three.js editor

Milestone 9 adds runtime exports: true 16-bit heightmaps with metadata, CSV,
NumPy grids, complete vegetation, county features and a single ZIP bundle.
Vista provides deterministic terrain generation and hydraulic/thermal erosion
in a cancellable Web Worker, with draft controls and undoable publication.
Settlement planning includes regions, roads, buildings, surveys, buildability,
house candidates, frontage assistance and linked vegetation exclusions.
Vegetation supports deterministic forest/scatter generation,
exact tree counts and a capped, instanced preview. Terrain visualization
includes elevation colours, shaded relief, contours and an adaptive metric grid.
Terrain authoring and Manual Sculpt remain available in Top view, with immediate
perspective inspection. The editor loads `.cfe.json` schemas v3–v8 and saves schema v8.
The frozen Python reference remains in `../PCE-2.0.1/`.
The governing plan is [`../instructions_and_milestones.md`](../instructions_and_milestones.md).

Draw landmasses, hills, ridges, valleys, gradients and benches. Select layers,
reshape control points, move features, edit properties, and undo or redo persistent
edits. Create, select and clear sculpt layers, paint signed offsets, and undo a whole
stroke at once. Draw and edit forests, tree scatter and vegetation exclusions.
Draw settlement regions and roads, place houses, analyse sites and accept candidate
groups with shared undo/redo. Create and regenerate Vista layers, targeting the
whole map or a landmass. Export the authored result for downstream runtimes.

## Run

From the workspace root:

```sh
cd PCTE-rewrite/threejs-editor
npm ci
npm run dev
```

Open **http://127.0.0.1:8870/**. The server binds only to loopback and reports an
error if that port is occupied; use `npm run dev -- --port 8871` to choose another.
Dependencies are already installed in the implementation workspace; `npm ci` is
for a fresh checkout. Installation needs npm package access, but the running app
uses local assets only: no CDNs, hosted fonts, external services, or Python runtime.

Node 18.19+ is supported by this pinned toolchain. Vite 6.4.3 and Vitest 3.2.7
were selected for the workspace's Node 18 installation; see the
[Vite 6 requirements](https://v6.vite.dev/guide/) and
[Vitest 3 requirements](https://v3.vitest.dev/guide/).
Three.js, pako compression, TypeScript and all transitive dependencies are pinned
in the lockfile. No application framework or prototype-local Playwright dependency
is used.

For a production build:

```sh
npm run build
npm run preview
```

The build emits `dist/`, which any HTTP static server can serve, including from a
subdirectory. Use HTTP rather than opening `index.html` directly from disk.

## Application menus and New project

The permanent **File / Edit / Generate / Settings** bar stays above the workspace. File
contains New project, Open, Save and all runtime export formats. Edit contains
Undo, Redo, Duplicate selected and Delete selected, respecting the active terrain,
vegetation or settlement selection and its lock state. Settings → Terrain
visualization opens a resizable modal with live preview. Drawing,
sculpting and camera tools retain their existing locations. Generate → Vistawasm
opens the Vista generator window.

**File → New project…** opens a centered modal with name, width, depth, spacing,
starting surface and expandable elevation references. Invalid fields show errors
without replacing the project. If there are unsaved edits, **Create project**
leads to a separate **Discard changes and create** confirmation; **Back to
settings** preserves the entered values. Cancel, the close button and Escape
leave the accepted project and history intact. Backdrop clicks do not dismiss it.
Opening the dialog cancels unfinished gestures and pending generation; accepted
terrain stays unchanged. Closing retains form values during the session. Opening
or successfully creating a project initializes them from that project's settings.

Menus support arrow keys, Home/End, Enter/Space and Escape. The dialog contains
Tab/Shift+Tab focus and blocks editor shortcuts; closing returns focus to File.
Text fields retain their normal text-editing shortcuts. On narrow screens the
form scrolls inside the dialog while Create and Cancel remain accessible.
Successful creation resets selection/history and fits both cameras. The New
project form and duplicate header action buttons have been removed from the sidebar
and project strip respectively.

The project/filename strip, map view/dimensions row, drawing toolbar, and authoring
hint row are removed so the center map fills its panel. The browser tab shows the
project name with an asterisk for unsaved changes. Authoring errors appear only
when needed in the properties sidebar. Enter, Backspace and Escape retain their
drawing actions. Window/form state is not saved in `.cfe.json`.

Implementation details and validation evidence are in [Menu bar and New project](docs/MENU_BAR.md).

## Controls and manual acceptance

| Action | Control |
| --- | --- |
| Open an existing project | **File → Open project…**, or **Ctrl/Cmd+O** |
| Download a schema-v8 project | **File → Save project**, or **Ctrl/Cmd+S** |
| Download terrain, vegetation and county runtime files | **File → Export** → choose format |
| Cancel project loading/composition, saving or exporting | **Cancel** while an operation is running |
| View terrain from above | **Top view** (north up, east right) |
| Inspect in 3D | **Perspective** |
| Select / move a feature | **Select / move**, then left-click/drag its body in Top view |
| Reshape a polygon or spline | Select it, then drag a numbered control point |
| Draw vegetation | **Draw forest**, **Draw tree scatter**, or **Draw vegetation exclusion**; finish as a polygon |
| Generate vegetation preview | **Refresh vegetation** in the Vegetation preview panel; use **Cancel** for long jobs |
| Count trees | Select a forest/scatter region, then **Count selected region trees**; shrubs are reported separately |
| Plan settlements | **Draw settlement**, **Draw road**, **Place house**, or **Place setback house** in Top view |
| Edit settlement geometry | **Select settlement**, then drag a body or region/road vertex; edit exact fields in Settlement planning |
| Survey terrain | **Draw survey**, finish the polygon, then optionally **Convert survey to region** |
| Analyse a region, road or building | Select it and press **Analyse selection** in Settlement planning |
| Generate houses along roads | Select a region, configure House candidates, then **Preview houses** and **Accept candidates** or **Cancel candidates** |
| Link a vegetation exclusion | Select a region, road or building and press **Create linked vegetation exclusion** |
| Draw terrain | Choose a drawing tool and click points; **Place hill** needs one click |
| Finish a polygon or spline | **Enter** or double-click |
| Remove the last drawing point | **Backspace** |
| Cancel drawing, a drag or a brush stroke | **Escape** |
| Edit feature properties | Select a layer, change its fields, then **Apply properties** |
| Create a sculpt layer | **New sculpt layer** inserts above the selected layer (after all landmasses) |
| Create procedural terrain | **Generate → Vistawasm → New Vista layer** inserts directly above the landmass foundation |
| Configure a Vista layer | **Generate → Vistawasm**, choose a layer, and edit its draft controls |
| Generate and publish Vista | **Regenerate and Apply**; **Cancel generation** preserves the accepted field |
| Refit Vista coordinates | **Refit frame to target** updates the draft frame and base noise scale |
| Select the active sculpt layer | Select a visible, unlocked Manual Sculpt row; check the Brush panel indicator |
| Paint a sculpt layer | **Raise / Lower**, **Flatten to height**, or **Smooth**, then left-drag in Top view |
| Adjust a brush | Brush panel: radius, strength, falloff; mode, target height, or iterations as appropriate |
| Clear a sculpt layer | Select it and use **Clear layer**; undo restores its offsets |
| Change the stack | Layer buttons: show/hide, lock/unlock, raise/lower, duplicate/delete |
| Delete selected feature | **Delete** while the viewport is focused, or the layer button |
| Undo / redo | **Ctrl/Cmd+Z**, **Ctrl/Cmd+Shift+Z** or **Ctrl/Cmd+Y**; **Edit → Undo / Redo** also work |
| Pan in top view | Right/middle drag, **Space/Alt + left drag**, or left drag with **Pan** selected |
| Orbit in perspective | Left mouse drag |
| Pan in perspective | Right mouse drag; Ctrl/Shift + left drag also works |
| Zoom | Scroll wheel at the pointer; middle drag also dollies in perspective |
| Fit current view | **Fit world**, or **Home** while the viewport is focused |
| Change terrain display | Open **Terrain visualization**: elevation colours, greyscale, or shaded relief |
| Change palette | **Temperate European** or **Sandy Desert** in Terrain visualization |
| Toggle contour, coast and feature lines | Checkboxes in Terrain visualization |
| Adjust grid/contour spacing/lighting | Change **Saved view settings**, then **Apply view settings**; undo/redo is supported |
| Inspect mesh topology | **Show triangles** |
| Read world position, height, slope | Move pointer over the terrain |
| Create another blank project | **File → New project…**, configure settings, then **Create project** |

Authoring takes place in Top view. Perspective retains normal orbit/pan controls;
choosing an authoring tool returns to Top view. Hidden layers remain selectable
in the list, and locked layers must be unlocked before geometry/property edits.
The layer list reads top-to-bottom; ordinary layers compose bottom-to-top, while
landmasses form an order-independent foundation. A landmass referenced by Vista
cannot be deleted until the referencing Vista layer is removed.

Brush radius accepts **1 m** through the longest world dimension. A tiny brush
between terrain samples can have no effect; it does not increase grid resolution.
Raise/Lower strength is metres per dab; Flatten and Smooth use a blend in (0, 1].
Flatten targets an absolute elevation within the project range. Smooth runs 1–20
iterations per dab. Flatten and Smooth see the active offsets plus the surface
below that layer, excluding features above it. The selected layer's affected area
has an outline and a read-only grid/range/bounds summary. Hidden or locked sculpt
selections block brushing, even if another editable sculpt layer exists.

Try a hill with a sculpt layer below it: Flatten to 30 m leaves the upper hill
visible. Clear the sculpt, Smooth below the hill, then move the layer above the
hill and reapply Smooth to see the stack-aware difference. Undo each stroke, try
Escape midway through a drag, and save/reopen the result. Inspect the relief in
Perspective on your own GPU.

Property forms edit a local draft. Apply validates the complete feature and creates
one history entry; invalid drafts leave the model unchanged. Names, exact integer
seeds, polygon/spline coordinates, coast controls, and nested elevation/cross-section
profiles are editable. Profile preset selection fills the reference curve parameters.
Defaults fit small worlds and use the project's elevation references. World boundaries
clamp a whole dragged shape as a unit. An active gesture is cancelled when changing
tools/views/projects, starting a file operation, or losing pointer ownership/focus.

Each completed drag is one undo entry. Selection, camera navigation and unfinished
drawing are transient. Undo/redo preserves unrelated full-grid arrays; a new edit
after undo discards the redo branch. The header tracks saved/unsaved history state;
Save marks the downloaded snapshot as saved, and opening a project resets history.

Each camera retains its view when switching. Opening or creating a project replaces
the current project and fits both cameras. Opening first validates and composes in
a worker. Invalid input, composition failure or cancellation leaves the current
project intact. Save downloads a portable `.cfe.json` file; the
browser controls its destination and does not overwrite the source through this app.
Save before opening another project if you want to retain the current one.
New project explicitly confirms replacement when there are unsaved edits.
The New project dialog configures the next blank project; they do not modify an
imported project. Imported display settings initialize the visualization controls.
The triangle toggle is a transient viewer control.
The Seabed option uses the world's minimum; Land uses its lowland reference.
The triangle overlay displays actual terrain cells. The adaptive metric display
grid is controlled separately in Visualization.

On your real browser/GPU:

1. Check the initial 6000 × 6000 m grid, 121 × 121 samples, and −20 m elevation.
2. Pan and zoom at an off-centre point, then press Home. Confirm north remains up
   and the status bar reports X east, Z south, −20 m elevation, and 0° slope.
3. Switch to Perspective, orbit and zoom, then return to Top view. Confirm the
   previous top view is retained and the terrain has not changed.
4. Create an 800 × 300 m world with 25 m spacing and Land selected. Expect 33 × 13
   samples and +10 m elevation. Try a non-divisible width and check the error.
5. Expand Elevation references, try −120 / 15 / 40 / 900, and recreate the Land
   grid. Expect +40 m. Toggle triangles and resize the window.
6. Open `../PCE-2.0.1/examples/polygon_county_example.cfe.json`. Check the project
   name, contents and composed elevation range. Switch to Perspective to inspect
   the relief, then point at slopes in both views to check the elevation/slope
   readout. Save and reopen the download: terrain and contents should be unchanged.
   The Python editor can also open that v8 download.
7. Try opening malformed JSON with a `.cfe.json` suffix. Confirm the error is
   readable and the previously loaded project remains available to save.

Headless tests do not establish real-GPU presentation, desktop input feel, or
mobile-device behavior. The layout adapts to narrow screens, but touch authoring is
outside this milestone.

## Visualization

Open **Settings → Terrain visualization…**. Display mode, palette,
and the contour/coastline/feature switches are session preferences. Grid visibility,
grid style, contour interval, light bearing/elevation and shading exaggeration use
the six existing saved project settings. All valid edits preview immediately;
invalid input retains the last valid preview and shows an error. **Apply** keeps
the preferences and closes the modal, making one undoable saved-settings change; it does not recompose terrain or change its revision. Open/New restore the
project's saved settings. Save remains schema v8.

The modal opens centered with an undimmed terrain preview. Drag its title bar to
move it, including partly offscreen; a usable title-bar segment stays visible.
Double-click the title bar to recenter, or focus it and press Home. Arrow keys
move the focused title bar (Shift for larger steps). Drag its lower-left
corner to resize both dimensions, or focus that handle and use arrow keys
(Shift for larger steps). Position and size are retained for the session.
After moving, resizing keeps the opposite corner fixed. Browser resizing brings
the title bar within reach and limits the window size to the viewport; controls scroll while the heading and Apply/Cancel remain fixed.
Cancel, Escape and × restore every opening preference, including session-only
controls, without changing history or dirty state. The native modal blocks
background editing and camera navigation, traps Tab/Shift+Tab, and restores focus
to Settings on close. Opening cancels unfinished gestures and pending generation.
Window position, size and draft values are not serialized.

Elevation colours use the reference water and inland ramps, normalized against the
project's minimum, sea, lowland and maximum references. Greyscale maps minimum to
black and maximum to white. Shaded relief uses Three.js terrain normals with an
adjustable compass-bearing light. Shading exaggeration changes lighting only.
The sea-to-lowland band has deterministic, world-anchored sand grain that fades
when too small to resolve; camera motion does not move the sand across the ground.
This is native per-fragment rendering, not a Pillow raster reproduction.

Contours and the exact sea-level coastline intersect the authoritative NW–SE
triangles. A worker builds disposable line buffers after completed terrain edits;
lines remain static during strokes/drags and rebuild on release. Navigation and
palette/lighting changes reuse those buffers. Extremely dense output is capped at
250,000 segments per overlay with a visible message; increase contour interval or
hide an overlay. Intervals too small to enumerate safely are rejected.

The metric grid is origin-aligned at 50, 100, 250, 500 or 1000 m, using the
reference zoom thresholds and 8% hysteresis. A shader draws antialiased lines
without allocating a second grid mesh. Perspective uses the span at the orbit
focus to choose an interval. Grid style widths are in CSS pixels. The status bar
shows display interval separately from sample spacing, plus CPU render submission
time; it is not a GPU benchmark or idle FPS estimate.

Terrain, vegetation and settlement regions have editable outlines and control
handles. Roads follow the CPU terrain and buildings use instanced box previews with
footprint/frontage indicators for selection. Feature overlays can
be hidden for unobstructed terrain inspection; the sculpt cursor remains a separate
interaction aid. None of these visuals alters terrain samples or persisted geometry.

## Architecture

| Location | Ownership |
| --- | --- |
| `src/model/worldSpec.ts` | Immutable validated world dimensions and vertical references |
| `src/model/terrainGrid.ts` | Owned Float32Array samples; authoritative coordinate, height and slope queries |
| `src/model/project.ts` | Project aggregate, settings/seeds, UUID identity, validation and separate derived terrain buffers |
| `src/model/terrainFeatures.ts`, `crossSectionProfiles.ts` | All eight feature definitions and nested elevation/cross-section/Vista data |
| `src/model/vegetationRegions.ts`, `settlements.ts` | Vegetation/exclusion and settlement/road/building definitions |
| `src/model/validation.ts`, `geometry.ts` | Strict typed validation and shared geometry constraints |
| `src/generation/terrainGenerator.ts` | Starting surface, landmass foundation, ordered stages, final clamp and explicit regeneration |
| `src/generation/hill.ts`, `spline.ts`, `polygons.ts` | Feature rasterizers, coast profiles and accepted Vista field composition |
| `src/generation/numeric.ts`, `profiles.ts`, `rasterGeometry.ts` | Float32 operation boundaries, profile evaluation, polygon masks and spline smoothing |
| `src/generation/pythonRandom.ts`, `valleyReference.ts` | Python-compatible seeded hill asymmetry and incoming-surface valley grading |
| `src/io/` | v3–v8 migration, v8 encoding, exact integer JSON, bounded float32 compression, worker transfer and runtime export encoders |
| `src/app/RuntimeExportJobs.ts`, `panels/ExportPanel.ts`, `src/workers/exportWorker.ts` | Fresh CPU export snapshots, progress, cancellation and complete-artifact browser delivery |
| `src/app/ProjectFiles.ts`, `src/workers/projectIOWorker.ts` | Cancellable worker file reading, parsing, validation, terrain composition and compression |
| `src/rendering/TerrainMaterial.ts`, `visualization.ts` | Native palette/shading/grid shaders, shared colour stops and reference grid policy |
| `src/rendering/contours.ts`, `TerrainContours.ts`, `src/workers/visualizationWorker.ts` | Triangle contour extraction, deferred worker lifecycle and disposable line buffers |
| `src/app/panels/VisualizationPanel.ts`, `src/model/viewSettingsCommand.ts` | Session display controls and undoable existing saved view preferences |
| `src/rendering/TerrainMesh.ts` | Derived Three.js geometry, triangle overlay and stable buffer refresh helper |
| `src/rendering/TerrainScene.ts` | Scene/resources, separate cameras, navigation, picking and frame scheduling |
| `src/app/EditorApp.ts` | Shared menu/shortcut routing, atomic project replacement, editor coordination and view/readout state |
| `src/app/AppActions.ts`, `MenuBar.ts`, `NewProjectDialog.ts` | Action availability, accessible application menus, modal validation and explicit discard confirmation |
| `src/model/brushes.ts`, `sculptPatch.ts`, `sculptLayers.ts` | Float32 brush math, compact patches, active-layer policy and affected bounds |
| `src/app/SculptEditor.ts`, `src/rendering/BrushCursor.ts` | Brush settings, pointer strokes and a reusable cursor buffer |
| `src/model/commands.ts`, `featureEditing.ts` | Focused history, feature creation, picking and bounds-preserving edits |
| `src/app/FeatureEditor.ts`, `panels/FeatureProperties.ts` | Tool/gesture state, layer controls and validated property drafts |
| `src/app/TerrainComposer.ts`, `src/workers/terrainWorker.ts` | Coalesced terrain work and stale-result suppression |
| `src/rendering/FeatureOverlays.ts` | Drapeable outlines, bands and numbered control handles |
| `src/analysis/settlements.ts`, `src/model/settlementGeometry.ts` | Surveys, grades, sites, indexed overlaps, deterministic candidates, frontage and clipped road buffers |
| `src/model/settlementEditing.ts` | Settlement factories, bounded geometry edits and atomic commands with linked exclusions |
| `src/app/SettlementEditor.ts`, `panels/SettlementProperties.ts` | Settlement tools, property drafts, survey/candidate state and worker lifecycle |
| `src/workers/settlementWorker.ts` | Cached terrain analysis, candidate generation and capped buildability transfer |
| `src/rendering/SettlementView.ts` | Terrain-draped roads, outlines, buildability and instanced building/candidate previews |
| `src/generation/vistaGenerator.ts` | Versioned Vista float32 noise, shaping and simultaneous erosion |
| `src/model/vistaLayers.ts`, `src/app/panels/VistaPanel.ts` | Blank Vista definitions, explicit frame fitting and local control drafts |
| `src/app/VistaGeneration.ts`, `src/workers/vistaWorker.ts` | Progress, cancellation, stale-result rejection and completed-field publication |
| `src/main.ts`, `src/styles.css` | Startup, hot-reload cleanup, errors, styling and layout |
| `src/tests/`, `tools/`, `tests/browser.cjs` | Domain/render contracts, Python fixture generation and browser checks |

Model code imports no Three.js or DOM APIs. Geometry copies model samples; its
position buffers are not authoritative elevations. Every cell has the NW–SE
diagonal, with upward-facing index winding. `heightAt()` and `slopeAt()` use the
same triangle vertex sets. Top-view picking maps directly to X/Z; perspective
picking uses raycasting only for X/Z and reads elevation/slope from the CPU model.

Navigation marks rendering dirty, and `requestAnimationFrame` draws the next frame.
It does not regenerate terrain or continuously redraw an idle scene. Camera switches
reuse the scene and geometry. World replacement disposes the old mesh resources.
`updateTerrainGeometry()` retains the geometry, index and position-buffer identities
for same-grid elevation updates. The project aggregate owns distinct `terrain` and
`finalTerrain` buffers. `regenerateProjectTerrain()` composes into a temporary grid
and publishes a completed result into the existing final buffer. `terrainRevision`
increases only when sample values change; renaming, locking, saving or moving a
camera does not change it. The starting foundation is rebuilt from the selected
Sea/Land reference, never from mutable internal `terrain` samples.

Visible landmasses form an order-independent maximum envelope beneath all ordinary
features. Ordinary features follow list order, hidden features contribute nothing,
and locking has no effect on composition. Elevations are clamped once after the
complete stack. `composeTerrainUntil()` and `composeSurfaceBelow()` provide independent
unclipped surfaces for stack-aware brushes. Terrain-following valleys sample
the incoming triangle surface, smooth and grade their centreline, and apply a
lower-only, maximum-carve-bounded correction. Vista composition uses accepted fields
without invoking noise or erosion generation.

Terrain editing uses a worker with at most one running job and one latest pending
request. Drag/stroke previews are coalesced (100 ms scheduling window). Completed
snapshots within an ongoing gesture may display while newer work is pending, with
the terrain marked as updating. Release bypasses the preview timer and requests
an exact rebuild. Release, cancellation, undo and project replacement invalidate
older work, preventing stale previews from overwriting the result. Successful results
update the existing CPU/GPU buffers, leaving camera framing intact. Failures retain
the previous terrain and report an error. Terrain operations do not regenerate
vegetation or settlements.

Brush sessions remember original affected samples lazily and mutate only the active
float32 offset array. Release records one owned rectangular before/after patch;
clear records the nonzero bounding rectangle. UUID lookup makes patch replay safe
across layer deletion/restoration. No project or whole-grid snapshot is taken per
dab. Stack-aware brushes cache one unclipped lower surface at stroke start. Smooth
computes a local window and a one-sample halo, preserving the reference's ordered
float32 neighborhood sums and masked iterations. Dabs follow world distance at
max(half a terrain cell, 18% of radius); release adds a final dab when the remaining
distance reaches 40% of that spacing. Stationary holding does not accumulate dabs.
The lower-surface computation runs once synchronously at stroke start; very large
stacks/brushes can still have noticeable CPU cost. Final terrain composition runs
in the existing worker, and completed results update the stable CPU/mesh buffers.

File work runs in a disposable module worker. Only a completely decoded and
composed project replaces the active one; cancelling terminates the worker.
Derived terrain crosses the worker boundary separately from project definitions.
Save validates a snapshot and serializes authoritative state only. Generated
placements, final terrain and
transient UI state are never persisted. Large Python integer seeds use `bigint`
internally and ordinary JSON integer tokens on disk, avoiding silent rounding.
Packed grids preserve little-endian float32 bytes, including negative zero, and
enforce a 64 MiB decoded limit, exact shape/length, checksum and stream termination.
Unknown fields and unsupported versions are rejected instead of dropped.

See [`docs/MILESTONE_0.md`](docs/MILESTONE_0.md) for the pre-implementation file plan,
reference contracts, numerical evidence and baseline exceptions. See
[`docs/MILESTONE_1.md`](docs/MILESTONE_1.md) for persistence decisions, changed files,
acceptance results and remaining limits. Current composition contracts and evidence
are in [`docs/MILESTONE_2.md`](docs/MILESTONE_2.md). Authoring contracts, changed files and acceptance evidence are in [`docs/MILESTONE_3.md`](docs/MILESTONE_3.md).
Manual Sculpt contracts and acceptance are in [`docs/MILESTONE_4.md`](docs/MILESTONE_4.md).
Visualization implementation and acceptance are in [`docs/MILESTONE_5.md`](docs/MILESTONE_5.md).
Vegetation implementation, changed files and acceptance are in [`docs/MILESTONE_6.md`](docs/MILESTONE_6.md).
Settlement implementation, changed files and acceptance are in [`docs/MILESTONE_7.md`](docs/MILESTONE_7.md).
Vista implementation, numerical evidence, changed files and acceptance are in [`docs/MILESTONE_8.md`](docs/MILESTONE_8.md).

## Vegetation

Draw a forest, tree-scatter region or exclusion polygon in Top view. Select its
layer to edit points, name, species weights, seed, terrain constraints and distribution
settings. Species use one `species: weight` pair per line. Forests support
Continuous / Even and Natural Patchy modes, density variation, edge falloff and
shrubs. Scatter supports isolated trees and compact copses. Shared layer controls
provide show/hide, lock, duplicate, delete, ordering and undo/redo.

Press **Refresh vegetation** explicitly. The default preview cap is 5,000 trees and
shrubs (1–20,000 configurable); counts describe the full result after exclusions.
**Count selected region trees** computes that region independently, with shrubs
listed separately. Dense jobs run in a cancellable worker. Terrain/region edits
leave the old preview stale until refresh; camera and name/lock edits do not.
**Show generated preview** hides or restores the existing preview without regeneration.

New forests use adaptive growth and a 30 m shrub radius; new scatter uses compact
copses. Loaded sampler/legacy settings survive unrelated edits. Blank shrub radius
retains the old spacing-relative rule. All four copse fields must be present together,
or all blank for legacy diffuse groups. Negative seeds and scatter spacing ratios
over 19:1 produce generation errors without modifying the definitions.

Save/Open preserves authored vegetation with the existing schema. Preview instances,
counts, caps and visibility switches are disposable session state. After opening,
refresh to regenerate. Start with a **Land** blank surface for vegetation; the
default minimum vegetation elevation is 10 m.

## Settlement planning

Start with a **Land** blank project. In Top view, draw a survey polygon to see
elevation/slope statistics and coloured buildability cells. **Convert survey to
region** makes the boundary persistent; an unconverted survey is session state.
Draw a road and place houses, then select objects in the Settlement planning list
or with **Select settlement**. Properties include exact points, road width/surface,
building footprint/rotation, type, asset ID and frontage road. Show/hide, lock,
duplicate, ordering, delete and shared undo/redo apply to these objects.

**Analyse selection** reports region statistics, road grades or building site
warnings, including overlaps. Analysis runs in a cancellable worker. Terrain or
settlement changes mark the previous report stale; analyse again to update it.
Buildability display is capped at 25,000 cells while statistics cover every sample.

New houses can face nearby roads automatically. **Place setback house** also moves
the footprint away from the road edge; existing houses have **Orient selected house**
and **Orient and apply setback** buttons. House candidates use reference 12 × 18 m
footprints, spacing, setback and slope constraints. Select a settlement region,
optionally select roads (none means all visible roads), enter an exact integer seed,
and press **Preview houses**. **Accept candidates** adds the whole group and seed as
one undo entry. Cancel leaves the project unchanged. Input, selection or terrain
changes discard obsolete candidates.

**Create linked vegetation exclusion** creates a region/footprint polygon or a road
corridor extending 5 m beyond each road edge. Later owner geometry edits update that
exclusion in the same undo entry; deleting the owner removes it. Deleting a road
also clears its building frontage references. Refresh vegetation explicitly to see
the exclusion's effect.

Settlement edits preserve terrain samples and revision. Buildings derive their
preview elevation from the CPU terrain; road points remain authored X/Z coordinates.
Use the separate **Draw bench** terrain tool when grading is intended. Save/Open
preserves accepted definitions using schema v8; reports, survey drafts and candidate
previews are disposable. Building boxes are planning placeholders, not asset models.

## Vista generation

Open **Generate → Vistawasm** for a centered, resizable modal window. Choose an
existing Vista layer or press **New Vista layer**. Opening the window alone creates
nothing. A new layer has no generated output and leaves terrain unchanged until
**Regenerate and Apply** finishes. It begins directly above the landmass foundation
and can be moved among ordinary terrain layers. Selecting a layer in the sidebar
preselects it when you next open the window.

Drag the lower-left corner to resize, or focus the handle and use arrow keys
(Shift for larger steps). Drag the title bar to move the window partly offscreen,
with enough of the title bar kept visible to retrieve it. Double-click the title
bar to recenter; when focused, arrow keys move it and Home recenters it. Position
and size persist for the session. Resizing after moving anchors the opposite
corner, and browser resizing keeps the title bar reachable. Controls scroll while generation status,
progress, Reset draft, Cancel generation and Regenerate and Apply remain fixed.
The × button or Escape closes the window and cancels unfinished generation,
preserving accepted terrain. Draft inputs survive closing/reopening the same layer;
selecting another layer or replacing the project reloads the appropriate controls.
Background editing and camera navigation are blocked while the window is open.
Tab/Shift+Tab remain inside; closing returns focus to Generate.

The panel provides an exact integer seed, seven noise families, octave/gain/
lacunarity/warp controls, physical noise scale, optional island/terrace/basin/canyon/
crater shaping, vertical scale and base elevation. Blank shape inputs disable that
shape. **Reroll seed** changes only the draft; **Reset draft** restores accepted
controls. “Simplex” retains the reference's public name for smooth value noise.

Target the entire map or a landmass UUID. **Additive relief** adds generated height
minus base height to incoming terrain; **Generated elevation** blends toward the
generated elevation. Blend strength and an inward landmass feather control influence.
A hidden target suppresses the Vista layer. **Refit frame to target** explicitly
fits the frame to map/landmass bounds and sets base noise scale to its longest side
divided by six. Editing a landmass does not refit or regenerate its Vista field.

Hydraulic and thermal erosion use simultaneous four-neighbour passes. Quality caps
each requested pass count at Preview 16, Balanced 64, High 160 or Offline 320.
Evaporation is preserved as a reserved value with no numerical effect, matching the
reference. Output is `base elevation + normalized noise × 900 m × vertical scale`;
the complete terrain stack applies the project's final elevation clamp.

Generation runs in a disposable worker with progress and immediate cancellation.
The accepted field stays active during generation. Completion publishes controls
and the whole field as one undo entry; cancellation, invalid settings and worker
failure preserve the project. Selection/layer changes, target removal, file operations and
project replacement invalidate obsolete work. Close the window to resume camera navigation.

Schema v8 stores accepted controls and compressed little-endian float32 output.
Open, navigation and ordinary composition never rerun noise or erosion. Selected
Vista layers display their saved frame and optional target outline. Large grids,
many octaves and high erosion budgets take more worker time; each accepted generation
also retains its previous full field for undo. No runtime generation dependency or
hosted asset is required.

## Runtime exports

Open **File → Export** and choose all formats, PNG with metadata, CSV, NPY,
vegetation JSON or county JSON. Each job composes fresh CPU terrain in a worker.
The full bundle downloads as `polygon_county_runtime.zip`; PNG and metadata
arrive together as `terrain_heightmap.zip`. Extract before importing. Individual
CSV, NPY and JSON selections download directly. Progress, errors and **Cancel
export** remain visible beneath the menu bar after the menu closes. Cancel
discards the whole job.

Exports do not mark the project saved, change history, or refresh its vegetation
preview. Vegetation generation includes all placements from visible regions after
exclusions, regardless of preview cap or staleness. County schema v3 retains hidden
objects, centre-samples building elevations and preserves ordered road X/Z controls
with `follow_terrain`. Repair dangling frontage IDs before county/bundle export.

The [runtime format guide](docs/RUNTIME_EXPORTS.md) documents coordinates, encoding,
importer responsibilities and failure recovery. It and the unchanged county schema
are included in production builds. [Milestone 9](docs/MILESTONE_9.md) records the
parity audit, implementation files, acceptance evidence and remaining limitations.

## Validation

```sh
npm test
npm run typecheck
npm run build
```

Vitest tests are display-independent. They check world validation, both triangles,
boundaries, conversion, slope, float32 storage, upward mesh winding, mesh/query
agreement, 32-bit indices, stable buffer updates, and non-mutation of model data.
The complete suite currently has **661 passing tests**, including 129 persistence
tests covering 17 Python-derived input/canonical fixture pairs, 72 Python-verified
malformed cases, migration defaults, UUIDs, exact integer seeds and hostile arrays.
The 120 composition tests cover 91 Python-derived projects, eight profile curves,
seven Python RNG sequences, ordering, visibility, clipping, intermediate surfaces,
stable buffers/revisions, non-mutation and terrain-following valley constraints.
The 24 editing/history/worker tests cover gesture-sized commands, geometry, validation, saved-state branching, array ownership and stale-result suppression.
Milestone 4 adds 73 tests: 48 Python brush fixture cases plus focused brush, compact
patch, layer policy and continuous-preview regressions. All 48 brush cases have
exact float32 offsets and final terrain, including 25/50 m grids, repeated dabs,
world edges, 1 m radius, clipped intermediate heights and twenty Smooth iterations.
Milestone 5 adds 109 tests, including 32 Python contour cases, 64 palette samples,
grid thresholds/hysteresis, settings history/persistence and deferred worker tests.
Milestone 6 adds 33 tests: 18 Python placement cases (2,664 placements), 1,440 exact
mixed NumPy RNG operations, authoring/history/persistence and worker lifecycle checks.
Milestone 7 adds 36 tests covering frozen-Python surveys, buildability, road/site
analysis, frontage, seeded candidates, road corridors, indexed overlaps and atomic
settlement/exclusion history. Numeric parity uses a 5e-9 absolute tolerance; candidate
UUIDs and metadata match exactly, including negative and greater-than-64-bit seeds.
Milestone 8 adds 56 tests: 25 generated Python fields, 12 isolated erosion cases,
exact hashes/value-noise samples, world alignment, determinism, quality caps,
reserved settings, target composition, and worker/history/failure boundaries.
All 37 Vista field/erosion cases match exactly in the current Node run; generated
fields permit a documented 0.0001 m cross-platform tolerance.
Milestone 9 adds 24 export/worker tests, with four six-format Python fixtures,
156 generated placements, exact CSV/NPY bytes, decoded PNG16 pixels, metadata,
county/vegetation contracts, state isolation and cancellation/failure recovery.
All 273 golden grids (143,949 samples) matched exactly in the current Node run.
The portable acceptance bounds are 0.0001 m for grid/interpolated heights and
0.001° for slope queries; measured differences are reported rather than hidden.

The Milestone 0 grid fixture contains five Python-oracle grids and 245 queries. Float32
storage bytes and height results match exactly; slope comparisons allow an absolute
1e-12 degrees for JS/Python libm differences. Tests read raw JSON to retain negative
zero, which bundler JSON-to-JavaScript transforms can otherwise erase.

To deliberately regenerate migration fixtures (NumPy required only for these tools):

```sh
PYTHONDONTWRITEBYTECODE=1 python3 tools/generate_terrain_fixture.py
PYTHONDONTWRITEBYTECODE=1 python3 tools/generate_project_fixtures.py
PYTHONDONTWRITEBYTECODE=1 python3 tools/generate_composition_fixtures.py
PYTHONDONTWRITEBYTECODE=1 python3 tools/generate_brush_fixtures.py
PYTHONDONTWRITEBYTECODE=1 python3 tools/generate_visualization_fixtures.py
PYTHONDONTWRITEBYTECODE=1 python3 tools/generate_vegetation_fixtures.py
PYTHONDONTWRITEBYTECODE=1 python3 tools/generate_settlement_fixtures.py
PYTHONDONTWRITEBYTECODE=1 python3 tools/generate_vista_fixtures.py
PYTHONDONTWRITEBYTECODE=1 python3 tools/generate_export_fixtures.py
```

With `npm run preview` running, the browser smoke check uses the shared workspace
Playwright installation at `../../browser_validation/`:

```sh
npm run test:browser
npm run test:editor-browser
npm run test:sculpt-browser
npm run test:visualization-browser
npm run test:vegetation-browser
npm run test:settlement-browser
npm run test:vista-browser
npm run test:export-browser
npm run test:menu-browser
# Optional alternate loopback URL / temporary screenshots:
PCTE_URL=http://127.0.0.1:8871/?test=1 PCTE_SCREENSHOT_DIR=/tmp/pcte-check npm run test:browser
```

The check launches a temporary headless Chromium, exercises the production app and
closes the browser. It reports console/network errors and warnings; optional
screenshots remain at the explicitly selected path for inspection. Stop your preview
server with Ctrl+C afterward. `?test=1` exposes read-only diagnostic snapshots and
an explicit copy of CPU/mesh elevations and a read-only world-to-screen projection for browser checks.
The authoring browser check creates all six tools, exercises geometry/whole-feature
drags, cancellation, property validation, locked/hidden layers, nonlinear reordering,
duplication/deletion, history branching, navigation and exact save/reopen terrain.
The earlier browser check also uploads v3–v8 projects and the supplied Python example,
downloads their v8 equivalents, reopens a download, checks exact large integer seeds,
and exercises failure recovery and cancellation. It compares composed CPU samples
with Python goldens, mesh heights with CPU samples, checks height/slope in both
views, and verifies nonlinear stack order and final-only clamping through uploads.

For repeatable Python verification of actual saved files (NumPy required):

```sh
PCTE_ROUNDTRIP_DIR=/tmp/pcte-roundtrips npm test
PYTHONDONTWRITEBYTECODE=1 python3 tools/verify_project_roundtrips.py /tmp/pcte-roundtrips
# With the preview server running:
PCTE_BROWSER_ROUNDTRIP_DIR=/tmp/pcte-browser-roundtrips npm run test:browser
PYTHONDONTWRITEBYTECODE=1 python3 tools/verify_project_roundtrips.py /tmp/pcte-browser-roundtrips
```

The verifier reads files through the reference loader and checks canonical data,
with exact integer and decoded float32 bytes and an absolute 1e-10 tolerance for
scalar floating-point migration calculations. It does not launch the Python UI.
For the new authoring scenario, verify the downloaded definitions and terrain in Python:

```sh
PCTE_BROWSER_ROUNDTRIP_DIR=/tmp/pcte-m3 npm run test:editor-browser
PYTHONDONTWRITEBYTECODE=1 python3 tools/verify_authored_project.py /tmp/pcte-m3
```

The sculpt browser check saves its screenshot, authored project and diagnostic
heights under `/tmp/pcte-m4-browser` (override with `PCTE_M4_ARTIFACTS`). Verify that
download with the Python reference:

```sh
PYTHONDONTWRITEBYTECODE=1 python3 tools/verify_authored_project.py /tmp/pcte-m4-browser --stem m4-sculpt
```

For visualization screenshots, run `PCTE_M5_ARTIFACTS=/tmp/pcte-m5-browser npm run test:visualization-browser`.
For vegetation screenshots, run `PCTE_BROWSER_ARTIFACTS=/tmp/pcte-m6-browser npm run test:vegetation-browser`.
For settlement screenshots, run `PCTE_M7_ARTIFACTS=/tmp/pcte-m7-browser npm run test:settlement-browser`.
For Vista screenshots and a saved project, run `PCTE_M8_ARTIFACTS=/tmp/pcte-m8-browser npm run test:vista-browser`.
Verify that saved project's composition and regenerate its accepted Vista field with
the frozen Python oracle (no Python UI is launched):

```sh
PYTHONDONTWRITEBYTECODE=1 python3 tools/verify_authored_project.py /tmp/pcte-m8-browser --stem m8-vista
PYTHONDONTWRITEBYTECODE=1 python3 tools/verify_vista_project.py /tmp/pcte-m8-browser/m8-vista.cfe.json
```

For actual runtime downloads and independent Python/Pillow/NumPy verification:

```sh
PCTE_M9_ARTIFACTS=/tmp/pcte-m9-browser npm run test:export-browser
PYTHONDONTWRITEBYTECODE=1 python3 tools/verify_runtime_exports.py /tmp/pcte-m9-browser
```

The export scenario checks all formats, fresh terrain after authoring, uncapped
vegetation, hidden county objects, unchanged history/CPU/GPU, cancellation,
atomic generation failure and recovery, bundled docs and narrow layout.
Headless checks do not establish real-GPU presentation or downstream-engine behavior.

The menu scenario checks keyboard and pointer navigation, modal focus, invalid
settings, cancellation preserving project/history, unsaved replacement confirmation,
terrain/settlement action routing, file shortcuts, export busy/cancel/retry and
narrow layouts. Use `PCTE_MENU_ARTIFACTS=/tmp/pcte-menu-check npm run test:menu-browser`
to capture the desktop menus and desktop/narrow New project dialog.

Remove these temporary output directories when you finish reviewing them.

## License

The original editor modules use GPL-3.0-or-later; see [`LICENSE`](LICENSE).
The Vista numerical port uses AGPL-3.0-or-later; see its
[license text](docs/LICENSE-AGPL-3.0-or-later.txt) and
[provenance and retained copyright](docs/VISTA-NOTICES.md).
Terrain model and composition code are
ported from Polygon County Editor 2.0.1's model and generation modules, under the
same license. NumPy random algorithm notices are preserved in
[`docs/NUMPY-LICENSE.md`](docs/NUMPY-LICENSE.md) and copied into `dist/licenses/` by the build.
Three.js retains its MIT license and pako its MIT/Zlib notices in their installed
packages. The production build bundles NumPy and Vista notices and the AGPL text
under `dist/licenses/`.
