docs(architecture): D-240 — registry orbit/star non-canonical; class-envelope climate (T-1033)

Record D-240: a body's orbital position and stellar assignment are placeholder
registry data with no authored physical intent, so no derivation may use them as
physical inputs. Climate derives from authored planet_class (temperature envelope)
+ atmosphere + hydrosphere + seed. Supersedes the sun-driven Stefan-Boltzmann
portion of D-239 §2, which produced +356 C 'temperate' worlds (NULL spectral_class
-> G-Sun default on an M-dwarf catalog).

File T-1033 under T-974: rewrite derive_temperature_c to the class-envelope model,
drop the orbit/star inputs, regen the T-1031 golden. Audit confirmed the blast
radius is one function (derive_temperature_c).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 18:06:49 +02:00
co-authored by Claude Opus 4.8
parent f519dc6e76
commit bf7da1105d
4 changed files with 53 additions and 1 deletions
+19
View File
@@ -184,3 +184,22 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by,
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1030', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 14:59:47', '2026-06-08 14:59:47', '2026-06-08 14:59:47', NULL, 'fb4b7f7f6a68990a7e917eb3078c7054', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1030', 'status', 'in_progress', 'review', NULL, '2026-06-08 15:16:48', '2026-06-08 15:16:48', '2026-06-08 15:16:48', NULL, 'c8cc30e7389c1a90e006fd35ccc435d5', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1030', 'status', 'review', 'done', NULL, '2026-06-08 15:34:04', '2026-06-08 15:34:04', '2026-06-08 15:34:04', NULL, '46641ba2f664fe87e24237814c56afff', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1031', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 15:34:33', '2026-06-08 15:34:33', '2026-06-08 15:34:33', NULL, '61cceb69a98c51899481bc3fed3b0cb0', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1033', 'description', 'Rewrite derive_temperature_c (region_profile.rs) to the D-240 planet_class temperature-envelope model: drop ALL orbit/star physics (derive_distance_au, the luminosity() lookup + [star_luminosity] table, orbital_period_days, spectral_class, star_type as inputs). Temperature base = planet_class envelope (source-canonical in climate_constants.toml); latitude maps across it (pole=cold end, equator=warm end); atmosphere/hydrosphere/elevation modulate within bounds; the body seed adds a small bounded nudge for variety. Strip the now-dead orbit/star fields from BodyParams + BodyParamsReader + the bodies-table read (audit 2026-06-08 confirmed derive_temperature_c is the only consumer). Then regenerate the T-1031 golden so it pins correct temps. Blocks T-974 completion.', 'Rewrite derive_temperature_c (region_profile.rs) to the D-240 planet_class temperature-envelope model: drop ALL orbit/star physics (derive_distance_au, the luminosity() lookup + [star_luminosity] table, orbital_period_days, spectral_class, star_type as inputs). Temperature base = planet_class envelope (source-canonical in climate_constants.toml); latitude maps across it (pole=cold end, equator=warm end); atmosphere/hydrosphere/elevation modulate within bounds; the body seed adds a small bounded nudge for variety. Strip the now-dead orbit/star fields from BodyParams + BodyParamsReader + the bodies-table read (audit 2026-06-08 confirmed derive_temperature_c is the only consumer). Then regenerate the T-1031 golden so it pins correct temps. Blocks T-974 completion.
Approved first-pass planet_class temperature envelope (°C, pole end → equator end). Tunable in server/data/climate_constants.toml. Parse cold_*/hot_*/warm_* prefixes to shift the band; sane default for unknown classes.
| planet_class | band (pole → equator) |
|---|---|
| frozen / ice | -90 → -25 |
| boreal | -35 → 12 |
| cold_arid | -40 → 20 |
| temperate / oceanic | -12 → 28 |
| subtropical / warm_ocean | 2 → 34 |
| tropical | 16 → 40 |
| arid | -5 → 45 |
| hot_arid | 20 → 58 |
| volcanic / geothermal | 30 → 90 (special-cased) |
| barren (airless / atmosphere=none) | none (climate branch absent, per D-239 §2) |
Latitude maps the region linearly across the band (|lat|/90). Atmosphere greenhouse shifts toward the warm end + sets diurnal swing; elevation lapse pulls highlands toward the cold end; clamp to the band. Seed nudge: a few °C of deterministic per-body variation, clamped within the band.', NULL, '2026-06-08 16:06:37', '2026-06-08 16:06:37', '2026-06-08 16:06:37', NULL, 'cf12b3fc266a7fce22de0fdbfdb5c27e', 1) ON CONFLICT(hash) DO NOTHING;
+23
View File
@@ -552,3 +552,26 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority,
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1030', 'task', 'T-974', 'Scattered transient freeze/snow model', 'D-239 section 3. Phase transition as a band with deterministic spatially-coherent scatter (clustered patches, never per-tile dice): freshwater (lakes/rivers) +5 to -10 C district mean (the +5 is night-frost reaching the coldest tiles); sea ice own lower/wider band (~-2 onset, pack-ice sheets+leads pattern); snow (land) moisture-gated. TRANSIENT in the marginal band — forms in the cold phase, melts in the warm phase (day/night, season) -> time-of-day passability shifts. The transient/clock-bound portion is a forward contract to Q-105 (region seasonal/clock state); the static scattered model is buildable now. Blocked by climate-derived fields.', 'in_progress', 'medium', NULL, 'server', 'D-239', '2026-06-07 10:34:11', '2026-06-08 14:59:47', NULL, 'eb3e8c2431e2500a0a9a135881a4596f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1030', 'task', 'T-974', 'Scattered transient freeze/snow model', 'D-239 section 3. Phase transition as a band with deterministic spatially-coherent scatter (clustered patches, never per-tile dice): freshwater (lakes/rivers) +5 to -10 C district mean (the +5 is night-frost reaching the coldest tiles); sea ice own lower/wider band (~-2 onset, pack-ice sheets+leads pattern); snow (land) moisture-gated. TRANSIENT in the marginal band — forms in the cold phase, melts in the warm phase (day/night, season) -> time-of-day passability shifts. The transient/clock-bound portion is a forward contract to Q-105 (region seasonal/clock state); the static scattered model is buildable now. Blocked by climate-derived fields.', 'review', 'medium', NULL, 'server', 'D-239', '2026-06-07 10:34:11', '2026-06-08 15:16:48', NULL, 'bdb3b3f21bebba5abb885195d42a3c19', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1030', 'task', 'T-974', 'Scattered transient freeze/snow model', 'D-239 section 3. Phase transition as a band with deterministic spatially-coherent scatter (clustered patches, never per-tile dice): freshwater (lakes/rivers) +5 to -10 C district mean (the +5 is night-frost reaching the coldest tiles); sea ice own lower/wider band (~-2 onset, pack-ice sheets+leads pattern); snow (land) moisture-gated. TRANSIENT in the marginal band — forms in the cold phase, melts in the warm phase (day/night, season) -> time-of-day passability shifts. The transient/clock-bound portion is a forward contract to Q-105 (region seasonal/clock state); the static scattered model is buildable now. Blocked by climate-derived fields.', 'done', 'medium', NULL, 'server', 'D-239', '2026-06-07 10:34:11', '2026-06-08 15:34:04', NULL, '058f876ba9c10de81574529ab7adb748', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1031', 'task', 'T-974', 'Believability + determinism + <5ms budget verification harness', 'D-239 sections 1,8,9. Golden-seed regression for the binding laws (drainage monotonicity, lithology->landform, glaciation->form with no glacial-U on GlaciationGrade 0, climate->vegetation no-skip + riparian band), per-family <5ms/chunk budget assertion, and the validation-case bodies: Kallast=alluvial plain/Soil, Velen=tidal-flat/dune coast (VERIFY it has a moon param for the tidal term first), Cygni B=volcanic immature drainage, Ross 128=ocean island terrain, Gruenfeld=marginal Gravel/Scrub. If a body reads wrong, fix its params — never patch the derivation (D-239 section 1). Blocked by the family generators.
---
Note from T-1028 review (Tyre, 2026-06-08): VoxelCache LRU eviction is an O(capacity) linear scan for the minimum access_gen (default cap 8192). Latent — at the D-239 §10 ~2.2-4.2 ms/chunk budget (4096 voxels) an 8k scan is measurable noise, not a blocker. If it shows up in this ticket''s profiling, the fix is a secondary BTreeMap<(access_gen, CacheKey), ()> eviction index (stays D-010-compliant). Scope before the budget test runs.', 'in_progress', 'high', NULL, 'server', 'D-239', '2026-06-07 10:34:11', '2026-06-08 15:34:33', NULL, '721cea579cbb83d01fdd4184c6793933', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1033', 'story', 'T-974', 'Class-envelope temperature model — drop non-canonical orbit/star inputs (D-240)', 'Rewrite derive_temperature_c (region_profile.rs) to the D-240 planet_class temperature-envelope model: drop ALL orbit/star physics (derive_distance_au, the luminosity() lookup + [star_luminosity] table, orbital_period_days, spectral_class, star_type as inputs). Temperature base = planet_class envelope (source-canonical in climate_constants.toml); latitude maps across it (pole=cold end, equator=warm end); atmosphere/hydrosphere/elevation modulate within bounds; the body seed adds a small bounded nudge for variety. Strip the now-dead orbit/star fields from BodyParams + BodyParamsReader + the bodies-table read (audit 2026-06-08 confirmed derive_temperature_c is the only consumer). Then regenerate the T-1031 golden so it pins correct temps. Blocks T-974 completion.', 'backlog', 'high', NULL, 'server', 'D-240', '2026-06-08 16:06:13', '2026-06-08 16:06:13', NULL, 'f7e8f9ee6165f2ab5ecbbcddfa2cb871', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1033', 'story', 'T-974', 'Class-envelope temperature model — drop non-canonical orbit/star inputs (D-240)', 'Rewrite derive_temperature_c (region_profile.rs) to the D-240 planet_class temperature-envelope model: drop ALL orbit/star physics (derive_distance_au, the luminosity() lookup + [star_luminosity] table, orbital_period_days, spectral_class, star_type as inputs). Temperature base = planet_class envelope (source-canonical in climate_constants.toml); latitude maps across it (pole=cold end, equator=warm end); atmosphere/hydrosphere/elevation modulate within bounds; the body seed adds a small bounded nudge for variety. Strip the now-dead orbit/star fields from BodyParams + BodyParamsReader + the bodies-table read (audit 2026-06-08 confirmed derive_temperature_c is the only consumer). Then regenerate the T-1031 golden so it pins correct temps. Blocks T-974 completion.
Approved first-pass planet_class temperature envelope (°C, pole end → equator end). Tunable in server/data/climate_constants.toml. Parse cold_*/hot_*/warm_* prefixes to shift the band; sane default for unknown classes.
| planet_class | band (pole → equator) |
|---|---|
| frozen / ice | -90 → -25 |
| boreal | -35 → 12 |
| cold_arid | -40 → 20 |
| temperate / oceanic | -12 → 28 |
| subtropical / warm_ocean | 2 → 34 |
| tropical | 16 → 40 |
| arid | -5 → 45 |
| hot_arid | 20 → 58 |
| volcanic / geothermal | 30 → 90 (special-cased) |
| barren (airless / atmosphere=none) | none (climate branch absent, per D-239 §2) |
Latitude maps the region linearly across the band (|lat|/90). Atmosphere greenhouse shifts toward the warm end + sets diurnal swing; elevation lapse pulls highlands toward the cold end; clamp to the band. Seed nudge: a few °C of deterministic per-body variation, clamped within the band.', 'backlog', 'high', NULL, 'server', 'D-240', '2026-06-08 16:06:13', '2026-06-08 16:06:37', NULL, '366cfc67a53bea69db80181326a488e3', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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
@@ -305,6 +305,7 @@ line in place — keep the Q-record for the audit trail rather than deleting it.
- [D-237: Authored per-system specialisation layer — `economic_specialization` + `cultural_specialization` + `dominant_faction`](decisions/architecture.md#d-237-authored-per-system-specialisation-layer--economic-specialization--cultural-specialization--dominant-faction) — _architecture_
- [D-238: Symmetric shadowcasting (Albert Ford) selected for LOS computation](decisions/perception.md#d-238-symmetric-shadowcasting-albert-ford-selected-for-los-computation) — _perception_
- [D-239: Tile derivation contract — coarse→fine refinement chain (resolves Q-101)](decisions/architecture.md#d-239-tile-derivation-contract--coarsefine-refinement-chain-resolves-q-101) — _architecture_
- [D-240: Registry orbit/star data is non-canonical — climate derives from a `planet_class` temperature envelope](decisions/architecture.md#d-240-registry-orbitstar-data-is-non-canonical--climate-derives-from-a-planet-class-temperature-envelope) — _architecture_
## Open questions
+10 -1
View File
@@ -1776,4 +1776,13 @@ Technical foundation decisions that constrain implementation: engine, client-ser
---
*97 decisions (D-001 through D-239, excluding gaps). Last updated: 2026-06-07 (tile-derivation-contract workshop — D-239 tile derivation contract resolving Q-101: three-carrier refinement chain, district temperature primitive + scattered transient freeze/snow model, 8 morphology families + frozen 17-zone vocabulary, prevent-at-source seams).*
### D-240: Registry orbit/star data is non-canonical — climate derives from a `planet_class` temperature envelope
- **Decision:** A body's registry **orbital position and stellar assignment** (`orbital_period_days`, `spectral_class`, `star_type`, and any derived `distance_au`/luminosity) are **non-canonical placeholder data** — bodies were dropped into orbit and stars taken as they sat in the registry, with no authored physical intent. **No derivation layer may consume them as physical inputs.** Climate derives **only** from the authored, intent-bearing fields: `planet_class` (sets the temperature envelope), `atmosphere`, `hydrosphere`, plus the intrinsic latitude/elevation of the generated surface — with per-world variety from the deterministic body **seed** ([D-010](#d-010)). Temperature is a **`planet_class` → (min, max) °C envelope** (source-canonical in `server/data/climate_constants.toml`, tunable): latitude maps the region across the envelope (pole → cold end, equator → warm end); atmosphere / hydrosphere / elevation modulate **within** the bounds; the seed adds a small bounded nudge for inter-world character. A body can never derive outside its class band by construction. This **supersedes the sun-driven (Stefan-Boltzmann luminosity + distance) portion of [D-239](#d-239) §2**.
- **Rationale:** Deriving physics from arbitrary registry values dresses placeholder data as authoritative — *worse* than an obvious error, because it looks real. The sun-driven path produced physically impossible results (a `temperate`/liquid-water world deriving to **+356 °C**, because a NULL `spectral_class` defaulted to a G-type Sun on an M-dwarf-dominated Gliese catalog — a 25× luminosity error). The 2026-06-08 audit found the blast radius is exactly one function (`derive_temperature_c`); every other reference merely plumbs the columns, so once temperature is re-based the orbit/star columns can be dropped from the derivation inputs entirely. Anchoring to `planet_class` — which already encodes the authored intent (`frozen`, `tropical`, `hot_arid`, …) — keeps every world inside its band and makes the T-1031 believability harness's class-consistency check true by construction.
- **Raised by:** Jeroen (2026-06-08), reviewing T-1024 climate output across 25 non-Kallast bodies.
- **Cross-reference:** [D-239](#d-239) §2 (temperature primitive — the sun-driven portion is superseded here), [D-010](#d-010) (determinism — seed-based variety), [D-227](#d-227) (derive-don't-store), [D-228](#d-228) (tile axes).
---
*98 decisions (D-001 through D-240, excluding gaps). Last updated: 2026-06-08 (climate-canonicity correction — D-240 declares registry orbit/star data non-canonical; temperature flips from sun-driven [D-239 §2] to a `planet_class` envelope, superseding the Stefan-Boltzmann path that produced +356 °C "temperate" worlds).*