Core/Scripts/RegionLabeling.cs is the shared-infra contract, built to the letter: it runs on the CLASSIFY (raw) field; land is 8-connected, the deliberate complement of water's 4 (a diagonal isthmus joins; the water either side stays separate); a component is a maximal 8-connected set of land cells; the MAINLAND is the component containing the map centre — not merely the largest, which a later fragmentation step could flip — with a flagged fallback to the largest if the centre were ever water (asserted, never needed: oracle m); every other component is an island; per component id / sizeCells / centroid / hemisphere (by centroid, one label per island) / isMainland. Ids come from a fixed scan order and are proven stable across two generations (oracle o, 16.8M cells). It knows nothing about offshore or stamped. Engine-free, in Core as C++-candidate math; the hemisphere convention moved there with it, OffshoreAnalysis aliases it. Tools/Scripts/RegionPass.cs is pass 1c: label, revert, relabel, tag. The island tag (renamed IsIsland; IslandHemisphere from the component's centroid; Pass1Result.Regions carries the whole table) is now a CONSEQUENCE of labeling — every non-mainland component. That is the fix for the chat2/06 overlay, which tagged only what the offshore pass raised: 1063685222 has 11 natural islands including a 94,511-cell detached mass, 20260821 has 19, all grey in 06's tags.png and all coloured now. The offshore pass itself is untouched; its internal Tag stays for its own guards and is no longer exported. The speck revert (TerrainGenConfig.SpeckRevert / MinLandComponentFrac) lowers every non-mainland component below the threshold to the mean of its ring of adjacent sea cells, held strictly below sea. Origin-blind: a natural nub goes the same way as an offshore dot (6 natural components / 273 cells on the bare 1063685222 field at threshold_mid — reported as a3r, informational). Lower-only and component-only are asserted cell by cell in the pass and re-proven on the finished fields by oracle n (mainland bit-identical filter OFF vs ON; every changed cell in a sub-threshold island, lowered below sea); the mainland is never a candidate and its size is asserted unchanged across the revert. A reverted offshore island leaves its submerged skirt as a shoal — not this component, by the rule. Classify/render consistency is by construction (pass 1, curve identity at sea) and asserted by oracle k. Deliberately OFF in the bare TerrainGenConfig for the reason the shelf and islets are: the raw field has natural specks, so default-ON would move the calibration pool and every regression dump; the batch turns it on. Thresholds swept on 8 seeds at 4096 (1e-5 / 3e-5 / 1e-4 of the map = 168 / 503 / 1,678 cells): low removes 0–6 nubs per seed, mid (the config default, equal to the offshore guard) 2–11, high 26–36 — most of the offshore islands, the "fewer, bigger" bookend. The count/size table carries natural / pre / post counts per hemisphere, min/median/mean/max and a log-spaced size histogram — the instrument for the southern-stretch step. Oracle, all passing: a1, a3, a4 (8192, 67M cells) with labeling ON + revert OFF; a6 NEW — labeling ON + revert OFF on the 06 preset bit-identical to the 06 batch's render field (labeling is pure analysis); j0; m, n, o, i, j, k, l, b per field. Batch: BatchRoot(7, "region_labeling") — exactly 4 plates (three thresholds on 1063685222, threshold_mid on 20260821, the table's most-natural-islands seed), each with grayscale / .f32 / relief / the labeled-regions overlay / the tag overlay, plus count_size_table.md/.csv. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013EY3ZTF6NwzF8ukBHQXSK7
343 lines
16 KiB
C#
343 lines
16 KiB
C#
using System.Collections.Generic;
|
||
using Godot;
|
||
using IslaApocalypse.Core;
|
||
|
||
namespace IslaApocalypse.Tools
|
||
{
|
||
/// <summary>
|
||
/// ⭐⭐ PASS 2a — the redistribution curve and the shelf detail, applied per column, producing the
|
||
/// TWO HEIGHT FIELDS. Ported from the reference's <c>MapGenerator.GenerateTopography</c> pass-2
|
||
/// loop (<c>Tools/Scripts/MapGenerator.cs</c> ~:692-735 at tag <c>pre-rewrite-reference</c>,
|
||
/// commit <c>ab78883</c>). → D-050.
|
||
///
|
||
/// ═══ WHAT RUNS HERE, AND WHAT DELIBERATELY DOES NOT ═══
|
||
///
|
||
/// The reference's pass 2 is three sub-passes in a fixed order:
|
||
///
|
||
/// 2a curve + detail ← THIS FILE
|
||
/// 2b hydraulic erosion (render map only) — a later chat2 task
|
||
/// 2c the crater carve (both maps, last word) — a later chat2 task
|
||
///
|
||
/// Only 2a is ported. The ORDER matters and is recorded here so the later two land in the right
|
||
/// place: erosion runs AFTER detail and BEFORE the carve, and the carve stays last because it is
|
||
/// the final authority on its own terrain.
|
||
///
|
||
/// ═══ ⚠ THE PASS-1/PASS-2 BOUNDARY IS HARD, NOT AN INTERLEAVE ═══
|
||
///
|
||
/// The curve's summit spike maps <c>[K6, hMaxSeed]</c> onto the peak band, so no pixel can be
|
||
/// curved until every pixel has been scanned. <see cref="Pass1Result.HMaxSeed"/> carries that
|
||
/// number across the boundary, and <c>AssertMonotonic</c> runs in between — after the max is
|
||
/// known, before the first column is shaped.
|
||
///
|
||
/// ═══ ⚠ ONE DELIBERATE DEVIATION FROM THE REFERENCE, AND WHY ═══
|
||
///
|
||
/// The reference shaped IN PLACE: <c>_heightMap</c> held pass-1 raw, then pass 2 overwrote it
|
||
/// column by column. This port leaves <see cref="Pass1Result.Height"/> untouched and allocates
|
||
/// the render field.
|
||
///
|
||
/// The arithmetic is identical — every column still reads <c>raw</c> and writes <c>curvedH</c>.
|
||
/// What changes is that the raw field SURVIVES the pass, which is what lets oracle checks (a) and
|
||
/// (b) compare against it directly instead of regenerating and trusting that the regeneration
|
||
/// matched. An invariant you can check by construction beats one you have to believe.
|
||
///
|
||
/// (With the curve OFF nothing is allocated at all and both fields alias the pass-1 array,
|
||
/// exactly as the reference did — see <see cref="Pass2Result.FieldsAreAliased"/>.)
|
||
/// </summary>
|
||
public static class Shaping
|
||
{
|
||
/// <summary>
|
||
/// Apply pass 2a to a pass-1 field.
|
||
///
|
||
/// ⚠ Pure with respect to <paramref name="p1"/>: nothing here writes to its arrays.
|
||
/// </summary>
|
||
public static Pass2Result Shape(Pass1Result p1, TerrainGenConfig cfg)
|
||
{
|
||
ulong t0 = Time.GetTicksMsec();
|
||
|
||
int mapSize = p1.MapSize;
|
||
var notes = new List<string>();
|
||
|
||
// ═══ THE OFF PATH — the A/B control ═══
|
||
//
|
||
// Nothing to separate, so nothing is allocated: both fields reference the pass-1 array,
|
||
// as the reference's `_heightMapClassify = (_curveOn || _erosionOn) ? new[…] : _heightMap`
|
||
// did. This path must be BIT-IDENTICAL to Phase 1's output — oracle (a).
|
||
if (!cfg.Curve)
|
||
{
|
||
notes.Add("[Shaping] curve OFF — render and classify alias the pass-1 field (the A/B control).");
|
||
// ⚠ Detail REQUIRES the curve — it slides the curve's KNOTS, so there is nothing to
|
||
// warp without one. The reference said so out loud rather than silently no-op'ing,
|
||
// because a dial that does nothing is worth a line in the log.
|
||
if (cfg.ShelfDetail)
|
||
notes.Add("[Shaping] shelf detail requested but the curve is off — no-op (detail warps the curve's knots).");
|
||
return new Pass2Result(mapSize, p1.Seed, p1.Height, p1.Height,
|
||
curveOn: false, detailOn: false, curveModeLabel: "off", variantLabel: cfg.VariantLabel, continuous: null,
|
||
knots: null, anchors: null, hMaxSeed: p1.HMaxSeed,
|
||
edgeAmpRaw: 0f, maxEdgeShiftRaw: 0f, hMin: p1.HMinSeed, hMax: p1.HMaxSeed,
|
||
elapsedMs: Time.GetTicksMsec() - t0, notes: notes,
|
||
isIsland: p1.IsIsland, islandHemisphere: p1.IslandHemisphere);
|
||
}
|
||
|
||
// ═══ WHICH CURVE (chat2/02) ═══
|
||
//
|
||
// Staircase falls through to the faithful task-01 path below, UNTOUCHED — it is the
|
||
// control, and controls do not get refactored while they are being compared against.
|
||
if (cfg.CurveMode == CurveModeKind.Continuous)
|
||
return ShapeContinuous(p1, cfg, notes, t0);
|
||
if (cfg.CurveMode == CurveModeKind.LiftedWrong)
|
||
return ShapeLifted(p1, cfg, notes, t0);
|
||
|
||
CurveKnots knots = cfg.Knots;
|
||
CurveAnchors anchors = cfg.Anchors;
|
||
GenerationScale scale = cfg.Scale;
|
||
|
||
// ═══ THE MODULATION FIELDS ═══
|
||
//
|
||
// Three for the curve (bench anchor, plateau anchor, shelf strength), two for detail
|
||
// (micro-relief, edge warp). Each is decorrelated by SEED OFFSET and sampled at the bare
|
||
// (x, y) — there are no coordinate offsets in this path to normalize. → TerrainNoise.
|
||
FastNoiseLite benchNoise = TerrainNoise.CreateModulation(p1.Seed, anchors.BenchSeedOffset, anchors.ElevFreqPerMapWidth, scale);
|
||
FastNoiseLite plateauNoise = TerrainNoise.CreateModulation(p1.Seed, anchors.PlateauSeedOffset, anchors.ElevFreqPerMapWidth, scale);
|
||
FastNoiseLite strengthNoise = TerrainNoise.CreateModulation(p1.Seed, anchors.StrengthSeedOffset, anchors.StrengthFreqPerMapWidth, scale);
|
||
|
||
// The curve-off case already returned above, so detail is simply on-or-off from here.
|
||
bool detailOn = cfg.ShelfDetail;
|
||
|
||
FastNoiseLite reliefNoise = null, edgeNoise = null;
|
||
float reliefAmpRaw = 0f, edgeAmpRaw = 0f;
|
||
float maxEdgeShiftRaw = TerrainDetailPass.MaxEdgeShift(knots);
|
||
|
||
if (detailOn)
|
||
{
|
||
reliefNoise = TerrainNoise.CreateModulation(p1.Seed, TerrainDetailPass.ReliefSeedOffset, TerrainDetailPass.ReliefFreqPerMapWidth, scale);
|
||
edgeNoise = TerrainNoise.CreateModulation(p1.Seed, TerrainDetailPass.EdgeSeedOffset, TerrainDetailPass.EdgeFreqPerMapWidth, scale);
|
||
|
||
reliefAmpRaw = WorldScale.RawFromMetres(Mathf.Max(cfg.ShelfReliefAmpM, 0f));
|
||
edgeAmpRaw = WorldScale.RawFromMetres(Mathf.Max(cfg.ShelfEdgeVariationM, 0f));
|
||
|
||
// ⚠ THE CLAMP IS LOUD. The warp is bounded to the largest shift that keeps the knot
|
||
// set strictly ordered, so monotonicity can never become a tuning question — but a
|
||
// silently ignored dial is worse than a refused one, because the developer A/Bs a
|
||
// number that never reached the terrain.
|
||
if (edgeAmpRaw > maxEdgeShiftRaw)
|
||
{
|
||
notes.Add($"[Shaping] ⚠ ShelfEdgeVariation {cfg.ShelfEdgeVariationM:F2} m exceeds this knot set's " +
|
||
$"safe bound {WorldScale.MetresFromRaw(maxEdgeShiftRaw):F2} m — CLAMPING.");
|
||
edgeAmpRaw = maxEdgeShiftRaw;
|
||
}
|
||
|
||
notes.Add($"[Shaping] detail v{TerrainDetailPass.Version}: relief ±{cfg.ShelfReliefAmpM:F1} m @ " +
|
||
$"{TerrainDetailPass.ReliefFreqPerMapWidth:F0}/map, edge warp " +
|
||
$"±{WorldScale.MetresFromRaw(edgeAmpRaw):F2} m of INPUT height @ " +
|
||
$"{TerrainDetailPass.EdgeFreqPerMapWidth:F0}/map (bound " +
|
||
$"{WorldScale.MetresFromRaw(maxEdgeShiftRaw):F2} m).");
|
||
}
|
||
|
||
// ═══ ⭐ THE MONOTONICITY PROOF — between the passes, before the first column ═══
|
||
notes.Add(HeightCurve.AssertMonotonic(p1.HMaxSeed, knots, anchors, detailOn ? edgeAmpRaw : 0f));
|
||
|
||
// ═══ THE CRATER SEAM — INERT THIS PHASE ═══
|
||
//
|
||
// No crater exists yet. CraterRadius defaults to 0, which makes CraterDetailWeight return
|
||
// 1 everywhere: detail applies unmasked, and the distance is not even computed. Ported
|
||
// now because the exclusion is part of THIS pass's contract — bolting it on after the
|
||
// carve arrives is exactly how the reference's 532-px below-sea bug happened.
|
||
float craterRadius = cfg.CraterRadius;
|
||
bool craterActive = craterRadius > 0f;
|
||
var craterCentre = new Vector2(cfg.CraterCenterX, cfg.CraterCenterY);
|
||
|
||
var height = new float[mapSize, mapSize];
|
||
var classify = new float[mapSize, mapSize];
|
||
|
||
float hMin = float.MaxValue, hMax = float.MinValue;
|
||
|
||
for (int x = 0; x < mapSize; x++)
|
||
{
|
||
for (int y = 0; y < mapSize; y++)
|
||
{
|
||
float raw = p1.Height[x, y];
|
||
|
||
// ⭐ THE CLASSIFY FIELD IS THE RAW FIELD. Not "approximately", not "before most
|
||
// things" — bit-for-bit, and asserted as such by oracle (b).
|
||
classify[x, y] = raw;
|
||
|
||
// ── the per-column shelf modulation (reference v4) ──
|
||
// Anchors and strength come from three very-low-frequency fields, so the bench
|
||
// and plateau elevations drift across the island instead of being one global
|
||
// terrace. Amplitudes are bounded and every extreme is swept by AssertMonotonic,
|
||
// so ordering safety is by construction rather than by hope.
|
||
float benchLo = anchors.BenchBase + benchNoise.GetNoise2D(x, y) * anchors.BenchAmp;
|
||
float plateauLo = anchors.PlateauBase + plateauNoise.GetNoise2D(x, y) * anchors.PlateauAmp;
|
||
float shelfSpan = HeightCurve.ShelfSpan((strengthNoise.GetNoise2D(x, y) + 1f) * 0.5f, anchors);
|
||
|
||
// ── detail yields to the crater (inert until the carve lands) ──
|
||
float wCrater = 0f;
|
||
if (detailOn)
|
||
{
|
||
wCrater = craterActive
|
||
? TerrainDetailPass.CraterDetailWeight(
|
||
new Vector2(x, y).DistanceTo(craterCentre), craterRadius)
|
||
: 1f;
|
||
}
|
||
|
||
// ── PASS B: the knot-block warp. Slides K3/K4/K5 for THIS column. ──
|
||
float edgeShift = detailOn ? edgeNoise.GetNoise2D(x, y) * edgeAmpRaw * wCrater : 0f;
|
||
|
||
float curvedH = HeightCurve.Apply(raw, p1.HMaxSeed,
|
||
benchLo, shelfSpan, plateauLo, shelfSpan, knots, anchors, edgeShift);
|
||
|
||
// ── PASS A: the micro-relief skin, on the shelves only ──
|
||
// ⚠ Fed the SAME edgeShift, so the skin follows the shelf wherever pass B moved
|
||
// its boundary. Passing 0 here would put the texture on the wrong ground.
|
||
if (detailOn && wCrater > 0f)
|
||
{
|
||
float wShelf = TerrainDetailPass.ShelfWeight(raw, knots, edgeShift);
|
||
if (wShelf > 0f)
|
||
curvedH += reliefNoise.GetNoise2D(x, y) * reliefAmpRaw * wShelf * wCrater;
|
||
}
|
||
|
||
if (curvedH < hMin) hMin = curvedH;
|
||
if (curvedH > hMax) hMax = curvedH;
|
||
height[x, y] = curvedH;
|
||
}
|
||
}
|
||
|
||
// ⚠ THE REFERENCE'S PASS 2 CONTINUES HERE with erosion (~:737-805, render map only) and
|
||
// then the crater carve (~:807-833, both maps, last word). Both DEFERRED to later chat2
|
||
// tasks. The classify field above is finalized bar the carve — which is exactly the
|
||
// property that makes it an oracle.
|
||
|
||
return new Pass2Result(mapSize, p1.Seed, height, classify,
|
||
curveOn: true, detailOn: detailOn, curveModeLabel: "staircase", variantLabel: cfg.VariantLabel, continuous: null,
|
||
knots: knots, anchors: anchors, hMaxSeed: p1.HMaxSeed,
|
||
edgeAmpRaw: edgeAmpRaw, maxEdgeShiftRaw: maxEdgeShiftRaw, hMin: hMin, hMax: hMax,
|
||
elapsedMs: Time.GetTicksMsec() - t0, notes: notes,
|
||
isIsland: p1.IsIsland, islandHemisphere: p1.IslandHemisphere);
|
||
}
|
||
|
||
/// <summary>
|
||
/// ⭐ THE CONTINUOUS MODE (chat2/02): the staircase's toe+red lowland preserved bit-for-bit,
|
||
/// everything above it replaced by one smooth monotone Fritsch–Carlson climb.
|
||
/// → <see cref="ContinuousCurve"/> for the shape and its refusals.
|
||
///
|
||
/// ═══ ⚠ WHY THIS LOOP IS SO MUCH SIMPLER THAN THE STAIRCASE'S ═══
|
||
///
|
||
/// No bench/plateau/strength modulation fields — the anchors they modulated are dropped.
|
||
/// No shelf detail — the flat benches it existed to de-slab no longer exist above the
|
||
/// lowlands, and the preserved lowland never needed it. Micro-relief/roughness on the
|
||
/// continuous upper grade is DEFERRED: erosion is the real detail source, and painting
|
||
/// noise on the climb now would pre-judge what erosion should carve. So: one spline per
|
||
/// seed, one Apply per column, and the classify field untouched as always.
|
||
/// </summary>
|
||
private static Pass2Result ShapeContinuous(Pass1Result p1, TerrainGenConfig cfg,
|
||
List<string> notes, ulong t0)
|
||
{
|
||
int mapSize = p1.MapSize;
|
||
CurveKnots knots = cfg.Knots;
|
||
CurveAnchors anchors = cfg.Anchors;
|
||
|
||
// Same per-seed summit normalization as the staircase — the pass-1/pass-2 boundary
|
||
// stays hard for the same reason.
|
||
float spikeMax = HeightCurve.EffectiveSpikeMax(p1.HMaxSeed, knots, anchors);
|
||
|
||
// ⭐ CALIBRATED when a measurement is supplied (chat2/03 — the staircase's mountain with
|
||
// the terraces melted out), ANALYTIC otherwise (chat2/02's feather/drama curve, kept as
|
||
// the "before" contrast). Both throw and refuse rather than degrade; the tool's _Ready
|
||
// catches and Quit(2)s.
|
||
var curve = cfg.ClimbCalibration != null
|
||
? ContinuousCurve.BuildCalibrated(knots, anchors, spikeMax,
|
||
cfg.LowlandCeilingM, cfg.ClimbCalibration)
|
||
: ContinuousCurve.Build(knots, anchors, spikeMax,
|
||
cfg.LowlandCeilingM, cfg.ClimbFeather, cfg.SummitDrama);
|
||
|
||
// Monotone by construction — and proven anyway, per seed, because "cannot fail" is
|
||
// exactly the claim worth a millisecond of checking.
|
||
notes.Add(curve.AssertStrictlyIncreasing());
|
||
notes.Add($"[Shaping] continuous: {curve.DescribeControlPoints()}");
|
||
|
||
if (cfg.ShelfDetail)
|
||
notes.Add("[Shaping] ⚠ shelf detail requested but FORCED OFF in continuous mode — the flat " +
|
||
"benches it de-slabbed no longer exist, and detail on the climb is deferred to erosion.");
|
||
|
||
var height = new float[mapSize, mapSize];
|
||
var classify = new float[mapSize, mapSize];
|
||
float hMin = float.MaxValue, hMax = float.MinValue;
|
||
|
||
for (int x = 0; x < mapSize; x++)
|
||
{
|
||
for (int y = 0; y < mapSize; y++)
|
||
{
|
||
float raw = p1.Height[x, y];
|
||
classify[x, y] = raw; // ⭐ the oracle field, as always
|
||
|
||
float h = curve.Apply(raw); // sea identity + lowland delegation
|
||
// + climb + tail, all inside
|
||
if (h < hMin) hMin = h;
|
||
if (h > hMax) hMax = h;
|
||
height[x, y] = h;
|
||
}
|
||
}
|
||
|
||
return new Pass2Result(mapSize, p1.Seed, height, classify,
|
||
curveOn: true, detailOn: false, curveModeLabel: "continuous", variantLabel: cfg.VariantLabel, continuous: curve,
|
||
knots: knots, anchors: anchors, hMaxSeed: p1.HMaxSeed,
|
||
edgeAmpRaw: 0f, maxEdgeShiftRaw: 0f, hMin: hMin, hMax: hMax,
|
||
elapsedMs: Time.GetTicksMsec() - t0, notes: notes,
|
||
isIsland: p1.IsIsland, islandHemisphere: p1.IslandHemisphere);
|
||
}
|
||
|
||
/// <summary>
|
||
/// ⚠⚠ THE DELIBERATELY-WRONG BOOKEND (chat2/02): an even linear remap of ALL land —
|
||
/// <c>[SEA, spikeMax] → [SEA, PEAK_CAP]</c>. Every land column above sea is lifted by the
|
||
/// same ×~1.5 factor, which hoists the entire island off its shoreline and destroys the
|
||
/// broad low plain the developer likes.
|
||
///
|
||
/// It exists so the preserved-lowland variants can be seen AGAINST the mistake — the batch's
|
||
/// contrast anchor, per the rev-3 task. It is not a candidate, it takes no knobs, and its
|
||
/// oracle-(d) FAILURE is expected and reported as confirmation, not as a bug.
|
||
/// </summary>
|
||
private static Pass2Result ShapeLifted(Pass1Result p1, TerrainGenConfig cfg,
|
||
List<string> notes, ulong t0)
|
||
{
|
||
int mapSize = p1.MapSize;
|
||
CurveKnots knots = cfg.Knots;
|
||
CurveAnchors anchors = cfg.Anchors;
|
||
|
||
float spikeMax = HeightCurve.EffectiveSpikeMax(p1.HMaxSeed, knots, anchors);
|
||
float scale = (anchors.PeakCap - anchors.Sea) / (spikeMax - anchors.Sea);
|
||
|
||
notes.Add($"[Shaping] ⚠⚠ LIFTED_WRONG: even remap [sea, {spikeMax:F4}] → [sea, {anchors.PeakCap:F4}] " +
|
||
$"(×{scale:F3} on every land height) — the WRONG direction, kept as the contrast bookend.");
|
||
|
||
var height = new float[mapSize, mapSize];
|
||
var classify = new float[mapSize, mapSize];
|
||
float hMin = float.MaxValue, hMax = float.MinValue;
|
||
|
||
for (int x = 0; x < mapSize; x++)
|
||
{
|
||
for (int y = 0; y < mapSize; y++)
|
||
{
|
||
float raw = p1.Height[x, y];
|
||
classify[x, y] = raw;
|
||
|
||
float h;
|
||
if (raw <= anchors.Sea) h = raw; // even the wrong direction keeps the
|
||
// coastline — sea identity is not optional
|
||
else if (raw >= spikeMax) h = anchors.PeakCap + (raw - spikeMax) * anchors.TailSlope;
|
||
else h = anchors.Sea + (raw - anchors.Sea) * scale;
|
||
|
||
if (h < hMin) hMin = h;
|
||
if (h > hMax) hMax = h;
|
||
height[x, y] = h;
|
||
}
|
||
}
|
||
|
||
return new Pass2Result(mapSize, p1.Seed, height, classify,
|
||
curveOn: true, detailOn: false, curveModeLabel: "lifted_WRONG", variantLabel: cfg.VariantLabel, continuous: null,
|
||
knots: knots, anchors: anchors, hMaxSeed: p1.HMaxSeed,
|
||
edgeAmpRaw: 0f, maxEdgeShiftRaw: 0f, hMin: hMin, hMax: hMax,
|
||
elapsedMs: Time.GetTicksMsec() - t0, notes: notes,
|
||
isIsland: p1.IsIsland, islandHemisphere: p1.IslandHemisphere);
|
||
}
|
||
}
|
||
}
|