Ports the ROUTING PORTION of the reference's RiverCarvePass (RouteToOcean, the
routed/lake-ender sort, SmoothCourse). NOT CarveRiver (bed stamp) and NOT
AddSteppedWater (water bodies) — those are later tasks.
RED LINE: no height mutated, no water filled, nothing carved. Asserted per seed
by an FNV digest of both height fields before/after routing.
- RiverRouting: deterministic LOWGROUND Dijkstra, uphill penalised so a route may
cross the basin rim, empty-list-on-no-path. Effective == declared constants
(verified: private const, no ConfigManager key, no [Export] in the reference).
- The sort is the REFERENCE's — Kind = basinHasLake ? lake-ender : routed. The
task's stated "a path exists -> routed" cannot discriminate: on an 8-connected
grid a path to the ocean always exists, confirmed empirically (43/43 probes
reached). The ocean route is probed for every giant anyway, so the missing
affordability threshold is reported as a number rather than guessed.
- RegionLabeling.SignificantWaterMask: interim substitute for v2's missing
water-bodies table — 8-connected classify-water components >= 20,000 px.
- RiverCandidates: the candidate enumeration extracted out of RiverPromotionTool
so routing ranks the identical set the count gate was judged on. Behaviour
neutral — rivers/02b's twelve plates are byte-identical across the extraction.
- DrainageRenderer.RoutedMix: three classes, with each routed river's added
lowland reach and the rim it crossed drawn distinctly from its natural stem.
Taste gate: no count, no K, no style, no default set.
A re-selection over rivers/02's candidates, not a new analysis: DrainageAnalysis is
reused untouched and the 8-seed distribution sweep is not re-run.
- RiverPromotionTool: ISLA_PROMOTE_MODE=composition builds both compositions at one
fixed total — PURE (top N by drainage, terminus irrelevant) and QUOTA (the K largest
sea-reaching forced in + the N-K largest endorheic). Deterministic; shortfall is
handled and flagged rather than back-filled. BindCourses now takes an explicit
candidate set, because a forced sea river can sit far below rank N (measured: #29).
- DrainageRenderer.RiverComposition: the composition plate, at ONE ABSOLUTE
width->drainage constant (1/180 px per sqrt(drainage px)) shared across both plates
and all seeds — per-plate normalisation cannot answer "is this river thin?" — plus
per-river drainage/rank labels with collision-avoided placement.
- ToolingPaths.BatchRoot: additive overload for a lettered sub-task, so 02b writes to
02b_composition instead of claiming task 03's number.
Taste gate: no N, no K, no default set anywhere. Nothing carved, no routing, and
Giant.ProvisionalRoute is still never drawn.
Choose the river count against the terrain that actually exists. The M3 count of
3 was tuned on topography the southern stretch (D-065) and coastal fragmentation
(D-063) have since replaced, and TrunkCount/GiantCount/EndorheicMaxCount = 3 are
LEAN REPORTING CAPS, not a statement about this island.
UNIFIED RANKING. The reference promoted from two lists with two quotas — N sea
trunks, N endorheic giants — which encodes the assumption that reaching the sea
is what makes a drainage a river. This terrain does not satisfy it: 54-78% of
land drains inland across the gallery, 106-121 terminal basins per seed. So
selection is unified — every major drainage ranked by contributing-cell count in
ONE list, top N promoted, and the sea/endorheic split FALLS OUT. A quota would
have promoted small coastal drainages over far larger inland ones purely because
of where they end. Deliberate departure from the reference's structure (D-050
noted, approved in chat). Only SELECTION is unified: the per-terminus tag is
retained per river because rivers/03's routing branches on it, and Trunk/Giant
are untouched.
The metric is the same unit on both sides, and that is now ASSERTED rather
than argued: sea Acc and endorheic BasinInflow are both counts of contributing
land cells on the same D8 field, and every land cell has exactly one
destination, so Σ sea Acc + Σ BasinInflow + unrouted == LandCells exactly. The
tool refuses to rank unless it holds, per seed. It held on all 8.
DrainageAnalysis.cs is NOT modified. The complete candidate set is re-derived
from state it already exposes (Dir/Acc/BasinId/BasinInflow/FullFilled); the
three internal per-basin values that are not exposed are exactly reconstructible
because terminal basins are reverted to the real surface while FullFilled keeps
the fill. Only REPORTING caps were raised, and only so the analysis's own
TraceStem produces a real upland course per promotable candidate.
EndorheicMinDepthM/MinAreaPx were left alone — they decide which depressions
BECOME terminal basins, i.e. they define the routing surface itself, and a count
chosen on a moved surface would be a count for terrain that does not exist.
TWO FINDINGS.
1. THE TERRAIN HAS NO NATURAL COUNT. The knee (largest ratio between
consecutive ranks) lands at rank 19/7/15/5/4/12/3/8 across the eight
gallery seeds at ratios of 1.33-1.57x, with one uncorroborated 2.37x. A
break that wanders across the whole plausible range at that strength is not
a break — on a log axis the candidates fall on a near-straight line.
N is a DESIGN choice, and the diagnostic says so instead of inventing one.
2. THE ISLAND'S MAJOR RIVERS ARE INLAND RIVERS. Six of eight seeds have ZERO
sea-reaching drainages in their top 8; the median at N=16 is one; one seed
has none at 16. On the primary seed the largest sea drainage ranks 11th at
736,088 px against 2,329,573 px for the largest endorheic — 3.2x. (That
outlet is exactly batch 12's recorded top trunk, to the cell.)
A concern I raised and then CLOSED by measuring: MinOutletSeparationPx is a
plain Euclidean test with no notion of which landmass a coastline belongs to, so
on a fragmented archipelago it could suppress an island's only river. Measured:
0-6 cross-landmass suppressions per seed, and — decisively — the largest
suppressed above-floor outlet anywhere is 209,611 px against a smallest ladder
cutoff of 439,592 px, so ZERO suppressed outlets clear any rung on any seed. The
rule provably cannot have altered the ladder. Real but harmless; flagged for
rivers/03, not a blocker. The rule was NOT changed — it belongs to the analysis,
and moving it would move the candidate set the developer is being asked to judge.
Also found: the params are ABSOLUTE pixel counts, so this analysis is only valid
at the size they were tuned for. At 1024 nothing qualifies as endorheic (the
whole endorheic half cannot be exercised) and separation suppresses 15,043 of
15,048 outlets. The tool now refuses loudly to have a non-8192 run read as a
count decision. Flagged for rivers/03: these want to become scale-free fractions
as MinLandComponentFrac already is.
Giant.ProvisionalRoute is never rendered — the "comb" is rivers/03's to replace,
and drawing it would make a count judgment look like a finished network. The
plates draw the real erosion-carved upland stems. Endorheic rivers are marked at
where their stem POOLS, not at the basin's deepest cell: the analysis
distinguishes these deliberately and they sit up to 395 px apart, which drew
every stem detached from its own endpoint until it was fixed.
TASTE GATE: 8/12/16 are presented and nothing is decided. No count is chosen, no
default is set, and neither TerrainGenConfig nor DrainageAnalysis.Params changed.
-> XX_Human/output/rivers/02_promotion.report.md
-> batches/rivers/02_promotion/INDEX.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhkXBQh2tDmcWKpXYcj8vj