feat(simulation): voxel-tier mid-scale relief — navigable hills (T-1081)

The voxel tier read flat (the D-245 "0–3 m, no hills to navigate by" bug):
family generators set elevation from elev_q at a compressed scale plus only ±4 m
micro-scatter, and detail-scatter's mid-scale relief (D-243 §2) only ever reached
the district tier (elev_q), never per-voxel elevation_m.

- detail_scatter.rs: extract the enveloped-fBm core; add `voxel_relief` at the
  0.25–2 km octave band (vs the district 4–40 km band).
- voxel.rs: add the mid-scale relief post-dispatch in derive_voxel_column, body-global
  SeedDomain::VoxelRelief seed (position-keyed), f64 truncated to integer metres (D-010).
  Envelope = slope_q*3 + elev_q — the coarse heightmap gives slope_q ≈ 0 even on high
  ground (Arbour max 13), so elevation must drive ruggedness. Flat families only
  (Alluvial/Lava/BraidedDelta/Dune/Meander); the dramatic families already carry strong
  internal relief and WaterBody stays at sea level.
- seed.rs: SeedDomain::VoxelRelief (D-224 domain separation).
- believability.rs: new `contrast.voxel_relief_m` metric — mean within-district elevation
  range over a 2 km transect (a single 64 m sample chunk is narrower than the relief
  band) + a "voxel relief" criterion.

Arbour @ yolo: voxel relief 5 m → 59 m, 6/8 → 7/8 criteria (remaining fail = the
Q-123 vegetation-denominator item). Edict 59 m. Golden regenerated; 1586 lib tests +
believability harness (determinism) pass; clippy -D warnings clean.

D-239 amended (T-1081); Q-123 item 4 updated. Deferred to Q-123/follow-up: the
provisional span + threshold, and a per-body hypsometric absolute-elevation model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 20:41:59 +02:00
co-authored by Claude Opus 4.8
parent dcc422f0d8
commit ab6e1413d9
9 changed files with 375 additions and 4 deletions
+53
View File
@@ -2771,3 +2771,56 @@ Stack-review note (2026-06-28): the believability sampling-bias fix (contiguous-
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJTXTADJRHYEE1WTAENYAG', 'status', 'in_progress', 'done', NULL, '2026-06-28 16:02:46', '2026-06-28 16:02:46.330', '2026-06-28 16:02:46.330', NULL, '66fcb4c2c045444b78cfc270ba3e14b7', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJWWZNWYT3C4T828ZF1QCG', 'status', 'in_progress', 'done', NULL, '2026-06-28 16:02:46', '2026-06-28 16:02:46.337', '2026-06-28 16:02:46.337', NULL, '3d4736181c620fea8037c2e863bb4a89', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJZG3WFPQ3VXBFQFM4YEWW', 'status', 'in_progress', 'done', NULL, '2026-06-28 16:02:46', '2026-06-28 16:02:46.338', '2026-06-28 16:02:46.338', NULL, 'fe177606db9f2ee174a5cc0131ec8b64', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJVW30PSP8K0XAWV02X7M0', 'status', 'backlog', 'in_progress', NULL, '2026-06-28 16:08:41', '2026-06-28 16:08:41.257', '2026-06-28 16:08:41.257', NULL, '0c31df90c425803dc23a6ac9369cd751', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJVW30PSP8K0XAWV02X7M0', 'description', 'Probe evidence (Arbour @ yolo): voxel elevation 0-3 m across nearly all sampled districts (only one snow upland reached 42-49 m). Flat terrain => nothing to navigate by, no landmarks, no ''specificity of place''. Either the heightmap relief is being flattened by the 512x256 working downsample + district derivation, or detail-scatter (D-227, derive-don''t-store) is not producing the mid-scale relief it is supposed to invent at the voxel tier. Direction to discuss: the relief envelope taken from the heightmap, detail-scatter amplitude, and whether the elev_q -> elevation_m mapping is collapsing the range. Believability gap.', 'Probe evidence (Arbour @ yolo): voxel elevation 0-3 m across nearly all sampled districts (only one snow upland reached 42-49 m). Flat terrain => nothing to navigate by, no landmarks, no ''specificity of place''. Either the heightmap relief is being flattened by the 512x256 working downsample + district derivation, or detail-scatter (D-227, derive-don''t-store) is not producing the mid-scale relief it is supposed to invent at the voxel tier. Direction to discuss: the relief envelope taken from the heightmap, detail-scatter amplitude, and whether the elev_q -> elevation_m mapping is collapsing the range. Believability gap.
---
Refinement (Si pass, 2026-06-28) READY. Four compounding causes (all confirmed):
1. elev_qelevation_m scale: generate_alluvial_plain uses `elev_q / 2` whole-body relief is 050 m (pancake). Other flat families similar (/2,/3,/4). The dramatic families (cliff/fjord/gorge) carry real internal relief; the flat ones (alluvial/lava/dune/braided/meander) don''t.
2. detail_scatter (D-227) is applied ONLY at the district tier (derive_district elev_q); it never reaches per-voxel elevation_m. So within a chunk relief = base ± 4 m micro-scatter = flat. The promised mid-scale (sub-district, ~tens-to-hundreds m) relief is missing at the voxel tier.
3. Envelope collapses to 0 on flat-slope districts (terrain_detail returns 0 when envelope==0) by design ("no mountain on an authored plain"); consider a small floor so even gentle land rolls.
4. WaterBody correctly keeps elevation_m=0 (sea level) must stay.
PLAN (data-driven, tune via probe like moisture):
- Add MID-SCALE rolling relief at the voxel tier: a detail-scatter octave term (sub-district wavelengths ~2562048 m) added to elevation_m for DRY-LAND columns, enveloped by district ruggedness (slope_q) so plains stay flat and rugged terrain rolls. f64 perturbation truncated to i32 (D-010, same as the warp). Applied centrally in derive_voxel_column (one site, like derive_cover) so all land families benefit.
- Bump the flat-family base scale from the compressed /N toward a believable span if the probe shows mountains still too low (per-body absolute relief = T-1032 follow-up).
- Add a VOXEL-RELIEF metric to believability.rs (mean within-chunk elevation range across sampled non-water districts) the current "elevation relief" criterion measures DISTRICT elev_q spread (already passes), not voxel relief, so it does not catch T-1081.
- VERIFY: probe per-location elevation range rises from ~0-3 m; the new voxel-relief criterion flips green. Determinism: integer/D-010.
Files: voxel.rs (generators + central relief), detail_scatter.rs (sub-district wavelengths), believability.rs (metric).', NULL, '2026-06-28 16:16:05', '2026-06-28 16:16:05.190', '2026-06-28 16:16:05.190', NULL, 'c8b9b791152f463f455dfc08d15027c6', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJVW30PSP8K0XAWV02X7M0', 'description', 'Probe evidence (Arbour @ yolo): voxel elevation 0-3 m across nearly all sampled districts (only one snow upland reached 42-49 m). Flat terrain => nothing to navigate by, no landmarks, no ''specificity of place''. Either the heightmap relief is being flattened by the 512x256 working downsample + district derivation, or detail-scatter (D-227, derive-don''t-store) is not producing the mid-scale relief it is supposed to invent at the voxel tier. Direction to discuss: the relief envelope taken from the heightmap, detail-scatter amplitude, and whether the elev_q -> elevation_m mapping is collapsing the range. Believability gap.
---
Refinement (Si pass, 2026-06-28) READY. Four compounding causes (all confirmed):
1. elev_qelevation_m scale: generate_alluvial_plain uses `elev_q / 2` whole-body relief is 050 m (pancake). Other flat families similar (/2,/3,/4). The dramatic families (cliff/fjord/gorge) carry real internal relief; the flat ones (alluvial/lava/dune/braided/meander) don''t.
2. detail_scatter (D-227) is applied ONLY at the district tier (derive_district elev_q); it never reaches per-voxel elevation_m. So within a chunk relief = base ± 4 m micro-scatter = flat. The promised mid-scale (sub-district, ~tens-to-hundreds m) relief is missing at the voxel tier.
3. Envelope collapses to 0 on flat-slope districts (terrain_detail returns 0 when envelope==0) by design ("no mountain on an authored plain"); consider a small floor so even gentle land rolls.
4. WaterBody correctly keeps elevation_m=0 (sea level) must stay.
PLAN (data-driven, tune via probe like moisture):
- Add MID-SCALE rolling relief at the voxel tier: a detail-scatter octave term (sub-district wavelengths ~2562048 m) added to elevation_m for DRY-LAND columns, enveloped by district ruggedness (slope_q) so plains stay flat and rugged terrain rolls. f64 perturbation truncated to i32 (D-010, same as the warp). Applied centrally in derive_voxel_column (one site, like derive_cover) so all land families benefit.
- Bump the flat-family base scale from the compressed /N toward a believable span if the probe shows mountains still too low (per-body absolute relief = T-1032 follow-up).
- Add a VOXEL-RELIEF metric to believability.rs (mean within-chunk elevation range across sampled non-water districts) the current "elevation relief" criterion measures DISTRICT elev_q spread (already passes), not voxel relief, so it does not catch T-1081.
- VERIFY: probe per-location elevation range rises from ~0-3 m; the new voxel-relief criterion flips green. Determinism: integer/D-010.
Files: voxel.rs (generators + central relief), detail_scatter.rs (sub-district wavelengths), believability.rs (metric).', 'Probe evidence (Arbour @ yolo): voxel elevation 0-3 m across nearly all sampled districts (only one snow upland reached 42-49 m). Flat terrain => nothing to navigate by, no landmarks, no ''specificity of place''. Either the heightmap relief is being flattened by the 512x256 working downsample + district derivation, or detail-scatter (D-227, derive-don''t-store) is not producing the mid-scale relief it is supposed to invent at the voxel tier. Direction to discuss: the relief envelope taken from the heightmap, detail-scatter amplitude, and whether the elev_q -> elevation_m mapping is collapsing the range. Believability gap.
---
Refinement (Si pass, 2026-06-28) READY. Four compounding causes (all confirmed):
1. elev_qelevation_m scale: generate_alluvial_plain uses `elev_q / 2` whole-body relief is 050 m (pancake). Other flat families similar (/2,/3,/4). The dramatic families (cliff/fjord/gorge) carry real internal relief; the flat ones (alluvial/lava/dune/braided/meander) don''t.
2. detail_scatter (D-227) is applied ONLY at the district tier (derive_district elev_q); it never reaches per-voxel elevation_m. So within a chunk relief = base ± 4 m micro-scatter = flat. The promised mid-scale (sub-district, ~tens-to-hundreds m) relief is missing at the voxel tier.
3. Envelope collapses to 0 on flat-slope districts (terrain_detail returns 0 when envelope==0) by design ("no mountain on an authored plain"); consider a small floor so even gentle land rolls.
4. WaterBody correctly keeps elevation_m=0 (sea level) must stay.
PLAN (data-driven, tune via probe like moisture):
- Add MID-SCALE rolling relief at the voxel tier: a detail-scatter octave term (sub-district wavelengths ~2562048 m) added to elevation_m for DRY-LAND columns, enveloped by district ruggedness (slope_q) so plains stay flat and rugged terrain rolls. f64 perturbation truncated to i32 (D-010, same as the warp). Applied centrally in derive_voxel_column (one site, like derive_cover) so all land families benefit.
- Bump the flat-family base scale from the compressed /N toward a believable span if the probe shows mountains still too low (per-body absolute relief = T-1032 follow-up).
- Add a VOXEL-RELIEF metric to believability.rs (mean within-chunk elevation range across sampled non-water districts) the current "elevation relief" criterion measures DISTRICT elev_q spread (already passes), not voxel relief, so it does not catch T-1081.
- VERIFY: probe per-location elevation range rises from ~0-3 m; the new voxel-relief criterion flips green. Determinism: integer/D-010.
Files: voxel.rs (generators + central relief), detail_scatter.rs (sub-district wavelengths), believability.rs (metric).
Implemented (2026-06-28, branch voxel-relief). The voxel tier now carries mid-scale relief (D-243 §2): a `voxel_relief` fBm pass at the 0.252 km octave band (detail_scatter.rs), body-global SeedDomain::VoxelRelief seed, added post-dispatch in derive_voxel_column, truncated to integer metres (D-010).
Root cause refined during impl: the coarse heightmap yields slope_q 0 even on high ground (Arbour max slope_q = 13), so the relief envelope is `slope_q*3 + elev_q` (elevation drives ruggedness), not slope alone. Applied to the FLAT families only (AlluvialPlain/LavaField/BraidedDelta/DuneStrand/MeanderReach) the dramatic families (Cliff/Fjord/Gorge) already carry strong internal relief and a position-varying field warps their features; WaterBody stays at sea level.
Verification: new believability metric `contrast.voxel_relief_m` (mean within-district elevation range over a 2 km transect a single 64 m chunk is narrower than the relief band). Arbour @ yolo: voxel relief 5 m 59 m, criteria 6/8 7/8 (remaining fail = the Q-123 vegetation-denominator item, not relief). Edict also 59 m. 1586 lib tests + believability harness (determinism + golden) green; clippy -D warnings clean.
Deferred (Q-123 / follow-up): provisional VOXEL_RELIEF_SPAN_M=300 + the 8 m threshold need calibration; the ABSOLUTE elevation span is still the compressed elev_q/N base a per-body hypsometric relief model is the later refinement. D-239 amended (T-1081); Q-123 item 4 updated.', NULL, '2026-06-28 18:41:37', '2026-06-28 18:41:37.651', '2026-06-28 18:41:37.651', NULL, 'ceea6fa43cd99a8a108633e74a1d05f0', 2) ON CONFLICT(hash) DO NOTHING;
+39
View File
@@ -3738,3 +3738,42 @@ DEFERRED (ticket item 3 — explicitly optional): per-layer PNG maps (the T-960
This is the D-245 enforcer; advisory now, ratchets to strict (BELIEVABILITY_STRICT) once Q-123 calibrates thresholds and T-1080/1081/1082 flip the criteria.
Stack-review note (2026-06-28): the believability sampling-bias fix (contiguous-prefix -> seeded Fisher-Yates spread in sample_indices) is logically a T-1083 enforcer-correctness fix but landed in the T-1082 commit cdc55032d (found while verifying T-1082). git history records it accurately under the water-body work; flagged so a bisect lands in the expected place. The Fisher-Yates termination fix + drainage-proxy exclusion of water districts (stack review) are in the T-1080 review-fix commit.', 'done', 'high', NULL, 'server', 'D-239', '2026-06-28 05:55:19.712', '2026-06-28 16:02:46.338', NULL, '682f72f4c7d0310247d4cfe20a2f347e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJVW30PSP8K0XAWV02X7M0', 'story', '06FGSJNP8YM537DJCC82ZD6544', 'Near-zero voxel relief (0-3 m) — generated terrain has no hills or landmarks', 'Probe evidence (Arbour @ yolo): voxel elevation 0-3 m across nearly all sampled districts (only one snow upland reached 42-49 m). Flat terrain => nothing to navigate by, no landmarks, no ''specificity of place''. Either the heightmap relief is being flattened by the 512x256 working downsample + district derivation, or detail-scatter (D-227, derive-don''t-store) is not producing the mid-scale relief it is supposed to invent at the voxel tier. Direction to discuss: the relief envelope taken from the heightmap, detail-scatter amplitude, and whether the elev_q -> elevation_m mapping is collapsing the range. Believability gap.', 'in_progress', 'high', NULL, 'server', 'D-227', '2026-06-28 05:54:50.008', '2026-06-28 16:08:41.256', NULL, 'e9823e01ab027322b5f6cd022dfedc1e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJVW30PSP8K0XAWV02X7M0', 'story', '06FGSJNP8YM537DJCC82ZD6544', 'Near-zero voxel relief (0-3 m) — generated terrain has no hills or landmarks', 'Probe evidence (Arbour @ yolo): voxel elevation 0-3 m across nearly all sampled districts (only one snow upland reached 42-49 m). Flat terrain => nothing to navigate by, no landmarks, no ''specificity of place''. Either the heightmap relief is being flattened by the 512x256 working downsample + district derivation, or detail-scatter (D-227, derive-don''t-store) is not producing the mid-scale relief it is supposed to invent at the voxel tier. Direction to discuss: the relief envelope taken from the heightmap, detail-scatter amplitude, and whether the elev_q -> elevation_m mapping is collapsing the range. Believability gap.
---
Refinement (Si pass, 2026-06-28) READY. Four compounding causes (all confirmed):
1. elev_qelevation_m scale: generate_alluvial_plain uses `elev_q / 2` whole-body relief is 050 m (pancake). Other flat families similar (/2,/3,/4). The dramatic families (cliff/fjord/gorge) carry real internal relief; the flat ones (alluvial/lava/dune/braided/meander) don''t.
2. detail_scatter (D-227) is applied ONLY at the district tier (derive_district elev_q); it never reaches per-voxel elevation_m. So within a chunk relief = base ± 4 m micro-scatter = flat. The promised mid-scale (sub-district, ~tens-to-hundreds m) relief is missing at the voxel tier.
3. Envelope collapses to 0 on flat-slope districts (terrain_detail returns 0 when envelope==0) by design ("no mountain on an authored plain"); consider a small floor so even gentle land rolls.
4. WaterBody correctly keeps elevation_m=0 (sea level) must stay.
PLAN (data-driven, tune via probe like moisture):
- Add MID-SCALE rolling relief at the voxel tier: a detail-scatter octave term (sub-district wavelengths ~2562048 m) added to elevation_m for DRY-LAND columns, enveloped by district ruggedness (slope_q) so plains stay flat and rugged terrain rolls. f64 perturbation truncated to i32 (D-010, same as the warp). Applied centrally in derive_voxel_column (one site, like derive_cover) so all land families benefit.
- Bump the flat-family base scale from the compressed /N toward a believable span if the probe shows mountains still too low (per-body absolute relief = T-1032 follow-up).
- Add a VOXEL-RELIEF metric to believability.rs (mean within-chunk elevation range across sampled non-water districts) the current "elevation relief" criterion measures DISTRICT elev_q spread (already passes), not voxel relief, so it does not catch T-1081.
- VERIFY: probe per-location elevation range rises from ~0-3 m; the new voxel-relief criterion flips green. Determinism: integer/D-010.
Files: voxel.rs (generators + central relief), detail_scatter.rs (sub-district wavelengths), believability.rs (metric).', 'in_progress', 'high', NULL, 'server', 'D-227', '2026-06-28 05:54:50.008', '2026-06-28 16:16:05.190', NULL, '1450d38cb522267b239186b96d8ad0a2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJVW30PSP8K0XAWV02X7M0', 'story', '06FGSJNP8YM537DJCC82ZD6544', 'Near-zero voxel relief (0-3 m) — generated terrain has no hills or landmarks', 'Probe evidence (Arbour @ yolo): voxel elevation 0-3 m across nearly all sampled districts (only one snow upland reached 42-49 m). Flat terrain => nothing to navigate by, no landmarks, no ''specificity of place''. Either the heightmap relief is being flattened by the 512x256 working downsample + district derivation, or detail-scatter (D-227, derive-don''t-store) is not producing the mid-scale relief it is supposed to invent at the voxel tier. Direction to discuss: the relief envelope taken from the heightmap, detail-scatter amplitude, and whether the elev_q -> elevation_m mapping is collapsing the range. Believability gap.
---
Refinement (Si pass, 2026-06-28) READY. Four compounding causes (all confirmed):
1. elev_qelevation_m scale: generate_alluvial_plain uses `elev_q / 2` whole-body relief is 050 m (pancake). Other flat families similar (/2,/3,/4). The dramatic families (cliff/fjord/gorge) carry real internal relief; the flat ones (alluvial/lava/dune/braided/meander) don''t.
2. detail_scatter (D-227) is applied ONLY at the district tier (derive_district elev_q); it never reaches per-voxel elevation_m. So within a chunk relief = base ± 4 m micro-scatter = flat. The promised mid-scale (sub-district, ~tens-to-hundreds m) relief is missing at the voxel tier.
3. Envelope collapses to 0 on flat-slope districts (terrain_detail returns 0 when envelope==0) by design ("no mountain on an authored plain"); consider a small floor so even gentle land rolls.
4. WaterBody correctly keeps elevation_m=0 (sea level) must stay.
PLAN (data-driven, tune via probe like moisture):
- Add MID-SCALE rolling relief at the voxel tier: a detail-scatter octave term (sub-district wavelengths ~2562048 m) added to elevation_m for DRY-LAND columns, enveloped by district ruggedness (slope_q) so plains stay flat and rugged terrain rolls. f64 perturbation truncated to i32 (D-010, same as the warp). Applied centrally in derive_voxel_column (one site, like derive_cover) so all land families benefit.
- Bump the flat-family base scale from the compressed /N toward a believable span if the probe shows mountains still too low (per-body absolute relief = T-1032 follow-up).
- Add a VOXEL-RELIEF metric to believability.rs (mean within-chunk elevation range across sampled non-water districts) the current "elevation relief" criterion measures DISTRICT elev_q spread (already passes), not voxel relief, so it does not catch T-1081.
- VERIFY: probe per-location elevation range rises from ~0-3 m; the new voxel-relief criterion flips green. Determinism: integer/D-010.
Files: voxel.rs (generators + central relief), detail_scatter.rs (sub-district wavelengths), believability.rs (metric).
Implemented (2026-06-28, branch voxel-relief). The voxel tier now carries mid-scale relief (D-243 §2): a `voxel_relief` fBm pass at the 0.252 km octave band (detail_scatter.rs), body-global SeedDomain::VoxelRelief seed, added post-dispatch in derive_voxel_column, truncated to integer metres (D-010).
Root cause refined during impl: the coarse heightmap yields slope_q 0 even on high ground (Arbour max slope_q = 13), so the relief envelope is `slope_q*3 + elev_q` (elevation drives ruggedness), not slope alone. Applied to the FLAT families only (AlluvialPlain/LavaField/BraidedDelta/DuneStrand/MeanderReach) the dramatic families (Cliff/Fjord/Gorge) already carry strong internal relief and a position-varying field warps their features; WaterBody stays at sea level.
Verification: new believability metric `contrast.voxel_relief_m` (mean within-district elevation range over a 2 km transect a single 64 m chunk is narrower than the relief band). Arbour @ yolo: voxel relief 5 m 59 m, criteria 6/8 7/8 (remaining fail = the Q-123 vegetation-denominator item, not relief). Edict also 59 m. 1586 lib tests + believability harness (determinism + golden) green; clippy -D warnings clean.
Deferred (Q-123 / follow-up): provisional VOXEL_RELIEF_SPAN_M=300 + the 8 m threshold need calibration; the ABSOLUTE elevation span is still the compressed elev_q/N base a per-body hypsometric relief model is the later refinement. D-239 amended (T-1081); Q-123 item 4 updated.', 'in_progress', 'high', NULL, 'server', 'D-227', '2026-06-28 05:54:50.008', '2026-06-28 18:41:37.651', NULL, 'bd8abb0e2034f7dcb9a811f4d40b9eff', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
+1
View File
@@ -1778,6 +1778,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser
- **Implementation note (T-1025/T-1027, 2026-06-08):** §6's frozen 17-zone `MorphologyZone` enum is realised in `server/src/simulation/generator.rs` (`repr(u8)`, discriminant-pinned). The §5 8-family gated classifier + §7 compatibility-matrix invariants live in `derive_morphology_zone` (`region_profile.rs`). **16 of the 17 zones are reachable at RegionProfile scale; `BraidedPlain` is the exception** — distinguishing it from `Delta` needs a lithology signal (§8 Gravel→braided) that `RegionProfile` does not carry, so `BraidedPlain` is **deferred to ChunkContext** sub-classification. The §7 compatibility invariant is enforced as a **classifier-gate-ordering** property (a build-time test that a single region cannot yield a forbidden pair), per §7's "build-time test" language; genuine cross-region sharp seams (cliff↔fjord, lithology faults) remain permitted. §2 climate-derived fields (`precipitation_class`, `glaciation_grade`, `vegetation_class`) all derive from the temperature(+moisture) primitive (T-1025).
- **Amendment (T-1082, 2026-06-28):** §5's family set grows from **8 to 9** — a `WaterBody` generator family is added for the water zones **OpenOcean / Lake / TidalFlat**, which previously fell through to the dry-land `AlluvialPlain` fallback (the D-245 believability bug: oceans rendered as dry forested land — `water=Dry` + `Forest` on the sea). `WaterBody` lays `Water::Deep`/`Shallow` (a Shallow shoal band on the district-anchored coast line `coast_anchor_m`, Deep beyond; TidalFlat is all-Shallow), a seabed `TerrainMaterial` (`Rock` on steep districts / `Sand` on gentle / `Wetland` for tidal mud), `Vegetation::Barren`, the water surface at `elevation_m = 0` (the §8 mouths-at-sea-level convention), and seasonal `Ice` via `derive_cover` (frozen seas). This **refines §6**: open ocean / lake / tidal flat are still derived zone *labels*, but their *generator* is now `WaterBody`, not `AlluvialPlain` — so §6's "tidal flat … not [a] distinct generator famil[y]" is superseded for the dispatch of those three zones. `Wetland` stays a land zone on `AlluvialPlain` (a marsh — saturated ground, not open water). Lives in `server/src/atlas/voxel.rs` (`MorphologyFamily::WaterBody` + `generate_water_body`, dispatched by `zone_to_family`); verified by the T-1083 believability harness (water-renders-wet flips PASS for Arbour + Edict). The §5 8-family decision-tree classifier (`derive_morphology_zone`) is unchanged — this is a voxel-tier *generator* family, selected by zone, not a new RegionProfile classifier branch.
- **Amendment (T-1080, 2026-06-28):** §2's **moisture** primitive is now a **body ceiling × per-district spatial gradient**, not a single body constant. The hydrosphere+atmosphere value (`derive_moisture_q`) is the *wettest* a district on the body can be; per-district **latitude** (equator wet → pole dry), **elevation** (orographic / rain-shadow), and **continentality** (`100 ocean_fraction_q`, coast wet → interior dry) subtract from it. Integer (D-010); coefficients in `ClimateConstants` / `climate_constants.toml` `[moisture_gradient]` (provisional, Q-123 calibrates). This mirrors D-240's latitude-graded *temperature* model and fixes the T-1080 believability bug (`moisture_q = 80` for all 2 048 districts → uniform vegetation/terrain — "nothing to fuzz"). It **propagates**: `precipitation_class`, `vegetation_class`, and the `morphology_zone` Wetland gate all read `moisture_q`, so those diversify for free. Lives in `district_profile.rs::derive_moisture_q`; verified by the T-1083 believability harness (moisture-gradient flips PASS, `distinct` 1 → ~49 on Arbour). The body-scale ceiling keeps each world's character; the gradient varies it within.
- **Amendment (T-1081, 2026-06-28):** the voxel tier now carries **mid-scale relief** (D-243 §2's invented terrain). Before, the family generators set `elevation_m` from `elev_q` at a compressed scale plus only ±4 m per-voxel micro-scatter — the walkable surface read flat (the D-245 "03 m, no hills to navigate by" bug). A `voxel_relief` pass (`detail_scatter.rs`, the `terrain_detail` fBm at the **0.252 km octave band** rather than the district 440 km band, body-global `SeedDomain::VoxelRelief` seed, position-keyed; the `f64` perturbation truncated to integer metres before assignment — D-010) is added post-dispatch in `derive_voxel_column`. Two load-bearing choices: **(a) the relief envelope is `slope_q·3 + elev_q`, not slope alone** — the coarse heightmap (~4078 km/px) yields `slope_q ≈ 0` even on high ground (observed max 13 on Arbour), so gating on slope would invent nothing; folding elevation in makes high terrain read rugged and coastal flats stay gentle. **(b) Only the flat families** (AlluvialPlain / LavaField / BraidedDelta / DuneStrand / MeanderReach) take it — CliffCoast / FjordWall / IncisedGorge already generate strong internal relief and a position-varying field would warp those features (e.g. drown a gorge's wall-to-floor drop); WaterBody stays at sea level. The relief **span** (`VOXEL_RELIEF_SPAN_M`) and the believability voxel-relief threshold are provisional (Q-123 calibrates, like the moisture coefficients). Verified by the T-1083 harness — a new `voxel_relief` contrast metric (mean within-district elevation range across a district-spanning transect, since a single 64 m sample chunk is narrower than the relief band) flips PASS (≈59 m on Arbour + Edict). **Deferred:** the *absolute* elevation span is still the compressed `elev_q/N` base — a per-body hypsometric relief model (so a body's true max relief sets the ceiling, not a constant) is a later refinement; the mid-scale relief gives navigable hills now without it.
- **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)
---
+1 -1
View File
@@ -477,7 +477,7 @@ Technical foundation questions: engine, protocols, data structures, performance,
1. **Sampling power** — probes per body, number of bodies, number of seeds that constitute "anywhere" (enough that one lucky/unlucky draw is not decisive).
2. **Non-stationarity** — the similarity metric for "two same-class km² differ" (histogram distance / feature-vector cosine) and the *minimum* dissimilarity that counts as non-repeating; plus a tile-period autocorrelation ceiling (the Netherlands "no two km² alike" rule, made numeric).
3. **Intra-class variety** — minimum distinct micro-features per sampled patch (K), per terrain class (T-1084).
4. **Relief floor** — minimum relief variance, *conditioned on the macro terrain's expectation* (a plain's floor ≠ a montane floor) (T-1081).
4. **Relief floor** — minimum relief variance, *conditioned on the macro terrain's expectation* (a plain's floor ≠ a montane floor) (T-1081). *(T-1081 landed the metric — `contrast.voxel_relief_m`, mean within-district elevation range over a 2 km transect — with a provisional `≥ 8 m` threshold and a provisional `VOXEL_RELIEF_SPAN_M = 300`; calibrate both, and make the floor macro-conditioned rather than a single global number. Also pending: the **absolute** elevation span is still the compressed `elev_q/N` base — a per-body hypsometric model would set true relief ceilings.)*
5. **Coherence** — the vegetation ↔ (moisture / water-distance / slope / aspect) correlation floor; the water-zone-renders-wet check (T-1082); the allowed "impossible combo" set (should be empty). *(T-1082 review note: the current 50% "water renders wet" floor is weak for ocean-dominant bodies — calibrate it **per zone**: post-T-1082 an `OpenOcean` district should render wet ~100% (Dry is impossible), while a mixed coastal district may legitimately read partial.)* Also calibrate the **vegetation-present** denominator: dividing by *all* sampled districts (incl. water) makes the criterion structurally unreachable for a 2/3-ocean world — it should be **land-relative** (exclude water-body districts).
6. **Climate-appropriateness** — how "*structured-sparse* passes / *blank* fails" is measured on cold/arid bodies, so a frozen world is not failed for being legitimately sparse.
7. **Pass budget + ratchet** — the % of land probes that must pass to ship (budgeted), and the trigger to ratchet advisory → strict (hard push-gate block).
+56
View File
@@ -67,6 +67,11 @@ const WATER_PRESENCE_Q: i32 = 10;
const VOXEL_SAMPLE_DISTRICTS: usize = 64;
/// Stride over a district's representative 64 m chunk (8 → an 8×8 = 64-voxel sample).
const VOXEL_SAMPLE_STRIDE: usize = 8;
/// Chunk offsets within a district (×64 m ⇒ 128 m … 1920 m) for the mid-scale relief
/// transect (T-1081) — samples the elevation range a character crosses over the
/// district's ~2 km span (the voxel-relief band), which the single 64 m sample chunk
/// is too narrow to register.
const RELIEF_TRANSECT_CHUNK_OFFSETS: [i32; 6] = [2, 8, 14, 20, 26, 30];
// ---------------------------------------------------------------------------
// Report types (serde — golden-snapshot-able)
@@ -101,6 +106,12 @@ pub struct ContrastMetrics {
pub vegetation_classes: usize,
/// Distinct voxel `TerrainMaterial` values across the voxel sample.
pub terrain_materials: usize,
/// Mean within-district voxel elevation *range* (metres) across a district-spanning
/// transect of the sampled land districts (T-1081). The walkable-relief screen:
/// near 0 on flat terrain (the "03 m, no hills" symptom), tens of metres once the
/// voxel tier carries mid-scale relief. Measured over the 0.252 km band a character
/// traverses — not the single 64 m sample chunk, which is too narrow to register it.
pub voxel_relief_m: i32,
}
/// Coherence checks — does the world render as a *caused* place? Pass/fail counts
@@ -171,6 +182,7 @@ pub fn analyze(
.map(|d| format!("{:?}", d.vegetation_class)),
),
terrain_materials: 0, // filled from the voxel sample below
voxel_relief_m: 0, // filled from the relief transect below
};
// ── Voxel-derived metrics over a deterministic seeded spread of districts ──
@@ -181,6 +193,9 @@ pub fn analyze(
let mut terrain_set: BTreeSet<String> = BTreeSet::new();
let mut coh = CoherenceMetrics::default();
let mut sampled = 0usize;
// Mid-scale relief accumulators (T-1081): sum of per-district transect ranges over
// land districts, and the count contributing, → mean within-district relief.
let (mut relief_range_sum, mut relief_district_count) = (0i64, 0i64);
let keys: Vec<DistrictPos> = districts.keys().copied().collect();
for idx in sample_indices(world_seed, keys.len(), VOXEL_SAMPLE_DISTRICTS) {
@@ -243,8 +258,43 @@ pub fn analyze(
coh.drainage_monotonic += 1;
}
}
// Mid-scale relief transect (T-1081): the dry-ground elevation range across the
// district's ~2 km span — where the voxel tier's mid-scale relief lives. The
// single 64 m sample chunk above is narrower than the relief's 0.252 km band,
// so it would read flat even on rolling terrain; this transect is the screen.
// Only land-dominant districts count (≥3 of 6 transect points dry) — coastal
// districts with one stray dry point would otherwise contribute a 0 range and
// bias the mean toward "flat" on an ocean world (a measurement artifact, not
// flat land).
let (mut relief_lo, mut relief_hi, mut relief_dry) = (i32::MAX, i32::MIN, 0i32);
for &co in &RELIEF_TRANSECT_CHUNK_OFFSETS {
let cpos = (
dp.0 * CHUNKS_PER_DISTRICT + co,
dp.1 * CHUNKS_PER_DISTRICT + co,
);
let cctx = derive_chunk_context(world_seed, body_id, prof, cpos, None);
let tx = cpos.0 * CHUNK_M + CHUNK_M / 2;
let ty = cpos.1 * CHUNK_M + CHUNK_M / 2;
let col = derive_voxel_column(world_seed, body_id, prof, &cctx, tx, ty);
if col.water == Water::Dry {
relief_lo = relief_lo.min(col.elevation_m);
relief_hi = relief_hi.max(col.elevation_m);
relief_dry += 1;
}
}
if relief_dry >= 3 {
relief_range_sum += (relief_hi - relief_lo) as i64;
relief_district_count += 1;
}
}
let voxel_relief_m = if relief_district_count > 0 {
(relief_range_sum / relief_district_count) as i32
} else {
0
};
BelievabilityReport {
body_id: body_id.to_string(),
world_seed,
@@ -252,6 +302,7 @@ pub fn analyze(
voxel_sampled_districts: sampled,
contrast: ContrastMetrics {
terrain_materials: terrain_set.len(),
voxel_relief_m,
..contrast_scalar
},
coherence: coh,
@@ -280,6 +331,11 @@ pub fn evaluate_criteria(r: &BelievabilityReport) -> Vec<Criterion> {
pass: c.elev_q.spread() >= 10,
detail: format!("elev_q spread={}", c.elev_q.spread()),
},
Criterion {
name: "voxel relief",
pass: c.voxel_relief_m >= 8,
detail: format!("mean within-district relief {} m", c.voxel_relief_m),
},
Criterion {
name: "morphology variety",
pass: c.morphology_zones >= 2,
+75 -1
View File
@@ -24,6 +24,12 @@ use crate::seed::splitmix64;
/// coarsest (~33 km) the heightmap itself carries the shape.
const OCTAVE_WAVELENGTHS_M: [f64; 4] = [32_768.0, 16_384.0, 8_192.0, 4_096.0];
/// Voxel-tier octave wavelengths in metres — the ≈0.252 km band that the
/// district-tier [`terrain_detail`] (440 km) is too coarse to carry and the
/// per-voxel scatter (<64 m) is too fine to reach. This is the [`voxel_relief`]
/// band: the rolling/ridged hills a *walking character* navigates by (T-1081).
const VOXEL_OCTAVE_WAVELENGTHS_M: [f64; 4] = [2_048.0, 1_024.0, 512.0, 256.0];
/// Deterministic lattice value in `[-1, 1)` for an integer noise cell.
#[inline]
fn lattice(seed: u64, ix: i64, iy: i64) -> f64 {
@@ -64,6 +70,40 @@ fn value_noise(seed: u64, wx: f64, wy: f64, wavelength_m: f64) -> f64 {
/// Returns roughly `[-envelope, +envelope]`, smaller and smoother as `ruggedness`
/// drops toward 0 (gentle flats), larger and ridged as it rises toward 1.
pub fn terrain_detail(seed: u64, wx: f64, wy: f64, envelope: f64, ruggedness: f64) -> f64 {
enveloped_fbm(seed, wx, wy, envelope, ruggedness, &OCTAVE_WAVELENGTHS_M)
}
/// The voxel-tier mid-scale relief perturbation in `[0,1]`-normalized units
/// (multiply by the body's relief span for metres) — the ≈0.252 km rolling/ridged
/// hills a walking character navigates by (T-1081). Identical envelope/ruggedness
/// contract as [`terrain_detail`]; only the octave band differs
/// ([`VOXEL_OCTAVE_WAVELENGTHS_M`] vs the district [`OCTAVE_WAVELENGTHS_M`]).
///
/// Seed must be **body-global** (constant across the body) — the position
/// `(wx, wy)` carries the variation. A per-voxel seed would make every voxel a
/// fresh lattice (white noise, not smooth hills).
pub fn voxel_relief(seed: u64, wx: f64, wy: f64, envelope: f64, ruggedness: f64) -> f64 {
enveloped_fbm(
seed,
wx,
wy,
envelope,
ruggedness,
&VOXEL_OCTAVE_WAVELENGTHS_M,
)
}
/// Shared adaptive-fBm core for [`terrain_detail`] and [`voxel_relief`] — the only
/// difference between the two tiers is the octave wavelength band. Returns the
/// enveloped, ruggedness-modulated perturbation (roughly `[-envelope, +envelope]`).
fn enveloped_fbm(
seed: u64,
wx: f64,
wy: f64,
envelope: f64,
ruggedness: f64,
wavelengths: &[f64],
) -> f64 {
let env = envelope.clamp(0.0, 1.0);
let rug = ruggedness.clamp(0.0, 1.0);
if env == 0.0 {
@@ -73,7 +113,7 @@ pub fn terrain_detail(seed: u64, wx: f64, wy: f64, envelope: f64, ruggedness: f6
let mut sum = 0.0;
let mut amp = 1.0;
let mut norm = 0.0;
for (i, &wl) in OCTAVE_WAVELENGTHS_M.iter().enumerate() {
for (i, &wl) in wavelengths.iter().enumerate() {
let mut n = value_noise(
seed.wrapping_add((i as u64).wrapping_mul(0x1000)),
wx,
@@ -159,4 +199,38 @@ mod tests {
(base - near).abs()
);
}
// ── voxel_relief (T-1081): same contract, sub-district band ──────────────
#[test]
fn voxel_relief_deterministic_and_bounded() {
let a = voxel_relief(42, 12_345.0, -6_789.0, 0.6, 0.5);
let b = voxel_relief(42, 12_345.0, -6_789.0, 0.6, 0.5);
assert_eq!(a, b);
// Same envelope rule + amplitude ceiling as terrain_detail.
assert_eq!(voxel_relief(7, 1_000.0, 2_000.0, 0.0, 1.0), 0.0);
for i in 0..400 {
let v = voxel_relief(99, i as f64 * 137.0, i as f64 * -91.0, 0.5, 1.0);
assert!(v.abs() <= 0.5 + 1e-9, "v={v} exceeded envelope at {i}");
}
}
#[test]
fn voxel_relief_varies_at_sub_district_scale() {
// The voxel band must produce DISTINCT relief within a single 2 km district —
// exactly the variation terrain_detail's 440 km band cannot carry, and the
// flatness T-1081 fixes. Body-global seed; position carries the variation. Steps
// are deliberately non-aligned with the octave wavelengths to avoid aliasing.
let seed = 1234;
let vals: Vec<f64> = (0..16)
.map(|i| voxel_relief(seed, i as f64 * 137.0, i as f64 * 89.0, 0.7, 0.6))
.collect();
let min = vals.iter().cloned().fold(f64::INFINITY, f64::min);
let max = vals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
assert!(
max - min > 0.1,
"voxel relief is near-flat across a district: range {}",
max - min
);
}
}
+141
View File
@@ -333,6 +333,15 @@ fn zone_to_family(zone: &MorphologyZone) -> MorphologyFamily {
/// ## Returns
///
/// A fully derived `VoxelColumn` with all D-228 axes populated.
/// T-1081: the metre span of the voxel-tier mid-scale relief field — the amplitude
/// ceiling for the ≈0.252 km rolling/ridged hills added to the family base
/// elevation. Actual relief at a voxel is `voxel_relief(...) * SPAN`, which the
/// envelope (district ruggedness) scales down on gentle terrain, so this is the
/// steep-terrain ceiling, not a uniform amplitude. Provisional — tuned against the
/// believability probe (T-1079) like the moisture gradient; per-body *absolute*
/// elevation span is a later refinement (the T-1080 climate-fields lineage).
const VOXEL_RELIEF_SPAN_M: i32 = 300;
pub fn derive_voxel_column(
world_seed: u64,
body_id: &str,
@@ -427,6 +436,58 @@ pub fn derive_voxel_column(
}
};
// ── 3b. Voxel-tier mid-scale relief (T-1081, D-227 / D-243 §2) ────────
// The family generators set a coarse district-derived base elevation plus only
// ±4 m of per-voxel micro-scatter, leaving the walkable surface near-flat — no
// hills or landmarks to navigate by (the T-1081 symptom). Add the mid-scale
// (≈0.252 km) rolling/ridged relief the district→voxel seam does not otherwise
// reach (D-243 §2): detail-scatter enveloped by district ruggedness (slope_q) so
// an authored plain stays flat and rugged terrain gains real relief. The relief
// field is body-global (SeedDomain::VoxelRelief, position-keyed); the f64
// perturbation is truncated to integer metres before assignment (D-010,
// truncate-before-decision — the same sanctioned f64 path as the domain warp).
//
// Skipped for the WaterBody family: open ocean / lake / tidal-flat surfaces stay
// at sea level (elevation 0). River channels inside the land families ride up
// with their banks — value noise is smooth at 1 m steps, so a channel voxel and
// its bank get near-identical relief and the family's relative channel cut holds.
// Only the FLAT families take the mid-scale relief. The dramatic families
// (CliffCoast / FjordWall / IncisedGorge) already generate strong internal relief
// from their own geometry — layering a position-varying field over them would warp
// those features (e.g. drown a gorge's wall-to-floor drop under a relief swell).
// WaterBody stays at sea level. The flat families are the ones that read flat and
// need invented relief; their only feature is a 315 m channel, narrow enough that
// the smooth relief shifts it bodily with its banks.
let takes_mid_scale_relief = matches!(
family,
MorphologyFamily::AlluvialPlain
| MorphologyFamily::LavaField
| MorphologyFamily::BraidedDelta
| MorphologyFamily::DuneStrand
| MorphologyFamily::MeanderReach
);
if takes_mid_scale_relief {
let relief_seed = SeedChain::for_body(world_seed, body_id)
.derive(SeedDomain::VoxelRelief, 0)
.seed();
// Relief envelope (T-1081): the coarse heightmap (~4078 km/px) carries almost
// no slope at this tier (slope_q tops out near 0.1 on a real body), so gating on
// slope_q alone would kill the invented relief everywhere. Combine slope with
// elevation — high ground reads rugged, coastal lowlands stay gentle — so
// detail-scatter raises hills where the body has high terrain and keeps flats
// soft. (Tectonic-class ridge sharpening is a later refinement.)
let signal = (district_eff.slope_q * 3 + district_eff.elev_q).clamp(0, 100) as f64 / 100.0;
let relief = crate::atlas::detail_scatter::voxel_relief(
relief_seed,
voxel_x as f64,
voxel_y as f64,
signal,
signal,
);
let relief_m = (relief * VOXEL_RELIEF_SPAN_M as f64) as i32; // truncate (D-010)
column.elevation_m = (column.elevation_m + relief_m).max(0);
}
// ── 4. Seasonal cover overlay (D-239 §3, T-1030) ──────────────────────
// Applied AFTER family dispatch: the 8 family generators produce the base
// axes (terrain/water/vegetation/elevation); cover is a separate orthogonal
@@ -2235,6 +2296,86 @@ mod tests {
assert_eq!(a, b);
}
// ── Voxel-tier mid-scale relief (T-1081) ─────────────────────────────────
#[test]
fn voxel_relief_gives_rugged_land_navigable_hills() {
// T-1081: a rugged land district must produce mid-scale relief across a ~2 km
// span far exceeding the ±4 m micro-scatter (the "flat 03 m, no hills" symptom).
let mut district = alluvial_district();
district.slope_q = 60; // rugged → relief headroom
let chunk = alluvial_chunk(&district);
let mut elevs = Vec::new();
for i in 0..12 {
let p = i * 256; // step across the voxel-relief band (0.252 km)
let col = derive_voxel_column(42, "GJ1c", &district, &chunk, p, p);
if col.water == Water::Dry {
elevs.push(col.elevation_m);
}
}
assert!(elevs.len() >= 4, "expected mostly dry-land samples");
let min = *elevs.iter().min().unwrap();
let max = *elevs.iter().max().unwrap();
assert!(
max - min > 20,
"rugged district should have navigable mid-scale relief, got {min}..{max} m"
);
}
#[test]
fn voxel_relief_respects_low_flat_envelope() {
// No slope AND no elevation → no terrain signal → no invented relief (the
// envelope rule: don't sprout hills on a flat sea-level plain). Elevation stays
// the family base (elev_q/2 = 0 m) plus only the ±4 m micro-scatter / channel cut.
let mut district = alluvial_district();
district.slope_q = 0;
district.elev_q = 0;
let chunk = alluvial_chunk(&district);
for i in 0..12 {
let p = i * 256;
let col = derive_voxel_column(42, "GJ1c", &district, &chunk, p, p);
assert!(
col.elevation_m <= 4,
"low-flat district gained mid-scale relief: {} m",
col.elevation_m
);
}
}
#[test]
fn voxel_relief_from_elevation_even_with_zero_slope() {
// The T-1081 root cause: the coarse heightmap gives slope_q ≈ 0 even on high
// terrain, so ELEVATION must drive the relief envelope. A high district with
// slope_q == 0 must still gain navigable hills (else high ground reads flat).
let mut district = alluvial_district();
district.slope_q = 0;
district.elev_q = 90; // high ground, coarse-heightmap-flat
let chunk = alluvial_chunk(&district);
let mut elevs = Vec::new();
for i in 0..12 {
let p = i * 256;
let col = derive_voxel_column(42, "GJ1c", &district, &chunk, p, p);
if col.water == Water::Dry {
elevs.push(col.elevation_m);
}
}
let range = elevs.iter().max().unwrap() - elevs.iter().min().unwrap();
assert!(
range > 20,
"high terrain must gain relief despite slope_q=0, got range {range} m"
);
}
#[test]
fn voxel_relief_is_deterministic() {
let mut district = alluvial_district();
district.slope_q = 50;
let chunk = alluvial_chunk(&district);
let a = derive_voxel_column(42, "GJ1c", &district, &chunk, 777, 1234);
let b = derive_voxel_column(42, "GJ1c", &district, &chunk, 777, 1234);
assert_eq!(a.elevation_m, b.elevation_m);
}
// -----------------------------------------------------------------------
// TerrainMaterial discriminant pins
// -----------------------------------------------------------------------
+5
View File
@@ -110,6 +110,11 @@ pub enum SeedDomain {
/// so the per-cluster cover hash can never collide with the per-voxel terrain
/// stream — domain separation guarantees no freeze-to-terrain correlation.
Cover = 10,
/// Voxel-tier mid-scale relief noise field (T-1081, D-243 §2). A body-global,
/// position-keyed elevation perturbation at sub-district wavelengths (≈0.252 km),
/// keyed by a single constant id (one field per body). Distinct domain so the
/// relief lattice can never correlate with the per-voxel terrain or cover streams.
VoxelRelief = 11,
}
/// A position in the deterministic seed tree (D-224).
+4 -2
View File
@@ -27,7 +27,8 @@
},
"morphology_zones": 9,
"vegetation_classes": 3,
"terrain_materials": 4
"terrain_materials": 4,
"voxel_relief_m": 49
},
"coherence": {
"water_districts": 49,
@@ -66,7 +67,8 @@
},
"morphology_zones": 9,
"vegetation_classes": 1,
"terrain_materials": 4
"terrain_materials": 4,
"voxel_relief_m": 45
},
"coherence": {
"water_districts": 25,