fix(assets): mountain rock could never fire — kelvin compared to 0.35 (T-1295)
compute_biome receives absolute kelvin (the Whittaker table is kelvin; the
0-1 normalisation happens afterwards in simulate(), for the renderer only),
but two terrain rules compared it to 0.35. That was the case from the
generator's first commit (f84275edf):
- mountain rock, `temperature < 0.35`: never true, so the class never
appeared through this rule on any body;
- sulfuric scrub, `temperature > 0.35`: always true.
0.35 can only mean a fraction of the world's own range, the normalisation
compute_moisture already uses, so both rules now compare against that. A
world with no temperature range reads 0 (the cold end).
tooling/test_planet_biome_rules.py pins the rule both ways on a synthetic
body: cold high ground turns to rock (>90%, since anomaly scatter repaints
~3%), and warm high ground and low ground do not. Mutation-proven: restoring
the kelvin comparison fails it at a rock share of exactly 0.000. Wired into
make test-tooling.
Sulfuric scrub remains unreachable for a different reason: it converts rock
only at elev_norm 0.25-0.65, and rock exists only above 0.65. It is left as
found, since making it reachable is a design call and belongs to the balancing
pass, so it is not pinned.
Found while shaping the D-258 biome bake. The committed reliefmaps and globes
were rendered with the bug, and the coming bake reflects the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -716,7 +716,15 @@ def compute_biome(body_def, elevation, sea_level, surface_water,
|
||||
has_ice_deposition = atmo not in ("none",) and hydro_here not in ("none", "subsurface")
|
||||
if has_ice_deposition:
|
||||
biome[land & (elev_norm > 0.85)] = 17
|
||||
biome[land & (elev_norm > 0.65) & (temperature < 0.35)] = 18
|
||||
# `temperature` is absolute kelvin here, but the two rules below were
|
||||
# written against a 0-1 scale and compared kelvin to 0.35 from the first
|
||||
# commit: mountain rock could never fire, and the sulfuric test was always
|
||||
# true (T-1295). 0.35 only means something as a fraction of this world's own
|
||||
# range — the same normalisation simulate() applies for the renderer — so
|
||||
# that is what they now compare against. A world with no range reads 0.
|
||||
t_span = float(temperature.max() - temperature.min())
|
||||
t_norm = (temperature - temperature.min()) / t_span if t_span > 0 else np.zeros_like(temperature)
|
||||
biome[land & (elev_norm > 0.65) & (t_norm < 0.35)] = 18
|
||||
|
||||
# ── Modifier stack ─────────────────────────────────────────────────────
|
||||
env = body_def.get("environment", {})
|
||||
@@ -751,7 +759,7 @@ def compute_biome(body_def, elevation, sea_level, surface_water,
|
||||
|
||||
# Sulfuric substrate: scrub on volcanic mid-elevations
|
||||
if substrate == "sulfuric":
|
||||
scrub = land & (elev_norm > 0.25) & (elev_norm < 0.65) & (temperature > 0.35)
|
||||
scrub = land & (elev_norm > 0.25) & (elev_norm < 0.65) & (t_norm > 0.35)
|
||||
biome[scrub & (biome == 18)] = EXOTIC_CLASSES["sulfuric_scrub"]
|
||||
|
||||
# ── Anomaly scatter ─────────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Pins the mountain-rock rule in `compute_biome`, which compared kelvin
|
||||
against 0.35 (T-1295).
|
||||
|
||||
`compute_biome` receives absolute kelvin, but the rule was written against a
|
||||
0-1 scale, so from the generator's first commit mountain rock could never
|
||||
fire. Nothing noticed, because no test asked the rule to fire, or to hold
|
||||
back. These do both, on a synthetic body whose answer is known.
|
||||
|
||||
The sulfuric-scrub rule had the same unit bug and is fixed alongside it, but
|
||||
it is not pinned here: it converts rock only at elev_norm 0.25-0.65, while
|
||||
rock exists only above 0.65, so it cannot fire at all. Making it reachable is
|
||||
a design call, not a units fix.
|
||||
|
||||
Run directly or via `make test-tooling`:
|
||||
.venv/bin/python tooling/test_planet_biome_rules.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
import numpy as np # noqa: E402
|
||||
|
||||
from tooling.domains.atlas.planet.planet_simulation import ( # noqa: E402
|
||||
GRID_H,
|
||||
GRID_W,
|
||||
compute_biome,
|
||||
)
|
||||
|
||||
MOUNTAIN_ROCK = 18
|
||||
SEA_LEVEL = 0.2
|
||||
|
||||
|
||||
def _body(substrate: str = "silicate") -> dict:
|
||||
return {
|
||||
"seed": 7,
|
||||
"planet_class": "temperate",
|
||||
"body_type": "planet",
|
||||
"physical": {"atmosphere": "standard"},
|
||||
# No hydrosphere: keeps high ground out of the snow rule, so rock is
|
||||
# the only rule that can claim it.
|
||||
"environment": {"hydrosphere": "none", "substrate": substrate},
|
||||
}
|
||||
|
||||
|
||||
def _world(temp_k: np.ndarray, elev: np.ndarray) -> np.ndarray:
|
||||
water = np.zeros((GRID_H, GRID_W), dtype=bool)
|
||||
moisture = np.full((GRID_H, GRID_W), 0.5, dtype=np.float32)
|
||||
return compute_biome(_body(), elev, SEA_LEVEL, water, temp_k, moisture)
|
||||
|
||||
|
||||
class MountainRockTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# West half high ground, east half low. Temperature runs 275-305 K
|
||||
# west to east, the temperate band, so the high west is the cold third.
|
||||
self.elev = np.full((GRID_H, GRID_W), 0.3, dtype=np.float32)
|
||||
self.elev[:, : GRID_W // 2] = 0.95 # elev_norm ~0.94, above the 0.65 gate
|
||||
ramp = np.linspace(275.0, 305.0, GRID_W, dtype=np.float32)
|
||||
self.temp = np.tile(ramp, (GRID_H, 1))
|
||||
|
||||
def test_cold_high_ground_is_rock(self):
|
||||
biome = _world(self.temp, self.elev)
|
||||
cold_high = biome[:, : int(GRID_W * 0.30)]
|
||||
share = float((cold_high == MOUNTAIN_ROCK).mean())
|
||||
# The anomaly scatter repaints ~3% of land, so "all of it" is too
|
||||
# strong; the dead rule scored exactly 0 here.
|
||||
self.assertGreater(share, 0.9, f"cold high ground rock share {share:.3f}")
|
||||
|
||||
def test_warm_or_low_ground_is_not_rock(self):
|
||||
biome = _world(self.temp, self.elev)
|
||||
warm_high = biome[:, int(GRID_W * 0.40) : GRID_W // 2]
|
||||
low = biome[:, GRID_W // 2 :]
|
||||
self.assertEqual(int((warm_high == MOUNTAIN_ROCK).sum()), 0)
|
||||
self.assertEqual(int((low == MOUNTAIN_ROCK).sum()), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user