magicciv/.project/objectives/p2-63-mc-flora-biome-substrate-migration.md
Natalie d6b3e8f158 feat(@projects/@magic-civilization): migrate biome filter to substrate_climate path
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-13 16:16:29 -07:00

3.8 KiB

id title priority status scope category owner created updated_at k_of_n blocked_by evidence
p2-63 mc-flora generation: migrate biome filter to substrate_climate-aware path p2 done game1 ecology simulator-infra 2026-05-04 2026-05-13 4/4
src/simulator/crates/mc-flora/src/generation.rs — SubstrateClimateEntry typed struct + RawSubstrateClimate untagged enum (canonical | legacy apex shape); BiomeSubstrateClimateMap JSON tunable loader; load_authored_species_for_biome predicate path: substrate_climate intersection (primary) → terrain_affinity (secondary) → legacy biomes[] (tertiary, kept for hand-written fixtures).
public/games/age-of-dwarves/data/flora/biome_substrate_climate.json — JSON tunable mapping 35 procedural biome IDs to typed (substrate, t_band range, p_band range) cells. Cave + spring intentionally omitted (geothermal / subterranean override climate; resolve via terrain_affinity[] only).
cargo test -p mc-flora — 65/65 pass (62 pre-existing + 3 new: substrate_climate_predicate_matches_species_without_terrain_affinity, biome_substrate_climate_map_loads_and_covers_known_biomes, substrate_climate_intersection_is_inclusive). 2026-05-13.
cargo test -p mc-ecology — 324/324 + 8/8 + 6/6 pass, no regression from the mc-flora schema widening. 2026-05-13.

Summary

Authored flora JSON files have migrated from a top-level biomes: [...] array to the substrate_climate ontology (see p2-52-substrate-flora-cover-ontology-split.md). The biome-filter loop in mc-flora/src/generation.rs was not updated, so the AuthoredSpeciesFile.biomes field is now empty for every authored file and the candidate-pool query returns nothing.

Two pre-existing regression tests in mc-flora document the gap:

  • load_authored_returns_species_for_known_biome (generation.rs:645)
  • generate_flora_for_biome_more_species_with_authored_files (generation.rs:695)

Both fail because the biome filter at generation.rs:508-510 still checks raw.biomes.iter().any(|b| b == biome_id) while the JSON now encodes biome eligibility through substrate_climate blocks the loader does not currently inspect.

Acceptance

  • cargo test -p mc-flora load_authored_returns_species_for_known_biome green. Evidence: generation.rs:653 — passes via terrain_affinity[] inclusion path (acacia lists 'savanna'). 65/65 mc-flora tests green 2026-05-13.
  • cargo test -p mc-flora generate_flora_for_biome_more_species_with_authored_files green. Evidence: generation.rs:703 — grassland yields >5 species; authored species pulled in via both substrate_climate intersection (e.g. chanterelle for soil T 2-3 P 1) and terrain_affinity (e.g. acacia).
  • No behavioural regression in flora generation: existing all_authored_flora_species_deserialize and other passing tests remain green. Evidence: cargo test -p mc-flora 65/65 pass. mc-ecology downstream 324/324 + 8/8 + 6/6 pass.
  • No new stringly-typed substrate IDs in code — substrate matching flows through typed structs introduced in p2-52 wherever possible. Evidence: generation.rs SubstrateClimateEntry mirrors mc-ecology::flora_select::SubstrateClimateEntry; predicate intersection via SubstrateClimateEntry::intersects (typed range comparison, no string matching). BiomeSubstrateClimateMap deserialised as HashMap<String, Vec<SubstrateClimateEntry>>. The four substrate strings (substrate names) are unavoidable because the substrate enum itself is stringly-keyed in mc-core grid::TileState (substrate_id: String) — that decision is upstream of p2-63 and outside this objective's scope.

Notes

  • Owner field is unassigned; pick up via mcp__objectives__objective_assign.
  • Touch surface is intentionally narrow: the filter loop and the AuthoredSpeciesFile deserializer.