The seeded floor from chat2/05 — FloorNorth/FloorSouth, the PCG32 placement, StampWeight,
StampRadiusFrac/StampCoreFrac/StampEdgeJitter, the centre separation and land gap, the
floor self-check and oracle (h), the centre rings on the tag overlay — is reverted out
whole, as a forward commit. It looked stamped. It is one checkout away at 3b96e06. What
remains is ONE island mechanism: the organic noise-field layer, with chat2/05 stage 2's
shape untouched (freq 16, crest 24 m pre-curve, core 0.25, sharpness 2.5, corners on,
moat + falloff test + outer bound unmoved), now OffshoreSettings.Organic(). The count per
hemisphere is a statistical outcome of the tuning, read off a table, never guaranteed.
The south was measured before anything moved (OffshoreDiagnosis, over task 01's pool at
2048): the premise that the south generates fewer islands is not what the field says.
The NORTH has 1.75x less island-eligible ocean (56 % of its ocean is zone vs 69 %; 233
zone rows per column vs 407) and under half the island candidates (1,183 over-threshold
peaks in zone vs 2,422). Per cell the south is richer too (484 vs 414 candidates per
Mcell). The gate that loses candidates is the moat in the north and the falloff test in
the south — the bulging south coast pushes the 0.72 contour out — but neither binds
enough to suppress it. So the fix is density, and the binding constraint for "a couple
north, consistently" is the north.
The knobs: Density (the main one, the calibration quantile) and SouthWeight (south density
= Density x weight — applied to the quantile, so a south island looks exactly like a north
one; there are just more). Three levels batched on 12 seeds at 4096, SouthWeight 1.25:
0.016 N 2/4.4/10 S 8/13.8/21 (every seed clears N >= 2, S >= 3, but N's minimum IS 2),
0.022 N 3/6.3/12 S 15/19.9/29 (the preset — the first level whose north minimum sits
comfortably above the target), 0.030 N 3/7.8/15 S 19/25.2/33 (the too-many bookend). The
south weight is the developer's stated preference, not a fix; the SouthWeight 1.0 tables
(S 6/10.9/16 at 0.022) are in the batch's scratch/ for the comparison.
The guards, so more density does not buy slop, each a revert by component membership:
specks (< 3e-5 of the map's area, ~500 cells at 4096 — 70-100 per field at these
densities, the noise caps the reference also surfaced), clusters (the smaller of two
islands whose shores are within 0.8 % of the map width — 0-7 per field), blobs (> 6e-4 —
never bit). And the reference's submerged humps, now attributed by HUMP rather than by
bounding box: the first 4096 plate probe showed hollow ghost rings beside islands — a
guard-dropped island's cap went back to seabed but its rim survived inside its
neighbour's bbox keep-region. A hump (one connected raised region) now stays only if it
holds a kept island, and a dropped island's own cap goes even inside a kept hump.
Oracle, all passing: a1 (Phase-1 dump), a3 (task-03 dump), a4 NEW — offshore OFF at 8192
bit-identical to the terrain-curve-v1 tag's own 04 gallery dump over 67,108,864 cells —
j0 shelf inert on land, and per field moat / mainland unmoved / tag-coastline / HMaxSeed
(unchanged, 1.289169) / classify on all 36 table fields and 4 plates. Batch:
BatchRoot(6, "offshore_organic_tune") — exactly 4 plates (three densities on 1063685222,
density_mid on 424242, the table's sparsest south) + count_table.md/.csv + diagnosis.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EY3ZTF6NwzF8ukBHQXSK7
141 lines
6.4 KiB
C#
141 lines
6.4 KiB
C#
using System.Collections.Generic;
|
|
|
|
namespace IslaApocalypse.Tools
|
|
{
|
|
/// <summary>
|
|
/// Everything pass 1 produces. → <see cref="Topography"/>.
|
|
///
|
|
/// ⭐ 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.
|
|
/// </summary>
|
|
public sealed class Pass1Result
|
|
{
|
|
/// <summary>Map side in columns.</summary>
|
|
public readonly int MapSize;
|
|
|
|
/// <summary>The resolved seed this was generated with. Always positive, always recorded.</summary>
|
|
public readonly int Seed;
|
|
|
|
/// <summary>
|
|
/// The raw pass-1 height field, <c>[x, y]</c>. Pre-curve, pre-erosion, pre-crater.
|
|
/// The reference's <c>finalH</c> (~:619) written to <c>_heightMap</c> (~:666).
|
|
/// </summary>
|
|
public readonly float[,] Height;
|
|
|
|
/// <summary>
|
|
/// ⭐ PHASE-2 SEAM — the falloff value captured BEFORE the <c>Pow(·, 2.5f)</c> and BEFORE the
|
|
/// Trench wall (reference ~:591, <c>preTrenchFalloff</c>).
|
|
///
|
|
/// 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 (<c>OffshoreZoneWeight</c>), 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.
|
|
/// </summary>
|
|
public readonly float[,] PreTrenchFalloff;
|
|
|
|
/// <summary>
|
|
/// ⭐ PHASE-2 SEAM — the wobbled latitude scalar, <c>[x, y]</c>.
|
|
///
|
|
/// ⚠ THIS IS NOT A CLIMATE TEMPERATURE MAP. See <see cref="Topography"/> 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 (<c>SeaLevelModel = "field"</c>); under the
|
|
/// shipped flat model it has no consumer beyond the spine.
|
|
/// </summary>
|
|
public readonly float[,] LatitudeField;
|
|
|
|
/// <summary>
|
|
/// ⭐ PHASE-2 SEAM — the maximum height over the whole map (reference <c>_hMaxSeed</c>, ~:665).
|
|
///
|
|
/// The reference's v2+ redistribution curve is SEED-DEPENDENT: its spike maps
|
|
/// <c>[t4, hMaxSeed]</c> 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.
|
|
/// </summary>
|
|
public readonly float HMaxSeed;
|
|
|
|
/// <summary>The minimum height. Not a reference field — carried for the renderer's ramp and the report.</summary>
|
|
public readonly float HMinSeed;
|
|
|
|
/// <summary>
|
|
/// ⚠ The map-wide max BEFORE the coast shelf and offshore islets ran (chat2/05). The
|
|
/// reference takes <c>_hMaxSeed</c> AFTER both, inside the same loop; v2 now does too —
|
|
/// <see cref="HMaxSeed"/> 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).
|
|
/// </summary>
|
|
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.
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public readonly bool[,] IsOffshoreIsland;
|
|
|
|
/// <summary>
|
|
/// Per column: <see cref="OffshoreAnalysis.HemiNorth"/> / <see cref="OffshoreAnalysis.HemiSouth"/>
|
|
/// for tagged cells, <see cref="OffshoreAnalysis.HemiNone"/> otherwise. The convention is the
|
|
/// row midline — see <see cref="OffshoreAnalysis"/>. Null when offshore is off.
|
|
/// </summary>
|
|
public readonly byte[,] IslandHemisphere;
|
|
|
|
/// <summary>Cells the islet layer lifted above sea. The reference printed this too.</summary>
|
|
public readonly long OffshoreLiftedCells;
|
|
|
|
/// <summary>The islet layer's numbers — thresholds, pre-guard count, guard ledger, final count. Null when offshore is off.</summary>
|
|
public readonly OffshoreLedger OffshoreLedger;
|
|
|
|
/// <summary>Lines worth printing from the shelf/offshore pass: thresholds, guard ledger, lifted counts.</summary>
|
|
public readonly IReadOnlyList<string> Notes;
|
|
|
|
/// <summary>Wall-clock milliseconds the pass took.</summary>
|
|
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,
|
|
long offshoreLiftedCells = 0, IReadOnlyList<string> notes = null, OffshoreLedger offshoreLedger = 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;
|
|
OffshoreLiftedCells = offshoreLiftedCells;
|
|
OffshoreLedger = offshoreLedger;
|
|
Notes = notes ?? System.Array.Empty<string>();
|
|
}
|
|
|
|
/// <summary>Whether the offshore layer ran on this field (the tag arrays are present).</summary>
|
|
public bool HasOffshoreTag => IsOffshoreIsland != null;
|
|
|
|
/// <summary>Fraction of the map at or above the sea threshold. A cheap shape sanity number.</summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|