fix(simulation): address PR #172 review — C1 over-vegetation + H1 biosphere gate (T-1084/T-1085)

Hoshe + Tyre review of the micro-habitat-mosaic branch requested changes on two
data-confirmed correctness bugs plus follow-through. All addressed.

Blocking:
- C1 (voxel.rs): the mosaic re-granted vegetation the climate withheld — a frozen
  ice world (Edict) read 61% vegetated tundra. The Grassland apply-gate now fires
  only where the climate already grants cover (!Barren); a climatically-barren
  district (frozen < -50C = surface ice/geology D-239 §2, or hyper-arid moisture<5,
  both resolved to VegetationClass::Barren upstream) is skipped. Genuine tundra
  (cold + moisture>=15) is Scrub upstream and still reaches the tundra palette via
  the same !Barren path. believability.json regenerated: Edict vegetated_districts
  39->0, Arbour 20->8 (its real cold-pole/arid districts revert to barren);
  intra-class variety preserved (Arbour vegetation_classes 3, micro_habitat_distinct 2).
- H1 (bodies.py): biosphere Gate 0 keyed on `inhabited`, conflating settlement with
  biosphere and force-Airless'ing uninhabited-but-alive worlds (D-247: worlds were
  alive before humans). Gate 0 is now settlement-independent — keyed on
  atmosphere/hydrosphere/planet_class. 24 uninhabited-alive worlds now classified
  (was 0); GJ0g-1 (dense atm + rivers) -> NativeMirror. Split 122 Compatible :
  121 Mirror; all 9 canon exemplars hold.

Follow-through:
- C2 (bodies.py + D-247): Gate 0 now reads planet_class (barren -> Airless) as
  D-247's "+ planet_class" phrasing names. D-247 amended to record the actual
  two-gate signal set (atm/hydro/planet_class habitability; economic_base_primary
  chirality; settlement-independence).
- C3 (believability.rs): micro_habitat_distinct criterion relaxed to `== 0 || >= 2`
  — the min-over-patches estimator makes K=3 unreachable against a dominant-entry
  palette; K=2 provisional, Q-123 calibrates.
- C4 + H2 (D-246): amended to record the v1 scope cut — only the Soil-derived
  vegetated/wet classes take the mosaic; material-driven families are `_ => false`
  so Hardpan/Scree are authored but unreachable in v1; no elevation change in v1;
  the mosaic OWNS the §8 climate->vegetation law for the classes it touches.
- H3 (voxel.rs): three dedicated mosaic-pass regression tests — C1 barren-respect,
  §8 material-untouched + forest-textured, T-1040 no-water-without-channel.
- H4 (voxel.rs): relief_signal comment corrected to ~[-1,+1].
- H5 (bodies.py): frontmatter override now strips quotes + validates against the
  4-value enum (warn-and-skip on typo).

systems.db regenerated + stamped (H1). believability golden regenerated (C1).
Full cargo test green; clippy -D warnings + ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 17:46:19 +02:00
co-authored by Claude Opus 4.8
parent b71df601b2
commit c17b4f5442
9 changed files with 239 additions and 41 deletions
+32 -15
View File
@@ -209,17 +209,18 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
Reads ``environment.biosphere_class`` from each body's index.md frontmatter (the
AUTHORED override) and, where absent, applies the D-247 two-gate default from the
body's atmosphere / hydrosphere / inhabited flag + the system's
body's atmosphere / hydrosphere / planet_class + the system's
``economic_base_primary``. Recomputes for ALL bodies each run (the value is fully
determined by frontmatter + the default, with no other authoritative writer to
preserve), so an edited override re-applies on the next regen.
Authored input, not seed entropy (D-247): the default is a function of other
AUTHORED fields, and explicit frontmatter always wins.
- Gate 0 (habitability): uninhabited or no atmosphere -> Airless; inhabited +
thin atmosphere -> engineered -> TerraformedSterile; inhabited + real
atmosphere + open liquid water -> Alive -> Gate 1; inhabited + real atmosphere
but no liquid water (ice/subsurface) -> sealed/engineered -> Airless.
- Gate 0 (habitability — independent of SETTLEMENT, since D-247 holds worlds were
alive before humans): no atmosphere or an explicitly ``barren`` planet_class ->
Airless; ``thin`` atmosphere -> TerraformedSterile if inhabited (engineered),
else Airless (marginal natural); real atmosphere + open liquid water -> Alive ->
Gate 1; real atmosphere but no liquid water -> Airless (sealed / marginal).
- Gate 1 (Compatible vs Mirror): agriculture/service exporters -> Compatible;
penal/extraction/manufacturing -> Mirror; the neutral remainder -> a stable
body_id split toward the ~50/50 design target (canon worlds carry explicit
@@ -276,7 +277,8 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
bc = stripped.partition(":")[2].strip() or None
return (body_id, bc)
# 1. Authored overrides from frontmatter.
# 1. Authored overrides from frontmatter (quote-stripped + enum-validated — H5).
_VALID = {"NativeCompatible", "NativeMirror", "TerraformedSterile", "Airless"}
override: dict[str, str] = {}
body_files = sorted(_glob.glob(str(WIKI_STAR_SYSTEMS / "*" / "bodies" / "*" / "index.md")))
for fpath in body_files:
@@ -286,6 +288,10 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
continue
body_id, bc = _biosphere_from_frontmatter(text)
if body_id and bc:
bc = bc.strip().strip("'\"")
if bc not in _VALID:
print(f" WARNING: {fpath}: biosphere_class {bc!r} not in {_VALID}; ignored")
continue
override[body_id] = bc
# 2. Bodies needing a value + the inputs for the two-gate default.
@@ -295,7 +301,8 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
# an edited override must re-apply on the next regen.
rows = conn.execute(
"""
SELECT b.body_id, b.inhabited, b.atmosphere, b.hydrosphere, e.economic_base_primary
SELECT b.body_id, b.inhabited, b.atmosphere, b.hydrosphere, b.planet_class,
e.economic_base_primary
FROM bodies b
LEFT JOIN system_economy e ON e.system_id = b.system_id
"""
@@ -303,16 +310,24 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
_LIQUID = ("ocean", "liquid", "rivers", "moderate", "extensive", "coastal", "lakes")
def _default(inhabited, atmosphere, hydrosphere, econ, body_id) -> str:
def _default(inhabited, atmosphere, hydrosphere, planet_class, econ, body_id) -> str:
atm = (atmosphere or "").strip().lower()
hyd = (hydrosphere or "").strip().lower()
# Gate 0 — habitability.
if not inhabited or atm in ("", "none", "trace"):
return "Airless"
pclass = (planet_class or "").strip().lower()
# Gate 0 — habitability. Independent of SETTLEMENT (H1 fix): D-247's premise is
# that habitable worlds were alive before humans, so an UNINHABITED world with a
# real atmosphere + open liquid water is a living world nobody has settled — not
# Airless. Keyed on atmosphere / hydrosphere / planet_class (C2: these are the
# real habitability signals; D-247's "+ planet_class" phrasing amended to match).
if atm in ("", "none", "trace") or pclass == "barren":
return "Airless" # no atmosphere, or an explicitly lifeless world
if atm == "thin":
return "TerraformedSterile"
# A thin *breathable* atmosphere is an engineering achievement only where
# INHABITED (maintained infrastructure — D-247 Oshima); an uninhabited
# thin-atmosphere world is a marginal natural one with no open-air biosphere.
return "TerraformedSterile" if inhabited else "Airless"
if not any(k in hyd for k in _LIQUID):
return "Airless" # real atmosphere, no open liquid water -> sealed/engineered
return "Airless" # real atmosphere but no open liquid water -> sealed / marginal
# Gate 1 — Compatible vs Mirror (Alive).
base = (econ or "").strip().lower()
if "agricultur" in base or "service" in base:
@@ -323,8 +338,10 @@ def populate_biosphere_class(conn: sqlite3.Connection, dry_run: bool) -> int:
return "NativeCompatible" if h % 2 == 0 else "NativeMirror"
updates: list[tuple[str, str]] = []
for body_id, inhabited, atmosphere, hydrosphere, econ in rows:
val = override.get(body_id) or _default(inhabited, atmosphere, hydrosphere, econ, body_id)
for body_id, inhabited, atmosphere, hydrosphere, planet_class, econ in rows:
val = override.get(body_id) or _default(
inhabited, atmosphere, hydrosphere, planet_class, econ, body_id
)
updates.append((val, body_id))
if not dry_run and updates: