From c95f9d9a70bde9870f5eb14a8569b0843679d727 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sun, 28 Jun 2026 07:59:34 +0200 Subject: [PATCH] docs(governance): D-245 nature-layer believability acceptance gate + believability epic (T-1079..T-1084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lock the cascade's nature-half deliverable to "reads alive anywhere", not "implemented" — the holy-grail objective surfaced by probing the generation cascade. - D-245 (architecture): the nature layers are accepted only when the believability litmus passes at randomly-sampled locations across all habitable bodies/seeds — multi-scale + never-repeating (macro identity, meso non-stationarity, micro mosaic), coherence + non-stationarity + intra-class variety + relief + climate-appropriateness, automated screen under a human sign-off, budgeted -> strict. Q-123: threshold calibration. - T-1079 (epic, north-star, DoD=D-245) + findings T-1080 (uniform climate fields), T-1081 (near-zero relief), T-1082 (no water-body generator), T-1084 (intra-class micro-habitat mosaic), and T-1083 (the repeatable believability test protocol / D-245 enforcer). - aliveness_probe bin: the exploratory probe that found these (runs the deterministic cascade for a body+seed, reports per-district nature + verdict). Seed for T-1083. Co-Authored-By: Claude Opus 4.8 (1M context) --- .pql/changelog/ticket_history/2026-06.sql | 53 +++ .pql/changelog/ticket_idmap/2026-06.sql | 6 + .pql/changelog/ticket_labels/2026-06.sql | 1 + .pql/changelog/tickets/2026-06.sql | 43 +++ governance/README.md | 2 + governance/decisions/architecture.md | 18 +- governance/questions/architecture.md | 16 +- server/src/bin/aliveness_probe.rs | 415 ++++++++++++++++++++++ 8 files changed, 552 insertions(+), 2 deletions(-) create mode 100644 server/src/bin/aliveness_probe.rs diff --git a/.pql/changelog/ticket_history/2026-06.sql b/.pql/changelog/ticket_history/2026-06.sql index b3dc6409b..66a1dc37d 100644 --- a/.pql/changelog/ticket_history/2026-06.sql +++ b/.pql/changelog/ticket_history/2026-06.sql @@ -2616,3 +2616,56 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang 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 ('06FBPTXP5N42SSGW300FEDTS3M', 'status', 'in_progress', 'review', NULL, '2026-06-17 07:48:14', '2026-06-17 07:48:14.920', '2026-06-17 07:48:14.920', NULL, 'e7115e66b6bd755538db65e42fbc162d', 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 ('06FBPQ0381QWDAQABZAY7MRD8M', 'status', 'review', 'done', NULL, '2026-06-17 08:14:43', '2026-06-17 08:14:43.302', '2026-06-17 08:14:43.302', NULL, 'f90f10ea8bfed4aceb71c55b391bc153', 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 ('06FBPTXP5N42SSGW300FEDTS3M', 'status', 'review', 'done', NULL, '2026-06-17 08:14:43', '2026-06-17 08:14:43.306', '2026-06-17 08:14:43.306', NULL, '1f6b136fdd32b361532caee73a6df1c7', 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 ('06FGSJNP8YM537DJCC82ZD6544', 'description', 'Surfaced by the aliveness probe (server/src/bin/aliveness_probe.rs, branch fillchunk-shell-derive) run on Arbour (GJ338Bd — temperate, ocean hydrosphere) at seed ''yolo'' (u64 15217401773476501448). The deterministic cascade produces 2048 districts; the D-239 derivation LAWS fire correctly, yet the generated surface does not read as a living, real place. Across 6 probes (1 anchored ~150 chunks west of the principal city ''Arbour'' + 5 random districts) the world is uniform wet forest with the sea rendered as dry land. Key insight: this is the ''passes every binding law yet reads dead'' case — the lever is input-field VARIANCE + missing water rendering, not the laws. This epic groups the believability gaps (children) + the repeatable test protocol that found them, as a direction to discuss improvements. Sibling to T-974 (Atlas-to-tile derivation, the laws) and T-1031 (law-correctness harness).', 'Surfaced by the aliveness probe (server/src/bin/aliveness_probe.rs, branch fillchunk-shell-derive) run on Arbour (GJ338Bd — temperate, ocean hydrosphere) at seed ''yolo'' (u64 15217401773476501448). The deterministic cascade produces 2048 districts; the D-239 derivation LAWS fire correctly, yet the generated surface does not read as a living, real place. Across 6 probes (1 anchored ~150 chunks west of the principal city ''Arbour'' + 5 random districts) the world is uniform wet forest with the sea rendered as dry land. Key insight: this is the ''passes every binding law yet reads dead'' case — the lever is input-field VARIANCE + missing water rendering, not the laws. This epic groups the believability gaps (children) + the repeatable test protocol that found them, as a direction to discuss improvements. Sibling to T-974 (Atlas-to-tile derivation, the laws) and T-1031 (law-correctness harness). + +--- +UNIFYING PRINCIPLE (added during design discussion): multi-scale, never-repeating realization. + +Real-world anchor: the entire Netherlands is ONE macro class (river delta / marshland) — coherent regional identity — yet it realizes as dunes, peat bogs, tidal flats, river-clay, heath, fen, reed marsh, polder, wet woodland, and NO TWO km^2 ARE THE SAME. Macro coherence and local non-stationarity are both required and are not in tension. + +This reframes the epic as a multi-scale model: + - MACRO (region/body): identity — "this is a delta" (coherent). -> D-210 / T-948 (SubBiomeVariant macro tag). + - MESO (~km^2, between AND within districts): the sub-biome DISTRIBUTION drifts — same class, different mix every km^2. -> T-1080 (this is the real shape of that fix). + - MICRO (sub-chunk <64 m): the habitat mosaic within a patch. -> T-1084. + +Two design implications: + 1. Sub-biome must be a spatially-varying DISTRIBUTION, not a single tag. "Delta" resolves to a blend {tidal flat, peat bog, river-clay, reed marsh, ...} whose proportions drift across space; the micro-mosaic (T-1084) draws from the LOCAL blend. One label, many local realizations. + 2. The enemy is the visible tile-repeat. "Every km^2 differs" = non-stationarity via multi-octave, domain-warped noise (extend the existing domain_warp "seam cannot align to the chunk edge" property up to the sub-biome tier). The fix for T-1080 is therefore non-repeating multi-octave VARIATION, not merely a smooth gradient (a gradient alone is still locally bland). + +Test-protocol consequence (T-1083): the believability screen must measure NON-STATIONARITY — e.g. that two same-class km^2 are not identical, and that local sub-biome mix drifts — not just class-label diversity.', NULL, '2026-06-28 05:55:45', '2026-06-28 05:55:45.024', '2026-06-28 05:55:45.024', NULL, '1aa7d032336545d443f2698dfa57ef91', 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 ('06FGSJNP8YM537DJCC82ZD6544', 'description', 'Surfaced by the aliveness probe (server/src/bin/aliveness_probe.rs, branch fillchunk-shell-derive) run on Arbour (GJ338Bd — temperate, ocean hydrosphere) at seed ''yolo'' (u64 15217401773476501448). The deterministic cascade produces 2048 districts; the D-239 derivation LAWS fire correctly, yet the generated surface does not read as a living, real place. Across 6 probes (1 anchored ~150 chunks west of the principal city ''Arbour'' + 5 random districts) the world is uniform wet forest with the sea rendered as dry land. Key insight: this is the ''passes every binding law yet reads dead'' case — the lever is input-field VARIANCE + missing water rendering, not the laws. This epic groups the believability gaps (children) + the repeatable test protocol that found them, as a direction to discuss improvements. Sibling to T-974 (Atlas-to-tile derivation, the laws) and T-1031 (law-correctness harness). + +--- +UNIFYING PRINCIPLE (added during design discussion): multi-scale, never-repeating realization. + +Real-world anchor: the entire Netherlands is ONE macro class (river delta / marshland) — coherent regional identity — yet it realizes as dunes, peat bogs, tidal flats, river-clay, heath, fen, reed marsh, polder, wet woodland, and NO TWO km^2 ARE THE SAME. Macro coherence and local non-stationarity are both required and are not in tension. + +This reframes the epic as a multi-scale model: + - MACRO (region/body): identity — "this is a delta" (coherent). -> D-210 / T-948 (SubBiomeVariant macro tag). + - MESO (~km^2, between AND within districts): the sub-biome DISTRIBUTION drifts — same class, different mix every km^2. -> T-1080 (this is the real shape of that fix). + - MICRO (sub-chunk <64 m): the habitat mosaic within a patch. -> T-1084. + +Two design implications: + 1. Sub-biome must be a spatially-varying DISTRIBUTION, not a single tag. "Delta" resolves to a blend {tidal flat, peat bog, river-clay, reed marsh, ...} whose proportions drift across space; the micro-mosaic (T-1084) draws from the LOCAL blend. One label, many local realizations. + 2. The enemy is the visible tile-repeat. "Every km^2 differs" = non-stationarity via multi-octave, domain-warped noise (extend the existing domain_warp "seam cannot align to the chunk edge" property up to the sub-biome tier). The fix for T-1080 is therefore non-repeating multi-octave VARIATION, not merely a smooth gradient (a gradient alone is still locally bland). + +Test-protocol consequence (T-1083): the believability screen must measure NON-STATIONARITY — e.g. that two same-class km^2 are not identical, and that local sub-biome mix drifts — not just class-label diversity.', 'Surfaced by the aliveness probe (server/src/bin/aliveness_probe.rs, branch fillchunk-shell-derive) run on Arbour (GJ338Bd — temperate, ocean hydrosphere) at seed ''yolo'' (u64 15217401773476501448). The deterministic cascade produces 2048 districts; the D-239 derivation LAWS fire correctly, yet the generated surface does not read as a living, real place. Across 6 probes (1 anchored ~150 chunks west of the principal city ''Arbour'' + 5 random districts) the world is uniform wet forest with the sea rendered as dry land. Key insight: this is the ''passes every binding law yet reads dead'' case — the lever is input-field VARIANCE + missing water rendering, not the laws. This epic groups the believability gaps (children) + the repeatable test protocol that found them, as a direction to discuss improvements. Sibling to T-974 (Atlas-to-tile derivation, the laws) and T-1031 (law-correctness harness). + +--- +UNIFYING PRINCIPLE (added during design discussion): multi-scale, never-repeating realization. + +Real-world anchor: the entire Netherlands is ONE macro class (river delta / marshland) — coherent regional identity — yet it realizes as dunes, peat bogs, tidal flats, river-clay, heath, fen, reed marsh, polder, wet woodland, and NO TWO km^2 ARE THE SAME. Macro coherence and local non-stationarity are both required and are not in tension. + +This reframes the epic as a multi-scale model: + - MACRO (region/body): identity — "this is a delta" (coherent). -> D-210 / T-948 (SubBiomeVariant macro tag). + - MESO (~km^2, between AND within districts): the sub-biome DISTRIBUTION drifts — same class, different mix every km^2. -> T-1080 (this is the real shape of that fix). + - MICRO (sub-chunk <64 m): the habitat mosaic within a patch. -> T-1084. + +Two design implications: + 1. Sub-biome must be a spatially-varying DISTRIBUTION, not a single tag. "Delta" resolves to a blend {tidal flat, peat bog, river-clay, reed marsh, ...} whose proportions drift across space; the micro-mosaic (T-1084) draws from the LOCAL blend. One label, many local realizations. + 2. The enemy is the visible tile-repeat. "Every km^2 differs" = non-stationarity via multi-octave, domain-warped noise (extend the existing domain_warp "seam cannot align to the chunk edge" property up to the sub-biome tier). The fix for T-1080 is therefore non-repeating multi-octave VARIATION, not merely a smooth gradient (a gradient alone is still locally bland). + +Test-protocol consequence (T-1083): the believability screen must measure NON-STATIONARITY — e.g. that two same-class km^2 are not identical, and that local sub-biome mix drifts — not just class-label diversity. + +--- +DELIVERABLE / DEFINITION OF DONE (D-245 — the holy grail): this epic is "done" when the cascade''s nature layers pass the believability litmus at RANDOMLY-SAMPLED locations across all habitable bodies and world seeds ("anywhere", not curated spots). Multi-scale + never-repeating: macro identity, meso non-stationarity (no two same-class km^2 alike), micro mosaic. Automated necessary-conditions screen — coherence, non-stationarity, intra-class variety, relief/landmark, climate-appropriateness — under a human narrate-the-natural-history sign-off (necessary != sufficient). Enforced by T-1083; strictness ramps budgeted+advisory -> strict as thresholds calibrate. D-245 (architecture) records this as the acceptance gate for the cascade''s nature half.', NULL, '2026-06-28 05:55:49', '2026-06-28 05:55:49.763', '2026-06-28 05:55:49.763', NULL, '067845dcc8df3a2a4f89ff00d3a40f51', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/ticket_idmap/2026-06.sql b/.pql/changelog/ticket_idmap/2026-06.sql index b381b8470..b9a83a566 100644 --- a/.pql/changelog/ticket_idmap/2026-06.sql +++ b/.pql/changelog/ticket_idmap/2026-06.sql @@ -86,3 +86,9 @@ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCDG73V9R6N024QSFDEJ4R68', 'T-1076', '2026-06-14 15:29:50', '2026-06-14 15:29:50', NULL, '8df24639ed7dc3c6a29bc2019da02f74', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCF56EAJ55PHQ977SV866CFM', 'T-1077', '2026-06-14 19:21:18', '2026-06-14 19:21:18', NULL, '957e1846040da6c5fc25d19db17c20c3', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FCF56ED5ZJZRXFMF3PNSNZF0', 'T-1078', '2026-06-14 19:21:18', '2026-06-14 19:21:18', NULL, 'f1eda12cb9d42f52b353b30dcca81db6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJNP8YM537DJCC82ZD6544', 'T-1079', '2026-06-28 05:53:59.367', '2026-06-28 05:53:59.367', NULL, 'c4838a87c26ee8fdc7d5d3179909943b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJTXTADJRHYEE1WTAENYAG', 'T-1080', '2026-06-28 05:54:42.258', '2026-06-28 05:54:42.258', NULL, '506d3b7112ba9d28b2ac403b6a1ab2a9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJVW30PSP8K0XAWV02X7M0', 'T-1081', '2026-06-28 05:54:50.008', '2026-06-28 05:54:50.008', NULL, '496a38931adff601f5d0295c5dd5d5d6', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJWWZNWYT3C4T828ZF1QCG', 'T-1082', '2026-06-28 05:54:58.429', '2026-06-28 05:54:58.429', NULL, '7ed56526f26196884a01f04f45e99a5b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJZG3WFPQ3VXBFQFM4YEWW', 'T-1083', '2026-06-28 05:55:19.712', '2026-06-28 05:55:19.712', NULL, 'c53c770f47d4f9209d44951e9158a1bb', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); +INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSK19FGZAC3H25VRAV4JRFW', 'T-1084', '2026-06-28 05:55:34.396', '2026-06-28 05:55:34.396', NULL, 'bcc5598f936d9e3845fbe839914126b5', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash); diff --git a/.pql/changelog/ticket_labels/2026-06.sql b/.pql/changelog/ticket_labels/2026-06.sql index d75e73b38..9503f03f4 100644 --- a/.pql/changelog/ticket_labels/2026-06.sql +++ b/.pql/changelog/ticket_labels/2026-06.sql @@ -89,3 +89,4 @@ INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, dele INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRWJYWJKF9KNTBW69VC', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'fe70ef92aebc1538a7a25be743264718', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash); INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRXMXGWS6NJ1FBJQ1GM', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'ff96af73a95cb3ef5c7e54eb1a9af4f7', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash); INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNSRX3R6G3Y7QR4FAJPNM', 'needs-decision', '2026-06-12 10:23:21', '2026-06-12 10:23:21', NULL, 'a6311544e9503dd33805793528fc5baf', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash); +INSERT INTO ticket_labels (ticket_record_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGSJNP8YM537DJCC82ZD6544', 'north-star', '2026-06-28 05:55:55.737', '2026-06-28 05:55:55.737', NULL, '768bb1700ba67614530a5a60bb694b12', 2) ON CONFLICT(ticket_record_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash); diff --git a/.pql/changelog/tickets/2026-06.sql b/.pql/changelog/tickets/2026-06.sql index 3f92e236c..69d846180 100644 --- a/.pql/changelog/tickets/2026-06.sql +++ b/.pql/changelog/tickets/2026-06.sql @@ -3533,3 +3533,46 @@ CURRENT LOCATIONS: derive_basin_direction = chunk_context.rs:453-483 (false comm RESOLUTION (D-239 §8 BINDS the D8 thalweg → flow_accumulation alone is insufficient; use the true fdir): in run_layer1 (where fdir is still alive, before it''s discarded), aggregate the dominant D8 direction PER DISTRICT and carry that small per-district direction map on Layer1Output — which composes directly with T-1044''s Layer1Output caching. Thread it through derive_all_districts → derive_district_profile → DistrictProfile.basin_direction; derive_chunk_context reads district.basin_direction. Fix the false doc comment + module contract either way. Do NOT expose the full 131KB fdir grid on DrainageResult — aggregate to per-district direction in the pass. COMPOSE WITH T-1044 (same batch, same layer1.rs/cascade.rs flow). Acceptance: chunk-scale channels respect the covering district''s D8 thalweg (flow downhill, connect across the chunk/region-seed boundary); false comment corrected; golden-seed determinism preserved.', 'done', 'medium', NULL, 'server', 'D-239', '2026-06-12 10:40:58', '2026-06-17 08:14:43.306', NULL, 'e221e440c3be0db6d0de73a2544dfb9d', 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 ('06FGSJNP8YM537DJCC82ZD6544', 'epic', '06FB0TNSRZXCHGS16BFHSSGSV4', 'World believability — generated terrain reads uniform / incoherent', 'Surfaced by the aliveness probe (server/src/bin/aliveness_probe.rs, branch fillchunk-shell-derive) run on Arbour (GJ338Bd — temperate, ocean hydrosphere) at seed ''yolo'' (u64 15217401773476501448). The deterministic cascade produces 2048 districts; the D-239 derivation LAWS fire correctly, yet the generated surface does not read as a living, real place. Across 6 probes (1 anchored ~150 chunks west of the principal city ''Arbour'' + 5 random districts) the world is uniform wet forest with the sea rendered as dry land. Key insight: this is the ''passes every binding law yet reads dead'' case — the lever is input-field VARIANCE + missing water rendering, not the laws. This epic groups the believability gaps (children) + the repeatable test protocol that found them, as a direction to discuss improvements. Sibling to T-974 (Atlas-to-tile derivation, the laws) and T-1031 (law-correctness harness).', 'backlog', 'high', NULL, 'server', 'D-239', '2026-06-28 05:53:59.367', '2026-06-28 05:53:59.367', NULL, 'c09388c27f79ef43cfe8d9fe9d8fc59c', 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 ('06FGSJTXTADJRHYEE1WTAENYAG', 'story', '06FGSJNP8YM537DJCC82ZD6544', 'District climate fields lack spatial gradient — uniform moisture/precip/vegetation yields monotone terrain', 'Probe evidence (Arbour @ yolo): moisture_q=80, precip=SuperHumid, veg_class=Forest in ~every one of 2048 districts; the voxel layer then derives terrain=Wetland 100% everywhere. The derivation is LAW-CORRECT (flat + wet -> Wetland, D-239 section 8) — the defect is upstream: the district-scale climate/moisture field has near-zero cross-district variance, so there is no gradient to drive ecotones, material variety, or vegetation bands. Without gradient the ''edge-fuzzed climate'' promise is invisible (nothing to fuzz). Direction to discuss: where moisture/precip SHOULD vary (latitude, orography/rain-shadow, continentality, distance-to-water) and how to introduce believable spatial gradient into the district derivation. Believability gap, not a crash.', 'backlog', 'high', NULL, 'server', 'D-239', '2026-06-28 05:54:42.258', '2026-06-28 05:54:42.258', NULL, 'aaddcb91ab512b3ad4c6082e2eac7a7a', 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.', 'backlog', 'high', NULL, 'server', 'D-227', '2026-06-28 05:54:50.008', '2026-06-28 05:54:50.008', NULL, '9f28b4d0b936b41704fe0050ade0c0de', 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 ('06FGSJWWZNWYT3C4T828ZF1QCG', 'story', '06FGSJNP8YM537DJCC82ZD6544', 'No water-body voxel generator — OpenOcean/Lake/TidalFlat render as dry land', 'Probe evidence (Arbour @ yolo, an OCEAN world): OpenOcean districts derive water=Dry 100% with vegetation Forest 62% on Wetland substrate — i.e. the sea is painted as dry forested land. Root cause (voxel.rs zone_to_family): OpenOcean/Lake/TidalFlat/Wetland fall back to the AlluvialPlain (dry-land) generator; there is no water-body generator that lays Water::Deep/Shallow for water zones, sets a sea/lake surface elevation, or forms coastlines. On an ocean world this means the majority of the surface renders wrong. Known incompleteness (the fallback is documented), filed so it has a home. Direction to discuss: a water-body morphology family (ocean / lake / tidal-flat) that sets Deep/Shallow water + substrate + coast, and how sea level (heightmap sea_level threshold) feeds it.', 'backlog', 'high', NULL, 'server', 'D-239', '2026-06-28 05:54:58.429', '2026-06-28 05:54:58.429', NULL, '32335455bf6a4aa3594d9608ffcc36f4', 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 ('06FGSJZG3WFPQ3VXBFQFM4YEWW', 'story', '06FGSJNP8YM537DJCC82ZD6544', 'Repeatable believability test protocol — cross-district contrast + coherence metrics (promote aliveness_probe)', 'Promote the throwaway probe (server/src/bin/aliveness_probe.rs) into a committed, repeatable believability test protocol — the instrument that found T-1080/T-1081/T-1082. For a body+seed it runs the deterministic cascade and emits: (1) cross-district CONTRAST/variance metrics — moisture, elevation/relief, slope, morphology-zone diversity, terrain-material diversity, vegetation gradient — the screen that catches ''uniform/dead'' worlds; (2) COHERENCE checks with pass/fail — water zones must render wet, vegetation must correlate with moisture/water-distance/slope/aspect, drainage monotonic; (3) optional per-layer PNG maps (a stopgap for the T-960 Atlas viewer). LESSON BAKED IN: the first naive ''is it vegetated'' metric reported a broken uniform world as ALIVE — so the protocol MUST measure CONTRAST + COHERENCE across samples, never marginal per-tile counts. Make it runnable per-body, deterministic (string seed -> u64), snapshot a baseline for regression. This is the D-245 ENFORCER. NOTE: today it samples at the DISTRICT tier because no production code maps a world chunk -> covering DistrictProfile yet (voxel layer is a walking skeleton). Distinct from T-1031 (binding-LAW harness); believability sibling. Relates to T-960.', 'backlog', 'high', NULL, 'server', 'D-239', '2026-06-28 05:55:19.712', '2026-06-28 05:55:19.712', NULL, '3e1032301c73c7f67834767c2a12261a', 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 ('06FGSK19FGZAC3H25VRAV4JRFW', 'story', '06FGSJNP8YM537DJCC82ZD6544', 'Intra-class micro-habitat mosaic — seed-scattered sub-features within every terrain type', 'A third believability lever, distinct from T-1080 (macro field gradient) and T-1081 (relief): within a SINGLE terrain class the sub-chunk seed should paint a spatially-coherent mosaic of micro-habitats, generalized to ALL terrain types. Motivating case: the probe''s ''Wetland 100%'' should resolve into clearings, copses, creeks, meadows, bogs, marshes — and likewise for every other class. NOT D-210/T-948: D-210 SubBiomeVariant is a MACRO biome tag per attractor (Tundra vs TemperateForest vs Wetland) — ''which biome''. THIS is finer: the intra-class MICRO-mosaic (''texture within a class''), at sub-chunk (<64 m) scale. MECHANIC: district/biome picks the PALETTE; the sub-chunk seed (SeedDomain::Voxel) paints the MOSAIC, reusing the derive-don''t-store tier (detail_scatter.rs, domain_warp, D-227). Three properties: (1) spatially COHERENT — patches (copse/clearing/bog/meadow) are blobs via low-frequency noise; creeks are lines following micro-drainage; never per-tile salt-and-pepper. (2) causally CONDITIONED on micro-relief/micro-drainage/moisture/slope/aspect (bogs in wettest lows, meadows on drier rises, copses where drainage improves, creeks along micro-thalwegs) — legible, not random. (3) MODULATES the existing D-228 axes, not a new axis. PER-CLASS VOCAB (one mechanism, per-class data): Wetland: clearing/copse/creek/meadow/bog/marsh; Forest: glade/dense stand/deadfall/brook/fern undergrowth; Grassland: tussock/scrub island/wildflower/dry wash; Sand: dune crest/slack/hardpan/oasis/scrub clump; Rock: outcrop/scree/ledge/lichen/talus; Gravel: bar/active channel/vegetated island; Lava: fresh sheet/weathered crust/tube collapse/pioneer scrub. Needs a Q/D-record before implementation. Relates to T-948 (D-210 macro), T-1081 (relief, a conditioning input), T-1083 (protocol must measure intra-class mosaic diversity).', 'backlog', 'high', NULL, 'server', 'D-227', '2026-06-28 05:55:34.396', '2026-06-28 05:55:34.396', NULL, '07753cff879301374f7dd5c6bd343b83', 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 ('06FGSJNP8YM537DJCC82ZD6544', 'epic', '06FB0TNSRZXCHGS16BFHSSGSV4', 'World believability — generated terrain reads uniform / incoherent', 'Surfaced by the aliveness probe (server/src/bin/aliveness_probe.rs, branch fillchunk-shell-derive) run on Arbour (GJ338Bd — temperate, ocean hydrosphere) at seed ''yolo'' (u64 15217401773476501448). The deterministic cascade produces 2048 districts; the D-239 derivation LAWS fire correctly, yet the generated surface does not read as a living, real place. Across 6 probes (1 anchored ~150 chunks west of the principal city ''Arbour'' + 5 random districts) the world is uniform wet forest with the sea rendered as dry land. Key insight: this is the ''passes every binding law yet reads dead'' case — the lever is input-field VARIANCE + missing water rendering, not the laws. This epic groups the believability gaps (children) + the repeatable test protocol that found them, as a direction to discuss improvements. Sibling to T-974 (Atlas-to-tile derivation, the laws) and T-1031 (law-correctness harness). + +--- +UNIFYING PRINCIPLE (added during design discussion): multi-scale, never-repeating realization. + +Real-world anchor: the entire Netherlands is ONE macro class (river delta / marshland) — coherent regional identity — yet it realizes as dunes, peat bogs, tidal flats, river-clay, heath, fen, reed marsh, polder, wet woodland, and NO TWO km^2 ARE THE SAME. Macro coherence and local non-stationarity are both required and are not in tension. + +This reframes the epic as a multi-scale model: + - MACRO (region/body): identity — "this is a delta" (coherent). -> D-210 / T-948 (SubBiomeVariant macro tag). + - MESO (~km^2, between AND within districts): the sub-biome DISTRIBUTION drifts — same class, different mix every km^2. -> T-1080 (this is the real shape of that fix). + - MICRO (sub-chunk <64 m): the habitat mosaic within a patch. -> T-1084. + +Two design implications: + 1. Sub-biome must be a spatially-varying DISTRIBUTION, not a single tag. "Delta" resolves to a blend {tidal flat, peat bog, river-clay, reed marsh, ...} whose proportions drift across space; the micro-mosaic (T-1084) draws from the LOCAL blend. One label, many local realizations. + 2. The enemy is the visible tile-repeat. "Every km^2 differs" = non-stationarity via multi-octave, domain-warped noise (extend the existing domain_warp "seam cannot align to the chunk edge" property up to the sub-biome tier). The fix for T-1080 is therefore non-repeating multi-octave VARIATION, not merely a smooth gradient (a gradient alone is still locally bland). + +Test-protocol consequence (T-1083): the believability screen must measure NON-STATIONARITY — e.g. that two same-class km^2 are not identical, and that local sub-biome mix drifts — not just class-label diversity.', 'backlog', 'high', NULL, 'server', 'D-239', '2026-06-28 05:53:59.367', '2026-06-28 05:55:45.024', NULL, '57f128e1897b024f8dd6700b44f14564', 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 ('06FGSJNP8YM537DJCC82ZD6544', 'epic', '06FB0TNSRZXCHGS16BFHSSGSV4', 'World believability — generated terrain reads uniform / incoherent', 'Surfaced by the aliveness probe (server/src/bin/aliveness_probe.rs, branch fillchunk-shell-derive) run on Arbour (GJ338Bd — temperate, ocean hydrosphere) at seed ''yolo'' (u64 15217401773476501448). The deterministic cascade produces 2048 districts; the D-239 derivation LAWS fire correctly, yet the generated surface does not read as a living, real place. Across 6 probes (1 anchored ~150 chunks west of the principal city ''Arbour'' + 5 random districts) the world is uniform wet forest with the sea rendered as dry land. Key insight: this is the ''passes every binding law yet reads dead'' case — the lever is input-field VARIANCE + missing water rendering, not the laws. This epic groups the believability gaps (children) + the repeatable test protocol that found them, as a direction to discuss improvements. Sibling to T-974 (Atlas-to-tile derivation, the laws) and T-1031 (law-correctness harness). + +--- +UNIFYING PRINCIPLE (added during design discussion): multi-scale, never-repeating realization. + +Real-world anchor: the entire Netherlands is ONE macro class (river delta / marshland) — coherent regional identity — yet it realizes as dunes, peat bogs, tidal flats, river-clay, heath, fen, reed marsh, polder, wet woodland, and NO TWO km^2 ARE THE SAME. Macro coherence and local non-stationarity are both required and are not in tension. + +This reframes the epic as a multi-scale model: + - MACRO (region/body): identity — "this is a delta" (coherent). -> D-210 / T-948 (SubBiomeVariant macro tag). + - MESO (~km^2, between AND within districts): the sub-biome DISTRIBUTION drifts — same class, different mix every km^2. -> T-1080 (this is the real shape of that fix). + - MICRO (sub-chunk <64 m): the habitat mosaic within a patch. -> T-1084. + +Two design implications: + 1. Sub-biome must be a spatially-varying DISTRIBUTION, not a single tag. "Delta" resolves to a blend {tidal flat, peat bog, river-clay, reed marsh, ...} whose proportions drift across space; the micro-mosaic (T-1084) draws from the LOCAL blend. One label, many local realizations. + 2. The enemy is the visible tile-repeat. "Every km^2 differs" = non-stationarity via multi-octave, domain-warped noise (extend the existing domain_warp "seam cannot align to the chunk edge" property up to the sub-biome tier). The fix for T-1080 is therefore non-repeating multi-octave VARIATION, not merely a smooth gradient (a gradient alone is still locally bland). + +Test-protocol consequence (T-1083): the believability screen must measure NON-STATIONARITY — e.g. that two same-class km^2 are not identical, and that local sub-biome mix drifts — not just class-label diversity. + +--- +DELIVERABLE / DEFINITION OF DONE (D-245 — the holy grail): this epic is "done" when the cascade''s nature layers pass the believability litmus at RANDOMLY-SAMPLED locations across all habitable bodies and world seeds ("anywhere", not curated spots). Multi-scale + never-repeating: macro identity, meso non-stationarity (no two same-class km^2 alike), micro mosaic. Automated necessary-conditions screen — coherence, non-stationarity, intra-class variety, relief/landmark, climate-appropriateness — under a human narrate-the-natural-history sign-off (necessary != sufficient). Enforced by T-1083; strictness ramps budgeted+advisory -> strict as thresholds calibrate. D-245 (architecture) records this as the acceptance gate for the cascade''s nature half.', 'backlog', 'high', NULL, 'server', 'D-239', '2026-06-28 05:53:59.367', '2026-06-28 05:55:49.763', NULL, 'f44dd0ce875c225d366064a218753165', 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); diff --git a/governance/README.md b/governance/README.md index 77556b834..704dc4d88 100644 --- a/governance/README.md +++ b/governance/README.md @@ -310,6 +310,7 @@ line in place — keep the Q-record for the audit trail rather than deleting it. - [D-242: Corp HQ settlement model — HQs are not cities; separate from the name pool, specialization-keyed, install-baked](decisions/architecture.md#d-242-corp-hq-settlement-model--hqs-are-not-cities-separate-from-the-name-pool-specialization-keyed-install-baked) — _architecture_ - [D-243: Spatial scale ladder — nested absolute containment with a single elastic planetary seam (resolves Q-110)](decisions/architecture.md#d-243-spatial-scale-ladder--nested-absolute-containment-with-a-single-elastic-planetary-seam-resolves-q-110) — _architecture_ - [D-244: Asset rendering — 3D objects in-world; 2D limited to textures + flat artwork](decisions/architecture.md#d-244-asset-rendering--3d-objects-in-world-2d-limited-to-textures--flat-artwork) — _architecture_ +- [D-245: Nature-layer believability acceptance gate](decisions/architecture.md#d-245-nature-layer-believability-acceptance-gate) — _architecture_ ## Open questions @@ -396,6 +397,7 @@ line in place — keep the Q-record for the audit trail rather than deleting it. - [Q-120: Adopt a garment-token vocabulary with generic fallback parents (the D-235 pattern, applied to clothing)?](questions/content.md#q-120-adopt-a-garment-token-vocabulary-with-generic-fallback-parents-the-d-235-pattern-applied-to-clothing) — _content_ - [Q-121: Where does cultural dress canon live — TOML registers now, prose pages in Phase 6?](questions/content.md#q-121-where-does-cultural-dress-canon-live--toml-registers-now-prose-pages-in-phase-6) — _content_ - [Q-122: Third-party asset sourcing policy — when to use CC0/purchased bases vs Trellis generation, per asset class?](questions/content.md#q-122-third-party-asset-sourcing-policy--when-to-use-cc0purchased-bases-vs-trellis-generation-per-asset-class) — _content_ +- [Q-123: Believability gate threshold calibration](questions/architecture.md#q-123-believability-gate-threshold-calibration) — _architecture_ ## Resolved questions diff --git a/governance/decisions/architecture.md b/governance/decisions/architecture.md index 5c6d30e84..b54cde02a 100644 --- a/governance/decisions/architecture.md +++ b/governance/decisions/architecture.md @@ -1854,4 +1854,20 @@ Technical foundation decisions that constrain implementation: engine, client-ser --- -*101 decisions (D-001 through D-244, excluding gaps). Last updated: 2026-06-17 (D-244 — asset rendering direction: 3D objects in-world (Trellis environment + Quaternius characters via CharacterVisualDescriptor); 2D limited to textures + flat artwork; repurposes the early-spike sprite pipeline as the 2D-artwork generator; builds on D-148/D-149 and reconciles the sprite-era decisions D-043/D-044/D-049/D-066 (all amended); re-scopes T-961/T-1049/T-1050).* +### D-245: Nature-layer believability acceptance gate + +- **Date:** 2026-06-28 +- **Decision:** The cascade's **nature layers** (terrain, hydrology, climate, vegetation, sub-biome — everything that produces the *natural* environment, as distinct from the built layers: settlements/roads/buildings) are **"done" only when the believability litmus passes at randomly-sampled locations across all habitable bodies and world seeds.** The deliverable of the nature half of the cascade is **"the world reads as a living, real place *anywhere*,"** not "the layers are implemented." This is the current **holy-grail objective** for the generation cascade's nature half (the counterpart to "walkable exteriors" for the built half, [D-166](scope.md#d-166)). + - **"Anywhere" = held-out random sampling.** The gate draws *seeded-random* locations across the full surface of *multiple* habitable bodies and *multiple* world seeds — never curated spots. A body passes only when (essentially) all **land** probes pass; water is a separate coherence check. This is what stops "looks alive in the demo, dead everywhere else." + - **The litmus is multi-scale and never-repeating** (the Netherlands principle): one **macro** identity (e.g. "delta") that is **locally non-stationary** — the sub-biome *distribution* drifts every km², and no two same-class km² are identical. Macro coherence and local non-repetition are both required. + - **Automated necessary-conditions** (the screen, per probe): **(1) Coherence** — water zones render wet; vegetation is *conditioned* on moisture/water-distance/slope/aspect (not random); drainage monotonic; no impossible combos. **(2) Non-stationarity** — same-class km² measurably differ; sub-biome mix drifts; no detectable tile-period repeat. **(3) Intra-class variety** — a patch shows ≥K distinct micro-features, never 100% one material. **(4) Relief/landmark** — relief variance above a floor where the macro terrain warrants it. **(5) Climate-appropriateness** — ecology matches the climate envelope; *structured-sparse* passes, *blank* fails. + - **Irreducible human gate** — the automated checks are *necessary, not sufficient*. Final sign-off is a person reading rendered layer-maps of random probes and answering the *narrate-the-natural-history* litmus. Locked by **process** (periodic sampled review + recorded sign-off), not by code. + - **Strictness ramp** — starts **budgeted + advisory** (≥X% of land probes pass + human sign-off) and ratchets to **strict** (every land probe; hard push-gate block) once the enforcer's first baselines calibrate the thresholds — so all of Phase 4 is not blocked on an uncalibrated metric. +- **Rationale:** "Implemented" is a false summit. The aliveness probe ([T-1083](../../.pql)) showed the nature layers can pass **every binding law** ([D-239](#d-239) §8) and still render a **dead, uniform world** (`Wetland 100%`, dry ocean, no gradient). The deliverable that actually matters is *believability*, and the only way to make a gestalt judgment lockable is a random-sample gate where automated necessary-conditions **screen** a human sufficiency **sign-off**. Naming this the acceptance gate turns "feels alive" from an aspiration into a measurable, regressable target. +- **Implementation:** Enforced by **T-1083** (the believability sampling protocol — automated screen + per-layer map export for the human gate; snapshots baselines and runs in the push gate so nature cannot silently rot). Becomes the **definition-of-done** for epic **T-1079** and the needle-movers under it (**T-1080** meso non-stationarity, **T-1081** relief, **T-1082** water-bodies, **T-1084** intra-class micro-mosaic). Seeded by the probe `server/src/bin/aliveness_probe.rs`. Concrete thresholds are **TBD** — calibrated from T-1083's first baselines (a calibration Q-record may follow). +- **Cross-reference:** [D-239](#d-239) (binding laws — *necessary but not sufficient*; this is the sufficiency gate **above** them), [D-227](#d-227) (derive-don't-store — the tier the non-stationary variation lives in), [D-243](#d-243) (the scale ladder the multi-scale litmus spans: macro region / meso district / micro chunk), [D-210](#d-210) (SubBiomeVariant — amended in spirit: sub-biome must be a *spatially-varying distribution*, not a single tag, [T-948](../../.pql)), [D-166](scope.md#d-166) (generation-before-player — this defines "done" for the nature half). Tickets: T-1079 (epic), T-1080/T-1081/T-1082/T-1084 (findings), T-1083 (enforcer). +- **Dissent:** None + +--- + +*102 decisions (D-001 through D-245, excluding gaps). Last updated: 2026-06-28 (D-245 — nature-layer believability acceptance gate: the cascade's nature layers are "done" only when the believability litmus passes at randomly-sampled locations across all habitable bodies/seeds — multi-scale, never-repeating, coherence + non-stationarity + intra-class variety + relief + climate-appropriateness, automated screen under a human sign-off, budgeted→strict; enforced by T-1083, DoD for epic T-1079).* diff --git a/governance/questions/architecture.md b/governance/questions/architecture.md index 694347e04..7b23d4008 100644 --- a/governance/questions/architecture.md +++ b/governance/questions/architecture.md @@ -471,4 +471,18 @@ Technical foundation questions: engine, protocols, data structures, performance, --- -*64 questions (13 resolved, 1 partially resolved, 50 open). Last updated: 2026-06-12.* +### Q-123: Believability gate threshold calibration +- **Status:** Open — filed 2026-06-28 alongside [D-245](../decisions/architecture.md#d-245-nature-layer-believability-acceptance-gate) (the gate names the criteria; this fixes their numbers) +- **Question:** D-245 makes "the nature layers read alive *anywhere*" the acceptance gate, screened by five automated necessary-conditions, but leaves the thresholds **TBD** pending baselines from the T-1083 enforcer. Calibrate, from T-1083's first runs across several habitable bodies × seeds: + 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). + 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). + 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). +- **Cross-reference:** [D-245](../decisions/architecture.md#d-245-nature-layer-believability-acceptance-gate) (the gate this calibrates), [D-239](../decisions/architecture.md#d-239) §8/§10 (laws + budget), [D-227](../decisions/architecture.md#d-227), [D-243](../decisions/architecture.md#d-243); tickets T-1083 (enforcer — produces the baselines), T-1079 (epic), T-1080/T-1081/T-1082/T-1084. + +--- + +*65 questions (13 resolved, 1 partially resolved, 51 open). Last updated: 2026-06-28 (Q-123 — believability gate threshold calibration, alongside D-245).* diff --git a/server/src/bin/aliveness_probe.rs b/server/src/bin/aliveness_probe.rs new file mode 100644 index 000000000..18dacd6be --- /dev/null +++ b/server/src/bin/aliveness_probe.rs @@ -0,0 +1,415 @@ +//! Aliveness probe — sample what the generated world *yields* at locations on a +//! body, to judge whether nature reads as a living, caused place (not just +//! law-correct). Exploratory tooling, not a test. +//! +//! It runs the real deterministic cascade (`run_cascade_from_heightmap`, up to +//! the road graph) for one body + seed, then probes a set of locations and +//! prints, for each, the authoritative per-area nature summary (`DistrictProfile` +//! — real elevation/slope/drainage/morphology/vegetation/climate derived from the +//! committed heightmap) plus a derived `VoxelColumn` sample (terrain / vegetation / +//! water / seasonal-cover / elevation) and a one-line "does it read alive" verdict. +//! +//! ## Why the *district* tier +//! +//! As of this writing the voxel layer (T-1028/T-1029) is a walking skeleton: no +//! production code maps a world chunk → its covering `DistrictProfile` yet (every +//! `derive_voxel_column` caller is a test). The `DistrictProfile` (2 km cell) is +//! therefore the finest *authoritative* nature unit the cascade actually produces, +//! so probes are addressed in district space; the voxel sample inside each is +//! illustrative ground-truth of what that district's 1 m tiles derive to. +//! +//! ```sh +//! cargo run --bin aliveness_probe -- --body GJ338Bd --seed yolo --probes 5 +//! ``` + +use std::collections::BTreeMap; +use std::path::PathBuf; + +use settled_reach_server::atlas::attractor_matching::CityRecord; +use settled_reach_server::atlas::body_params_reader::BodyParamsReader; +use settled_reach_server::atlas::cascade::{run_cascade_from_heightmap, CascadeLayer}; +use settled_reach_server::atlas::chunk_context::derive_chunk_context; +use settled_reach_server::atlas::district_profile::DistrictProfile; +use settled_reach_server::atlas::heightmap::{load_heightmap_png, GRID_H, GRID_W}; +use settled_reach_server::atlas::scale::{ + self, ChunkPos, DistrictPos, CHUNKS_PER_DISTRICT, CHUNK_M, +}; +use settled_reach_server::atlas::voxel::derive_voxel_column; +use settled_reach_server::seed::SeedChain; +use settled_reach_server::simulation::generator::SettlementClass; + +const DEFAULT_SEA_LEVEL: f32 = 0.3; +/// 150 chunks (the original question's offset) in metres = 9 600 m. +const ANCHOR_OFFSET_CHUNKS: i32 = 150; + +fn main() { + let args = Args::parse(std::env::args().skip(1)); + let world_seed = fnv1a64(args.seed.as_bytes()); + + eprintln!( + "aliveness probe — body={} seed=\"{}\" (u64={world_seed}) probes={}", + args.body, args.seed, args.probes + ); + + // ── Inputs (DB-free cascade; we resolve its inputs here) ──────────────── + let db_path = first_existing(&["server/data/systems.db", "data/systems.db"]) + .expect("systems.db not found (run from repo root)"); + let hm_path = find_heightmap(&args.body).unwrap_or_else(|| { + panic!( + "no heightmap.png under wiki/star-systems/*/bodies/{}/", + args.body + ) + }); + + let params = BodyParamsReader::open(&db_path) + .expect("open systems.db") + .read_body_params(&args.body) + .expect("read body params"); + let cities = read_cities(&db_path, &args.body); + eprintln!( + "loaded heightmap {} | planet_class={:?} hydrosphere={:?} | {} settlements", + hm_path.display(), + params.planet_class, + params.hydrosphere, + cities.len() + ); + + // ── Run the real cascade through the road graph ───────────────────────── + let hm = load_heightmap_png(&hm_path, &args.body, DEFAULT_SEA_LEVEL).expect("load heightmap"); + let working = if hm.width > GRID_W || hm.height > GRID_H { + hm.downsample(GRID_W, GRID_H) + } else { + hm + }; + let body_seed = SeedChain::for_body(world_seed, &args.body); + let snapshot = run_cascade_from_heightmap( + body_seed, + working, + &cities, + None, + Some(¶ms), + CascadeLayer::RoadGraph, + ); + let bws = snapshot.into_body_world_state(); + + let districts = &bws.districts; + if districts.is_empty() { + eprintln!("cascade produced no districts — cannot probe (body params missing?). Aborting."); + std::process::exit(1); + } + let keys: Vec = districts.keys().copied().collect(); + eprintln!( + "cascade: {} districts, {} settlement placements", + districts.len(), + bws.placements.len() + ); + + // ── Principal city (ranked by placement score; per-city pop is 0 in DB) ── + let name_of: BTreeMap = cities + .iter() + .map(|c| (c.city_id, c.name.as_str())) + .collect(); + let principal = bws.placements.iter().max_by_key(|p| p.score); + println!( + "\n================ {} (seed \"{}\") ================", + args.body, args.seed + ); + let anchor_district = match principal { + Some(p) => { + let dp = scale::heightmap_pixel_to_district(p.position); + println!( + "principal settlement: {} @ pixel {:?} → district {:?} (ranked by placement score {}, since per-city population is 0 in the DB)", + name_of.get(&p.city_id).copied().unwrap_or(""), + p.position, + dp, + p.score + ); + // "≈150 chunks west" = 9 600 m west = ~4.7 districts. West = −x. + let west_districts = (ANCHOR_OFFSET_CHUNKS * CHUNK_M) / scale::DISTRICT_M; // = 4 + Some((dp.0 - west_districts.max(1) - 1, dp.1)) // round 4.7 → 5 west + } + None => { + println!("no settlement placements — skipping the anchored probe."); + None + } + }; + + // ── The anchored probe (the location originally asked about) ──────────── + if let Some(adp) = anchor_district { + println!( + "\n---- ANCHORED PROBE: ≈{ANCHOR_OFFSET_CHUNKS} chunks (9.6 km) west of the principal city ----" + ); + match nearest_present(districts, adp) { + Some((dp, prof)) => probe( + &args.body, + world_seed, + dp, + prof, + if dp == adp { + "" + } else { + "(nearest land district to the target point — target itself is off-map / open ocean)" + }, + ), + None => println!(" target {adp:?} and neighbours are off-map; nothing to sample."), + } + } + + // ── N random probes (deterministic from the seed) ─────────────────────── + println!("\n---- {} RANDOM PROBES ----", args.probes); + for i in 0..args.probes { + let pick = (fnv1a64(format!("{}/{i}", args.seed).as_bytes()) % keys.len() as u64) as usize; + let dp = keys[pick]; + probe(&args.body, world_seed, dp, &districts[&dp], ""); + } + + println!("\n(district tier — voxel addressing is not production-wired yet; voxel rows are illustrative ground-truth derived for a representative chunk of each district.)"); +} + +/// Probe one district: print its profile + a derived voxel-column sample + verdict. +fn probe(body: &str, world_seed: u64, dp: DistrictPos, prof: &DistrictProfile, note: &str) { + // Representative chunk at the district centre (32 chunks / district). + let chunk: ChunkPos = ( + dp.0 * CHUNKS_PER_DISTRICT + CHUNKS_PER_DISTRICT / 2, + dp.1 * CHUNKS_PER_DISTRICT + CHUNKS_PER_DISTRICT / 2, + ); + let ctx = derive_chunk_context(world_seed, body, prof, chunk, None); + + // Sample a 16×16 grid over the 64 m chunk (every 4 m). + let mut terrain: BTreeMap = BTreeMap::new(); + let mut veg: BTreeMap = BTreeMap::new(); + let mut water: BTreeMap = BTreeMap::new(); + let mut cover: BTreeMap = BTreeMap::new(); + let (mut elev_min, mut elev_max, mut elev_sum, mut n) = (i32::MAX, i32::MIN, 0i64, 0i64); + for sx in 0..16 { + for sy in 0..16 { + let tx = chunk.0 * CHUNK_M + sx * 4; + let ty = chunk.1 * CHUNK_M + sy * 4; + let col = derive_voxel_column(world_seed, body, prof, &ctx, tx, ty); + *terrain.entry(format!("{:?}", col.terrain)).or_default() += 1; + *veg.entry(format!("{:?}", col.vegetation)).or_default() += 1; + *water.entry(format!("{:?}", col.water)).or_default() += 1; + *cover.entry(format!("{:?}", col.cover)).or_default() += 1; + elev_min = elev_min.min(col.elevation_m); + elev_max = elev_max.max(col.elevation_m); + elev_sum += col.elevation_m as i64; + n += 1; + } + } + + println!("\n• district {dp:?} {note}"); + println!( + " zone={:?} elev_q={} slope_q={} ocean%q={} moisture_q={} temp={} precip={:?} veg_class={:?} glaciation={:?} basin={:?}", + prof.morphology_zone, + prof.elev_q, + prof.slope_q, + prof.ocean_fraction_q, + prof.moisture_q, + prof.temperature_c.map(|t| format!("{t:.1}°C")).unwrap_or_else(|| "n/a".into()), + prof.precipitation_class, + prof.vegetation_class, + prof.glaciation_grade, + prof.basin_direction, + ); + println!( + " channel: {} width={} m elevation: {}–{} m (mean {})", + if ctx.has_active_channel { + "ACTIVE river/stream" + } else { + "none" + }, + ctx.channel_width_m, + elev_min, + elev_max, + elev_sum / n.max(1), + ); + println!(" terrain: {}", fmt_hist(&terrain, n)); + println!(" vegetation: {}", fmt_hist(&veg, n)); + println!( + " water: {} cover: {}", + fmt_hist(&water, n), + fmt_hist(&cover, n) + ); + println!( + " → {}", + verdict(&ctx_summary(&veg, &water, &cover, n), elev_max - elev_min) + ); +} + +struct Summary { + veg_alive_pct: u32, + water_pct: u32, + frozen_pct: u32, +} + +fn ctx_summary( + veg: &BTreeMap, + water: &BTreeMap, + cover: &BTreeMap, + n: i64, +) -> Summary { + let n = n.max(1) as u32; + let barren = veg.get("Barren").copied().unwrap_or(0); + let dry = water.get("Dry").copied().unwrap_or(0); + let none_cover = cover.get("None").copied().unwrap_or(0); + Summary { + veg_alive_pct: 100 * (n - barren) / n, + water_pct: 100 * (n - dry) / n, + frozen_pct: 100 * (n - none_cover) / n, + } +} + +/// A blunt heuristic "does this read as living nature" line. Not a metric of +/// record — a prompt for the eye. +fn verdict(s: &Summary, relief_m: i32) -> String { + let mut signals: Vec = Vec::new(); + if s.veg_alive_pct >= 50 { + signals.push(format!("vegetated ({}%)", s.veg_alive_pct)); + } else if s.veg_alive_pct > 0 { + signals.push(format!("sparse life ({}%)", s.veg_alive_pct)); + } else { + signals.push("barren".into()); + } + if s.water_pct > 0 { + signals.push(format!("water present ({}%)", s.water_pct)); + } + if s.frozen_pct >= 50 { + signals.push(format!("snow/ice {}%", s.frozen_pct)); + } + if relief_m >= 20 { + signals.push(format!("relief {relief_m} m (hills/landmarks)")); + } else if relief_m <= 2 { + signals.push("flat".into()); + } + let alive = s.veg_alive_pct >= 30 || s.water_pct >= 20 || relief_m >= 20; + format!( + "{} — {}", + if alive { + "reads ALIVE" + } else { + "reads thin/dead" + }, + signals.join(", ") + ) +} + +fn fmt_hist(h: &BTreeMap, n: i64) -> String { + let n = n.max(1); + let mut v: Vec<(&String, &u32)> = h.iter().collect(); + v.sort_by(|a, b| b.1.cmp(a.1)); + v.iter() + .map(|(k, c)| format!("{} {}%", k, 100 * **c as i64 / n)) + .collect::>() + .join(", ") +} + +/// Find the district at `dp`, else the nearest present district within a small ring. +fn nearest_present( + districts: &BTreeMap, + dp: DistrictPos, +) -> Option<(DistrictPos, &DistrictProfile)> { + if let Some(p) = districts.get(&dp) { + return Some((dp, p)); + } + for r in 1..=8 { + for dx in -r..=r { + for dy in -r..=r { + let q = (dp.0 + dx, dp.1 + dy); + if let Some(p) = districts.get(&q) { + return Some((q, p)); + } + } + } + } + None +} + +// --------------------------------------------------------------------------- +// Inputs +// --------------------------------------------------------------------------- + +fn read_cities(db: &PathBuf, body: &str) -> Vec { + let conn = rusqlite::Connection::open(db).expect("open db for cities"); + let mut stmt = conn + .prepare( + "SELECT id, name, COALESCE(economic_role,'service_mixed'), COALESCE(population,0) + FROM atlas_city_names WHERE body_id = ?1 ORDER BY id", + ) + .expect("prepare city query"); + let rows = stmt + .query_map([body], |r| { + Ok(CityRecord { + city_id: r.get::<_, i64>(0)? as u64, + name: r.get(1)?, + // Per-city settlement_class is unset in the DB pool; the budget + // class is the default the read-set assumes (city_context_reader). + settlement_class: SettlementClass::PopulationBudget, + economic_role: r.get(2)?, + population: r.get(3)?, + }) + }) + .expect("city query") + .filter_map(Result::ok) + .collect(); + rows +} + +/// Glob `wiki/star-systems/*/bodies//heightmap.png`. +fn find_heightmap(body: &str) -> Option { + for base in ["wiki/star-systems", "../wiki/star-systems"] { + let root = PathBuf::from(base); + let Ok(systems) = std::fs::read_dir(&root) else { + continue; + }; + for sys in systems.flatten() { + let cand = sys.path().join("bodies").join(body).join("heightmap.png"); + if cand.is_file() { + return Some(cand); + } + } + } + None +} + +fn first_existing(paths: &[&str]) -> Option { + paths.iter().map(PathBuf::from).find(|p| p.is_file()) +} + +// --------------------------------------------------------------------------- +// Args + hashing +// --------------------------------------------------------------------------- + +struct Args { + body: String, + seed: String, + probes: u32, +} + +impl Args { + fn parse(args: impl Iterator) -> Self { + let mut body = "GJ338Bd".to_string(); + let mut seed = "yolo".to_string(); + let mut probes = 5u32; + let mut it = args.peekable(); + while let Some(a) = it.next() { + match a.as_str() { + "--body" => body = it.next().unwrap_or(body), + "--seed" => seed = it.next().unwrap_or(seed), + "--probes" => probes = it.next().and_then(|s| s.parse().ok()).unwrap_or(probes), + _ => {} + } + } + Args { body, seed, probes } + } +} + +/// FNV-1a 64-bit — a stable, dependency-free string→u64 so `--seed yolo` maps to +/// a deterministic world seed (reproducible across runs, unlike std's RandomState). +fn fnv1a64(bytes: &[u8]) -> u64 { + let mut h: u64 = 0xcbf2_9ce4_8422_2325; + for &b in bytes { + h ^= b as u64; + h = h.wrapping_mul(0x0000_0100_0000_01b3); + } + h +}