using System.Collections.Generic;
namespace IslaApocalypse.Tools
{
///
/// Everything pass 1 produces. → .
///
/// ⭐ THREE OF THESE FIELDS ARE THE PHASE-2 SEAM. The reference computed them in pass 1 and
/// consumed them in pass 2; exposing them now is what keeps the deferred ports clean, and it
/// costs nothing to carry.
///
public sealed class Pass1Result
{
/// Map side in columns.
public readonly int MapSize;
/// The resolved seed this was generated with. Always positive, always recorded.
public readonly int Seed;
///
/// The raw pass-1 height field, [x, y]. Pre-curve, pre-erosion, pre-crater.
/// The reference's finalH (~:619) written to _heightMap (~:666).
///
public readonly float[,] Height;
///
/// ⭐ PHASE-2 SEAM — the falloff value captured BEFORE the Pow(·, 2.5f) and BEFORE the
/// Trench wall (reference ~:591, preTrenchFalloff).
///
/// It is the "how far past the island body are we" number, uncontaminated by the power's
/// curvature and by the Trench's additive ramp. The DEFERRED offshore-islet layer reads it
/// as its distance mask (OffshoreZoneWeight), which is the only reason it is captured
/// mid-computation rather than recomputed.
///
/// ⚠ A port that "tidies" the falloff chain into one expression destroys this capture point,
/// and the loss would not show up until Phase 2's offshore mask moved. It is exposed here so
/// that cannot happen quietly.
///
public readonly float[,] PreTrenchFalloff;
///
/// ⭐ PHASE-2 SEAM — the wobbled latitude scalar, [x, y].
///
/// ⚠ THIS IS NOT A CLIMATE TEMPERATURE MAP. See for the full note.
/// It is a stage-1-local latitude field that terrain GEOMETRY reads. Phase 2 needs it if the
/// latitude sea-level model is ever switched on (SeaLevelModel = "field"); under the
/// shipped flat model it has no consumer beyond the spine.
///
public readonly float[,] LatitudeField;
///
/// ⭐ PHASE-2 SEAM — the maximum height over the whole map (reference _hMaxSeed, ~:665).
///
/// The reference's v2+ redistribution curve is SEED-DEPENDENT: its spike maps
/// [t4, hMaxSeed] onto the peak band, so pass 2 cannot start until pass 1 has scanned
/// every pixel. That is precisely why the pass-1/pass-2 boundary is a hard one and not an
/// interleave. Carried here so Phase 2 inherits it rather than rediscovering the dependency.
///
public readonly float HMaxSeed;
/// The minimum height. Not a reference field — carried for the renderer's ramp and the report.
public readonly float HMinSeed;
///
/// ⚠ The map-wide max BEFORE the coast shelf and offshore islets ran (chat2/05). The
/// reference takes _hMaxSeed AFTER both, inside the same loop; v2 now does too —
/// is the post-shelf/offshore value the curve normalizes against.
/// This one is carried so the report can state whether the two differed (expected: no, an
/// islet crest of ~34 m is far below any peak — but "expected" is measured, not assumed).
///
public readonly float HMaxSeedBeforeOffshore;
// ═══ ⭐ THE OFFSHORE TAG — chat2/05's one forward-looking piece ═══
//
// DATA, set here, carried downstream, READ BY NOTHING IN THIS PHASE. It exists so a later
// pass (biome, fertility, placement) can find offshore-island land without re-deriving it
// from geometry. Downstream that ignores it is unaffected; downstream that reads it gets a
// clean flag. Both arrays are NULL when the offshore layer is off — a consumer checks for
// null, not for all-false.
///
/// Per column: is this land an offshore island (as opposed to mainland)? Set for every cell
/// the islet layer lifted from below sea to at-or-above sea. Null when offshore is off.
///
public readonly bool[,] IsOffshoreIsland;
///
/// Per column: /
/// for tagged cells, otherwise. The convention is the
/// row midline — see . Null when offshore is off.
///
public readonly byte[,] IslandHemisphere;
/// The seeded-floor stamp centres and radii (Hybrid only; empty otherwise). For the overlay and the report.
public readonly IReadOnlyList<(int x, int y, int r)> OffshoreCentres;
/// Cells the islet layer lifted above sea. The reference printed this too.
public readonly long OffshoreLiftedCells;
/// Lines worth printing from the shelf/offshore pass: thresholds, floor placement, lifted counts.
public readonly IReadOnlyList Notes;
/// Wall-clock milliseconds the pass took.
public readonly ulong ElapsedMs;
public Pass1Result(int mapSize, int seed, float[,] height, float[,] preTrenchFalloff,
float[,] latitudeField, float hMaxSeed, float hMinSeed, ulong elapsedMs,
float hMaxSeedBeforeOffshore = float.NaN,
bool[,] isOffshoreIsland = null, byte[,] islandHemisphere = null,
IReadOnlyList<(int x, int y, int r)> offshoreCentres = null,
long offshoreLiftedCells = 0, IReadOnlyList notes = null)
{
MapSize = mapSize;
Seed = seed;
Height = height;
PreTrenchFalloff = preTrenchFalloff;
LatitudeField = latitudeField;
HMaxSeed = hMaxSeed;
HMinSeed = hMinSeed;
ElapsedMs = elapsedMs;
HMaxSeedBeforeOffshore = float.IsNaN(hMaxSeedBeforeOffshore) ? hMaxSeed : hMaxSeedBeforeOffshore;
IsOffshoreIsland = isOffshoreIsland;
IslandHemisphere = islandHemisphere;
OffshoreCentres = offshoreCentres ?? System.Array.Empty<(int, int, int)>();
OffshoreLiftedCells = offshoreLiftedCells;
Notes = notes ?? System.Array.Empty();
}
/// Whether the offshore layer ran on this field (the tag arrays are present).
public bool HasOffshoreTag => IsOffshoreIsland != null;
/// Fraction of the map at or above the sea threshold. A cheap shape sanity number.
public float LandFraction(float seaLevel)
{
long land = 0;
for (int x = 0; x < MapSize; x++)
for (int y = 0; y < MapSize; y++)
if (Height[x, y] >= seaLevel) land++;
return land / (float)((long)MapSize * MapSize);
}
}
}