docs(governance): file D-239 tile derivation contract, resolve Q-101
Resolves Q-101 — how a coarse Layer-1 cell + seed becomes coherent ~1m voxel geometry across the scale jumps. The tile-derivation-contract workshop output: - three-carrier refinement chain RegionProfile -> ChunkContext -> VoxelColumn, pure deterministic, no authoring at the derivation layers - district-temperature climate primitive (2x2 km, C, nullable) + separate moisture; everything climatic derives from temperature(+moisture) - scattered, transient freeze/snow model (freshwater +5..-10, sea ice own band, snow moisture-gated; forms cold phase / melts warm phase) - stateless f64-to-voxel domain warp (anti-squaring) - 8 morphology families over a frozen 17-zone vocabulary, gated decision tree - seams prevented at source (gate ordering + build-time matrix); valid geomorphic seams kept sharp + warped Includes workshop brief, round-1 positions, and workshop-outcomes.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# D-239 (REVISED post-verification): Tile derivation contract — coarse→fine refinement chain (Q-101)
|
||||
|
||||
domain: architecture · type: confirmed · date: 2026-06-07 · resolves: Q-101
|
||||
|
||||
**Decision.** The walkable tile is materialised by a **three-carrier refinement chain**, each
|
||||
stage a pure deterministic function of `(seed, atlas, body-params, position)` per D-227:
|
||||
`RegionProfile` (~1 km) → `ChunkContext` (64 m) → `VoxelColumn` (1 m). Each scale boundary is its
|
||||
own derivation with its own failure modes.
|
||||
|
||||
### Determinism — no authoring at the derivation layers, ever
|
||||
L1–L5 derivation has **zero per-body override hooks**. All authorial control lives **upstream** at
|
||||
the body-parameter / atlas / system-specialization layer; the gating params (`GlaciationGrade`,
|
||||
`precipitation_class`, `RIVER_THRESHOLD`, `tectonic_class`) are **derived** from body params
|
||||
(stellar type / orbit / hydrosphere / lithology), never authored. `RIVER_THRESHOLD` specifically is
|
||||
a derived **per-body-class** value `(hydrosphere, tectonic_activity, precipitation_class) → threshold`,
|
||||
not the global constant `200`. Lore-anchored bodies are honoured by **setting params** and stand as
|
||||
**validation cases** — if a body reads wrong, fix its params, never patch the derivation.
|
||||
**Validation caveat:** derived terrain can only honour a lore body if its params *permit* it — e.g.
|
||||
tidal flats require a moon param (the D-228 tidal term); confirm each validation body's params before
|
||||
treating its terrain as a contract (Velen's tidal-flat coast presumes Velen has a moon — verify).
|
||||
|
||||
### Anti-"squaring" (warp)
|
||||
A global position-keyed **domain-warp field** suppresses grid/seam artifacts. The warp is a
|
||||
**stateless, hash-based pure function of `(seed, body_id, position)`** — no lookup table, no
|
||||
thread-local cache (so it's order-independent across threads/platforms). It keeps **f64 sub-metre
|
||||
precision through to the final voxel coordinate, then quantises by truncation to the integer voxel
|
||||
address** — a cast, not a comparison, so IEEE-754-deterministic across targets. The ±8 m warp range
|
||||
makes FMA-contraction ULP variance (~1e-15 m) unable to shift the rounded voxel, so no platform
|
||||
guards are needed. The warp is *position math*, not a structural decision — D-010 integer discipline
|
||||
on all material/morphology decisions is preserved.
|
||||
|
||||
### Morphology — 8 families
|
||||
Selected by a strict **decision tree over integer `RegionProfile` inputs**: LavaField · FjordWall ·
|
||||
CliffCoast · BraidedDelta · DuneStrand · IncisedGorge · MeanderReach · AlluvialPlain (fallback).
|
||||
Requires adding `tectonic_class` to `RegionProfile`. MountainPass is a zone label sharing IncisedGorge
|
||||
geometry. **Hard gates (boolean, applied pre-selection in tree order):** fjord requires
|
||||
`GlaciationGrade ≥ 2`; LavaField requires `tectonic_class = Volcanic`; lithology bounds slope/form
|
||||
(see laws below).
|
||||
|
||||
### Frozen 17-zone `MorphologyZone` display vocabulary
|
||||
Derived labels per D-228, **enum→label map frozen at authoring time**. The canonical freeze point is
|
||||
the Rust `MorphologyZone` enum; **adding/renaming a zone requires a D-record amendment** (a code
|
||||
reviewer enforces this). The 17: open ocean, lake, tidal flat, dune strand, cliff coast, fjord,
|
||||
delta, estuarine, alluvial plain, river bank, meander reach, braided plain, valley floor, mountain
|
||||
pass, alpine, volcanic, wetland. **Note — zones ≠ families:** four zones (tidal flat, estuarine,
|
||||
alpine, wetland) have no distinct generator family; they are derived sub-classifications from
|
||||
family + elevation/water-height conditions (e.g. tidal flat = BraidedDelta at the tidal margin;
|
||||
alpine = AlluvialPlain/IncisedGorge above the treeline-elevation threshold).
|
||||
|
||||
### Zone seams — prevent incompatible, allow valid (don't patch)
|
||||
The boolean gates are discontinuities on continuous inputs, so "continuous→continuous" is NOT the
|
||||
mechanism. The mechanism is the **decision-tree gate ordering + a build-time compatibility matrix**:
|
||||
*incompatible* family pairs (e.g. MeanderReach↔Volcanic — geomorphically incoherent) cannot be
|
||||
adjacent classifier outputs; the matrix is a **build-time invariant test**, never a runtime override.
|
||||
*Valid* geomorphic seams (cliff↔fjord at the glaciation threshold, lithology faults) are **permitted
|
||||
sharp transitions** — kept sharp (real geology is sharp), made non-grid/organic by the warp; no
|
||||
modulation feathering. The warp also guarantees seams don't produce degenerate slivers at chunk scale
|
||||
(preserving the per-chunk tactical-content rule below).
|
||||
|
||||
### Believability laws (binding — inline, not just titles)
|
||||
- **Drainage monotonicity:** channels descend; respect the D8 thalweg (D-208); tributaries join from
|
||||
upstream; river mouths sit at sea level.
|
||||
- **Lithology → landform:** Rock → vertical faces/cliffs; Sand → ≤ angle of repose (~32°), dunes not
|
||||
cliffs; Gravel → braided channels/fans, not single-thread meander; Soil → rolling/floodplain;
|
||||
Wetland → ≤5° flats; Lava → sheets/shield slopes + lava tubes, immature drainage.
|
||||
- **Glaciation → form:** fjord requires GlaciationGrade ≥ 2; U-valleys ≥ 1; moraine ridges ≥ 1;
|
||||
cirques ≥ 2. GlaciationGrade 0 high-relief is V-ridges (fluvial), never glacial U-profiles.
|
||||
- **Climate → vegetation:** treeline transitions Forest → Scrub → Barren (no Forest→Barren skip);
|
||||
riparian Thicket/Scrub band 1–3 tiles along every perennial waterway; boundaries follow
|
||||
contours/moisture, not chunk edges.
|
||||
|
||||
### Game-feel constraints
|
||||
Every chunk carries ≥1 tactical decision point; cover ≠ concealment (mechanically distinct);
|
||||
high-ground asymmetric-but-not-dominant; chokepoints narrow enough to matter (river crossings
|
||||
3–15 m, gorge floors 2–8 m); **seasonal/tidal state must produce real passability changes, not
|
||||
cosmetic ones** — BraidedDelta/MeanderReach `ElevationDelta` calibrated so channels fall below the
|
||||
Q-105 high-water threshold and levees stay above it (flood = genuinely impassable, not recoloured).
|
||||
|
||||
### Mechanics
|
||||
- No per-tile `flow_direction[64×64]` in `ChunkContext` (D8 ≈ 152 m/cell — coarser than a chunk) →
|
||||
one basin-direction + global meander-curve params. Features with wavelength > 64 m (meanders,
|
||||
dunes, cliff continuations) seed from **Region-or-higher**, not the chunk seed.
|
||||
- **Caching:** `RegionProfile` is stored in `BodyWorldState` (D-203) (~6k/body, populated in the
|
||||
D-206 background pass) so the Atlas reads zone labels without triggering voxel derivation; voxel
|
||||
derivation is on-demand + cached, never persisted (D-227). Budget ~2.2–4.2 ms/chunk **(validate
|
||||
per-family in Phase 4 — FjordWall/IncisedGorge are far costlier than the AlluvialPlain fallback).**
|
||||
|
||||
**Resolves:** Q-101. **Touches:** Q-102 (cohesion = the warp). **Leaves open:** Q-103 (mutator op
|
||||
schema), Q-105 (region seasonal/clock state — the ElevationDelta calibration above is a forward
|
||||
contract to it).
|
||||
**Cross-refs:** D-227, D-228, D-203, D-206, D-208/209/210, D-010, D-234, D-142, D-217.
|
||||
**Raised by:** tile-derivation-contract workshop (Tyre, Gestalt, Troblum, Miri) + lead-interviewed
|
||||
decisions, 2026-06-07; verified by an adversarial pass from the same four.
|
||||
**Dissent:** Tyre's initial cross-family elevation-blend was resolved against (prevent-at-source).
|
||||
Early-integer-truncation of the warp (raised against Gestalt's `ElevationDelta` ranges and by Tyre)
|
||||
was resolved against in favour of f64-to-voxel.
|
||||
@@ -0,0 +1,595 @@
|
||||
---
|
||||
title: "Gestalt Round 1 — Morphology Algorithm Family + Game-Feel Constraints"
|
||||
workshop: tile-derivation-contract
|
||||
participant: Gestalt
|
||||
round: 1
|
||||
date: 2026-06-07
|
||||
---
|
||||
|
||||
# Gestalt Round 1: The Morphology Family and What Terrain Must Do
|
||||
|
||||
Let me break down what this actually means mechanically. We have two distinct
|
||||
problems that must be solved simultaneously: (1) the classification logic that
|
||||
selects which algorithm runs for a given region, and (2) the game-feel contract
|
||||
that algorithm must satisfy. These are not separable — an algorithm that is
|
||||
geologically plausible but produces flat, featureless terrain at the 64 m chunk
|
||||
scale is a failed algorithm regardless of its realism score.
|
||||
|
||||
---
|
||||
|
||||
## Framing: What the Morphology Algorithm Actually Produces
|
||||
|
||||
Tyre's API contract gives each morphology family a clean interface:
|
||||
|
||||
- **Receives:** `ChunkContext` (RegionProfile + warped position + elevation grid +
|
||||
flow data)
|
||||
- **Produces:** `ElevationDelta: i16` per voxel column (signed integer metres,
|
||||
delta from base heightmap)
|
||||
|
||||
That `ElevationDelta` output is then consumed by the material/vegetation/water
|
||||
derivation steps. So the morphology family's job is: given a region's coarse
|
||||
character, produce the fine-grain elevation variation within each chunk that (a)
|
||||
reads as physically coherent and (b) produces tactically interesting geometry.
|
||||
|
||||
The D-228 axis for "shape / geometry" is explicitly `f(elevation-step × material)` —
|
||||
sand slumps, rock faces go vertical. The morphology algorithm drives `elevation`;
|
||||
material drives how that elevation is expressed as renderable geometry. These two
|
||||
together produce cover, line-of-sight, and chokepoints.
|
||||
|
||||
---
|
||||
|
||||
## Part 1: The Six Morphology Families
|
||||
|
||||
I am enumerating eight families, not six — six is the minimum; eight is what the
|
||||
world actually needs. The selector logic is why this makes sense: some of these
|
||||
are statistically rare (fjord, lava field) but their game-feel is so distinct
|
||||
that collapsing them into a broader family would produce detectable sameness.
|
||||
|
||||
### Selection Logic Overview
|
||||
|
||||
The selector is a decision tree over integer inputs. All inputs are available in
|
||||
`RegionProfile` as Tyre has sketched it. I will specify what each input node
|
||||
needs to be, and flag one addition.
|
||||
|
||||
**Inputs required (all integer or enum — D-010 compliant):**
|
||||
|
||||
| Input | Type | Source |
|
||||
|-------|------|--------|
|
||||
| `slope_class` | `Flat / Gentle / Steep / Cliff` | 3×3 elevation kernel, already in RegionProfile |
|
||||
| `drainage_class` | `None / Ephemeral / Perennial / Major` | D8 flow accumulation |
|
||||
| `sea_level_margin` | `i32` metres | elevation − sea_level |
|
||||
| `lithology` | enum | body params + elevation strata |
|
||||
| `glaciation` | `u8` grade 0–4 | body params (temperature + tectonic age) |
|
||||
| `tectonic_class` | `Passive / Active / Volcanic` | body params (new — see below) |
|
||||
| `sub_biome` | enum | D-210 |
|
||||
|
||||
**One addition to RegionProfile I need:** `tectonic_class: TectonicClass` (Passive /
|
||||
Active / Volcanic). This is a body-level parameter (like `hydrosphere` and
|
||||
`tectonic_activity`) that modulates which high-energy families are eligible. A
|
||||
geologically dead body cannot produce lava fields or fault scarps. This keeps the
|
||||
classification honest and prevents algorithm outputs that contradict the body's
|
||||
established character.
|
||||
|
||||
---
|
||||
|
||||
### Family 1: Meander Reach
|
||||
|
||||
**Selection context:**
|
||||
- `slope_class` = Flat or Gentle
|
||||
- `drainage_class` = Perennial or Major
|
||||
- `sea_level_margin` > +20 m (above tidal influence)
|
||||
- `glaciation` <= 1 (not glaciated)
|
||||
- `lithology` = Alluvial or Sedimentary
|
||||
|
||||
**What the algorithm produces:**
|
||||
|
||||
A sinuous channel with a defined thalweg (deepest flow line), pointbar deposits
|
||||
on the inside of bends, and a cutbank face on the outside. The active floodplain
|
||||
is 3–12 m below the surrounding terrace. Oxbow lakes appear where prior meanders
|
||||
were cut off — these are ponds of still water on the floodplain, 4–20 m across.
|
||||
|
||||
The meander geometry is driven by a parametric sine-distortion of the D8 flow
|
||||
direction at chunk scale, with the amplitude controlled by `meander_intensity`
|
||||
(already in `RegionProfile`). The sinuosity parameter (ratio of channel length
|
||||
to valley length) ranges 1.3–2.8 depending on `meander_intensity`.
|
||||
|
||||
**ElevationDelta range:** −8 to +4 m (channel is below floodplain; terrace is
|
||||
above; total relief within a 64 m chunk is modest).
|
||||
|
||||
**Tactical character:** Long sight lines broken by river banks and terrace edges.
|
||||
The pointbar is open ground with poor cover; the cutbank is a steep earthen wall
|
||||
(1–3 m, provides hard cover). Oxbow lakes are impassable unless frozen or bridged.
|
||||
River crossing is a chokepoint — the channel itself, 3–15 m wide, forces engagement
|
||||
on the banks. Meander bends create natural flanking ambush positions where the bank
|
||||
curves away from a pursuer's sightline.
|
||||
|
||||
---
|
||||
|
||||
### Family 2: Incised Gorge / Badland
|
||||
|
||||
**Selection context:**
|
||||
- `slope_class` = Steep or Cliff
|
||||
- `drainage_class` = Perennial or Major
|
||||
- `sea_level_margin` > +50 m (high above sea level)
|
||||
- `tectonic_class` = Active (or high uplift rate from body params)
|
||||
- `lithology` = Sedimentary or Metamorphic
|
||||
|
||||
**What the algorithm produces:**
|
||||
|
||||
A V-shaped or slot canyon geometry. The channel is narrow (2–8 m wide at floor)
|
||||
and deeply incised (15–80 m below the surrounding plateau). Canyon walls are
|
||||
vertical or near-vertical rock faces. At the top, the plateau surface continues
|
||||
at the base heightmap elevation. Talus slopes appear at the base of cliff faces
|
||||
(coarser scree — Gravel lithology — where the cliff transitions to channel floor).
|
||||
|
||||
Within a 64 m chunk, a gorge manifests as: the plateau surface at base elevation
|
||||
(flat or gently rolling), a cliff edge dropping sharply, the canyon floor, and
|
||||
the far cliff rising again. Not every chunk contains the full cross-section —
|
||||
chunks on the plateau see only the cliff edge; chunks on the floor see only walls.
|
||||
|
||||
**ElevationDelta range:** −80 to 0 m (plateau stays at base; gorge cuts downward).
|
||||
|
||||
**Tactical character:** Extreme vertical asymmetry — the dominant tactical variable.
|
||||
A character on the plateau rim has total LOS advantage over the floor, can fire
|
||||
down, cannot be flanked from below. But cliff faces are un-climbable without gear
|
||||
(a mechanical constraint the tile data supports: Cliff shape on Rock material =
|
||||
impassable). Gorge floors are extremely exposed if the rim is held. Gorge passages
|
||||
are decisive chokepoints — a party controlling a narrow canyon floor controls the
|
||||
route. The slot geometry creates situations where the player cannot retreat without
|
||||
crossing the enemy's field of fire.
|
||||
|
||||
---
|
||||
|
||||
### Family 3: Braided Delta / Distributary Fan
|
||||
|
||||
**Selection context:**
|
||||
- `slope_class` = Flat or Gentle
|
||||
- `drainage_class` = Major
|
||||
- `sea_level_margin` between −5 and +15 m (near sea level)
|
||||
- `lithology` = Alluvial
|
||||
- NOT `glaciation` >= 3
|
||||
|
||||
**What the algorithm produces:**
|
||||
|
||||
Multiple shallow, anastomosing channels (2–6 m wide, 0.3–1.5 m deep) dividing
|
||||
and rejoining across a nearly-flat fan surface. Islands of slightly higher ground
|
||||
(natural levees and splays) sit between channels. The whole surface is within 2–4 m
|
||||
of sea level; many tiles are waterlogged or tidal-flat state at high water.
|
||||
|
||||
Channel positions are driven by a multi-seed Voronoi partition of the chunk area
|
||||
with D8 flow direction as an attractor — each Voronoi center generates a channel
|
||||
branch. The `meander_intensity` parameter controls how much the branches deviate
|
||||
from a straight downslope path.
|
||||
|
||||
**ElevationDelta range:** −2 to +3 m (almost flat; channels are slight incisions;
|
||||
levees are slight rises).
|
||||
|
||||
**Tactical character:** Mobility is the dominant constraint. The network of small
|
||||
channels fragments the surface into irregular islands — movement requires knowing
|
||||
which crossings are wadeable (Shallow Water at low tide) vs blocked (Deep Water
|
||||
at high tide). The Q-105 tidal/seasonal water model makes the same terrain
|
||||
tactically different at different times of day. Cover is minimal (low, flat
|
||||
vegetation, no high ground) but concealment is high (dense reed beds, tall grass,
|
||||
poor LOS in all directions). Ambushes are set in reed cover; pursuit is slow;
|
||||
routing is non-obvious.
|
||||
|
||||
---
|
||||
|
||||
### Family 4: Dune Strand / Aeolian Plain
|
||||
|
||||
**Selection context:**
|
||||
- `sea_level_margin` between −2 and +30 m
|
||||
- `lithology` = Sand
|
||||
- `sub_biome` = Arid or Coastal
|
||||
- `drainage_class` = None or Ephemeral
|
||||
- `slope_class` = Flat or Gentle
|
||||
|
||||
**What the algorithm produces:**
|
||||
|
||||
Transverse or barchan dune forms, oriented perpendicular to `dune_orientation`
|
||||
(compass octant in RegionProfile — prevailing wind). Dune height 2–12 m;
|
||||
inter-dune corridors are flat sand at base elevation. The slip face (steep lee
|
||||
side, ~32° angle of repose) is the sharp edge; the windward stoss side is gentle.
|
||||
|
||||
Within a 64 m chunk: typically 1–3 dune crests visible, with inter-dune troughs.
|
||||
The pattern repeats with seed-driven wavelength variation (0.7–1.4× nominal spacing)
|
||||
to prevent visible periodicity.
|
||||
|
||||
**ElevationDelta range:** −3 to +12 m (troughs below base; crests above).
|
||||
|
||||
**Tactical character:** Dune crests are the key terrain feature — short-range cover
|
||||
on the stoss side (the gentle approach), sudden exposure on the slip face (the sharp
|
||||
drop). Crest control is inherently ephemeral in the sense that the geometry forces
|
||||
skyline exposure — you are visible from anywhere on the stoss side the moment you
|
||||
crest. The inter-dune corridors are covered routes but lead into bowls with no
|
||||
exit cover. Movement slows on loose sand (a material-driven movement penalty).
|
||||
LOS is extremely range-dependent: from a dune top you see far; in a trough you see
|
||||
perhaps 20 m.
|
||||
|
||||
---
|
||||
|
||||
### Family 5: Cliff Coast / Sea Stack
|
||||
|
||||
**Selection context:**
|
||||
- `sea_level_margin` between −20 and +10 m
|
||||
- `slope_class` = Steep or Cliff
|
||||
- `lithology` = Rock or Metamorphic (NOT Sand — that is Dune Strand)
|
||||
- `glaciation` <= 1
|
||||
|
||||
**What the algorithm produces:**
|
||||
|
||||
A wave-cut platform at or just below sea level, then a cliff face rising 10–80 m
|
||||
to a clifftop plateau. The cliff face is sheer rock (Cliff shape, Rock material,
|
||||
impassable). Sea stacks are isolated rock pillars standing in shallow water
|
||||
offshore — generated as local elevation spikes in the sub-sea zone where a noise
|
||||
field exceeds a lithology-dependent threshold (hard rock produces stacks; soft rock
|
||||
produces a smooth platform).
|
||||
|
||||
The clifftop is the base heightmap elevation; the platform and stack geometry are
|
||||
ElevationDelta downward from there (platform −10 to −30 m relative to clifftop,
|
||||
meaning near sea level; stacks are +0 to +20 m above sea level but below the main
|
||||
cliff top).
|
||||
|
||||
**ElevationDelta range:** −30 to 0 m (cliff is a step-down to the coast; no upward
|
||||
delta from clifftop).
|
||||
|
||||
**Tactical character:** Unambiguous dominance of the high ground. Cliff-edge
|
||||
positions are impregnable from below (cliff face is impassable) and exposed from
|
||||
above. Clifftop defenders have infinite LOS over the ocean approach. Attackers from
|
||||
the sea face a wall. The wave-cut platform, when exposed at low tide, is a
|
||||
short-duration tactical route — accessible only during specific water states (the
|
||||
Q-105 tidal model), creating timed-window scenarios. Sea stacks provide intermediate
|
||||
cover in an otherwise featureless marine approach — the only interrupt of a long
|
||||
open crossing.
|
||||
|
||||
---
|
||||
|
||||
### Family 6: Fjord Wall / Glaciated Inlet
|
||||
|
||||
**Selection context:**
|
||||
- `sea_level_margin` between −50 and +200 m
|
||||
- `slope_class` = Cliff (required)
|
||||
- `glaciation` >= 2
|
||||
- `lithology` = Rock or Metamorphic
|
||||
|
||||
**What the algorithm produces:**
|
||||
|
||||
Near-vertical rock walls rising from deep water. The U-shaped cross-section
|
||||
(glacial, not V-shaped fluvial) means the wall base is at or below sea level and
|
||||
the wall face continues upward to the fjord rim hundreds of meters above. Hanging
|
||||
valleys produce waterfalls (a narrow shelf partway up the wall where a tributary
|
||||
glacier once entered). The water within the fjord is deep (ElevationDelta far
|
||||
below sea level on the water side).
|
||||
|
||||
At the 64 m chunk scale: most fjord wall chunks are almost entirely cliff face.
|
||||
The only variation is whether the chunk is at the waterline (bottom of cliff meets
|
||||
water), mid-wall (continuous cliff face), or near the rim (cliff top with overhanging
|
||||
vegetation at the edge). Hanging valley shelves appear occasionally as a narrow
|
||||
flat ledge at a consistent elevation band across the wall.
|
||||
|
||||
**ElevationDelta range:** −200 to +50 m (deep water below; wall can rise significantly
|
||||
above base heightmap where the hanging valley creates a ledge).
|
||||
|
||||
**Tactical character:** Maximum verticality. The fjord is effectively a sealed
|
||||
corridor — the wall faces are impassable; movement is restricted to the narrow
|
||||
strip at the water's edge (when it exists) or to the fjord rim. The hanging valley
|
||||
shelves are the critical feature: a narrow ledge 40–80 m above the waterline that
|
||||
provides fire positions looking down the full length of the fjord. Controlling a
|
||||
hanging valley shelf means controlling the entire fjord below. Approach by water is
|
||||
extremely exposed — no cover, walls on both sides, no retreat options.
|
||||
|
||||
---
|
||||
|
||||
### Family 7: Alluvial Plain / Floodplain Terrace
|
||||
|
||||
**Selection context:**
|
||||
- `slope_class` = Flat
|
||||
- `drainage_class` = None or Ephemeral (no active major channel in this region)
|
||||
- `sea_level_margin` > +5 m
|
||||
- `lithology` = Alluvial or Sedimentary
|
||||
- `glaciation` <= 1
|
||||
|
||||
**What the algorithm produces:**
|
||||
|
||||
The default "open country" family. Low-relief terrain with gentle undulation from
|
||||
a filtered noise field. Occasional low terrace edges (1–3 m) mark former floodplain
|
||||
levels. Isolated stone outcrops (lithology-dependent) provide local relief. The
|
||||
dominant feature is the long sight line — this is the family where LOS extends to
|
||||
the full render distance.
|
||||
|
||||
**ElevationDelta range:** −3 to +5 m (mild undulation, terrace edges).
|
||||
|
||||
**Tactical character:** This is the exposed terrain family, and it is here by design.
|
||||
The game needs open ground — not every biome should be a maze of cover. Alluvial
|
||||
plains force movement decisions about crossing open ground, using dead ground
|
||||
(slight hollows where elevation puts you below the local crestline), and timing
|
||||
movement to reach scattered cover (the occasional outcrop or terrace edge). The
|
||||
terrace edges are the key tactical micro-feature: a 2 m earthen drop that provides
|
||||
hard cover from one direction but exposes you from the other.
|
||||
|
||||
---
|
||||
|
||||
### Family 8: Lava Field / Volcanic Surface
|
||||
|
||||
**Selection context:**
|
||||
- `tectonic_class` = Volcanic
|
||||
- `lithology` = Lava (basalt)
|
||||
- `sub_biome` = Volcanic or Barren
|
||||
|
||||
**What the algorithm produces:**
|
||||
|
||||
Two sub-types depending on lava flow age (body parameter):
|
||||
- **Pahoehoe (fresh/smooth):** Gently rolling surface with occasional lava tubes
|
||||
(collapsed tunnels — impassable gaps, 2–8 m across, 5–20 m deep). Glassy,
|
||||
uneven surface with no vegetation.
|
||||
- **A'a (older/rough):** Sharply irregular surface with scoria ridges 1–4 m high.
|
||||
Near-impassable except along flow channels where the surface smoothed.
|
||||
|
||||
**ElevationDelta range:** −20 to +8 m (lava tubes cut down; scoria ridges add height).
|
||||
|
||||
**Tactical character:** Irregular terrain with extreme movement costs on A'a surface
|
||||
(material: Lava, movement penalty severe). Lava tubes are natural ambush corridors —
|
||||
narrow, enclosed, high-concealment, but with no exit options once committed. The
|
||||
pahoehoe surface, while traversable, produces a distinctive movement sound profile
|
||||
(glassy clicks vs soft dirt) that may affect stealth mechanics.
|
||||
|
||||
---
|
||||
|
||||
## Part 2: The Selection Decision Tree
|
||||
|
||||
This is the classifier that produces `MorphologyFamily` from `RegionProfile`. It
|
||||
is a deterministic decision tree, not a probability distribution — each input
|
||||
combination maps to exactly one primary family (with a secondary family for
|
||||
boundary blending, as Tyre's API already anticipates).
|
||||
|
||||
```
|
||||
if tectonic_class == Volcanic AND lithology == Lava:
|
||||
→ LavaField
|
||||
|
||||
else if sea_level_margin IN [-200, +10] AND slope_class IN [Steep, Cliff]:
|
||||
if glaciation >= 2:
|
||||
→ FjordWall
|
||||
else:
|
||||
→ CliffCoast
|
||||
|
||||
else if sea_level_margin IN [-5, +15] AND drainage_class == Major AND lithology == Alluvial:
|
||||
→ BraidedDelta
|
||||
|
||||
else if lithology == Sand AND drainage_class IN [None, Ephemeral]:
|
||||
→ DuneStrand
|
||||
|
||||
else if drainage_class IN [Perennial, Major] AND slope_class IN [Steep, Cliff]
|
||||
AND sea_level_margin > +50:
|
||||
→ IncisedGorge
|
||||
|
||||
else if drainage_class IN [Perennial, Major] AND slope_class IN [Flat, Gentle]
|
||||
AND sea_level_margin > +20:
|
||||
→ MeanderReach
|
||||
|
||||
else:
|
||||
→ AlluvialPlain ← the default / open-country family
|
||||
```
|
||||
|
||||
**Why AlluvialPlain is the fallback:** It is the correct geological answer for
|
||||
"didn't meet any special conditions." It is also the most tactically important
|
||||
family to have abundant — players need open ground. If everything is a special
|
||||
terrain family, every encounter becomes a cave-fight.
|
||||
|
||||
**Secondary family for blending:** When a chunk sits on a region boundary, the
|
||||
secondary family is the result of running this tree on the adjacent `RegionProfile`.
|
||||
The families that blend gracefully (MeanderReach + AlluvialPlain) are common and
|
||||
well-behaved. The families that do NOT blend gracefully (FjordWall + DuneStrand)
|
||||
should never be adjacent — and if the world generates that adjacency, it indicates
|
||||
a body parameter inconsistency that the body-params validator should flag.
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Family Transition Boundaries — Where the Families Actually Meet
|
||||
|
||||
These are the terrain edges that players will encounter most often. They are
|
||||
not smooth transitions — they are geomorphological discontinuities, and that is
|
||||
correct. The domain warp (Tyre's mechanism) prevents the discontinuity from
|
||||
aligning with a chunk grid line, but the discontinuity itself should be sharp.
|
||||
|
||||
**Meander Reach → Alluvial Plain:**
|
||||
The river floodplain (Meander) transitions to the surrounding upland (Alluvial)
|
||||
at the terrace edge. This is the most common transition in lowland terrain.
|
||||
Tactically: crossing from open plain to the floodplain means descending the terrace
|
||||
edge — a 1–3 m earthen step that provides cover in one direction.
|
||||
|
||||
**Incised Gorge → Alluvial Plain:**
|
||||
The plateau continues as AlluvialPlain; the gorge cuts into it. From the surface,
|
||||
this appears as a sudden cliff edge. The transition is the most dramatic visible
|
||||
boundary in the game — you walk across open ground and the terrain simply drops
|
||||
away. Tactically: the cliff edge is a LOS boundary — you cannot see into the gorge
|
||||
until you are at the rim.
|
||||
|
||||
**Braided Delta → Meander Reach:**
|
||||
Moving upstream, the multi-channel delta collapses into a single meandering channel.
|
||||
The transition is gradual — channels reduce in number, ground height increases
|
||||
slightly. Tactically: the covered, fragmented delta gives way to the more open
|
||||
meander terrain.
|
||||
|
||||
**Cliff Coast → Alluvial Plain:**
|
||||
Clifftop terrain is AlluvialPlain at the heightmap elevation; below is CliffCoast.
|
||||
The transition is the cliff edge — same character as Gorge → Plain but coastal.
|
||||
|
||||
**Fjord Wall → AlluvialPlain (at the fjord head):**
|
||||
At the landward end of a fjord, the walls close off and the valley floor becomes
|
||||
accessible. This narrow valley floor is AlluvialPlain — a classic settlement site.
|
||||
Tactically: the fjord head is the one location where the wall-dominated fjord opens
|
||||
into traversable terrain. It is always a strategic point.
|
||||
|
||||
---
|
||||
|
||||
## Part 4: Game-Feel Constraints the Contract Must Satisfy
|
||||
|
||||
This is the section I am most insistent on. The morphology algorithm family must
|
||||
satisfy these constraints, or it fails as a design — regardless of geological
|
||||
fidelity. These are not aesthetic preferences; they are mechanical requirements
|
||||
that flow from D-008 (action design) and the five pillars.
|
||||
|
||||
### Constraint 1: Every chunk must contain at least one tactical decision point
|
||||
|
||||
A "tactical decision point" is any terrain feature that creates a meaningful
|
||||
choice about movement, positioning, or engagement: a crest, a bank, a chokepoint,
|
||||
a crossing, a blind corner, a piece of cover.
|
||||
|
||||
Alluvial plain is the hardest family to satisfy this constraint — flat terrain
|
||||
with no features is not interesting. The solution: the terrace edges (1–3 m
|
||||
earthen drops, never absent from AlluvialPlain chunks) and the isolated outcrops
|
||||
(seed-driven, present in ~60% of chunks) always provide at least one feature.
|
||||
The constraint is violated if an algorithm produces a perfectly flat, featureless
|
||||
64×64 m surface. This must be checked during family implementation.
|
||||
|
||||
### Constraint 2: Cover and concealment must be distinct
|
||||
|
||||
D-228's composite tile schema gives us the tools: `TerrainMaterial` (hard cover
|
||||
— does a rock stop a projectile), `Vegetation` (soft cover / concealment — does
|
||||
a reed bed block LOS), and `elevation` / shape (geometric cover — am I below
|
||||
the crestline). These are orthogonal axes, and a well-designed morphology family
|
||||
exploits the distinction.
|
||||
|
||||
- Meander pointbar: low elevation (no geometric cover) + dense Vegetation
|
||||
(concealment) + Alluvial soil (no hard cover) → concealment without protection
|
||||
- Gorge cliff face: Cliff shape (un-crossable) + Rock material (hard cover) + no
|
||||
Vegetation → hard cover with no concealment
|
||||
- Dune crest: geometric cover only (elevation blocks LOS from below) + no hard cover
|
||||
(sand stops nothing) + no concealment (bare sand, no vegetation)
|
||||
- Reed delta: Vegetation concealment + no geometric cover + no hard cover → the
|
||||
ambush terrain
|
||||
|
||||
The morphology algorithm must place these in combination, not uniformly. A family
|
||||
that produces uniform concealment throughout is not interesting. The good terrain
|
||||
is the one where the player has to read the ground to understand which tiles give
|
||||
which kind of protection.
|
||||
|
||||
### Constraint 3: Verticality must create asymmetric engagement conditions
|
||||
|
||||
D-008 explicitly calls for Z-levels and vertical asymmetry. The terrain derivation
|
||||
is the source of that asymmetry at the world level (buildings create it at the
|
||||
settlement level). The constraints:
|
||||
|
||||
- **The high ground must be visibly identifiable** from the approach. Players should
|
||||
be able to see that the gorge rim dominates the floor, that the dune crest exposes
|
||||
them, that the fjord shelf controls the inlet. The rendered geometry must make this
|
||||
legible — and that geometry comes from the ElevationDelta values the morphology
|
||||
algorithm produces.
|
||||
|
||||
- **The high ground must not be universally dominant.** If every elevated position
|
||||
is trivially superior, the game collapses to "whoever controls the high ground
|
||||
wins." The counter: (a) the high ground is often exposed from range (silhouette),
|
||||
(b) close terrain (gorge wall, dense reed bed) negates ranged advantages, (c) the
|
||||
cliff face is impassable from below but also from above (you cannot exploit the
|
||||
position you cannot reach). The morpology algorithm must produce terrain where
|
||||
the high ground advantage is real but contestable.
|
||||
|
||||
- **Chokepoints must be narrow enough to matter.** A river ford that is 50 m wide
|
||||
is not a chokepoint. The meander algorithm must produce crossings narrow enough
|
||||
(3–15 m) that controlling one bank can meaningfully contest passage. The gorge
|
||||
floor must be narrow enough (2–8 m at the cliff base) that it cannot be flanked.
|
||||
|
||||
### Constraint 4: The derivation must be legible at the Atlas layer
|
||||
|
||||
The Atlas (Phase 3–4 deliverable) must show morphology zone boundaries as visible
|
||||
map features — players planning movement at the region scale need to read the terrain.
|
||||
This means:
|
||||
|
||||
- `RegionProfile.morphology_zone` must map cleanly to a distinct Atlas color / icon
|
||||
— Tyre's API already provides this; I am confirming the family vocabulary supports
|
||||
this legibility.
|
||||
- The transition between families must be visible at Atlas resolution (the 1 km
|
||||
region scale) even though the texture of that transition is driven by chunk-scale
|
||||
geometry. A "MeanderReach region" on the Atlas should look like a floodplain, not
|
||||
just a flat-colored zone.
|
||||
|
||||
### Constraint 5: Seasonal and dynamic state must change tactical character, not just appearance
|
||||
|
||||
Q-105 ties the tidal and seasonal water model to derived tile states. The morphology
|
||||
families that depend on this model are BraidedDelta and MeanderReach — both produce
|
||||
floodplain terrain where the Q-105 water-height determines which tiles are passable.
|
||||
|
||||
The contract: the morphology algorithm must produce ElevationDelta values that, when
|
||||
combined with the regional water-height model, create a genuine tactical difference
|
||||
between high-water and low-water states. "The delta floods at high tide" must mean
|
||||
"you cannot cross the delta at high tide, period" — not "the color of the water
|
||||
tiles changes." This requires the delta channel elevations to be genuinely below
|
||||
the Q-105 high-water threshold, and the island levees to be genuinely above it.
|
||||
|
||||
This is an explicit coupling between the morphology algorithm's ElevationDelta
|
||||
output and the Q-105 model's water-height range. The contract must pin this:
|
||||
BraidedDelta and MeanderReach algorithms must produce elevations relative to
|
||||
sea_level such that:
|
||||
- `channel_floor_elevation ≈ sea_level − 0.5 to sea_level + 0.5 m`
|
||||
- `levee_elevation ≈ sea_level + 1 to sea_level + 3 m`
|
||||
- `high_water_height ≈ sea_level + 1 to sea_level + 1.5 m` (Q-105 tidal term)
|
||||
|
||||
This puts channels under water at high tide and levees dry at low tide — a real
|
||||
tactical difference.
|
||||
|
||||
---
|
||||
|
||||
## Part 5: What I Need From Other Participants
|
||||
|
||||
**From Tyre:**
|
||||
- Confirmation that `tectonic_class` can be added to `RegionProfile` without
|
||||
touching the D8 / Layer-1 pipeline. My expectation: it derives from body params
|
||||
(already available at the region classification stage), so it is a free addition.
|
||||
- The morphology algorithm's contract for its per-chunk warp offset interaction.
|
||||
Specifically: does the warp apply before or after the algorithm sees the
|
||||
ElevationDelta? My position: warp applies to the spatial query position, not to
|
||||
the ElevationDelta output. The algorithm produces a delta for the warped position;
|
||||
material derivation then runs on that delta. This keeps the algorithm implementations
|
||||
simple.
|
||||
- Clarification on the secondary family blending: when `blend_weight < 255`,
|
||||
does the caller blend two ElevationDelta values (one per family), or does each
|
||||
family produce its own full `TileAxes` and the caller blends those? I prefer
|
||||
blending ElevationDelta only — material/vegetation derivation from a blended
|
||||
elevation is cleaner than blending two independent material sets.
|
||||
|
||||
**From Miri:**
|
||||
- Which families need sub-region variation that cannot be expressed by the algorithm
|
||||
alone? My expectation: MeanderReach needs oxbow lake positions (a secondary zone
|
||||
within a meander region chunk). I propose this is handled by the algorithm
|
||||
internally — oxbow lake detection runs a simple cutoff test on meander radius
|
||||
vs wavelength, producing a local sub-feature without requiring a second region
|
||||
classification. Miri should confirm this matches how real oxbow lakes form
|
||||
(spoiler: it does — they form when meander radius exceeds a threshold relative
|
||||
to channel width, exactly the kind of threshold test the algorithm can implement).
|
||||
- Confirmation that the 8 families cover the morphology diversity the worldbuilder
|
||||
expects. I anticipate Miri will want to add a Mountain Pass family (which I have
|
||||
omitted as a deliberate choice — see note below).
|
||||
|
||||
**Note on Mountain Pass:** D-234 references "mountain-pass → ribbon only (streets
|
||||
linear along the terrain axis)" as a morphology zone affecting settlement. I have
|
||||
not included MountainPass as a distinct morphology FAMILY because a mountain pass
|
||||
is not a terrain-generating algorithm — it is an Incised Gorge at high elevation
|
||||
with specific access semantics. The RegionProfile's `morphology_zone` can carry
|
||||
MountainPass as a zone classification (distinct from IncisedGorge in semantics and
|
||||
Atlas display) while using the IncisedGorge algorithm for geometry. This keeps the
|
||||
algorithm count lean without losing the semantic distinction D-234 requires.
|
||||
|
||||
**From Troblum:**
|
||||
- Performance validation on the algorithm implementations. The families I am most
|
||||
concerned about are BraidedDelta (multi-seed Voronoi is potentially expensive)
|
||||
and FjordWall (the hanging valley shelf detection may require neighbor lookups).
|
||||
Both should be implementable in ~1–2 ms/chunk; Troblum should stress-test.
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Family | Primary Selector | ElevationDelta | Key Tactical Feature |
|
||||
|--------|-----------------|----------------|---------------------|
|
||||
| MeanderReach | Flat + Perennial/Major + above tide | −8 to +4 m | River crossing chokepoints; bank cover |
|
||||
| IncisedGorge | Steep/Cliff + Perennial + high elevation + Active | −80 to 0 m | Extreme vertical asymmetry; floor exposed |
|
||||
| BraidedDelta | Flat + Major + near sea level + Alluvial | −2 to +3 m | Tidal-cycle movement changes; concealment |
|
||||
| DuneStrand | Sand + arid/coastal + no drainage | −3 to +12 m | Crest exposure; dead-ground corridors |
|
||||
| CliffCoast | Near sea level + Steep/Cliff + non-glaciated + Rock | −30 to 0 m | Timed access via wave-cut platform |
|
||||
| FjordWall | Cliff + glaciated >= 2 + Rock | −200 to +50 m | Hanging valley fire positions; sealed corridor |
|
||||
| AlluvialPlain | Fallback (no special conditions) | −3 to +5 m | Open ground; terrace edges; long LOS |
|
||||
| LavaField | Volcanic + Lava lithology | −20 to +8 m | Movement cost; tube ambush corridors |
|
||||
|
||||
The algorithm-family vocabulary doubles as the `MorphologyZone` vocabulary for D-228
|
||||
region-level semantics, with MountainPass added as a zone classification (not a
|
||||
distinct algorithm) for D-234 compatibility.
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
title: "Miri Round 1 — Morphological realism and named-feature stability"
|
||||
workshop: tile-derivation-contract
|
||||
participant: Miri
|
||||
round: 1
|
||||
date: 2026-06-07
|
||||
note: "Authored by the Miri agent (read-only tool-set); transcribed to disk by the lead."
|
||||
---
|
||||
|
||||
# Miri Round 1: What the Morphology Family Must Honor
|
||||
|
||||
## Framing: what "reads true" on an alien world
|
||||
|
||||
The Reach's bodies are not Earth — different gravity, hydrospheres, tectonic
|
||||
histories — but they obey the same physical laws. "Reads true" ≠ "looks like
|
||||
Earth"; it means terrain behaviour is internally consistent with the body's
|
||||
parameters. A moonless world shows no tidal flats; a tropical ocean world shows
|
||||
no fjords; a volcanic body has immature drainage and lava sheets, not deeply
|
||||
incised river valleys; a GlaciationGrade-0 body has V-shaped fluvial valleys, never
|
||||
glacial U-profiles. The uncanny valley in terrain is one rule broken visibly in
|
||||
one spot — one river flowing uphill destroys a hundred correct ones.
|
||||
|
||||
## 1. Morphological laws the derivation must honour
|
||||
|
||||
- **Drainage consistency (non-negotiable).** Channels descend monotonically; the
|
||||
meander algorithm adds sinuosity but must respect the D8 thalweg direction
|
||||
(D-208) — never raise the channel floor. Tributaries join from upstream; river
|
||||
mouths sit at sea level, no exceptions.
|
||||
- **Lithology → landform.** TerrainMaterial bounds the allowed slope/form: Rock →
|
||||
vertical faces/cliffs; Sand → ≤ angle of repose (~32°), dunes not cliffs; Gravel
|
||||
→ braided channels/fans (not single-thread meander); Soil → rolling/floodplain;
|
||||
Wetland → ≤5° flats; Lava → sheets/shield slopes + lava tubes, immature drainage.
|
||||
The family selector must honour this at region scale (no dune algorithm on a rock
|
||||
coast; no incised-gorge family on high-Lava regions).
|
||||
- **Glaciation history → form (globally visible).** Fjord requires GlaciationGrade
|
||||
≥ 2; U-valleys ≥ 1; moraine ridges ≥ 1; cirques ≥ 2. GlaciationGrade-0 high-relief
|
||||
is knife-edge V-ridges from fluvial erosion, not glacial. Fjord-vs-cliff-coast is
|
||||
a **boolean exclusion** on GlaciationGrade, not a weighted roll.
|
||||
- **Climate → vegetation coherence.** Treeline transitions Forest → Scrub → Barren
|
||||
(no direct Forest→Barren); polar latitudes are Tundra/Barren even at sea level;
|
||||
riparian Thicket/Scrub band 1–3 tiles along every perennial waterway (visual +
|
||||
tactical); natural vegetation boundaries follow contours/moisture, not chunk edges.
|
||||
- **Coast type is multi-factor** (slope × lithology × glaciation × tidal/moon ×
|
||||
sediment): fjord / cliff coast / dune strand / tidal flat / delta / lava coast,
|
||||
with the fjord↔cliff gate hard-keyed on GlaciationGrade.
|
||||
|
||||
## 2. Named features — the wiki/atlas consistency contract
|
||||
|
||||
D-228 is right that named types are derived display labels, never stored — which
|
||||
makes **label derivation canonical + stable** load-bearing: the `MorphologyZone`
|
||||
enum→label-string mapping must be frozen at content-authoring time, or every named
|
||||
feature across all bodies shifts at once (bulk lore contradiction). The stable API
|
||||
wiki authors/Atlas depend on is the **zone vocabulary**, not the underlying axis tuple.
|
||||
|
||||
**Priority-1 lore-anchored bodies (direct wiki→generator contracts):**
|
||||
- **Kallast (Ran)** — "grain fields visible from low orbit" ⇒ alluvial-plain dominance, Soil, very low slope; no mountain ranges/fjords.
|
||||
- **Velen (Van Maanen's Star, D-050)** — temperate-maritime, fog ⇒ CoastalLowland, tidal-flat/dune coast, no cliff coasts/fjords.
|
||||
- **Ross 128 primary** — "predominantly ocean" ⇒ low-elevation island terrain, dune/tidal coast, no cliff coasts/fjords.
|
||||
- **Cygni B** — dense high-iron ⇒ Rock/Lava substrate, volcanic plains, immature drainage; not incised valleys/alluvial plains.
|
||||
- **Grünfeld** — dim flux + decades of soil amendment ⇒ marginal Gravel/Rock substrate, Scrub/Barren natural baseline (settlement layer adds Crop).
|
||||
|
||||
**Priority-2 type-consistency:** wherever the Atlas shows "fjord"/"mountain pass"/
|
||||
"river bank"/"delta", the geometry must follow through (deep water by cliff walls;
|
||||
saddle profile + flanking elevation; riparian band + bench; distributary fan).
|
||||
|
||||
**Proposed canonical MorphologyZone vocabulary (~17):** open ocean, lake, tidal
|
||||
flat, dune strand, cliff coast, fjord, delta, estuarine, alluvial plain, river
|
||||
bank, meander reach, braided plain, valley floor, mountain pass, alpine, volcanic,
|
||||
wetland. Agree these before Phase-4 content authoring resumes (named now, not
|
||||
necessarily implemented).
|
||||
|
||||
## 3. Answers to Tyre's questions
|
||||
|
||||
- **Sub-chunk variation:** no secondary zone field needed on `ChunkContext`. Meander
|
||||
reach (ox-bows) and delta (distributary islands) handle sub-chunk features
|
||||
*internally*; tidal-flat wet/dry is a whole-region state from the Q-105 water
|
||||
height. Tyre's `ChunkContext` as sketched suffices.
|
||||
- **RegionProfile persistence:** store `RegionProfile` (incl. `morphology_zone`) in
|
||||
`BodyWorldState` at the **same cache tier as D-209 feature tags** — the Atlas
|
||||
reads labels at region resolution (~6,000 regions/body) and must not trigger
|
||||
chunk-level derivation. ~tens of KB/body; populate in the D-206 background pass
|
||||
(~50 ms/body order).
|
||||
|
||||
## 4. Body-class modulation of RIVER_THRESHOLD
|
||||
|
||||
The global `RIVER_THRESHOLD = 200` should vary by body class via an integer lookup
|
||||
`(hydrosphere, tectonic_activity, precipitation_class) → threshold`. Crucially,
|
||||
`precipitation_class` (and `GlaciationGrade`) should be **derived from body-level
|
||||
params** (stellar type, orbital radius, hydrosphere), not authored per body —
|
||||
authored values silently drift from the terrain the generator actually produces.
|
||||
|
||||
## 5. Setting-voice note (Phase-6, flagged not to foreclose)
|
||||
|
||||
Keep the label vocabulary as plain English geography terms; the Drifter's-Guide
|
||||
setting voice layers on top in content authoring. Round 2 should not constrain the
|
||||
vocabulary in a way that blocks that later layer.
|
||||
|
||||
## 6. What I need in Round 2
|
||||
- **Gestalt:** the ≥6 families + selectors; confirm the Priority-1 lore bodies are
|
||||
reliably producible, and that GlaciationGrade-0 → no-fjord is a hard pre-selector gate.
|
||||
- **Tyre:** confirm `RegionProfile` lives in `BodyWorldState` at the D-209 tier; confirm
|
||||
`GlaciationGrade`/`precipitation_class` are derived from body params, not authored.
|
||||
- **Troblum:** confirm memory feasibility of ~6,000 `RegionProfile`s/body in the cache.
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: "Workshop Brief: Tile Derivation Contract (Q-101)"
|
||||
description: "Resolve Q-101 — how a coarse Layer-1 cell + seed becomes coherent ~1m voxel geometry across ~3 scale jumps, deterministically. The keystone of the 'what's in a tile' question."
|
||||
type: workshop
|
||||
status: active
|
||||
workshop: tile-derivation-contract
|
||||
created: 2026-06-07
|
||||
decision_refs: [D-227, D-228]
|
||||
---
|
||||
|
||||
# Workshop Brief: Tile Derivation Contract (Q-101)
|
||||
|
||||
## The question
|
||||
|
||||
The generation cascade is built down through Layer 4 (heightmap → drainage → economy →
|
||||
settlement → quarter skeleton + per-building tags). The **tile itself (Layer 5) is the one
|
||||
unbuilt layer** — and the design stalled at **Q-101**, the keystone:
|
||||
|
||||
> How does a coarse, map-scale hint (a ~78 km Layer-1 cell) plus a seed become fine,
|
||||
> ~1 m-voxel **coherent** geometry — deterministically (D-010), without "squaring" (visible
|
||||
> grid artifacts at scale boundaries) — across ~3 scale jumps:
|
||||
> **78 km cell → ~1 km region → 64 m chunk → 1 m voxel**?
|
||||
|
||||
The frame is already decided:
|
||||
- **D-227** — derive-don't-store: `subtile(x,y,z) = derive(seed, atlas, pos)`, recomputed on
|
||||
demand + cached, never persisted; volumetric voxels (1 m) / subvoxels (0.5 m); surface +
|
||||
subsurface. Only tile-mutators are saved.
|
||||
- **D-228** — a tile is orthogonal derived axes (`TerrainMaterial`/`FloorMaterial`/`Vegetation`/
|
||||
`Water`/`elevation`); named types are display labels; snow/ice are seasonal region state.
|
||||
|
||||
What's **open**: the *how* — the inter-layer hint+seed→geometry API (Tyre sketched a
|
||||
`RegionHint`) and a **context-driven morphology algorithm family** (≥6 distinct algorithms
|
||||
selected by local context: rivers meander in lowlands / incise in mountains; coasts dune /
|
||||
crag / fjord by slope + lithology + glaciation; etc.).
|
||||
|
||||
Adjacent open questions to keep in view (not the focus, but the contract must not preclude them):
|
||||
Q-102 (cohesion matrix — seam-free variation), Q-103 (tile-mutator op schema), Q-105 (region
|
||||
seasonal/clock state).
|
||||
|
||||
**Deliverable:** a resolution for Q-101 → a D-record (claim a fresh `D` in the architecture
|
||||
domain) defining (a) the `RegionHint`/refinement API across the scale jumps, (b) the morphology
|
||||
algorithm-family selection model, (c) the anti-squaring / cross-scale-coherence approach, within
|
||||
the D-010 determinism + D-227 perf (~<5 ms/chunk) constraints.
|
||||
|
||||
## Participants
|
||||
|
||||
Tyre, Gestalt, Troblum, Miri (+ Qatux documenting)
|
||||
|
||||
## Questions for participants
|
||||
|
||||
**Tyre** (architect — owns the inter-layer API):
|
||||
1. The `RegionHint` shape: what does each layer hand *down* the chain (78 km → region → chunk →
|
||||
voxel)? Sketch the refinement-function signature(s) and what state each scale carries.
|
||||
2. The anti-"squaring" mechanism — how do adjacent cells/chunks derive coherently at their shared
|
||||
boundary without seams or visible grid? (domain warping? overlapping kernels? boundary
|
||||
blending?) How does this compose with the D-227 by-chunk cache + eviction?
|
||||
3. The determinism + perf contract: how does `derive(hint, seed, pos)` stay pure (D-010) and hit
|
||||
the per-chunk budget?
|
||||
|
||||
**Gestalt** (systems — the morphology family + fun):
|
||||
1. Enumerate the ≥6 distinct morphology algorithms and the **local context** that selects each
|
||||
(slope, lithology, glaciation, drainage, climate). Where are the boundaries between families?
|
||||
2. What makes the *derived* terrain tactically interesting (cover, LOS, chokepoints,
|
||||
verticality) — i.e. the game-feel constraints the contract must serve, not just plausibility.
|
||||
|
||||
**Troblum** (tech second opinion — feasibility/perf):
|
||||
1. Algorithmic morphology vs noise fields: which parts of Q-101 are best served by explicit
|
||||
morphology algorithms vs multi-octave/domain-warped noise? Cross-scale coherence techniques.
|
||||
2. Stress-test Tyre's API + the <5 ms/chunk + determinism claims; where does it break at scale?
|
||||
|
||||
**Miri** (worldbuilder — believability):
|
||||
1. Does the morphology family produce worlds that *read true* (rivers behave, coasts vary by
|
||||
geology, biomes cohere)? What real-world morphology must the family honor to avoid uncanny terrain?
|
||||
2. The D-228 derived-label layer — which named features matter for the wiki/atlas to stay consistent.
|
||||
|
||||
## Workshop Format
|
||||
|
||||
**Round 1 — independent positions.** Each participant writes their answer to disk at
|
||||
`docs/workshops/tile-derivation-contract/{agent}-round1.md`. No cross-talk yet.
|
||||
|
||||
**Round 2 — synthesis.** After a user checkpoint, converge: Tyre + Troblum lock the `RegionHint`
|
||||
API + coherence approach; Gestalt + Miri lock the morphology family + selection model; resolve
|
||||
tensions. Qatux drafts the Q-101 resolution + the new D-record.
|
||||
|
||||
(Lean 2-round design pass, not the full 5-round format — Q-101 is one focused contract.)
|
||||
@@ -0,0 +1,476 @@
|
||||
---
|
||||
title: "Troblum Round 1 — Feasibility, Performance, and Determinism Stress-Test"
|
||||
workshop: tile-derivation-contract
|
||||
participant: Troblum
|
||||
round: 1
|
||||
date: 2026-06-07
|
||||
---
|
||||
|
||||
# Troblum Round 1: Where the Proposal Holds and Where It Doesn't
|
||||
|
||||
I read Tyre's Round 1. There is a lot right in it. There are also several places
|
||||
where the numbers don't hold and one architectural decision I disagree with on
|
||||
technical grounds. I will work through both questions in order.
|
||||
|
||||
---
|
||||
|
||||
## Question 1: Algorithmic Morphology vs Noise Fields
|
||||
|
||||
The brief asks which parts of Q-101 are best served by explicit morphology algorithms
|
||||
versus multi-octave / domain-warped noise, and which cross-scale coherence techniques
|
||||
avoid "squaring" across the 78 km → 1 km → 64 m → 1 m jumps.
|
||||
|
||||
My position is that the question is slightly misframed. It is not morphology *versus*
|
||||
noise. The correct framing is: **which properties are constraint-satisfying (morphology)
|
||||
and which are detail-filling (noise)?** The answer to that determines which tool is
|
||||
appropriate at each scale jump.
|
||||
|
||||
### Scale jump 1: 78 km → 1 km (classification, not derivation)
|
||||
|
||||
At this boundary, the job is classification. You are asking: what *kind* of terrain
|
||||
does this region contain? The answer must be discrete — a MorphologyFamily enum, an
|
||||
integer slope class, a glaciation grade. Noise is wrong here. A noise field varies
|
||||
continuously; a morphology family does not. A valley floor does not gradually become
|
||||
a fjord coast as you move across it; it transitions. The classification inputs
|
||||
(elevation, flow_accumulation, slope, lithology, sea_level_margin) are all integer
|
||||
or categorical. The output (`MorphologyFamily`, `RegionProfile` parameters) should
|
||||
be a pure integer/lookup-table computation.
|
||||
|
||||
**Correct tool:** deterministic integer classification. Tyre has this right.
|
||||
|
||||
The anti-squaring work at this boundary is handled by Tyre's dual-classification
|
||||
blending at the `ChunkContext` level (primary + secondary with `blend_weight`). I
|
||||
have concerns about that mechanism that I will address under the stress-test section,
|
||||
but the *type* of tool is correct.
|
||||
|
||||
### Scale jump 2: 1 km → 64 m (structural shape, explicit algorithm)
|
||||
|
||||
At this boundary the job is structural shape derivation. What is the actual terrain
|
||||
doing within this chunk? Is there a meander channel? A cliff face? A dune ridge?
|
||||
|
||||
This is where morphology algorithms earn their place. A meander algorithm needs to
|
||||
produce a geometrically consistent channel — one that has a coherent sinuosity, a
|
||||
consistent bankfull width, and lateral migration that respects upstream continuity.
|
||||
A noise field cannot produce this. You would get a wandering path, not a river.
|
||||
|
||||
**Correct tool:** explicit parameterized algorithms, one per MorphologyFamily. Each
|
||||
algorithm receives `ChunkContext` and produces `ElevationDelta` values. The algorithm
|
||||
is deterministic given the seed and context.
|
||||
|
||||
However: explicit morphology algorithms have a failure mode that is not mentioned in
|
||||
Tyre's Round 1. **They produce features with characteristic scales.** A meander
|
||||
algorithm parameterized for 500m wavelength sinuosity will produce visible repetition
|
||||
if any chunk parameters hash to similar values. This is a form of squaring at the
|
||||
algorithm level rather than the grid level. The mitigation is:
|
||||
|
||||
1. The algorithm parameters must be seeded per-chunk (Tyre has this via `chunk_seed`).
|
||||
2. The seed must drive *continuous variation in shape parameters* (sinuosity, amplitude,
|
||||
phase offset), not just a discrete selection from a small parameter table. If there
|
||||
are only 8 possible parameter sets, you will see 8 repeating terrain shapes across
|
||||
a large area.
|
||||
3. The per-chunk seed derivation must use `SeedChain::derive(Block, chunk_id)` as Tyre
|
||||
specifies — this is correct and provides good avalanche properties per D-224.
|
||||
|
||||
### Scale jump 3: 64 m → 1 m (detail filling, noise is appropriate)
|
||||
|
||||
This is where noise fields are appropriate. The structural shape has been established
|
||||
by the morphology algorithm (the channel is here, the cliff face is here). The job
|
||||
now is to fill in detail variation: micro-roughness on a rock face, ripple patterns
|
||||
on a sand flat, surface irregularity on a soil plane. These are not structurally
|
||||
meaningful — they do not change what the terrain *is*, only its surface texture.
|
||||
|
||||
**Correct tool:** multi-octave value noise, position-keyed on world coordinates (not
|
||||
chunk-local coordinates). This is exactly what D-228 specifies for the cohesion
|
||||
matrix: "global, position-keyed continuous noise field — never per-chunk."
|
||||
|
||||
The specific noise type matters here. Tyre specifies "value-noise + bilinear
|
||||
interpolation" for the warp offset. I agree with avoiding Perlin/Simplex for
|
||||
structural work (gradient noise has characteristic directional artifacts that read
|
||||
as artificial), but for surface detail at the 1m scale, value noise with bilinear
|
||||
interpolation is appropriate and cheap.
|
||||
|
||||
### Cross-scale coherence: where "squaring" actually comes from
|
||||
|
||||
The workshop brief asks specifically about cross-scale coherence techniques that avoid
|
||||
squaring at the three boundaries. I want to be more precise about the failure modes,
|
||||
because each boundary has a different one:
|
||||
|
||||
**Boundary 1 (78 km → 1 km):** Squaring here would be abrupt transitions between
|
||||
morphology zones at region edges. This is visible in generated terrain as sudden
|
||||
changes in terrain character — the flat meander plain becoming a cliff coast in one
|
||||
chunk. Mitigation: Tyre's dual-classification blending. I have concerns about the
|
||||
implementation (see stress-test), but the mechanism is sound.
|
||||
|
||||
**Boundary 2 (1 km → 64 m):** Squaring here would be chunk-grid-aligned features —
|
||||
channels that terminate at chunk edges, cliff faces that align with chunk boundaries.
|
||||
Mitigation: domain warping. This is the primary purpose of the warp offset. When
|
||||
the warp magnitude is larger than 1 m but smaller than 64 m, it displaces the
|
||||
feature positions enough that no feature aligns with the chunk grid. Tyre's 8 m
|
||||
maximum warp is reasonable; I would argue for testing at 12 m to ensure grid
|
||||
artifacts are suppressed at moderate distances.
|
||||
|
||||
**Boundary 3 (64 m → 1 m):** Squaring at this boundary would be visible as texture
|
||||
tiling — the same 1 m surface detail pattern repeating at 64 m intervals. Mitigation:
|
||||
the global position-keyed noise field. Because the noise is keyed to world coordinates,
|
||||
the pattern at tile (x, y) is identical regardless of which chunk computed it.
|
||||
|
||||
One cross-scale coherence concern that is not addressed in Tyre's Round 1: **feature
|
||||
scale vs chunk scale mismatch.** Consider a meander river with a wavelength of ~200 m.
|
||||
A single 64 m chunk sees only a fraction of one meander bend. The algorithm that
|
||||
computes the channel position within the chunk must derive it from the global meander
|
||||
curve, not generate a local approximation. Otherwise adjacent chunks will produce
|
||||
incoherent sections of "river" that do not connect.
|
||||
|
||||
This requires the meander algorithm to evaluate the global curve at arbitrary world
|
||||
coordinates — the algorithm must be a *continuous spatial function*, not a
|
||||
per-chunk generator. This is a real design constraint that the MorphologyFamily
|
||||
interface needs to make explicit. Tyre's current interface hands the algorithm a
|
||||
`ChunkContext` and expects an `ElevationDelta` per tile. That is correct, but the
|
||||
algorithm implementation must evaluate a globally-consistent curve, seeded from
|
||||
something broader than `chunk_seed`. I would specify this as a requirement:
|
||||
any MorphologyFamily that produces features larger than a single chunk must derive
|
||||
its feature-scale parameters from a seed derived at a coarser level
|
||||
(`SeedChain::derive(Block, block_id)` or higher), not from `chunk_seed`.
|
||||
|
||||
---
|
||||
|
||||
## Question 2: Stress-Testing Tyre's RegionHint API
|
||||
|
||||
### 2.1 The warp budget claim: 0.5 ms for ~5,000 hash + bilinear evaluations
|
||||
|
||||
Tyre claims ~90 ns/evaluation. Let me check this.
|
||||
|
||||
A single evaluation is: two splitmix64 calls (one per axis), bilinear interpolation
|
||||
over a 2D integer grid (4 multiplications, 4 additions, one integer table lookup per
|
||||
corner = 4 lookups total), and truncation to i16. On an out-of-order superscalar CPU
|
||||
at 3.6 GHz:
|
||||
|
||||
- splitmix64: 5–6 instructions with good ILP, roughly 2–3 cycles per call.
|
||||
Two calls: ~5 cycles = ~1.4 ns.
|
||||
- Bilinear over 4 integer corners: 4 table lookups (likely L1-cached if the noise
|
||||
grid is small), 4 multiplications, additions: ~8–12 cycles = ~2.5–3.5 ns.
|
||||
- Total per evaluation: ~4–5 ns with warm L1 cache.
|
||||
|
||||
For 5,000 evaluations: ~20–25 µs, not 0.5 ms. Tyre's budget is 20x conservative
|
||||
on this particular item. That is fine — more headroom for the morphology algorithm.
|
||||
|
||||
But there is a catch: **cache behavior of the noise grid.** If the warp function
|
||||
uses a discrete noise grid to interpolate from, that grid must be in cache during
|
||||
the chunk derivation. Tyre says "integer-grid hash + bilinear interpolation." If
|
||||
"integer-grid hash" means he is hashing the grid coordinates directly (no lookup
|
||||
table), the cache issue goes away — it is pure computation. If there is a lookup
|
||||
table, its size matters. A 256×256 grid of i16 values = 128 KB, which fits in L2
|
||||
but not L1 on most hardware. Random access into a 128 KB table would incur L2
|
||||
latency (~8 cycles per access), raising the per-evaluation cost to ~40 ns and the
|
||||
total to ~200 µs. Still within the 0.5 ms budget, but Tyre should specify whether
|
||||
the warp function is hash-based (fully L1-resident) or table-lookup-based.
|
||||
|
||||
**My recommendation:** hash-based, no lookup table. `splitmix64` applied to the
|
||||
quantized grid coordinates gives well-distributed values with no cache footprint.
|
||||
This is the correct implementation for a position-keyed global field.
|
||||
|
||||
### 2.2 The ChunkContext construction cost: 0.2 ms claim
|
||||
|
||||
Tyre claims ~0.2 ms for `ChunkContext` construction. The work is:
|
||||
- 4×4 `RegionProfile` lookups (16 lookups from the RegionProfile cache)
|
||||
- 64×64 integer bilinear interpolation from Layer-1 resolution to chunk resolution
|
||||
- 64×64 D8 flow direction downsampling
|
||||
|
||||
The 64×64 bilinear interpolation: 4,096 interpolations, each ~8 cycles on out-of-order
|
||||
hardware = ~32,768 cycles = ~9 µs at 3.6 GHz. The D8 downsampling is similar. The
|
||||
`RegionProfile` lookups depend entirely on cache state — 16 lookups into what is
|
||||
presumably an in-memory hashmap or fixed-size array.
|
||||
|
||||
This is well under 0.2 ms if everything is in L1/L2 cache. The concern is the
|
||||
first access after eviction. If `RegionProfile` data has been evicted from cache
|
||||
(the player moved to a new area), the 16 lookups may each incur L3 or RAM latency.
|
||||
At 40 ns/access (L3) that is 640 ns — still trivial. At 100 ns/access (RAM) with
|
||||
cache miss: 1.6 µs. Still negligible.
|
||||
|
||||
**Verdict:** 0.2 ms is 20x conservative for `ChunkContext` construction. The actual
|
||||
cost is ~10–50 µs depending on cache state. This is good; it confirms the budget
|
||||
has real headroom.
|
||||
|
||||
### 2.3 The morphology algorithm budget: 1–3 ms, family-dependent
|
||||
|
||||
This is the one line in Tyre's budget that I flagged as "Gestalt/Troblum to validate."
|
||||
I cannot validate it without knowing the algorithm implementations, but I can define
|
||||
the constraint:
|
||||
|
||||
For 4,096 tiles at a 3 ms budget: 732 ns per tile. That is substantial time. A
|
||||
meander channel algorithm that requires computing a curve position (which involves
|
||||
a trigonometric or polynomial evaluation) per tile will cost roughly:
|
||||
- Sine/cosine: 20–30 cycles = ~7 ns (modern hardware with FPU)
|
||||
- Polynomial approximation: 10–15 cycles = ~3–4 ns
|
||||
|
||||
A full meander evaluation (curve position + distance to channel + bank profile):
|
||||
roughly 50–100 cycles = 15–30 ns per tile. At 4,096 tiles: 60–120 µs. Well within
|
||||
the 3 ms budget.
|
||||
|
||||
The risky algorithms are those with **per-tile branching** (if/else chains on
|
||||
material type) and **non-trivial data dependencies** (algorithms that need to know
|
||||
about adjacent tiles). The second is the critical one: any algorithm that reads
|
||||
adjacent tile results to compute the current tile is not embarrassingly parallel
|
||||
and cannot be SIMD-vectorized. The `ChunkContext` must provide all the context
|
||||
the algorithm needs without requiring cross-tile reads during derivation.
|
||||
|
||||
Tyre's interface (`ChunkContext` + warped position → `ElevationDelta`) correctly
|
||||
forces this constraint. The algorithm sees only its own position and the pre-computed
|
||||
chunk-level context. This is the right design. Gestalt needs to know it is a hard
|
||||
constraint.
|
||||
|
||||
### 2.4 The blend_weight dual-classification mechanism
|
||||
|
||||
This is where I have a concrete disagreement with Tyre's design.
|
||||
|
||||
The proposal: boundary chunks carry both a `primary` and `secondary` `RegionProfile`,
|
||||
with an integer `blend_weight` (255 = fully primary, 128 = equal mix). The blend
|
||||
width is "one chunk (64 m) on each side."
|
||||
|
||||
**Problem 1: What does "blending" two MorphologyFamily algorithms mean?**
|
||||
|
||||
Tyre's `derive_voxel` step 2 says: "resolve TileAxes twice (primary + secondary
|
||||
RegionProfile), then integer-blend the parameters before selecting materials." But
|
||||
`ElevationDelta` from a meander algorithm and `ElevationDelta` from a fjord algorithm
|
||||
are not the same kind of value. Interpolating them linearly produces terrain that
|
||||
is neither a meander nor a fjord — it is an incoherent average that reads as broken
|
||||
terrain. This is worse than a hard transition. A hard transition at a natural feature
|
||||
boundary (a ridge, a coastline) is geomorphically plausible. A 64 m wide zone of
|
||||
averaged-meander-fjord terrain is not.
|
||||
|
||||
This is not a theoretical concern. Amplitude-blended morphology transitions appear
|
||||
in real generated terrain systems and they produce what is colloquially called
|
||||
"mush terrain" — the transition zone has no readable character.
|
||||
|
||||
**Problem 2: The blend zone is 64 m wide, but 64 m is exactly one chunk.**
|
||||
|
||||
The blend is supposed to hide the region boundary. A 64 m blend zone covers one
|
||||
chunk on each side. At normal play distance (say, 50–200 m from the character),
|
||||
a 64 m zone is directly visible. If the terrain in that zone reads as "mush," the
|
||||
player sees it. This is the opposite of hiding the seam.
|
||||
|
||||
**My proposed fix:**
|
||||
|
||||
Do not blend morphology algorithms. Instead:
|
||||
|
||||
1. At the region boundary, select the morphology family based on which *local
|
||||
terrain type* is more consistent with the position. This is the "morphological
|
||||
footprint" approach: a fjord wall adjacent to a meander plain transitions at
|
||||
the geomorphically natural boundary — the base of the cliff — not at a 64 m
|
||||
blending zone.
|
||||
|
||||
2. For the small number of morphology family pairs that genuinely need a transition
|
||||
(e.g., meander reach → braided delta, which occurs naturally at a gradient change),
|
||||
the transition is driven by the *local slope at the chunk level*, not by a
|
||||
region-level blend weight. The chunk that contains the gradient break produces
|
||||
the transition because its terrain type naturally changes at that location.
|
||||
|
||||
3. The only parameter blending that makes sense is blending *continuous parameters
|
||||
within the same family* — `meander_intensity`, `erosion_grade`, `dune_orientation`.
|
||||
These are Tyre's `RegionProfile` parameters, and blending them is legitimate
|
||||
because they modulate a single algorithm's behavior. Do this.
|
||||
|
||||
This keeps the `secondary: Option<RegionProfile>` and `blend_weight` fields, but
|
||||
changes their semantics: they modulate parameters within a family, not select between
|
||||
families. The `MorphologyFamily` at a boundary chunk is always `primary.morphology_family`.
|
||||
|
||||
**Caveat:** Some family transitions are fundamentally incompatible (fjord → meander),
|
||||
some are compatible (meander → braided-delta). Gestalt needs to define the
|
||||
compatibility matrix. The incompatible pairs should be topographically prevented
|
||||
by the classifier — they should not be adjacent in the first place.
|
||||
|
||||
### 2.5 The warp truncation to i16: a correctness issue
|
||||
|
||||
Tyre says the warp offset is "immediately truncated to an i16 (integer metre
|
||||
displacement) before any structural decision uses it."
|
||||
|
||||
This is a problem. The warp offset's value is in the range ±8 m. An i16 can hold
|
||||
±32,767, so the range is fine. But **truncating to integer metres means the
|
||||
warp has 1 m quantization.** With a maximum warp of 8 m, the warp offset takes
|
||||
one of only 17 discrete values (−8, −7, ..., 0, ..., 8). The warp field is no
|
||||
longer a smooth displacement — it is a stepped field with 1 m jumps. At locations
|
||||
where the warp transitions from (say) 3 m to 4 m, you get a 1 m step in the
|
||||
effective position. This produces faint linear artifacts at the 1 m grid scale,
|
||||
exactly what the warp was supposed to eliminate.
|
||||
|
||||
The fix is to keep the warp offset at sub-metre precision during position computation
|
||||
and only quantize the *result* (the warped position) to voxel coordinates at the
|
||||
final step. The warp computation is f64; the structural decision (which voxel am I?)
|
||||
quantizes at the end. Tyre's constraint — "f64 confined to within-voxel interpolation"
|
||||
— is correct, but the warp itself is within-voxel precision. It should be:
|
||||
|
||||
```rust
|
||||
// warp_offset returns (f64, f64) in metres — NOT truncated
|
||||
let (dx, dy) = warp_offset(seed, body_id, pos.x as f64, pos.y as f64);
|
||||
// Apply warp to continuous world position, THEN quantize to voxel
|
||||
let warped_x = (pos.x as f64 + dx).round() as i32;
|
||||
let warped_y = (pos.y as f64 + dy).round() as i32;
|
||||
// All structural decisions use (warped_x, warped_y) — integer
|
||||
```
|
||||
|
||||
This preserves D-010 integer discipline for structural decisions while avoiding the
|
||||
1 m quantization artifact in the warp. The f64 is used only for the displacement
|
||||
computation; the output is an integer voxel coordinate.
|
||||
|
||||
### 2.6 The D-010 determinism claim: where it is actually fragile
|
||||
|
||||
Tyre's integer discipline is the right approach. I want to be specific about where
|
||||
it is fragile:
|
||||
|
||||
**f64 is deterministic on x86-64.** IEEE 754 double-precision arithmetic is
|
||||
bit-identical across all x86-64 implementations for the same instruction sequence.
|
||||
The risk is not arithmetic non-determinism — it is **compiler floating-point
|
||||
optimization**. Specifically:
|
||||
|
||||
- `fmadd` (fused multiply-add) contraction: a compiler may transform `a * b + c`
|
||||
into a single `VFMADD` instruction, which produces a different result than
|
||||
the separate `vmulsd` + `vaddsd` pair because the intermediate is not rounded.
|
||||
GCC/LLVM may do this with `-O2 -mfma` (which Rust enables by default on x86-64).
|
||||
- The fix: mark all warp computations as `#[inline(never)]` or explicitly disable
|
||||
FMA contraction for the warp function. Alternatively, cast through integer at
|
||||
the intermediate steps to force rounding.
|
||||
- If the warp function is ever run on a non-x86-64 target (ARM, WASM), f64
|
||||
arithmetic may differ. This is a future concern but worth noting.
|
||||
|
||||
**The `warp_offset` function must be in a tested regression harness.** Any change
|
||||
to it — even a "refactoring" — changes every generated world. This is the same
|
||||
class of stability concern that D-224 pins for `SeedDomain` discriminants and
|
||||
`AttractorType`. Add a golden-seed test for `warp_offset` at the same time as
|
||||
it is implemented.
|
||||
|
||||
**HashMap iteration order.** Tyre does not mention this because his design uses
|
||||
integer arrays and enums, not hashmaps. Good. But if any `MorphologyFamily`
|
||||
implementation uses a `HashMap` internally (for parameter tables, neighbor lookups),
|
||||
it must be replaced with a sorted data structure or fixed-size array. Flag this
|
||||
for Gestalt.
|
||||
|
||||
### 2.7 The global feature coherence problem: the river that doesn't connect
|
||||
|
||||
This is the largest structural risk in the proposal that Tyre has not addressed,
|
||||
and it is not a performance problem — it is a correctness problem.
|
||||
|
||||
Tyre's Open Issue 1 identifies it partially: "At 1 m voxel resolution, a river
|
||||
cell needs flow direction at meter scale." But the problem is deeper than flow
|
||||
direction.
|
||||
|
||||
A river with 200 m meander wavelength spans approximately 3 chunks (64 m each).
|
||||
The chunk that contains the meander peak must produce terrain that connects
|
||||
geometrically with the chunk that contains the meander trough. If each chunk
|
||||
derives its meander curve position independently from its `chunk_seed`, the curves
|
||||
will not connect — the river will be discontinuous at chunk boundaries.
|
||||
|
||||
Tyre's domain warp (maximum 8 m displacement) suppresses the visual artifact of
|
||||
the chunk boundary for materials and small features, but it cannot fix a 10 m
|
||||
lateral displacement of the river channel at the chunk edge. Domain warping moves
|
||||
positions, not the river channel.
|
||||
|
||||
**The fix requires a globally-continuous meander curve.** The meander algorithm
|
||||
must be parameterized by features that are consistent across chunks: the D8 flow
|
||||
direction, the channel centerline as derived from a global seed, and a phase offset
|
||||
that is continuous across chunk edges.
|
||||
|
||||
Concretely: the meander centerline should be a function of distance along the D8
|
||||
network, seeded from a river-specific seed (derived from `SeedChain::derive(Layer1Topography, river_id)`), not from the chunk seed. The chunk computes
|
||||
its local section of the curve by evaluating the function at the world coordinates
|
||||
of each tile. Adjacent chunks evaluate the same function at adjacent coordinates
|
||||
and get a connected curve.
|
||||
|
||||
This means the meander algorithm cannot be a simple per-chunk generator. It must be
|
||||
a *continuous spatial function evaluated at world coordinates*. This is implementable
|
||||
but it is a design constraint that the MorphologyFamily interface must make
|
||||
explicit.
|
||||
|
||||
I would add to Tyre's interface requirement for Gestalt:
|
||||
|
||||
> Any MorphologyFamily that produces features with characteristic wavelength larger
|
||||
> than 64 m must derive those features from a seed at Block level or above
|
||||
> (`SeedChain::derive(Block, block_id)`), not from `chunk_seed`. The chunk seed
|
||||
> may only drive sub-64m variation.
|
||||
|
||||
### 2.8 The river flow direction resolution gap
|
||||
|
||||
Tyre flags this as Open Issue 1 but does not resolve it. I will.
|
||||
|
||||
The D8 network is at 512×256 Layer-1 grid resolution. The body in Q-101 spans ~78 km.
|
||||
At 512 cells wide, each D8 cell is approximately 152 m × 152 m. The chunk is 64 m.
|
||||
Each chunk falls within a single D8 cell (64 < 152). So there is no D8 resolution
|
||||
mismatch at the chunk level — a chunk looks up one D8 cell.
|
||||
|
||||
But the river *channel* within the chunk is at 1 m resolution. The D8 direction
|
||||
(one of 8 octants) tells you the basin-scale flow direction. The channel centerline
|
||||
within the chunk needs to be at meter resolution. This is the meander algorithm's job:
|
||||
given D8 direction as a basin-scale constraint, derive a locally-coherent channel
|
||||
position at 1 m resolution using the global meander curve.
|
||||
|
||||
**Tyre's `flow_direction: [u8; 64*64]` in `ChunkContext` is wrong for this purpose.**
|
||||
At the D8 resolution, there is one flow direction per ~152 m cell, not per 1 m tile.
|
||||
Storing a `[u8; 64*64]` flow direction implies a per-tile D8 value, which does not
|
||||
exist. This array should be a single `flow_direction: u8` (the basin-scale D8 octant
|
||||
for this chunk's position) plus the global meander seed parameters.
|
||||
|
||||
The `river_cells: u64` bitmask of 8×8 macro-cells with river presence is more
|
||||
defensible — it tells the chunk which of its 64 m sub-regions are near a river —
|
||||
but "8×8 macro-cells" in a 64-tile chunk means 8 m cells, which is denser than
|
||||
the D8 resolution. This should be documented as a downsampled indicator, not a
|
||||
direct D8 output.
|
||||
|
||||
### 2.9 The RegionProfile persistence question
|
||||
|
||||
Tyre's Open Issue 2: "For Phase 4 Atlas use, we may want to compute `RegionProfile`
|
||||
lazily on Atlas request rather than precomputing for all regions — ~6,000 per body."
|
||||
|
||||
The cost numbers: 6,000 RegionProfile computations per body. Each RegionProfile
|
||||
is a classification (integer comparisons, lookup tables, one flood-fill shared
|
||||
across the whole body). The flood-fill is O(n) for n = 131,072 cells (512×256) —
|
||||
call it 1 ms per body. The per-region classification is ~10–50 µs per region.
|
||||
Total for 6,000 regions: 60–300 ms per body.
|
||||
|
||||
This is not expensive. Precomputing all RegionProfiles at body load is acceptable.
|
||||
Lazy recomputation adds request latency that is visible in the Atlas viewer —
|
||||
the first Atlas pan to an unloaded region takes 50–300 µs while RegionProfile
|
||||
is computed. Not bad, but not zero.
|
||||
|
||||
My recommendation: precompute all RegionProfiles at body load, store in a
|
||||
fixed-size Vec indexed by region_id. 6,000 × (size of RegionProfile struct) at
|
||||
roughly 32 bytes per struct = 192 KB per body. Trivial memory footprint.
|
||||
|
||||
---
|
||||
|
||||
## Summary of Disagreements and Concerns
|
||||
|
||||
**Agree with:**
|
||||
- Three-typed-carrier architecture (RegionProfile → ChunkContext → VoxelColumn).
|
||||
Correct decomposition.
|
||||
- Integer-only structural decisions, f64 confined to warp computation. Right.
|
||||
- Global position-keyed warp field for chunk-boundary seam suppression.
|
||||
Sound mechanism.
|
||||
- SeedChain::derive for per-chunk seeds. Correct.
|
||||
- Performance budget is conservative overall — the 5 ms cap has substantial headroom.
|
||||
|
||||
**Disagree with / flag for Round 2:**
|
||||
|
||||
1. **Algorithm blending across MorphologyFamily boundaries is wrong.** Blending
|
||||
meander + fjord ElevationDeltas produces incoherent terrain. Blend parameters
|
||||
within a family; do not interpolate between families. Requires a compatibility
|
||||
matrix from Gestalt.
|
||||
|
||||
2. **Warp truncation to integer metres will produce stepped artifacts.** Keep
|
||||
the warp at f64 sub-metre precision through the position computation; quantize
|
||||
only the final voxel coordinate.
|
||||
|
||||
3. **The globally-coherent feature constraint is missing from the interface
|
||||
contract.** Any family with feature wavelength > 64 m must use a Block-level or
|
||||
higher seed, not the chunk seed. This needs to be explicit in the MorphologyFamily
|
||||
trait/interface.
|
||||
|
||||
4. **`ChunkContext.flow_direction: [u8; 64*64]` is wrong.** A per-tile D8 direction
|
||||
does not exist at D8 resolution. Replace with a per-chunk basin direction (one u8)
|
||||
plus meander curve parameters.
|
||||
|
||||
5. **The warp function needs a golden-seed regression test.** Same class of
|
||||
stability concern as `SeedDomain` discriminants. Any change to it re-rolls
|
||||
every world.
|
||||
|
||||
6. **f64 FMA contraction is a determinism risk.** The warp function must be
|
||||
protected from compiler FMA optimization or explicitly tested as bit-identical
|
||||
across optimization levels.
|
||||
@@ -0,0 +1,342 @@
|
||||
---
|
||||
title: "Tyre Round 1 — RegionHint API, anti-squaring, determinism/perf"
|
||||
workshop: tile-derivation-contract
|
||||
participant: Tyre
|
||||
round: 1
|
||||
date: 2026-06-07
|
||||
---
|
||||
|
||||
# Tyre Round 1: The Derivation API Contract
|
||||
|
||||
## Framing the problem correctly
|
||||
|
||||
The 78 km → 1 km → 64 m → 1 m span is not a single derivation problem — it is
|
||||
three distinct problems with different failure modes, and conflating them is how
|
||||
you get squaring. My position up front: **each scale boundary needs a different
|
||||
coherence mechanism**, and the `RegionHint` I sketched earlier needs to be split
|
||||
into three typed carriers, one per boundary. The overall structure is:
|
||||
|
||||
```
|
||||
Layer 1 cell (78 km) → RegionProfile (region-scale morphology zone + parameters)
|
||||
RegionProfile → ChunkContext (64 m, resolved from heightmap position)
|
||||
ChunkContext + seed → VoxelColumn (1 m, the actual derive call)
|
||||
```
|
||||
|
||||
The `RegionHint` name survives but becomes specifically the `RegionProfile` — the
|
||||
thing the region-level pass fills once and hands to all chunks within it.
|
||||
|
||||
---
|
||||
|
||||
## Scale boundary 1: 78 km cell → ~1 km region (RegionProfile)
|
||||
|
||||
This boundary is **classification**, not interpolation. A region does not gradually
|
||||
become a fjord coast — it is or it isn't, based on contextual inputs. The squaring
|
||||
risk here is not visual grid artifacts; it is abrupt zone transitions at region edges.
|
||||
|
||||
### What the 78 km cell hands down
|
||||
|
||||
The Layer 1 outputs already available per D-208 / D-209 / D-227:
|
||||
- `elevation` (f32 heightmap, but read as integer-scaled u16 per D-010)
|
||||
- `flow_accumulation` (D8 integer count)
|
||||
- `flow_direction` (D8 integer 0–7)
|
||||
- `sub_biome` (D-210 / D-228 — climate + vegetation zone)
|
||||
- `river_network` membership (bool)
|
||||
|
||||
What the region pass adds (runs once per region, stored as `RegionProfile`):
|
||||
|
||||
```rust
|
||||
pub struct RegionProfile {
|
||||
// Classification inputs — all integer or enum
|
||||
pub morphology_zone: MorphologyZone, // fjord / meander / delta / etc.
|
||||
pub lithology: Lithology, // resolved from body parameters + elevation strata
|
||||
pub glaciation: GlaciationGrade, // 0..4 integer grade
|
||||
pub slope_class: SlopeClass, // Flat / Gentle / Steep / Cliff (computed from 3×3 elev kernel)
|
||||
pub drainage_class: DrainageClass, // None / Ephemeral / Perennial / Major
|
||||
pub sea_level_margin: i32, // elevation - sea_level, integer metres
|
||||
|
||||
// Morphology algorithm selector — derived from all above
|
||||
pub morphology_family: MorphologyFamily,
|
||||
|
||||
// Parameters handed to ChunkContext
|
||||
pub relief_amplitude: u16, // max voxel-height variation within region, integer metres
|
||||
pub meander_intensity: u8, // 0–255 integer
|
||||
pub erosion_grade: u8, // 0–255 integer
|
||||
pub dune_orientation: u8, // compass octant (0–7), prevailing wind
|
||||
}
|
||||
```
|
||||
|
||||
`MorphologyZone` is the D-228 "region-level morphology zone" field, resolved here.
|
||||
`MorphologyFamily` is the algorithm selector — that is Gestalt's domain to enumerate;
|
||||
I just need to know what I am selecting between.
|
||||
|
||||
### Lithology: resolving the ocean/lake mask gap
|
||||
|
||||
Q-101 flags the D-223 ocean-polygon removal as an "unowned dependency". My resolution:
|
||||
**sea-level threshold + flood-fill from heightmap edge, all integer**.
|
||||
|
||||
```rust
|
||||
fn classify_water_body(
|
||||
body_params: &BodyParams, // has sea_level, radius
|
||||
elevation_grid: &Grid<u16>, // integer-scaled metres
|
||||
pos: (u16, u16),
|
||||
) -> WaterBodyClass {
|
||||
// cells below sea_level threshold that are connected to the heightmap
|
||||
// edge (flood-fill) → ocean; isolated depressions below sea_level → lake
|
||||
}
|
||||
```
|
||||
|
||||
This is a single flood-fill pass — O(n) over the Layer-1 grid (512×256 = 131k cells),
|
||||
produces a bitmask. Deterministic, integer-only, no authored polygon dependency.
|
||||
Lithology strata are body-class parameters (tectonic activity, age) → integer lookup
|
||||
table per `(body_class, elevation_band)`. No f32 in classification.
|
||||
|
||||
### Region boundary blending (anti-squaring at this boundary)
|
||||
|
||||
The morphology zone must not flip discretely at region edges. Mechanism: **dual
|
||||
classification with a blending weight**, resolved at the `ChunkContext` level.
|
||||
|
||||
Each region classifies to a primary and a secondary `MorphologyFamily`, with an
|
||||
integer weight `primary_weight: u8` (255 = fully primary, 128 = equal mix). Chunks
|
||||
near region boundaries inherit both families and blend their parameter tables.
|
||||
The boundary width is one chunk (64 m) on each side — chosen to be invisible at
|
||||
normal view distances and narrow enough not to corrupt chunk interiors.
|
||||
|
||||
---
|
||||
|
||||
## Scale boundary 2: ~1 km region → 64 m chunk (ChunkContext)
|
||||
|
||||
This is the **critical** boundary for squaring. Adjacent chunks from different
|
||||
regions must tile seamlessly. The failure mode here is visible grid lines where
|
||||
chunk borders coincide with region zone changes.
|
||||
|
||||
### ChunkContext struct
|
||||
|
||||
```rust
|
||||
pub struct ChunkContext {
|
||||
// Position identity
|
||||
pub chunk_pos: ChunkPos, // integer chunk grid coordinates
|
||||
pub chunk_seed: SeedChain, // SeedChain::derive(Block, chunk_id)
|
||||
|
||||
// Inherited from RegionProfile (primary)
|
||||
pub primary: RegionProfile,
|
||||
|
||||
// Boundary blending — present only for boundary chunks
|
||||
pub secondary: Option<RegionProfile>,
|
||||
pub blend_weight: u8, // 255 = fully primary (no blend)
|
||||
|
||||
// Heightmap slice — integer, resolved at chunk resolution
|
||||
pub elevation_min: u16,
|
||||
pub elevation_max: u16,
|
||||
pub elevation_grid: [u16; 64*64], // 4096 u16 values, bilinear from Layer 1 → chunk res
|
||||
|
||||
// Flow data at chunk resolution
|
||||
pub flow_direction: [u8; 64*64], // D8 octant per chunk-scale cell
|
||||
pub river_cells: u64, // bitmask of 8×8 macro-cells with river presence
|
||||
}
|
||||
```
|
||||
|
||||
`ChunkContext` is **computed once per chunk** and cached per D-227 (same eviction
|
||||
tier as the derived tile data itself). Computing it requires reading the four
|
||||
surrounding `RegionProfile` records plus the heightmap tile. Cost: integer arithmetic
|
||||
over 64×64 arrays — well under 1 ms.
|
||||
|
||||
### Anti-squaring: domain warping at the chunk scale
|
||||
|
||||
The principal technique for seam-free transitions at chunk boundaries is
|
||||
**position-domain warping using a global continuous noise field** — matching the
|
||||
D-228 "global, position-keyed continuous noise field — never per-chunk" directive
|
||||
for the cohesion matrix at the material layer.
|
||||
|
||||
The same principle applies at the morphology level: the warping offsets are derived
|
||||
from a **global field** keyed on (body_id, world_seed), evaluated at any (x, y)
|
||||
position. Any chunk can independently compute the warp offset for any position
|
||||
within or adjacent to itself — producing identical values for the shared boundary.
|
||||
This is the structural guarantee of seam-freedom: warp values are global functions,
|
||||
not per-chunk state.
|
||||
|
||||
Concretely:
|
||||
|
||||
```rust
|
||||
fn warp_offset(seed: u64, body_id: u64, x: f64, y: f64) -> (f64, f64) {
|
||||
// Two independent value-noise fields (no coherent noise dependency)
|
||||
// Using integer-grid hash + bilinear interpolation to keep it f64-confined
|
||||
// Returns sub-voxel displacement in (dx, dy), magnitude capped at ~8m
|
||||
}
|
||||
```
|
||||
|
||||
The warp magnitude (~8 m maximum) is chosen to be larger than one tile (1 m) but
|
||||
smaller than a chunk (64 m). This means:
|
||||
- Chunk-grid lines are visibly displaced by up to 8 m in both directions
|
||||
- No straight-line grid artifact can persist for more than 8 m
|
||||
- Adjacent chunks compute the warp for their shared edge independently and get
|
||||
identical values (pure function of world coordinates + seed)
|
||||
|
||||
The warp is applied in **world-space coordinates before the morphology algorithm runs** —
|
||||
the algorithm sees warped positions and produces terrain that has no knowledge of
|
||||
the chunk grid. The chunk boundary is invisible because neither algorithm has it.
|
||||
|
||||
### Relation to the D-227 chunk cache
|
||||
|
||||
The domain-warp field does not need to be cached separately. Each chunk computes
|
||||
warp offsets only for its own tiles (the 64×64 grid) plus a 16-tile overlap margin
|
||||
on each side (for the blend zone). This overlap is cheap — 96×96 evaluations vs 64×64.
|
||||
The warp function itself is O(1) per position; the cost scales with chunk area, not
|
||||
with neighbor count.
|
||||
|
||||
---
|
||||
|
||||
## Scale boundary 3: 64 m chunk → 1 m voxel (the derive call)
|
||||
|
||||
This is where D-227's `subtile(x,y,z) = derive(seed, atlas, position)` lives.
|
||||
|
||||
### Refined function signature
|
||||
|
||||
```rust
|
||||
pub fn derive_voxel(
|
||||
ctx: &ChunkContext, // pre-computed chunk context (cached)
|
||||
pos: VoxelPos, // integer (x, y, z) — 1 m resolution
|
||||
) -> TileAxes {
|
||||
// Returns the D-228 composite:
|
||||
// TerrainMaterial, FloorMaterial, Vegetation, Water, elevation
|
||||
}
|
||||
|
||||
pub struct TileAxes {
|
||||
pub terrain_material: TerrainMaterial, // u8 enum
|
||||
pub floor_material: FloorMaterial, // u8 enum
|
||||
pub vegetation: VegetationKind, // u8 enum
|
||||
pub water: WaterDepth, // u8 enum
|
||||
pub elevation: u16, // integer metres
|
||||
}
|
||||
```
|
||||
|
||||
The `pos` parameter carries world-space coordinates, not chunk-local coordinates.
|
||||
The algorithm:
|
||||
|
||||
1. Apply `warp_offset(ctx.chunk_seed.seed(), body_id, pos.x, pos.y)` — warp the
|
||||
position.
|
||||
2. If `ctx.blend_weight < 255`: resolve `TileAxes` twice (primary + secondary
|
||||
`RegionProfile`), then integer-blend the parameters before selecting materials.
|
||||
The blend uses the same warp offset to prevent the blend seam from aligning with
|
||||
the chunk edge.
|
||||
3. Call the `MorphologyFamily`-specific derivation function with the `ChunkContext`
|
||||
parameters.
|
||||
4. Derive `TerrainMaterial` from `lithology + elevation + slope`.
|
||||
5. Derive `Vegetation` from `sub_biome + terrain_material + elevation`.
|
||||
6. Derive `Water` from regional water-height vs `elevation` (the D-228 cheap
|
||||
seasonal/tidal water model).
|
||||
7. Return `TileAxes`.
|
||||
|
||||
Step 3 is Gestalt's domain (the morphology algorithm implementations). The contract
|
||||
from my side: each family receives `ChunkContext` + warped position and returns an
|
||||
`ElevationDelta: i16` (delta from the base heightmap elevation, signed, integer metres).
|
||||
That delta feeds into step 4 onward.
|
||||
|
||||
### Structural decisions (D-010 integer discipline)
|
||||
|
||||
All structural decisions — which material, which morphology zone, which elevation band —
|
||||
are **integer-only**. f64 is confined to the warp offset computation and any
|
||||
within-voxel interpolation. This is not ceremonial: D-227 makes it save-critical.
|
||||
A single f32 comparison that flips on a different hardware FPU would desync every
|
||||
mutator reference in a save file.
|
||||
|
||||
Specific constraints I am placing:
|
||||
- `ElevationDelta` is `i16` (integer metres). No sub-voxel elevation in the
|
||||
structural layer. Sub-voxel geometry is a render concern (shape-from-material,
|
||||
D-228: "sand slumps to angle of repose, rock breaks to vertical face").
|
||||
- Material selection is a series of integer comparisons / lookup table reads.
|
||||
- The warp offset is f64 during computation but is immediately truncated to an
|
||||
`i16` (integer metre displacement) before any structural decision uses it.
|
||||
The sub-metre remainder is discarded — only the rendered visual uses sub-voxel
|
||||
precision, and that is a client concern.
|
||||
|
||||
---
|
||||
|
||||
## Performance contract
|
||||
|
||||
Target: <5 ms per chunk, per D-227.
|
||||
|
||||
Budget breakdown (64×64 = 4,096 tiles per chunk):
|
||||
|
||||
| Work item | Cost estimate | Basis |
|
||||
|-----------|--------------|-------|
|
||||
| `ChunkContext` construction (once per chunk) | ~0.2 ms | 4×4 RegionProfile lookups + 64×64 integer array ops |
|
||||
| Warp offset computation (4,096 + margin positions) | ~0.5 ms | ~5,000 hash + bilinear evaluations |
|
||||
| Morphology algorithm (4,096 tiles, varies by family) | ~1–3 ms | family dependent; Gestalt/Troblum to validate |
|
||||
| Material + vegetation + water derivation (4,096 tiles) | ~0.5 ms | integer table lookups |
|
||||
| **Total** | **~2.2–4.2 ms** | headroom for complex families |
|
||||
|
||||
The chunk cache means this cost is paid once per chunk per play session (or per
|
||||
Atlas view request). Adjacent chunks share no mutable state; they are embarrassingly
|
||||
parallel under Rayon (D-208 precedent).
|
||||
|
||||
The `ChunkContext` is the critical cache object — it amortizes the `RegionProfile`
|
||||
lookups across all 4,096 tile derivations in the chunk. Evicting a chunk from cache
|
||||
means recomputing its `ChunkContext` plus all tile derivations; the `RegionProfile`
|
||||
cache above it is longer-lived (one per ~1 km region vs one per 64 m chunk).
|
||||
|
||||
---
|
||||
|
||||
## What I need from teammates
|
||||
|
||||
**From Gestalt:**
|
||||
- The ≥6 morphology family names and their context selectors, so I can populate
|
||||
`MorphologyFamily` as a typed enum with explicit `#[repr(u8)]` discriminants
|
||||
(stability requirement — same as `SeedDomain`).
|
||||
- The `ElevationDelta` range each family can produce. I need to know if any family
|
||||
wants sub-voxel precision (answer: no — see D-010 argument above) or more than
|
||||
±255 m variation within a chunk (answer: physically implausible for a 64 m chunk).
|
||||
- Any context inputs the family selector needs beyond what `RegionProfile` already
|
||||
carries. My position: `RegionProfile` should be sufficient; if a family needs a
|
||||
per-tile input not in `RegionProfile`, that is a signal the classification belongs
|
||||
at the `ChunkContext` level, not the voxel level.
|
||||
|
||||
**From Troblum:**
|
||||
- Stress-test the warp budget specifically. 5,000 hash + bilinear evaluations in
|
||||
0.5 ms is ~90 ns/evaluation — plausible on modern hardware but worth verifying.
|
||||
The warp can be precomputed into a lookup grid within the `ChunkContext` if that
|
||||
is faster, at the cost of ~12 KB of additional cache memory per chunk.
|
||||
- Validate the `ChunkContext` construction cost. The 64×64 integer bilinear
|
||||
interpolation from Layer-1 resolution to chunk resolution is ~8× upsampling;
|
||||
that should be fast but Troblum should confirm.
|
||||
|
||||
**From Miri:**
|
||||
- Which morphology zones need sub-region variation within a single 64 m chunk?
|
||||
For example: a meander reach with an oxbow lake needs the lake to appear within
|
||||
a chunk that the region classifies as "meander reach". Does that require a
|
||||
secondary zone classification at the chunk level, or does the morphology algorithm
|
||||
handle it internally?
|
||||
- The D-228 "named features matter for the wiki/atlas" list — which ones require
|
||||
the `RegionProfile.morphology_zone` to be queryable at Atlas-generation time?
|
||||
That determines whether `RegionProfile` needs to be persisted to the layer cache
|
||||
or can be recomputed on Atlas queries.
|
||||
|
||||
---
|
||||
|
||||
## Open issues I am flagging for Round 2
|
||||
|
||||
1. **River flow direction at the voxel level.** Q-101 notes "river flow direction
|
||||
derives from the D8 network at query time." The D8 network is Layer-1 resolution
|
||||
(512×256). At 1 m voxel resolution, a river cell needs flow direction at meter
|
||||
scale. The meander algorithm (Gestalt's domain) presumably drives local course
|
||||
variation — but does the D8 direction act as a basin-scale constraint or a
|
||||
cell-level input? This affects whether `flow_direction` in `ChunkContext` is
|
||||
one value per chunk or one value per 8×8 macro-cell within the chunk.
|
||||
|
||||
2. **`RegionProfile` persistence vs recompute.** The Atlas viewer (D-225) currently
|
||||
caches `Layer1Output` (rivers, basins, attractors). `RegionProfile` is a finer
|
||||
grain. For Phase 4 Atlas use, we may want to compute `RegionProfile` lazily on
|
||||
Atlas request rather than precomputing for all regions — the number of regions
|
||||
per body is on the order of 78km/1km ≈ ~6,000 per body. That is cheap to
|
||||
recompute on demand but expensive to precompute at atlas-load time for all bodies.
|
||||
|
||||
3. **`MorphologyFamily` transition zones and pathological adjacencies.** What happens
|
||||
when a fjord coast is adjacent to a meander delta? The blend mechanism handles it
|
||||
at the chunk level but the `RegionProfile` classifier needs to produce a sensible
|
||||
`secondary` morphology zone for the boundary chunks. This requires the
|
||||
morphology-zone vocabulary (Gestalt's Round 1 output) before Round 2 can finalize.
|
||||
|
||||
4. **Body-class modulation of `RIVER_THRESHOLD`.** Q-101 flags this. My position:
|
||||
`RIVER_THRESHOLD` should be a body parameter computed from `(hydrosphere, tectonic_activity,
|
||||
precipitation_class)` using an integer lookup table. This gives Nigel the handle
|
||||
without touching the D8 algorithm itself. The lookup table lives in `body_params.rs`
|
||||
alongside other per-body scalar parameters.
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
title: "Workshop Outcomes: Tile Derivation Contract (Q-101)"
|
||||
description: "Final outcomes of the tile-derivation-contract workshop — resolution of Q-101 via D-239, the coarse→fine tile refinement chain, morphology family, anti-squaring warp, and district-temperature climate primitive."
|
||||
type: workshop
|
||||
status: complete
|
||||
workshop: tile-derivation-contract
|
||||
created: 2026-06-07
|
||||
decision_refs: [D-239]
|
||||
resolves: [Q-101]
|
||||
participants: [Tyre, Gestalt, Troblum, Miri, Qatux]
|
||||
---
|
||||
|
||||
# Workshop Outcomes: Tile Derivation Contract (Q-101)
|
||||
|
||||
**Result:** Q-101 resolved → **[[D-239]]** filed (architecture domain, confirmed, 2026-06-07).
|
||||
|
||||
The keystone "what's in a tile" question — how a coarse Layer-1 cell + seed becomes
|
||||
coherent ~1 m voxel geometry across ~3 scale jumps — is now contracted. This was the one
|
||||
unbuilt layer of the Phase-4 cascade (L0–L4 already exist).
|
||||
|
||||
## What was decided (D-239 in brief)
|
||||
|
||||
1. **Three-carrier refinement chain.** `RegionProfile` (~1 km) → `ChunkContext` (64 m) →
|
||||
`VoxelColumn` (1 m). Each a pure deterministic function of `(seed, atlas, body-params,
|
||||
position)` per D-227. Each scale boundary is its own derivation.
|
||||
|
||||
2. **No authoring at the derivation layers, ever.** Zero per-body override hooks. All
|
||||
gating params (`RIVER_THRESHOLD` → derived per-body-class, not the global `200`;
|
||||
`tectonic_class`; `GlaciationGrade`; `precipitation_class`) derive from body params.
|
||||
Lore bodies (Kallast, Velen, Cygni B) are honoured by *setting params* and serve as
|
||||
validation cases — fix params, never patch the derivation.
|
||||
|
||||
3. **District temperature is the climate primitive.** A scalar °C per 2×2 km district,
|
||||
**nullable** (no atmosphere → null → no climate branch; airless surface ice is geology).
|
||||
Derived from sun (luminosity + insolation), planet (latitude + elevation lapse +
|
||||
orbital/axial phase = season, day-phase = diurnal), atmosphere (greenhouse → base,
|
||||
heat-retention → diurnal-swing amplitude). **Moisture** is a separate primitive.
|
||||
Everything climatic — precipitation, vegetation/treeline, glaciation, the seasonal/clock
|
||||
state (Q-105) — derives from temperature (+ moisture). Formalises D-210's temperature proxy.
|
||||
|
||||
4. **Scattered, transient freeze/snow model.** Ice/snow is a *band with coherent spatial
|
||||
scatter*, not a hard `temp<0` contour: freshwater (lakes/rivers) **+5 → −10 °C** district
|
||||
mean (the +5 is night-frost reaching the coldest tiles); sea ice its own lower/wider band
|
||||
(≈ −2 onset, pack-ice pattern); snow (land) the same, **gated on moisture**. **Transient**
|
||||
in the marginal band — forms in the cold phase (night/season), melts in the warm phase →
|
||||
time-of-day passability shifts. Forward contract to Q-105.
|
||||
|
||||
5. **Anti-"squaring" warp.** A stateless, hash-based position-keyed domain-warp; **f64
|
||||
sub-metre precision through to the final voxel coordinate, then truncate to the integer
|
||||
voxel address** (a cast, IEEE-754-deterministic). ±8 m range makes FMA ULP variance
|
||||
harmless. Position math, not a structural decision — D-010 integer discipline preserved.
|
||||
|
||||
6. **8 morphology families** via a gated decision tree over integer `RegionProfile` inputs:
|
||||
LavaField · FjordWall · CliffCoast · BraidedDelta · DuneStrand · IncisedGorge ·
|
||||
MeanderReach · AlluvialPlain (fallback). Hard gates: fjord ≥ GlaciationGrade 2; LavaField
|
||||
= Volcanic; lithology bounds slope/form.
|
||||
|
||||
7. **Frozen 17-zone `MorphologyZone` vocabulary.** Freeze point = the Rust enum; changes need
|
||||
a D-record amendment (classifier *tuning* stays free). Four zones (tidal flat, estuarine,
|
||||
alpine, wetland) are derived sub-classifications, not generator families.
|
||||
|
||||
8. **Seams — prevent incompatible, allow valid (never patch).** Decision-tree gate ordering
|
||||
+ a build-time compatibility-matrix invariant blocks incompatible adjacencies (e.g.
|
||||
MeanderReach↔Volcanic). Valid geomorphic seams (cliff↔fjord, lithology faults) are kept
|
||||
**sharp** (real geology is sharp), made organic by the warp — no feathering.
|
||||
|
||||
9. **Believability laws + game-feel constraints** are binding (drainage monotonicity,
|
||||
lithology→landform, glaciation→form, climate→vegetation; ≥1 tactical point/chunk, cover ≠
|
||||
concealment, chokepoint widths, real seasonal passability via `ElevationDelta`↔Q-105).
|
||||
|
||||
## Process notes
|
||||
|
||||
- **2-round lean format** (independent positions → lead-interviewed synthesis), not the full
|
||||
5-round workshop. Round-1 positions: `tyre-round1.md`, `gestalt-round1.md`,
|
||||
`troblum-round1.md`, `miri-round1.md`.
|
||||
- The four core calls (seams, body-params-fully-derived, vocabulary lock, warp precision)
|
||||
were made by the human via AskUserQuestion. The climate model (temperature primitive,
|
||||
separate moisture, the three-sibling scattered freeze, day/night swing, transient ice/snow)
|
||||
was a human-driven design pivot during synthesis.
|
||||
- An **adversarial verification pass** (the same four perspectives) caught ~10 issues, all
|
||||
folded into the final D-239: the "continuous→continuous" seam argument was false (boolean
|
||||
gates are discontinuities) → rewritten to gate-ordering + matrix; warp determinism gaps
|
||||
(FMA / statelessness / truncate-not-compare); "Block-or-higher" → "Region-or-higher";
|
||||
D-209 tier → D-203; missing LavaField tectonic gate; seasonal regained mechanical teeth;
|
||||
the 17-zones-vs-8-families gap closed; believability laws inlined; "frozen" given a real
|
||||
enforcement mechanism.
|
||||
|
||||
## Open siblings (left for later, contract does not preclude them)
|
||||
|
||||
- **Q-102** — cohesion matrix: effectively *is* the D-239 warp; can be closed against it or
|
||||
folded in when the material-scatter layer is built.
|
||||
- **Q-103** — tile-mutator op schema: open, needed at the save system (Phase 5+).
|
||||
- **Q-105** — region seasonal/clock state: D-239's temperature(time) + `ElevationDelta`
|
||||
calibration is the forward contract to it.
|
||||
|
||||
## Process friction captured
|
||||
|
||||
- **Team mode is broken in clide** — see `docs/briefings/clide-team-mode-friction.md`.
|
||||
Teammates ran in-process (no pane), the Team Chat view stayed inert, no `clide team` verb.
|
||||
The workshop ran as background Agent subagents instead. Hand-off material for the clide team.
|
||||
- The `miri` agent type is **read-only** (no Write/Bash) — the lead transcribed
|
||||
`miri-round1.md` to disk.
|
||||
@@ -304,6 +304,7 @@ line in place — keep the Q-record for the audit trail rather than deleting it.
|
||||
- [D-236: Sol / GJ-0 — player-inaccessible, Atlas highest-level only, deeper Sol is future DLC](decisions/scope.md#d-236-sol--gj-0--player-inaccessible-atlas-highest-level-only-deeper-sol-is-future-dlc) — _scope_
|
||||
- [D-237: Authored per-system specialisation layer — `economic_specialization` + `cultural_specialization` + `dominant_faction`](decisions/architecture.md#d-237-authored-per-system-specialisation-layer--economic-specialization--cultural-specialization--dominant-faction) — _architecture_
|
||||
- [D-238: Symmetric shadowcasting (Albert Ford) selected for LOS computation](decisions/perception.md#d-238-symmetric-shadowcasting-albert-ford-selected-for-los-computation) — _perception_
|
||||
- [D-239: Tile derivation contract — coarse→fine refinement chain (resolves Q-101)](decisions/architecture.md#d-239-tile-derivation-contract--coarsefine-refinement-chain-resolves-q-101) — _architecture_
|
||||
|
||||
## Open questions
|
||||
|
||||
@@ -375,7 +376,6 @@ line in place — keep the Q-record for the audit trail rather than deleting it.
|
||||
- [Q-093: Tile-based exploration map in player insert (Google Maps for the implant)](questions/architecture.md#q-093-tile-based-exploration-map-in-player-insert-google-maps-for-the-implant) — _architecture_
|
||||
- [Q-097: Strip "What They Don't Talk About" from corporation pages](questions/content.md#q-097-strip-what-they-dont-talk-about-from-corporation-pages) — _content_
|
||||
- [Q-099: Mod content catalog — body rows / terrain_reference overlay for systems.db](questions/architecture.md#q-099-mod-content-catalog--body-rows--terrain-reference-overlay-for-systemsdb) — _architecture_
|
||||
- [Q-101: Refinement / derivation contract — coarse hint → fine geometry + morphology algorithm family](questions/architecture.md#q-101-refinement--derivation-contract--coarse-hint--fine-geometry--morphology-algorithm-family) — _architecture_
|
||||
- [Q-102: Cohesion-matrix algorithm — seam-free continuous variation](questions/architecture.md#q-102-cohesion-matrix-algorithm--seam-free-continuous-variation) — _architecture_
|
||||
- [Q-103: Tile-mutator op schema](questions/architecture.md#q-103-tile-mutator-op-schema) — _architecture_
|
||||
- [Q-105: Region seasonal/clock state — the shared cheap-dynamism source](questions/architecture.md#q-105-region-seasonalclock-state--the-shared-cheap-dynamism-source) — _architecture_
|
||||
@@ -416,6 +416,7 @@ line in place — keep the Q-record for the audit trail rather than deleting it.
|
||||
- [Q-096: Replace sprint workflow with kanban + milestones](questions/process.md#q-096-replace-sprint-workflow-with-kanban--milestones) — _process_
|
||||
- [Q-098: Persistence of generated river/city mapping outputs](questions/architecture.md#q-098-persistence-of-generated-rivercity-mapping-outputs) — _architecture_
|
||||
- [Q-100: Biome authority — Python sim vs Rust cascade](questions/architecture.md#q-100-biome-authority--python-sim-vs-rust-cascade) — _architecture_
|
||||
- [Q-101: Refinement / derivation contract — coarse hint → fine geometry + morphology algorithm family](questions/architecture.md#q-101-refinement--derivation-contract--coarse-hint--fine-geometry--morphology-algorithm-family) — _architecture_
|
||||
- [Q-104: Floor-index ↔ absolute voxel-z coordinate mapping](questions/architecture.md#q-104-floor-index--absolute-voxel-z-coordinate-mapping) — _architecture_
|
||||
- [Q-106: Era-band stacking depth for layered architecture-flavor](questions/architecture.md#q-106-era-band-stacking-depth-for-layered-architecture-flavor) — _architecture_
|
||||
|
||||
|
||||
@@ -1739,4 +1739,39 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
|
||||
---
|
||||
|
||||
*96 decisions (D-001 through D-237, excluding gaps). Last updated: 2026-05-31 (system-economic-specialization workshop — D-237 authored per-system specialization layer: `economic_specialization` + `cultural_specialization` + `dominant_faction`; D-233 re-amended to source from D-237 authored layer).*
|
||||
### D-239: Tile derivation contract — coarse→fine refinement chain (resolves Q-101)
|
||||
- **Date:** 2026-06-07
|
||||
- **Resolves:** [Q-101](../questions/architecture.md#q-101)
|
||||
- **Decision:** The walkable tile is materialised by a **three-carrier refinement chain**, each stage a pure deterministic function of `(seed, atlas, body-params, position)` per [D-227](#d-227): `RegionProfile` (~1 km) → `ChunkContext` (64 m) → `VoxelColumn` (1 m). Each scale boundary is its own derivation with its own failure modes.
|
||||
|
||||
**(1) Determinism — no authoring at the derivation layers, ever.** L1–L5 derivation has **zero per-body override hooks**. All authorial control lives **upstream** at the body-parameter / atlas / system-specialization layer; all gating params (`RIVER_THRESHOLD` — a derived per-body-class value `(hydrosphere, tectonic_activity, precipitation_class) → threshold`, not the global `200`; `tectonic_class`; `GlaciationGrade`; `precipitation_class`) are **derived from body params** (stellar type / orbit / hydrosphere / lithology / temperature history), never authored. Lore-anchored bodies (Kallast = plains, Velen = coast, Cygni B = volcanic) are honoured by **setting params** and stand as **validation cases** — if a body reads wrong, fix its params, never patch the derivation. *Validation caveat:* derived terrain can only honour a lore body if its params permit it (e.g. tidal flats require a moon param for the D-228 tidal term — verify Velen's params before treating its coast as a contract).
|
||||
|
||||
**(2) Climate — district temperature is the primitive; moisture is a separate primitive.** A simple scalar **district temperature in °C, resolved per 2×2 km district, nullable**, derived from: the **sun** (luminosity + insolation), the **planet** (the district's latitude + elevation lapse rate + orbital/axial phase = the season term, *and* day-phase = the diurnal term), and the **atmosphere** (greenhouse → the base, and heat-retention → the **diurnal-swing amplitude**; thick air = small day/night swing, thin = large). **No atmosphere → temperature is `null`**, and the entire climate/vegetation/weather branch is simply absent (an airless body's surface ice is *geology* per [D-227](#d-227), not climate). Tiles inherit their district's temperature. **Moisture** is the second primitive (water availability, from `hydrosphere`). **Everything climatic derives from temperature (+ moisture):** precipitation = f(temp, moisture); vegetation/treeline = temperature bands × elevation; long-term / seasonal-minimum temperature → `GlaciationGrade`; the cheap region seasonal/clock state ([Q-105](../questions/architecture.md#q-105)) is literally temperature(time). This formalises [D-210](#d-210)'s temperature proxy into the keystone scalar. No authored climate inputs anywhere.
|
||||
|
||||
**(3) Freeze & snow — a scattered phase transition, transient in the marginal band.** Ice/snow is not a hard `temp < 0` contour but a band with **deterministic, spatially-coherent scatter** (seed-noise → a ragged, natural freeze line + microclimate; clustered patches, never per-tile dice), branched by surface type:
|
||||
- **Fresh / "sweet" water (lakes, rivers):** scatter band **+5 °C → −10 °C (district mean)** — the upper +5 is night-frost (the diurnal swing) reaching the coldest / most-exposed tiles even when the mean is above zero; −10 = frozen across the whole day/night cycle.
|
||||
- **Salt water (oceans):** its own **lower, wider band** (seawater onset ≈ −2 °C) with a distinct **pack-ice spatial pattern** (sheets + leads, not a lake skinning over).
|
||||
- **Snow (land):** the land parallel, **gated on moisture** (cold + wet → snow accumulates; cold + dry → bare frozen ground), same scattered band.
|
||||
- **Transient:** because temperature is clock-bound (season + day/night), in the marginal band ice/snow **forms in the cold phase and melts in the warm phase** — dawn frost burns off, a stream iced at dawn is crossable by noon. Passability is therefore time-of-day dynamic. (Forward contract to [Q-105](../questions/architecture.md#q-105).)
|
||||
|
||||
**(4) Anti-"squaring" (warp).** A global position-keyed **domain-warp field** suppresses grid/seam artifacts — a **stateless, hash-based** pure function of `(seed, body_id, position)` (no lookup table / thread-local cache, so order-independent across threads/platforms). It keeps **f64 sub-metre precision through to the final voxel coordinate, then quantises by truncation to the integer voxel address** (a cast, not a comparison → IEEE-754-deterministic across targets). The ±8 m warp range makes FMA-contraction ULP variance (~1e-15 m) unable to shift the rounded voxel, so no platform guards are needed. The warp is *position math*, not a structural decision — [D-010](#d-010) integer discipline on all material/morphology decisions is preserved.
|
||||
|
||||
**(5) Morphology — 8 families** via a strict **decision tree over integer `RegionProfile` inputs**: LavaField · FjordWall · CliffCoast · BraidedDelta · DuneStrand · IncisedGorge · MeanderReach · AlluvialPlain (fallback). Adds `tectonic_class` to `RegionProfile`. MountainPass is a zone label sharing IncisedGorge geometry. **Hard gates (boolean, pre-selection, in tree order):** fjord requires `GlaciationGrade ≥ 2`; LavaField requires `tectonic_class = Volcanic`; lithology bounds slope/form (see laws).
|
||||
|
||||
**(6) Frozen 17-zone `MorphologyZone` display vocabulary** (derived labels per [D-228](#d-228), enum→label map frozen). The **canonical freeze point is the Rust `MorphologyZone` enum**; adding/renaming/removing a zone **requires a D-record amendment** (reviewer-enforced; classifier tuning that merely re-classifies a region is *not* a vocabulary change and stays free). The 17: open ocean, lake, tidal flat, dune strand, cliff coast, fjord, delta, estuarine, alluvial plain, river bank, meander reach, braided plain, valley floor, mountain pass, alpine, volcanic, wetland. **Zones ≠ families:** four (tidal flat, estuarine, alpine, wetland) are derived sub-classifications from family + elevation/water-height, not distinct generator families.
|
||||
|
||||
**(7) Zone seams — prevent incompatible, allow valid (never patch).** The boolean gates are discontinuities, so "continuous inputs → continuous classification" is *not* the mechanism. The mechanism is **decision-tree gate ordering + a build-time compatibility-matrix invariant**: incompatible family pairs (e.g. MeanderReach↔Volcanic) cannot be adjacent classifier outputs (a build-time test, never a runtime override). Valid geomorphic seams (cliff↔fjord at the glaciation threshold, lithology faults) are **permitted sharp transitions** — kept sharp (real geology is sharp), made non-grid/organic by the warp; no modulation feathering. The warp also prevents degenerate slivers at chunk scale.
|
||||
|
||||
**(8) Believability laws (binding):** drainage monotonicity (respect the D8 thalweg, [D-208](#d-208); tributaries join upstream; mouths at sea level); lithology→landform (Rock → vertical faces; Sand → ≤~32° angle of repose, dunes not cliffs; Gravel → braided channels/fans not single-thread meander; Soil → rolling/floodplain; Wetland → ≤5° flats; Lava → sheets/shield slopes + tubes, immature drainage); glaciation→form (fjord ≥2, U-valleys ≥1, moraines ≥1, cirques ≥2; grade 0 = V-ridges, never glacial U); climate→vegetation (treeline Forest→Scrub→Barren, no skip; riparian Thicket/Scrub 1–3 tiles along perennial waterways) — all flowing from the temperature primitive.
|
||||
|
||||
**(9) Game-feel constraints:** every chunk carries ≥1 tactical decision point; cover ≠ concealment; high-ground asymmetric-but-not-dominant; chokepoints narrow enough to matter (river crossings 3–15 m, gorge floors 2–8 m); seasonal/tidal state produces **real passability changes, not cosmetic** — BraidedDelta/MeanderReach `ElevationDelta` calibrated so channels fall below and levees above the [Q-105](../questions/architecture.md#q-105) high-water threshold.
|
||||
|
||||
**(10) Mechanics:** no per-tile `flow_direction[64×64]` in `ChunkContext` (D8 ≈ 152 m/cell, coarser than a chunk) → one basin-direction + global meander-curve params; features with wavelength > 64 m seed from **Region-or-higher**, not the chunk seed. `RegionProfile` is stored in `BodyWorldState` ([D-203](#d-203), ~6k/body, populated in the [D-206](#d-206) background pass) so the Atlas reads zone labels without triggering voxel derivation; voxel derivation is on-demand + cached, never persisted ([D-227](#d-227)). Budget ~2.2–4.2 ms/chunk (validate per-family in Phase 4 — FjordWall/IncisedGorge are far costlier than the AlluvialPlain fallback).
|
||||
- **Rationale:** [D-227](#d-227)/[D-228](#d-228) fixed *what* a tile is (derive-don't-store voxels; orthogonal derived axes); D-239 fixes *how* the finished upper cascade (L0–L4) becomes actual tiles — the one unbuilt layer. The district-temperature primitive collapses the scattered climate inputs (precipitation, glaciation, season, snow/ice, vegetation) onto one derived scalar + moisture, keeping the whole climate branch deterministic and null-cutting airless bodies cleanly. Prevent-at-source seams + the warp give coherent, organic terrain without a runtime patch. The frozen vocabulary protects authored Atlas/wiki content while letting the classifier evolve.
|
||||
- **Raised by:** tile-derivation-contract workshop (Tyre — refinement chain, warp, determinism; Gestalt — 8 families, game-feel; Troblum — feasibility, warp precision, scale corrections; Miri — believability laws, vocabulary, lore reconciliation), lead-interviewed decisions + an adversarial verification pass, 2026-06-07.
|
||||
- **Dissent:** Tyre's initial cross-family elevation-blend was resolved against (prevent-at-source). Early-integer-truncation of the warp (raised against Gestalt's `ElevationDelta` ranges and by Tyre) was resolved against in favour of f64-to-voxel.
|
||||
- **Cross-reference:** [D-227](#d-227) (derive-don't-store voxel model), [D-228](#d-228) (composite tile axes / cohesion / seasonal state), [D-210](#d-210) (temperature proxy — formalised), [D-203](#d-203) (BodyWorldState cache), [D-206](#d-206) (background analysis pass), [D-208](#d-208) (drainage / D8), [D-010](#d-010) (determinism), [D-234](#d-234) (street/footprint geometry — consumes morphology), [D-142](content.md#d-142) (zone types), [D-217](#d-217) (tile condition), [Q-102](../questions/architecture.md#q-102) (cohesion = the warp), [Q-103](../questions/architecture.md#q-103) (mutator schema — open), [Q-105](../questions/architecture.md#q-105) (seasonal/clock state — temperature/ElevationDelta forward contract)
|
||||
|
||||
---
|
||||
|
||||
*97 decisions (D-001 through D-239, excluding gaps). Last updated: 2026-06-07 (tile-derivation-contract workshop — D-239 tile derivation contract resolving Q-101: three-carrier refinement chain, district temperature primitive + scattered transient freeze/snow model, 8 morphology families + frozen 17-zone vocabulary, prevent-at-source seams).*
|
||||
|
||||
@@ -356,7 +356,8 @@ Technical foundation questions: engine, protocols, data structures, performance,
|
||||
---
|
||||
|
||||
### Q-101: Refinement / derivation contract — coarse hint → fine geometry + morphology algorithm family
|
||||
- **Status:** Open — the atlas-derivation workshop's primary technical output, not yet specified (2026-05-25)
|
||||
- **Status:** Resolved 2026-06-07 by [D-239](../decisions/architecture.md#d-239)
|
||||
- **Resolution:** Resolved by the tile-derivation-contract workshop → D-239: a three-carrier refinement chain (`RegionProfile` ~1 km → `ChunkContext` 64 m → `VoxelColumn` 1 m), pure deterministic functions of `(seed, atlas, body-params, position)` with no authoring at the derivation layers; a stateless f64-to-voxel domain-warp as the anti-squaring mechanism; 8 morphology families (LavaField · FjordWall · CliffCoast · BraidedDelta · DuneStrand · IncisedGorge · MeanderReach · AlluvialPlain) selected by a gated decision tree over a frozen 17-zone vocabulary; a district-temperature climate primitive (2×2 km, °C, nullable) + separate moisture from which all climate/vegetation/glaciation and a scattered, transient freeze/snow model derive; `RIVER_THRESHOLD` becomes a derived per-body-class value; seams prevented at source (gate ordering + build-time matrix), valid geomorphic seams kept sharp + warped. Body-class river-density modulation and D8 flow-direction are folded in. (Ocean/lake mask: derive from the heightmap sea-level threshold per D-239's body-params input.)
|
||||
- **Question:** How does a coarse, map-scale hint (a ~78 km Layer-1 cell) plus a seed become fine, ~1 m-voxel coherent geometry — deterministically, without squaring — across ~3 scale jumps (78 km cell → ~1 km region → 64 m chunk → 1 m voxel)? Needs: the inter-layer hint+seed→geometry API (Tyre sketched a `RegionHint`); a **context-driven morphology algorithm family** selected by local context (rivers meander in lowlands / incise in mountains; coasts dune / crag / fjord by slope + lithology + glaciation), ≥6 distinct generators (meander, incised gorge, braided delta, dune strand, cliff coast, fjord, mountain pass); integer-only *structural* decisions (D-010), f64 confined to within-voxel interpolation; clean transitions between adjacent morphology families. **Unowned dependency:** the ocean/lake mask — D-223 stripped the markers.json polygons that D-209 `CoastalAccess`/`LakeShore` extraction referenced, so the mask must instead be derived from the heightmap sea-level threshold or a baked water-bodies layer. Lithology is a required input (promoted load-bearing by the volumetric subsurface, D-227). Also in scope: **body-class parameters** (hydrosphere, tectonic activity, atmosphere) must modulate river-network *density* — the effective river threshold — so an arid body and an oceanic body differ in *how many* rivers, not just where they run (today `RIVER_THRESHOLD` is a global constant, Nigel); and **river flow direction** (upstream/downstream — gates effort vs speed, and feeds the seasonal water model) derives from the D8 network at query time.
|
||||
- **Context:** The heart of the atlas→tile derivation; surfaces as the cascade builds the region → chunk → voxel layers.
|
||||
- **Cross-reference:** [D-227](../decisions/architecture.md#d-227), [D-228](../decisions/architecture.md#d-228), [D-208](../decisions/architecture.md#d-208), [D-209](../decisions/architecture.md#d-209), [D-223](../decisions/architecture.md#d-223), [D-010](../decisions/architecture.md#d-010), [D-222](../decisions/architecture.md#d-222)
|
||||
|
||||
Reference in New Issue
Block a user