chore(meta): T-1295 done — close-out note on the registry and staleness gate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 15:56:17 +02:00
co-authored by Claude Opus 5.5
parent ecae65053e
commit db831117c8
2 changed files with 204 additions and 0 deletions
+102
View File
@@ -1426,3 +1426,105 @@ RE-SHAPED 2026-09-25 (D-258 amendment "ids, not fields"). No per-pixel temperatu
SCOPE ADDED 2026-09-25 (moved from T-1295): the class tables. Author a biome table and a terrain table as TOML. The source today is tooling/domains/atlas/planet/biomes.toml, and whether it moves into wiki/ is still open. Import them into systems.db via reach ledger import, under the generator stamp: register the TOML in tooling/generator_sources.py, add the DDL to systems-schema.sql and MIGRATION_SQL. Columns are whatever this ticket''s climate model needs: at minimum the biome climate envelope (from the Whittaker boxes, whose ids 9, 11, 16 and 17 have two boxes each). Terrain classes carry no climate.
Layer and id facts from T-1295: 255 means none on both maps; ids are literal biomes.toml ids; and layer is decided per rule, so class 17 (ice) appears in BOTH tables.', NULL, '2026-09-25 13:08:43', '2026-09-25 13:08:43.221', '2026-09-25 13:08:43.221', NULL, '58e03d1b8272be23f3fa899120aab7d9', 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 ('06GDGD2G9K7C8SPH57RCNB8V8C', 'status', 'in_progress', 'done', NULL, '2026-09-25 13:56:06', '2026-09-25 13:56:06.842', '2026-09-25 13:56:06.842', NULL, '4d0872d669f5e28321b27b20a4a3b2c2', 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 ('06GDGD2G9K7C8SPH57RCNB8V8C', 'description', 'D-258 amendment 2026-09-25, rulings 1-4. Bake, per body, the Python planet simulator''s biome class grid TOGETHER WITH the absolute temperature (kelvin, before the display normalisation in simulate()) and moisture [0,1] it was classified from. One model supplies all three, so they agree by construction.
PRE-SEED: simulate() is keyed on the body frontmatter''s seed only. The world seed must never enter this bake. Worlds stay recognisable across playthroughs; the ground varies server-side.
MEASURE FIRST, then choose the format. The server works at 512x256 (GRID_W/GRID_H downsample), so a 1024x512 bake may be twice the resolution the server can use on each axis. Temperature and moisture are smooth fields that compress far worse than a class grid. Across ~2,600 bodies, the repository cost of candidate formats must be measured: 8-bit class PNG, 16-bit temperature, 8-bit moisture, at 1024x512 and at 512x256. Report the numbers before committing a bake.
DO NOT re-render reliefmap.png. Its render drifts by a few hundred bytes (T-1291). The bake gets its own mode, a verb on reach atlas planet, that writes only the new file(s). The heightmap is byte-identical on re-simulation, so the simulator is deterministic.
Class ids become append-only (the gas-palette selection_order discipline in biomes.toml). Gas giants get nothing. Whatever the chosen files are, register them where the atlas and canvas registries expect: the canvas-version gate (tooling/canvas_sources.py) must see a change to them.
MEASURED 2026-09-25. Probe: simulate() on 24 bodies spaced evenly across the in-scope set, each field encoded as optimised PNG. The probe was deleted after the run.
SCOPE (Jeroen, 2026-09-25): airless bodies are out of scope for now. The format and class vocabulary must still have room for them, since classes 19-36 already cover lava, dust, ferric and lunar ground. The heightmap bake set is 267 bodies. By atmosphere: standard 231, thin 23, thick 1, reducing 1, trace 1, none 8, NULL 2. That leaves 257 bodies with an atmosphere in scope.
PER BODY (mean / max) AND TOTAL OVER 257 BODIES:
biome u8, 1024x512 11.7 / 16.2 KiB 2.9 MiB
biome u8, 512 (plurality) 5.4 / 7.5 KiB 1.4 MiB
temp u16 0.01 K, 1024 80.8 / 352 KiB 20.3 MiB
temp u16 0.01 K, 512 29.0 / 124 KiB 7.3 MiB
temp u8 1 K, 1024 14.8 / 54 KiB 3.7 MiB
temp u8 1 K, 512 6.1 / 22 KiB 1.5 MiB
moist u8, 1024 57.2 / 80 KiB 14.4 MiB
moist u8, 512 22.8 / 32 KiB 5.7 MiB
packed RGB (biome,t8,m8) 1024 96.7 / 172 KiB 24.3 MiB (worse than the three separate files at the same precision: 83.7 KiB)
REFERENCE: the committed heightmap.png averages 678 KiB per in-scope body, and reliefmap.png 295 KiB. Together that is about 245 MiB already in the tree.
Bake time: simulate() takes about 6.3 s per body, so the full bake is about 27 min.
FINDING: the simulator''s temperature is often clamped flat. temperature_bands in biomes.toml clamps each planet class ("fiction wins over physics"). On most temperate bodies that pins the field to exactly the band, 275-305 K. On some bodies it pins it to one value: GJ204c and GJ68e are 275 K everywhere, and GJ601Ad (275-281) and GJ722c (275-280) are nearly flat. On those bodies the biome variety comes from moisture alone.
RE-SHAPED 2026-09-25 (D-258 amendment "ids, not fields"). This supersedes the three-field bake above.
Bake two u8 id maps per in-scope body (257 bodies with an atmosphere) at 1024x512:
- biomemap.png: the Whittaker class, taken BEFORE compute_biome''s terrain overrides. It covers every land pixel and is empty on thin-atmosphere bodies.
- terrainmap.png: the terrain override class, 0 where there is none.
Per-pixel temperature and moisture are NOT baked.
Also in scope here: the class tables (biome: climate envelope, flora, cover; terrain: substrate, landform hints), authored as TOML and imported into systems.db by reach ledger import under the generator stamp.
Done so far: the mountain-rock units fix in compute_biome, with tooling/test_planet_biome_rules.py (in make test-tooling). The rule compared kelvin to 0.35, so it could never fire; it now uses the world''s own normalised temperature. The test is mutation-proven. Sulfuric scrub is unreachable (it converts rock at elev_norm 0.25-0.65, but rock exists only above 0.65); it is left as found, since fixing it is a design call.
Open: story features (the anomaly scatter, ~3% of land). Their placement and rate are for a balancing pass.
SCOPE MOVE 2026-09-25: the class tables (biome and terrain, TOML imported into systems.db) move to T-1297, their first consumer. Nothing in this ticket reads them, and their columns should be set by what the climate model needs, not guessed ahead of it. T-1295 is now the two id maps, the bake verb, and the simulator split.
Refactor proof: compute_biome was split into compute_biome_layers, and compose_layers(biome, terrain) is byte-identical to the old single grid on 65 real bodies. That covers every standard-atmosphere body at a stride of 8, plus every thin, thick, reducing and trace body. The baseline was captured before the edit and compared after it.', 'D-258 amendment 2026-09-25, rulings 1-4. Bake, per body, the Python planet simulator''s biome class grid TOGETHER WITH the absolute temperature (kelvin, before the display normalisation in simulate()) and moisture [0,1] it was classified from. One model supplies all three, so they agree by construction.
PRE-SEED: simulate() is keyed on the body frontmatter''s seed only. The world seed must never enter this bake. Worlds stay recognisable across playthroughs; the ground varies server-side.
MEASURE FIRST, then choose the format. The server works at 512x256 (GRID_W/GRID_H downsample), so a 1024x512 bake may be twice the resolution the server can use on each axis. Temperature and moisture are smooth fields that compress far worse than a class grid. Across ~2,600 bodies, the repository cost of candidate formats must be measured: 8-bit class PNG, 16-bit temperature, 8-bit moisture, at 1024x512 and at 512x256. Report the numbers before committing a bake.
DO NOT re-render reliefmap.png. Its render drifts by a few hundred bytes (T-1291). The bake gets its own mode, a verb on reach atlas planet, that writes only the new file(s). The heightmap is byte-identical on re-simulation, so the simulator is deterministic.
Class ids become append-only (the gas-palette selection_order discipline in biomes.toml). Gas giants get nothing. Whatever the chosen files are, register them where the atlas and canvas registries expect: the canvas-version gate (tooling/canvas_sources.py) must see a change to them.
MEASURED 2026-09-25. Probe: simulate() on 24 bodies spaced evenly across the in-scope set, each field encoded as optimised PNG. The probe was deleted after the run.
SCOPE (Jeroen, 2026-09-25): airless bodies are out of scope for now. The format and class vocabulary must still have room for them, since classes 19-36 already cover lava, dust, ferric and lunar ground. The heightmap bake set is 267 bodies. By atmosphere: standard 231, thin 23, thick 1, reducing 1, trace 1, none 8, NULL 2. That leaves 257 bodies with an atmosphere in scope.
PER BODY (mean / max) AND TOTAL OVER 257 BODIES:
biome u8, 1024x512 11.7 / 16.2 KiB 2.9 MiB
biome u8, 512 (plurality) 5.4 / 7.5 KiB 1.4 MiB
temp u16 0.01 K, 1024 80.8 / 352 KiB 20.3 MiB
temp u16 0.01 K, 512 29.0 / 124 KiB 7.3 MiB
temp u8 1 K, 1024 14.8 / 54 KiB 3.7 MiB
temp u8 1 K, 512 6.1 / 22 KiB 1.5 MiB
moist u8, 1024 57.2 / 80 KiB 14.4 MiB
moist u8, 512 22.8 / 32 KiB 5.7 MiB
packed RGB (biome,t8,m8) 1024 96.7 / 172 KiB 24.3 MiB (worse than the three separate files at the same precision: 83.7 KiB)
REFERENCE: the committed heightmap.png averages 678 KiB per in-scope body, and reliefmap.png 295 KiB. Together that is about 245 MiB already in the tree.
Bake time: simulate() takes about 6.3 s per body, so the full bake is about 27 min.
FINDING: the simulator''s temperature is often clamped flat. temperature_bands in biomes.toml clamps each planet class ("fiction wins over physics"). On most temperate bodies that pins the field to exactly the band, 275-305 K. On some bodies it pins it to one value: GJ204c and GJ68e are 275 K everywhere, and GJ601Ad (275-281) and GJ722c (275-280) are nearly flat. On those bodies the biome variety comes from moisture alone.
RE-SHAPED 2026-09-25 (D-258 amendment "ids, not fields"). This supersedes the three-field bake above.
Bake two u8 id maps per in-scope body (257 bodies with an atmosphere) at 1024x512:
- biomemap.png: the Whittaker class, taken BEFORE compute_biome''s terrain overrides. It covers every land pixel and is empty on thin-atmosphere bodies.
- terrainmap.png: the terrain override class, 0 where there is none.
Per-pixel temperature and moisture are NOT baked.
Also in scope here: the class tables (biome: climate envelope, flora, cover; terrain: substrate, landform hints), authored as TOML and imported into systems.db by reach ledger import under the generator stamp.
Done so far: the mountain-rock units fix in compute_biome, with tooling/test_planet_biome_rules.py (in make test-tooling). The rule compared kelvin to 0.35, so it could never fire; it now uses the world''s own normalised temperature. The test is mutation-proven. Sulfuric scrub is unreachable (it converts rock at elev_norm 0.25-0.65, but rock exists only above 0.65); it is left as found, since fixing it is a design call.
Open: story features (the anomaly scatter, ~3% of land). Their placement and rate are for a balancing pass.
SCOPE MOVE 2026-09-25: the class tables (biome and terrain, TOML imported into systems.db) move to T-1297, their first consumer. Nothing in this ticket reads them, and their columns should be set by what the climate model needs, not guessed ahead of it. T-1295 is now the two id maps, the bake verb, and the simulator split.
Refactor proof: compute_biome was split into compute_biome_layers, and compose_layers(biome, terrain) is byte-identical to the old single grid on 65 real bodies. That covers every standard-atmosphere body at a stride of 8, plus every thin, thick, reducing and trace body. The baseline was captured before the edit and compared after it.
DONE 2026-09-25. Commits 3cfec9025 (mountain-rock units fix), 76e3f3ec9 (layer split + bake verb), 1857c6405 (parser logging), ecae65053 (257 bodies baked, 5.2 MB).
The re-shape note above says "0 where there is none" and "empty on thin-atmosphere bodies". Both are superseded, as the D-258 amendment now states: none is 255 (class 0 is ocean_deep), and thin-atmosphere biome maps hold the anomaly scatter''s oasis life.
CANVAS REGISTRY, deliberately NOT done here: the maps change canvas bytes only once the server reads them. Register them in tooling/canvas_sources.py in T-1215, in the same commit as the loader, so the version gate covers them from that point.
STALENESS: the maps go stale whenever planet_simulation.py or biomes.toml changes. "reach atlas planet bake-biome --check" catches that (pixel compare, mutation-proven), but it takes ~23 minutes, too slow for the pre-push gate. Worth a targeted gate once the loader exists: run --check only when those files change.', NULL, '2026-09-25 13:56:16', '2026-09-25 13:56:16.123', '2026-09-25 13:56:16.123', NULL, '4647857948eb4ce189026554d6ee287b', 2) ON CONFLICT(hash) DO NOTHING;
+102
View File
@@ -1538,3 +1538,105 @@ RE-SHAPED 2026-09-25 (D-258 amendment "ids, not fields"). No per-pixel temperatu
SCOPE ADDED 2026-09-25 (moved from T-1295): the class tables. Author a biome table and a terrain table as TOML. The source today is tooling/domains/atlas/planet/biomes.toml, and whether it moves into wiki/ is still open. Import them into systems.db via reach ledger import, under the generator stamp: register the TOML in tooling/generator_sources.py, add the DDL to systems-schema.sql and MIGRATION_SQL. Columns are whatever this ticket''s climate model needs: at minimum the biome climate envelope (from the Whittaker boxes, whose ids 9, 11, 16 and 17 have two boxes each). Terrain classes carry no climate.
Layer and id facts from T-1295: 255 means none on both maps; ids are literal biomes.toml ids; and layer is decided per rule, so class 17 (ice) appears in BOTH tables.', 'backlog', 'high', NULL, 'server', 'D-258', '2026-09-25 10:55:04.014', '2026-09-25 13:08:43.221', NULL, '944729a58c334e95c504f040b35a9178', 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;
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 ('06GDGD2G9K7C8SPH57RCNB8V8C', 'task', '06FT0TX2W0BA10PRR7NMJ2362M', 'Bake the biome input pre-seed: class id + absolute temperature + moisture per body (D-258 2026-09-25)', 'D-258 amendment 2026-09-25, rulings 1-4. Bake, per body, the Python planet simulator''s biome class grid TOGETHER WITH the absolute temperature (kelvin, before the display normalisation in simulate()) and moisture [0,1] it was classified from. One model supplies all three, so they agree by construction.
PRE-SEED: simulate() is keyed on the body frontmatter''s seed only. The world seed must never enter this bake. Worlds stay recognisable across playthroughs; the ground varies server-side.
MEASURE FIRST, then choose the format. The server works at 512x256 (GRID_W/GRID_H downsample), so a 1024x512 bake may be twice the resolution the server can use on each axis. Temperature and moisture are smooth fields that compress far worse than a class grid. Across ~2,600 bodies, the repository cost of candidate formats must be measured: 8-bit class PNG, 16-bit temperature, 8-bit moisture, at 1024x512 and at 512x256. Report the numbers before committing a bake.
DO NOT re-render reliefmap.png. Its render drifts by a few hundred bytes (T-1291). The bake gets its own mode, a verb on reach atlas planet, that writes only the new file(s). The heightmap is byte-identical on re-simulation, so the simulator is deterministic.
Class ids become append-only (the gas-palette selection_order discipline in biomes.toml). Gas giants get nothing. Whatever the chosen files are, register them where the atlas and canvas registries expect: the canvas-version gate (tooling/canvas_sources.py) must see a change to them.
MEASURED 2026-09-25. Probe: simulate() on 24 bodies spaced evenly across the in-scope set, each field encoded as optimised PNG. The probe was deleted after the run.
SCOPE (Jeroen, 2026-09-25): airless bodies are out of scope for now. The format and class vocabulary must still have room for them, since classes 19-36 already cover lava, dust, ferric and lunar ground. The heightmap bake set is 267 bodies. By atmosphere: standard 231, thin 23, thick 1, reducing 1, trace 1, none 8, NULL 2. That leaves 257 bodies with an atmosphere in scope.
PER BODY (mean / max) AND TOTAL OVER 257 BODIES:
biome u8, 1024x512 11.7 / 16.2 KiB 2.9 MiB
biome u8, 512 (plurality) 5.4 / 7.5 KiB 1.4 MiB
temp u16 0.01 K, 1024 80.8 / 352 KiB 20.3 MiB
temp u16 0.01 K, 512 29.0 / 124 KiB 7.3 MiB
temp u8 1 K, 1024 14.8 / 54 KiB 3.7 MiB
temp u8 1 K, 512 6.1 / 22 KiB 1.5 MiB
moist u8, 1024 57.2 / 80 KiB 14.4 MiB
moist u8, 512 22.8 / 32 KiB 5.7 MiB
packed RGB (biome,t8,m8) 1024 96.7 / 172 KiB 24.3 MiB (worse than the three separate files at the same precision: 83.7 KiB)
REFERENCE: the committed heightmap.png averages 678 KiB per in-scope body, and reliefmap.png 295 KiB. Together that is about 245 MiB already in the tree.
Bake time: simulate() takes about 6.3 s per body, so the full bake is about 27 min.
FINDING: the simulator''s temperature is often clamped flat. temperature_bands in biomes.toml clamps each planet class ("fiction wins over physics"). On most temperate bodies that pins the field to exactly the band, 275-305 K. On some bodies it pins it to one value: GJ204c and GJ68e are 275 K everywhere, and GJ601Ad (275-281) and GJ722c (275-280) are nearly flat. On those bodies the biome variety comes from moisture alone.
RE-SHAPED 2026-09-25 (D-258 amendment "ids, not fields"). This supersedes the three-field bake above.
Bake two u8 id maps per in-scope body (257 bodies with an atmosphere) at 1024x512:
- biomemap.png: the Whittaker class, taken BEFORE compute_biome''s terrain overrides. It covers every land pixel and is empty on thin-atmosphere bodies.
- terrainmap.png: the terrain override class, 0 where there is none.
Per-pixel temperature and moisture are NOT baked.
Also in scope here: the class tables (biome: climate envelope, flora, cover; terrain: substrate, landform hints), authored as TOML and imported into systems.db by reach ledger import under the generator stamp.
Done so far: the mountain-rock units fix in compute_biome, with tooling/test_planet_biome_rules.py (in make test-tooling). The rule compared kelvin to 0.35, so it could never fire; it now uses the world''s own normalised temperature. The test is mutation-proven. Sulfuric scrub is unreachable (it converts rock at elev_norm 0.25-0.65, but rock exists only above 0.65); it is left as found, since fixing it is a design call.
Open: story features (the anomaly scatter, ~3% of land). Their placement and rate are for a balancing pass.
SCOPE MOVE 2026-09-25: the class tables (biome and terrain, TOML imported into systems.db) move to T-1297, their first consumer. Nothing in this ticket reads them, and their columns should be set by what the climate model needs, not guessed ahead of it. T-1295 is now the two id maps, the bake verb, and the simulator split.
Refactor proof: compute_biome was split into compute_biome_layers, and compose_layers(biome, terrain) is byte-identical to the old single grid on 65 real bodies. That covers every standard-atmosphere body at a stride of 8, plus every thin, thick, reducing and trace body. The baseline was captured before the edit and compared after it.', 'done', 'high', NULL, 'tooling', 'D-258', '2026-09-25 10:55:02.220', '2026-09-25 13:56:06.842', NULL, '28085d4d0118e0be7ebb216ed63a8699', 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;
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 ('06GDGD2G9K7C8SPH57RCNB8V8C', 'task', '06FT0TX2W0BA10PRR7NMJ2362M', 'Bake the biome input pre-seed: class id + absolute temperature + moisture per body (D-258 2026-09-25)', 'D-258 amendment 2026-09-25, rulings 1-4. Bake, per body, the Python planet simulator''s biome class grid TOGETHER WITH the absolute temperature (kelvin, before the display normalisation in simulate()) and moisture [0,1] it was classified from. One model supplies all three, so they agree by construction.
PRE-SEED: simulate() is keyed on the body frontmatter''s seed only. The world seed must never enter this bake. Worlds stay recognisable across playthroughs; the ground varies server-side.
MEASURE FIRST, then choose the format. The server works at 512x256 (GRID_W/GRID_H downsample), so a 1024x512 bake may be twice the resolution the server can use on each axis. Temperature and moisture are smooth fields that compress far worse than a class grid. Across ~2,600 bodies, the repository cost of candidate formats must be measured: 8-bit class PNG, 16-bit temperature, 8-bit moisture, at 1024x512 and at 512x256. Report the numbers before committing a bake.
DO NOT re-render reliefmap.png. Its render drifts by a few hundred bytes (T-1291). The bake gets its own mode, a verb on reach atlas planet, that writes only the new file(s). The heightmap is byte-identical on re-simulation, so the simulator is deterministic.
Class ids become append-only (the gas-palette selection_order discipline in biomes.toml). Gas giants get nothing. Whatever the chosen files are, register them where the atlas and canvas registries expect: the canvas-version gate (tooling/canvas_sources.py) must see a change to them.
MEASURED 2026-09-25. Probe: simulate() on 24 bodies spaced evenly across the in-scope set, each field encoded as optimised PNG. The probe was deleted after the run.
SCOPE (Jeroen, 2026-09-25): airless bodies are out of scope for now. The format and class vocabulary must still have room for them, since classes 19-36 already cover lava, dust, ferric and lunar ground. The heightmap bake set is 267 bodies. By atmosphere: standard 231, thin 23, thick 1, reducing 1, trace 1, none 8, NULL 2. That leaves 257 bodies with an atmosphere in scope.
PER BODY (mean / max) AND TOTAL OVER 257 BODIES:
biome u8, 1024x512 11.7 / 16.2 KiB 2.9 MiB
biome u8, 512 (plurality) 5.4 / 7.5 KiB 1.4 MiB
temp u16 0.01 K, 1024 80.8 / 352 KiB 20.3 MiB
temp u16 0.01 K, 512 29.0 / 124 KiB 7.3 MiB
temp u8 1 K, 1024 14.8 / 54 KiB 3.7 MiB
temp u8 1 K, 512 6.1 / 22 KiB 1.5 MiB
moist u8, 1024 57.2 / 80 KiB 14.4 MiB
moist u8, 512 22.8 / 32 KiB 5.7 MiB
packed RGB (biome,t8,m8) 1024 96.7 / 172 KiB 24.3 MiB (worse than the three separate files at the same precision: 83.7 KiB)
REFERENCE: the committed heightmap.png averages 678 KiB per in-scope body, and reliefmap.png 295 KiB. Together that is about 245 MiB already in the tree.
Bake time: simulate() takes about 6.3 s per body, so the full bake is about 27 min.
FINDING: the simulator''s temperature is often clamped flat. temperature_bands in biomes.toml clamps each planet class ("fiction wins over physics"). On most temperate bodies that pins the field to exactly the band, 275-305 K. On some bodies it pins it to one value: GJ204c and GJ68e are 275 K everywhere, and GJ601Ad (275-281) and GJ722c (275-280) are nearly flat. On those bodies the biome variety comes from moisture alone.
RE-SHAPED 2026-09-25 (D-258 amendment "ids, not fields"). This supersedes the three-field bake above.
Bake two u8 id maps per in-scope body (257 bodies with an atmosphere) at 1024x512:
- biomemap.png: the Whittaker class, taken BEFORE compute_biome''s terrain overrides. It covers every land pixel and is empty on thin-atmosphere bodies.
- terrainmap.png: the terrain override class, 0 where there is none.
Per-pixel temperature and moisture are NOT baked.
Also in scope here: the class tables (biome: climate envelope, flora, cover; terrain: substrate, landform hints), authored as TOML and imported into systems.db by reach ledger import under the generator stamp.
Done so far: the mountain-rock units fix in compute_biome, with tooling/test_planet_biome_rules.py (in make test-tooling). The rule compared kelvin to 0.35, so it could never fire; it now uses the world''s own normalised temperature. The test is mutation-proven. Sulfuric scrub is unreachable (it converts rock at elev_norm 0.25-0.65, but rock exists only above 0.65); it is left as found, since fixing it is a design call.
Open: story features (the anomaly scatter, ~3% of land). Their placement and rate are for a balancing pass.
SCOPE MOVE 2026-09-25: the class tables (biome and terrain, TOML imported into systems.db) move to T-1297, their first consumer. Nothing in this ticket reads them, and their columns should be set by what the climate model needs, not guessed ahead of it. T-1295 is now the two id maps, the bake verb, and the simulator split.
Refactor proof: compute_biome was split into compute_biome_layers, and compose_layers(biome, terrain) is byte-identical to the old single grid on 65 real bodies. That covers every standard-atmosphere body at a stride of 8, plus every thin, thick, reducing and trace body. The baseline was captured before the edit and compared after it.
DONE 2026-09-25. Commits 3cfec9025 (mountain-rock units fix), 76e3f3ec9 (layer split + bake verb), 1857c6405 (parser logging), ecae65053 (257 bodies baked, 5.2 MB).
The re-shape note above says "0 where there is none" and "empty on thin-atmosphere bodies". Both are superseded, as the D-258 amendment now states: none is 255 (class 0 is ocean_deep), and thin-atmosphere biome maps hold the anomaly scatter''s oasis life.
CANVAS REGISTRY, deliberately NOT done here: the maps change canvas bytes only once the server reads them. Register them in tooling/canvas_sources.py in T-1215, in the same commit as the loader, so the version gate covers them from that point.
STALENESS: the maps go stale whenever planet_simulation.py or biomes.toml changes. "reach atlas planet bake-biome --check" catches that (pixel compare, mutation-proven), but it takes ~23 minutes, too slow for the pre-push gate. Worth a targeted gate once the loader exists: run --check only when those files change.', 'done', 'high', NULL, 'tooling', 'D-258', '2026-09-25 10:55:02.220', '2026-09-25 13:56:16.123', NULL, 'd99c5f1523d47814830fd1857b483add', 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;