OverviewFilesCommitsIssuesDocsReleasesPeers

terrain-lookdev / probe_biome_continuity.py

144 lines · python ref:
#!/usr/bin/env python3
"""
DO BIOMES BLEND, OR DO THEY NEED AN OVERMAP TO BLEND THEM?

The question was whether terrain-diffusion needs a Voronoi overmap to lay out
biomes and blend them. Reading synthetic_map.py says no, and says why: the
model is CONDITIONED on a climate field, and for a synthetic world that field
is fBm Perlin (FastNoiseLite, 2-4 octaves, lacunarity 2, gain 0.5, base
frequency 0.05) histogram-matched onto real WorldClim distributions via
transform_perlin. A continuous field has no cells, so there is nothing to blend.

This measures it rather than trusting the reading.

THE STATISTIC. For each of the four climate channels (bio_1 temp, bio_4 temp
seasonality, bio_12 precip, bio_15 precip CV), walk a long transect and take
|d/dx| per step, normalised by the channel's own spread over the transect. A
smooth field has a max step close to its median step. A CELL-PARTITIONED field
is flat inside cells and jumps at the boundaries, so its max/median explodes.
That ratio is the discriminator.

THE CONTROL IS THE ALTERNATIVE BEING ASKED ABOUT. The same data is re-quantised
onto Voronoi cells and pushed through the identical statistic. The probe must
REJECT that. If it does not, the statistic cannot tell a blended field from a
partitioned one and nothing it says about the real field means anything —
an instrument that cannot fail is not evidence (#50 section 6).

⚠ VACUOUS PASS GUARD. A transect that never leaves one biome is continuous for
free. The run reports each channel's spread and REFUSES to pass if the transect
does not actually vary — a probe that only samples one biome has not tested
blending, and says so instead of printing green.

    python probe_biome_continuity.py --url http://127.0.0.1:8000
"""
import argparse, sys
import numpy as np
from server import fetch_diffusion

CHANNELS = [
    ("bio_1  mean temp",        "temperature"),
    ("bio_4  temp seasonality", "seasonality"),
    ("bio_12 annual precip",    "precipitation"),
    ("bio_15 precip CV",        "precip variability"),
]


def step_ratio(sig):
    """max step / median step, both normalised by the signal's own spread.

    Scale-free on purpose: the four channels are in different units and a
    threshold in degrees would not transfer to millimetres.
    """
    spread = float(np.nanmax(sig) - np.nanmin(sig))
    if spread <= 0:
        return float("nan"), 0.0, 0.0, spread
    d = np.abs(np.diff(sig)) / spread
    med = float(np.median(d))
    mx = float(np.max(d))
    if med <= 0:
        return float("inf"), mx, med, spread
    return mx / med, mx, med, spread


def voronoi_quantise(sig, n_cells, rng):
    """The CONTROL: the same signal, forced into cells with hard edges."""
    edges = np.sort(rng.choice(np.arange(1, len(sig)), size=n_cells - 1, replace=False))
    out = np.empty_like(sig)
    for a, b in zip(np.r_[0, edges], np.r_[edges, len(sig)]):
        out[a:b] = np.nanmean(sig[a:b])
    return out


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--url", default="http://127.0.0.1:8000")
    ap.add_argument("--i1", type=int, default=0, help="transect start, px")
    ap.add_argument("--j1", type=int, default=0)
    ap.add_argument("--n", type=int, default=1024,
                    help="transect length in px (30 m each)")
    ap.add_argument("--band", type=int, default=8, help="rows averaged, to damp pixel noise")
    ap.add_argument("--ratio-max", type=float, default=12.0,
                    help="max acceptable max/median step ratio")
    a = ap.parse_args()

    print(f"transect {a.n} px = {a.n * 30 / 1000:.2f} km at 30 m/px, "
          f"from i1={a.i1} j1={a.j1}\n")

    elev, clim = fetch_diffusion(a.url, a.i1, a.j1, a.n)
    if clim is None:
        raise SystemExit("service returned no climate channels — nothing to measure")
    if clim.shape[2] != 4:
        raise SystemExit(f"expected 4 climate channels, got {clim.shape[2]}")

    rng = np.random.default_rng(0)
    band = min(a.band, clim.shape[0])
    print(f"{'channel':>26} {'spread':>12} {'max step':>10} {'med step':>10} "
          f"{'ratio':>8}  {'control':>8}")
    fails, vacuous = [], []
    for c, (name, _) in enumerate(CHANNELS):
        sig = np.nanmean(clim[:band, :, c].astype(np.float64), axis=0)
        ratio, mx, med, spread = step_ratio(sig)
        ctl_ratio, _, _, _ = step_ratio(voronoi_quantise(sig, 8, rng))
        flag = ""
        if not np.isfinite(ratio) or ratio > a.ratio_max:
            fails.append(name); flag = "  <-- STEPPED"
        # A channel that barely moves has not tested anything.
        if spread <= 1e-6 or med <= 0:
            vacuous.append(name); flag = "  <-- FLAT, tests nothing"
        print(f"{name:>26} {spread:>12.4g} {mx:>10.4f} {med:>10.4f} "
              f"{ratio:>8.2f}  {ctl_ratio:>8.2f}{flag}")

    # The control must be rejected by the same threshold, or the test is blind.
    ctl_ratios = []
    for c in range(4):
        sig = np.nanmean(clim[:band, :, c].astype(np.float64), axis=0)
        r, _, _, s = step_ratio(voronoi_quantise(sig, 8, rng))
        if s > 0:
            ctl_ratios.append(r)
    print()
    if not ctl_ratios or min(ctl_ratios) <= a.ratio_max:
        print(f"CONTROL NOT REJECTED (min {min(ctl_ratios) if ctl_ratios else float('nan'):.2f}"
              f" <= {a.ratio_max}). The statistic cannot tell a blended field from")
        print("a cell-partitioned one, so nothing above is evidence. Fix the probe.")
        return 2
    print(f"control (same data forced into 8 Voronoi cells) rejected at "
          f"ratio {min(ctl_ratios):.1f}-{max(ctl_ratios):.1f}  — the probe discriminates")

    if vacuous:
        print(f"\nINCONCLUSIVE: {', '.join(vacuous)} did not vary over this transect.")
        print("A transect inside one biome is continuous for free. Move it or lengthen it.")
        return 3
    if fails:
        print(f"\nVERDICT: {', '.join(fails)} STEPS. The climate field is not continuous")
        print("         here, and a blending layer would be needed after all.")
        return 1
    print("\nVERDICT: all four channels vary smoothly across the transect. The")
    print("         conditioning field is continuous by construction — biomes blend")
    print("         without an overmap, and adding Voronoi cells could only")
    print("         introduce the boundaries this does not have.")
    return 0


if __name__ == "__main__":
    sys.exit(main())