using System.Collections.Generic; using Godot; namespace IslaApocalypse.Tools { /// /// The offshore TAG / HEMISPHERE debug overlay (chat2/05): mainland one tint, offshore-island /// land tinted by hemisphere, seeded centres ringed, the midline drawn — so the island count, /// the N/S split and the tag's correctness are all visible at one glance. /// /// ⚠ A DIAGNOSTIC, NOT A MAP. It draws the tag layer, which is DATA the shape pass set; it is /// the one artifact in the batch that shows what a downstream consumer of the tag would see. /// No hypsometry, no hillshade — three flat tints and some rings, on purpose. /// /// Presentation only: it is handed arrays and returns a PNG. It cannot change them. /// public static class TagOverlayRenderer { private static readonly Color Sea = new(0.055f, 0.110f, 0.235f); private static readonly Color Mainland = new(0.310f, 0.360f, 0.300f); private static readonly Color IslandN = new(0.250f, 0.850f, 0.950f); // cool — north private static readonly Color IslandS = new(0.980f, 0.600f, 0.200f); // warm — south private static readonly Color Untagged = new(0.950f, 0.150f, 0.800f); // ⚠ land that is neither — must never appear private static readonly Color Midline = new(0.700f, 0.720f, 0.760f); private static readonly Color Ring = new(1.000f, 1.000f, 1.000f); private static readonly Color Ink = new(0.941f, 0.949f, 0.961f); /// /// Per cell, land that is NOT offshore (from the offshore-OFF field, so a tag bug cannot hide /// by mis-tagging mainland). Null ⇒ derived as "land and not tagged", which is weaker. /// public static void SavePng(float[,] height, bool[,] tag, byte[,] hemi, int mapSize, float sea, IReadOnlyList<(int x, int y, int r)> centres, int countN, int countS, string absolutePath) { var img = Image.CreateEmpty(mapSize, mapSize, false, Image.Format.Rgb8); for (int x = 0; x < mapSize; x++) { for (int y = 0; y < mapSize; y++) { Color c; bool land = height[x, y] >= sea; bool tagged = tag != null && tag[x, y]; if (!land) c = Sea; else if (!tagged) c = Mainland; else if (hemi == null) c = Untagged; else c = hemi[x, y] switch { OffshoreAnalysis.HemiNorth => IslandN, OffshoreAnalysis.HemiSouth => IslandS, _ => Untagged, }; img.SetPixel(x, y, c); } } // The hemisphere midline — the tag's convention, drawn where it bites. int mid = mapSize / 2; for (int x = 0; x < mapSize; x += 3) img.SetPixel(x, mid, Midline); // Seeded centres: a ring at the stamp radius, so the floor islands can be told from the // organic ones by eye. if (centres != null) foreach (var (cx, cy, r) in centres) DrawRing(img, cx, cy, r, mapSize); // A legend that cannot be separated from the picture. int s = mapSize >= 4096 ? 4 : 3; int lh = TinyFont.Height(s) + 6; TinyFont.Draw(img, "OFFSHORE TAG OVERLAY", 12, 12, s, Ink); TinyFont.Draw(img, "GREY: MAINLAND CYAN: ISLAND N ORANGE: ISLAND S", 12, 12 + lh, s, Ink); TinyFont.Draw(img, $"ISLANDS: {countN} NORTH {countS} SOUTH - RINGS: SEEDED FLOOR", 12, 12 + lh * 2, s, Ink); TinyFont.Draw(img, "N ABOVE THE LINE - S BELOW - Y RUNS SOUTH", 12, 12 + lh * 3, s, Ink); Error err = img.SavePng(absolutePath); if (err != Error.Ok) GD.PrintErr($"[TagOverlayRenderer] SavePng failed ({err}) for {absolutePath}"); } private static void DrawRing(Image img, int cx, int cy, int r, int n) { int rr = r + 3; // just outside the rim int steps = System.Math.Max(64, rr * 4); for (int i = 0; i < steps; i++) { double a = i * 2.0 * System.Math.PI / steps; for (int t = 0; t < 2; t++) // 2 px thick { int px = cx + (int)System.Math.Round((rr + t) * System.Math.Cos(a)); int py = cy + (int)System.Math.Round((rr + t) * System.Math.Sin(a)); if (px >= 0 && py >= 0 && px < n && py < n) img.SetPixel(px, py, Ring); } } } } }