The three corner fixes (both presets): riser endpoint slope floors
0.2 -> 0.1 (0.1u + 0.9*smoothstep — climbs decelerate into shelves and
accelerate out); summit-spike base floor 0.1 -> 0.05
(0.05u + 0.95*u^4 — no hard skirt under the peaks); SHELF_SPAN_MIN
2 m -> 6 m (pronounced shelves keep a gentle tilt).
Knots are preset-parameterized (CurveKnots, explicit parameter — pure,
D-035), calibrated from the same pooled batch-04 CDF:
COMPACT P65/77/84/88/94/97 -> 65/12/7/4/6/3/3 land split
BALANCED P60/73/83/88/96/99 -> 60/13/10/5/8/3/1
Gate: "off"|"v5-compact"|"v5-balanced" (batch-only tri-state;
interim default v5-balanced pending the developer's verdict — the
winner becomes plain "v5" in a follow-up commit); v1-v4 retired
loudly. Assertion runs per preset (8 corners x per-seed spikeMax; the
lowered floors stay strictly positive). TCRV v5 extension (+9 B):
preset id + K5/K6 — the effective curve is unambiguous from the
record; old records parse (version-dispatched); harness compares.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per the task-07 gate: the terraces work but uniform anchors put a flat
ring at exactly 100 m and 220 m on every mountain. v4 keeps v3's
structure (frozen toe/rise, three risers, per-seed u4 spike to 420 m)
and turns the shelf anchors into smooth spatial fields: bench
100+-12 m and plateau 220+-20 m via two decorrelated very-low-freq
Simplex fields (~3 undulations per island width), plus a strength
field (~5/island) blending each shelf's output span between ~2 m
(pronounced flat) and ~25 m (barely a hint) — the optional strength
modulation shipped, ordering-safe by construction (worst-case bench
top 0.6962 < plateau min 0.9468). Field seeds derive from the RESOLVED
noise seed + fixed offsets (7101/7207/7303) — no config knob. Apply is
per-column with all modulated values as pure parameters (D-035); the
classify path never sees them.
Monotonicity assertion now sweeps all 8 modulation-extreme corners x
per-seed spikeMax. TerrainCurve gate "off"|"v4" (default v4);
v1-v3 retired loudly. TCRV extended (+36 B when version>=4:
amplitudes, span range, frequencies, seed offsets — length-framed
section makes the layout change safe; parser dispatches on the curve
version byte; harness compares the extension).
Note: MapGenerator.cs also carries the developer's editor whitespace
normalization (spaces->tabs, git diff -w empty before this change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per the task-05 hillshade-gate verdict: lowlands frozen (identity/toe/
rise/shoulder/plateau byte-unchanged); the spike now maps [t4,
hMaxSeed] — each seed's own raw pre-curve maximum, computed in a new
pass-1 over GenerateTopography — onto the peak band, so every island
reaches the ceiling (v1's pooled-max domain left mid-range seeds at
110–175 m). Spike stiffened to 0.1u + 0.9u^4; peak cap raised to
420 m above sea (1.82869). Degenerate near-flat guard: spike domain
floored at t4 + 0.01. The curve is now SEED-DEPENDENT: hMaxSeed is a
pure parameter (D-035), recorded in TCRV (field renamed
HMaxCal -> SpikeMax, same byte layout; v1 semantics = pooled max, v2 =
per-seed), and the monotonicity assertion runs per generation against
the effective curve. TerrainCurve gate: "off"|"v2" (default v2);
"v1" retired with a loud config error (task-05 blueprints are
regenerable). Classify path untouched — the biome/water oracle must
hold unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0_height: hypsometric storm-ladder tint x Lambert NW hillshade over
the curved heights, captured after topography in both curve modes —
relief is finally visible. Renumbers nothing.
TCRV (50 B, emitted when the curve is on): curve version + input knots
+ output bands, so blueprints are self-describing about the map their
heights went through (H7 spirit). Parsed into
WorldBlueprint.TerrainCurve (null = raw legacy profile); server treats
it as metadata. Round-trip harness compares it when present.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three new v2 tagged sections carrying the water-bodies stage output:
WBID (MapSize^2 u16 body ids; 0 none, 1 ocean, 2..N lakes), WBTB
(count-prefixed 20-byte records: id, type, provisional salinity,
transitional flat surface level, pixel count, centroid), WSRF
(MapSize^2 u16 quantized levels; 0 = no-water sentinel, 1 + L*32768
encoding, ~7.7 mm world resolution). Written only when the blueprint
carries water; absent on legacy re-encodes. Parser registers all
three (length + type checks) into new WorldBlueprint members —
consumed by nothing at runtime. Round-trip harness now compares the
water sections whenever the source carries them; v1-source baseline
stays green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IsWaterPixel/IsOceanPixel/IsLakePixel are the single per-pixel water
truth; AssignBiomesAndDraw's water branch now calls them (verbatim
rules — behavior-preserving, proven by the bit-identical-biomes oracle
at acceptance). New IdentifyWaterBodies stage between the masks and
biomes: ocean = body 1, lakes labeled 2..N with CalculateTrueOcean's
4-connectivity in deterministic scan order; one transitional surface
level per body (GetSeaLevel at the body centroid; ocean at map
centre). 0_water snapshot painted from the stage's own outputs.
Priority-flood pit-fill (heap + pit-queue variant) runs as validated
diagnostics only — serializes nothing, asserts filled>=original and
full-map non-ascending drainage (reverse BFS, no sampling), reports
closed-basin statistics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v2 tagged-section container (D-030): raw ISLA magic + u32 version gate,
[u32 tag][u64 length][payload] sections — params (resolved generation
inputs incl. impact centre), heights f32, biomes u8, towns with the
highway-node flag, four road tiers identified by tag not position.
- BlueprintFormat.cs: the single tag/constant registry.
- BlueprintWriter.WriteV2: blueprint-typed, callable outside a
generation run; generator and harness are both just callers.
- ExportMapData dual-writes: v2 under the primary seed name, legacy v1
beside it as _v1.dat (safety net; removal is a future task).
- MapDataParser: LoadMapDataFromPath entry point; v1 parse body
extracted intact as LoadV1 (v2 reader lands in the next commit).
- RoundTripHarness scene: load v1 -> write v2 -> load v2 -> semantic
equality, headless, seconds per cycle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>