diff --git a/.pql/changelog/ticket_history/2026-09.sql b/.pql/changelog/ticket_history/2026-09.sql index 23eaaa277..5748bb931 100644 --- a/.pql/changelog/ticket_history/2026-09.sql +++ b/.pql/changelog/ticket_history/2026-09.sql @@ -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; diff --git a/.pql/changelog/tickets/2026-09.sql b/.pql/changelog/tickets/2026-09.sql index 8d4ec8f7c..8a07f92ec 100644 --- a/.pql/changelog/tickets/2026-09.sql +++ b/.pql/changelog/tickets/2026-09.sql @@ -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;