using Godot; namespace IslaApocalypse.Tools { /// /// The hypsometric palettes — a continuous elevation tint, in the physical-atlas tradition. /// /// ═══ ⚠⚠ THIS IS CARTOGRAPHY, NOT CLASSIFICATION ═══ /// /// There are NO BIOME WORDS here and there must never be any. Nothing in this file knows about /// jungle, wasteland, desert or snow. It maps ONE FLOAT — height — onto a continuous colour /// ramp. White at the top is snow-COLOURED cartography; green at the bottom is /// lowland-COLOURED. Biomes are a stage-5 CLASSIFICATION of finished shape (D-049 §2, D-056), /// and the entire point of the rewrite's seam is that terrain never learns their names. /// /// ⚠ CONTINUOUS, NOT BANDED. The stops below are interpolated, never quantized. Hard bands /// would read as discrete classes — exactly the visual language of a biome map, and exactly the /// wrong thing to suggest. /// /// ═══ ⚠ THE STOPS ARE IN ABSOLUTE HEIGHT, AND THEY ARE FIXED ═══ /// /// Every stop is a raw height value, not a normalized fraction, and the same stops are used for /// every image in a batch. A map coloured on its own min/max cannot be compared with its /// neighbour — and comparison is the entire point of a batch. Out-of-range values clamp; the /// run prints the true min/max so saturation shows up as a number rather than a guess. /// /// ═══ WHY THE STOPS SIT WHERE THEY DO — measured, not eyeballed ═══ /// /// Land height is very bottom-heavy. Measured over all four pinned seeds at 2048 (8.6M land /// columns): p10 0.214 · p25 0.306 · MEDIAN 0.456 · p75 0.648 · p90 0.835 · p99 1.113 · /// p99.9 1.267 · max 1.415. /// /// A ramp spread linearly from 0.15 to 1.45 would therefore spend 99% of its colour range on /// 1% of the land, and the map would read as "green with a few white dots". So the land stops /// are placed on PERCENTILES of the measured distribution instead — the same thing an atlas /// plate does when it picks non-uniform contour intervals. /// /// Sea is the mirror problem: depth runs to −6.7 but 43% of it is shallower than 0.5 and the /// deep floor is featureless. So the bathymetric ramp is COMPRESSED — it is indexed by /// d / (d + k), which gives the shallow shelf most of the range and lets the abyss /// flatten to one dark tone. The interesting bathymetry is near shore. /// /// ⚠ THE SEABED IS RAW, AND IT IS SUPPOSED TO BE. The submarine coast shelf and the offshore /// islets are DEFERRED Phase-2 items. Plain, steep bathymetry here is the deferral showing, not /// a broken render. /// public static class ReliefPalette { /// Named palettes. Gated so the look can be A/B'd like any other change. public enum Kind { Atlas, Dusk } /// /// Bathymetric compression constant, in raw height units. The sea ramp is indexed by /// d / (d + SeaCompression). Smaller = more range spent on the shallows. /// At 0.35: depth 0.05 → 12.5% of the ramp, 0.5 → 59%, 2.0 → 85%, 6.7 → 95%. /// public const float SeaCompression = 0.35f; // ---- LAND: (absolute height, colour), interpolated. Placed on measured percentiles. ---- private static readonly (float h, Color c)[] AtlasLand = { (0.150f, new Color(0.427f, 0.561f, 0.376f)), // shoreline — soft green (0.220f, new Color(0.475f, 0.596f, 0.388f)), // p10 lowland green (0.310f, new Color(0.549f, 0.635f, 0.404f)), // p25 (0.460f, new Color(0.667f, 0.686f, 0.435f)), // p50 yellow-green (0.650f, new Color(0.780f, 0.729f, 0.494f)), // p75 khaki (0.840f, new Color(0.808f, 0.678f, 0.478f)), // p90 tan (0.980f, new Color(0.757f, 0.588f, 0.427f)), // p95 light brown (1.120f, new Color(0.667f, 0.510f, 0.404f)), // p99 brown (1.270f, new Color(0.706f, 0.667f, 0.639f)), // p99.9 grey rock (1.450f, new Color(0.988f, 0.988f, 0.980f)), // peak white }; private static readonly (float h, Color c)[] AtlasSea = { (0.000f, new Color(0.478f, 0.729f, 0.769f)), // waterline — pale teal (0.125f, new Color(0.325f, 0.596f, 0.706f)), // shelf (0.310f, new Color(0.196f, 0.451f, 0.627f)), // mid blue (0.590f, new Color(0.114f, 0.310f, 0.510f)), // deep (0.760f, new Color(0.063f, 0.196f, 0.376f)), // deeper (0.880f, new Color(0.035f, 0.118f, 0.259f)), // navy (1.000f, new Color(0.020f, 0.063f, 0.157f)), // abyss floor — flat }; // ---- DUSK: warmer land, deeper cooler water. A genuine alternative, not a tweak. ---- private static readonly (float h, Color c)[] DuskLand = { (0.150f, new Color(0.353f, 0.494f, 0.353f)), (0.220f, new Color(0.427f, 0.541f, 0.361f)), (0.310f, new Color(0.529f, 0.588f, 0.376f)), (0.460f, new Color(0.663f, 0.639f, 0.408f)), (0.650f, new Color(0.796f, 0.690f, 0.451f)), (0.840f, new Color(0.831f, 0.627f, 0.412f)), (0.980f, new Color(0.780f, 0.522f, 0.361f)), (1.120f, new Color(0.663f, 0.435f, 0.353f)), (1.270f, new Color(0.678f, 0.612f, 0.588f)), (1.450f, new Color(0.980f, 0.973f, 0.949f)), }; private static readonly (float h, Color c)[] DuskSea = { (0.000f, new Color(0.412f, 0.686f, 0.706f)), (0.125f, new Color(0.263f, 0.545f, 0.647f)), (0.310f, new Color(0.145f, 0.396f, 0.565f)), (0.590f, new Color(0.078f, 0.263f, 0.443f)), (0.760f, new Color(0.043f, 0.161f, 0.318f)), (0.880f, new Color(0.024f, 0.094f, 0.212f)), (1.000f, new Color(0.012f, 0.047f, 0.125f)), }; /// The hypsometric tint for a height, before any relief shading. public static Color Tint(Kind kind, float height, float seaLevel) { bool dusk = kind == Kind.Dusk; if (height >= seaLevel) return Sample(dusk ? DuskLand : AtlasLand, height); // Compressed bathymetry: index by d/(d+k), so the shallows get the range. float depth = seaLevel - height; float t = depth / (depth + SeaCompression); return Sample(dusk ? DuskSea : AtlasSea, t); } private static Color Sample((float h, Color c)[] stops, float v) { if (v <= stops[0].h) return stops[0].c; for (int i = 1; i < stops.Length; i++) { if (v <= stops[i].h) { float span = stops[i].h - stops[i - 1].h; float local = span <= 0f ? 0f : (v - stops[i - 1].h) / span; return stops[i - 1].c.Lerp(stops[i].c, local); } } return stops[stops.Length - 1].c; } /// The land range the stops cover, for a run header / INDEX. public static (float lo, float hi) LandAnchors => (AtlasLand[0].h, AtlasLand[AtlasLand.Length - 1].h); } }