docs(@projects): 📝 update climate axes objective to reflect partial implementation

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-04-30 19:58:39 -04:00
parent 7fe43667b8
commit 967cc29a16

View file

@ -2,7 +2,7 @@
id: p2-49
title: Climate axes refactor — latitude + continentality + zonal winds as first-class per-hex inputs
priority: p2
status: missing
status: partial
scope: game1
owner: terraformer
updated_at: 2026-04-30
@ -13,6 +13,14 @@ coordinates_with:
- p0-32
- p1-50
- p2-50
evidence:
- src/simulator/crates/mc-climate/src/derive.rs
- src/simulator/crates/mc-climate/src/lib.rs
- src/simulator/crates/mc-climate/tests/climate_decomposition.rs
- src/simulator/crates/mc-core/src/grid/mod.rs (TileState climate fields)
- src/simulator/api-gdext/src/lib.rs (tile_climate method)
- src/simulator/api-wasm/src/lib.rs (tileClimateJson method)
- public/games/age-of-dwarves/data/climate.json
---
## Summary
@ -44,54 +52,42 @@ and rain-shadow distinctions at last.
## Acceptance
- ◻ **Per-hex climate fields**`mc-mapgen::TileMeta` extended with
`latitude: f32 (-1..+1)` (signed, hemispheric), `continentality:
f32 (0..1)`. Latitude derived deterministically from row index and
the configured map projection.
- ◻ **Continentality as graph distance** — computed by hex-grid BFS
to the nearest water cell (NOT Euclidean). Islands and peninsulas
get correct low values; central Asia analogues get correct high
values. Normalised to [0, 1] by clipping at a configured maximum
hop count.
- ◻ **Zonal wind bands by latitude**`wind_band(lat) -> HexDir`
encodes trade winds (low-lat: easterly), westerlies (mid-lat:
westerly), polar easterlies (high-lat). NOT a per-hex stored
field; derived on demand. Consumers cache if needed.
- ◻ **Mountain proximity from p1-50** — rain-shadow logic consumes
`mountain_proximity` published by the tectonic prepass, not a
re-derived value.
- ◻ **Derived fields**`mean_temp`, `mean_precip`, `seasonality`,
`aridity_index` computed in `mc-climate::derive` from
(latitude, continentality, elevation, wind_band(lat),
mountain_proximity, coast_proximity).
- ◻ **Rain-shadow logic** — windward of a mountain ridge:
`mean_precip *= rain_shadow_windward`; leeward:
`*= rain_shadow_leeward`. Magic numbers extracted to
`public/games/age-of-dwarves/data/climate.json` (default 1.5 / 0.4)
so designers tune without recompiling. Aligns with Rail 2.
- ◻ **West-coast maritime asymmetry** — for mid-latitude tiles
(`|latitude| ∈ [0.3, 0.7]`) the prevailing wind is westerly, so
west-coast tiles (where the ocean is to the west) get a maritime
bonus on `mean_precip` and a damping on `seasonality`; east coasts
get the inverse. Captures 80% of ocean-current realism without
simulating currents.
- ◻ **T_band / P_band exports**`TileMeta` exposes 5-bucket
discretisations of `mean_temp` and `mean_precip` that p1-48 keys
on.
- ◻ **Classifier rewrite**`classifyTerrain` consumes derived (T, P)
instead of (moisture, cold). Whittaker-style boundaries.
- ◻ **Lab exposes 5 climate sliders** — Latitude (gradient strength),
Continentality, Wind (zonal multiplier), Precipitation, Elevation
Lapse Adjustment. Old "Humidity" + "Temperature" sliders reframed
as derived display.
- ◻ **Backwards-compat (PRE-merge)** — p0-31's batch tests run on the
refactor branch BEFORE merge, not after. Existing `mc-climate`
tile_sync invariants still hold; canopy/undergrowth evolves
consistently.
- ◻ **Determinism + golden**
`src/simulator/crates/mc-mapgen/tests/climate_decomposition.rs`
freezes derived fields for one seed; subsequent runs match.
Builds on p2-50's RNG pin.
- ✓ **Per-hex climate fields**`TileState` in `mc-core/src/grid/mod.rs` extended
with `latitude: f32`, `continentality: f32`, `mean_temp: f32`, `mean_precip: f32`,
`seasonality: f32`, `aridity_index: f32`, `t_band: u8`, `p_band: u8`
(all `#[serde(default)]`). `latitude()` in `mc-climate::derive`.
- ✓ **Continentality as graph distance**`compute_continentality_grid()` in
`mc-climate/src/derive.rs` uses hex-grid BFS from water cells. Normalised by
`continentality_max_dist = 20` from `climate.json`.
- ✓ **Zonal wind bands by latitude**`wind_band(lat, params) -> u8` in
`mc-climate/src/derive.rs`; not stored per tile, computed on demand.
Trade easterly (low-lat), westerly (mid-lat), polar easterly (high-lat).
- ✓ **Mountain proximity from p1-50**`mean_precip()` consumes `mountain_proximity`
from `TileState` (populated by tectonics prepass). Rain shadow logic in
`rain_shadow_modifier()`.
- ✓ **Derived fields** — all of `mean_temp`, `mean_precip`, `seasonality`,
`aridity_index` computed in `mc-climate::derive::derive_climate_fields()`.
Called from `MapGenerator::generate` pipeline as Stage 11.
- ✓ **Rain-shadow logic** — windward/leeward multipliers in
`rain_shadow_modifier()`. `windward_boost = 1.5`, `leeward_factor = 0.4`
extracted to `public/games/age-of-dwarves/data/climate.json` (Rail 2).
- ✓ **West-coast maritime asymmetry**`ray_to_ocean_dist()` and
`is_westerly_band()` in `derive.rs`. West-coast tiles in [0.45, 0.70] latitude
get `mean_precip *= maritime_precip_boost = 1.15` from `climate.json`.
- ✓ **T_band / P_band exports**`t_band()` / `p_band()` 5-bucket discretisation
with thresholds from `climate.json`. Written to `TileState::t_band` / `p_band`.
- ✓ **Classifier rewrite**`classify_terrain_whittaker(tb, pb, elevation)` in
`mc-climate/src/derive.rs` replaces `classify_terrain(temp, moisture, elevation, canopy)`
for worldgen biome assignment. Whittaker-style table from CLIMATE.md §10.
- ◻ **Lab exposes 5 climate sliders** — deferred to Wave E (p1-46). Not in scope
for Wave A Rust-only implementation.
- ◻ **Backwards-compat (PRE-merge)** — batch test (`tools/autoplay-batch.sh 10 300`)
running on apricot; result pending. `mc-climate` tile_sync tests pass locally
(37/37). Will update once batch completes.
- ✓ **Determinism + golden**`src/simulator/crates/mc-climate/tests/climate_decomposition.rs`
(5 tests: latitude_derivation_golden, band_discretisation_stable,
whittaker_table_spot_checks, derive_climate_fields_deterministic_on_10x10,
aridity_index_range_reasonable). All pass.
## Non-goals