Hydrology probes on the rivers v2 contract

- Ocean integrity fires on land columns near a mouth; shallow incision and bank continuity are gates
- Probe-local centerline and turn helpers replaced by production geometry
- v1 checks for ridge bores, endpoint ramps, terminal caps, transition recovery and pool lengths removed
This commit is contained in:
Brian Neumann-Fopiano
2026-09-01 21:39:46 -04:00
parent 0c317929f8
commit 3d96a8616a
4 changed files with 774 additions and 809 deletions
File diff suppressed because it is too large Load Diff
@@ -28,6 +28,9 @@ public final class HydrologyPlannerProbe {
private static final long[] DEFAULT_SEEDS = {1L, 19L, 52L, 77L, 331L, 501L, 712L, 992L}; private static final long[] DEFAULT_SEEDS = {1L, 19L, 52L, 77L, 331L, 501L, 712L, 992L};
private static final long SURFACE_INLAND_SEED = 7012L; private static final long SURFACE_INLAND_SEED = 7012L;
private static final long UNDERGROUND_INLAND_SEED = 7013L; private static final long UNDERGROUND_INLAND_SEED = 7013L;
private static final long COASTAL_SEED = 7014L;
static final Set<HydrologyFeatureType> REQUIRED_FEATURE_TYPES =
Set.copyOf(EnumSet.complementOf(EnumSet.of(HydrologyFeatureType.RIDGE_BORE)));
private static final HydrologyTileKey ORIGIN = new HydrologyTileKey(0, 0); private static final HydrologyTileKey ORIGIN = new HydrologyTileKey(0, 0);
private static final int WARM_SAMPLES = 2_048; private static final int WARM_SAMPLES = 2_048;
@@ -90,6 +93,7 @@ public final class HydrologyPlannerProbe {
collectAccepted(firstFeatures, surfaceInlandTile()); collectAccepted(firstFeatures, surfaceInlandTile());
collectAccepted(firstFeatures, undergroundInlandTile()); collectAccepted(firstFeatures, undergroundInlandTile());
collectAccepted(firstFeatures, surfaceMouthTile()); collectAccepted(firstFeatures, surfaceMouthTile());
collectAccepted(firstFeatures, coastalGrottoTile());
requireCompleteCoverage(firstFeatures.keySet()); requireCompleteCoverage(firstFeatures.keySet());
for (Map.Entry<HydrologyFeatureType, HydrologyFeatureRef> entry : firstFeatures.entrySet()) { for (Map.Entry<HydrologyFeatureType, HydrologyFeatureRef> entry : firstFeatures.entrySet()) {
@@ -134,6 +138,7 @@ public final class HydrologyPlannerProbe {
collectAccepted(coverage, surfaceInlandTile()); collectAccepted(coverage, surfaceInlandTile());
collectAccepted(coverage, undergroundInlandTile()); collectAccepted(coverage, undergroundInlandTile());
collectAccepted(coverage, surfaceMouthTile()); collectAccepted(coverage, surfaceMouthTile());
collectAccepted(coverage, coastalGrottoTile());
requireCompleteCoverage(coverage.keySet()); requireCompleteCoverage(coverage.keySet());
return Map.copyOf(coverage); return Map.copyOf(coverage);
} }
@@ -148,7 +153,7 @@ public final class HydrologyPlannerProbe {
} }
private static void requireCompleteCoverage(Set<HydrologyFeatureType> observed) { private static void requireCompleteCoverage(Set<HydrologyFeatureType> observed) {
EnumSet<HydrologyFeatureType> missing = EnumSet.allOf(HydrologyFeatureType.class); EnumSet<HydrologyFeatureType> missing = EnumSet.copyOf(REQUIRED_FEATURE_TYPES);
missing.removeAll(observed); missing.removeAll(observed);
if (!missing.isEmpty()) { if (!missing.isEmpty()) {
throw new IllegalStateException("Hydrology accepted-feature coverage is missing " + missing + "."); throw new IllegalStateException("Hydrology accepted-feature coverage is missing " + missing + ".");
@@ -175,9 +180,18 @@ public final class HydrologyPlannerProbe {
private static HydrologyTile surfaceMouthTile() { private static HydrologyTile surfaceMouthTile() {
return new HydrologyPlanner( return new HydrologyPlanner(
DEFAULT_SEEDS[0], COASTAL_SEED,
settings(false), settings(false),
terrain(DEFAULT_SEEDS[0]) coastalTerrain(false)
).plan(ORIGIN);
}
private static HydrologyTile coastalGrottoTile() {
return new HydrologyPlanner(
COASTAL_SEED,
settings(true),
coastalTerrain(true),
solidCaveView()
).plan(ORIGIN); ).plan(ORIGIN);
} }
@@ -230,9 +244,9 @@ public final class HydrologyPlannerProbe {
8, 8,
true, true,
96, 96,
8 8,
, HydrologyPlannerSettings.Banks.defaults()
HydrologyPlannerSettings.Banks.defaults()), ),
new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4), new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
undergroundDensity > 0D, undergroundDensity > 0D,
@@ -302,6 +316,57 @@ public final class HydrologyPlannerProbe {
}; };
} }
private static HydrologyTerrainSampler coastalTerrain(boolean cliffCoast) {
return (int x, int z) -> {
if (x >= 208) {
return oceanTerrain();
}
int height;
double slope;
if (x < 104) {
height = 100 - Math.floorDiv(x, 8);
slope = 1D;
} else if (cliffCoast) {
height = 84 - Math.floorDiv(x - 104, 26);
slope = 1D;
} else {
height = 79 - Math.floorDiv(x - 104, 12);
slope = x < 112 ? 4D : 1D;
}
boolean surfaceSource = x <= 40;
boolean undergroundSource = x <= 64;
return new HydrologyTerrainSample(
height,
slope,
false,
true,
height - 30,
height - 28,
true,
true,
surfaceSource,
surfaceSource,
undergroundSource,
undergroundSource,
0D,
surfaceSource ? 1D : 0D,
undergroundSource ? 1D : 0D,
1D,
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
"shore",
"bank",
"flooded",
List.of("water")
);
};
}
private static CaveVoxelView solidCaveView() { private static CaveVoxelView solidCaveView() {
return new CaveVoxelView() { return new CaveVoxelView() {
@Override @Override
@@ -397,9 +462,9 @@ public final class HydrologyPlannerProbe {
10, 10,
true, true,
96, 96,
10 10,
, HydrologyPlannerSettings.Banks.defaults()
HydrologyPlannerSettings.Banks.defaults()), ),
new HydrologyPlannerSettings.Hydraulics(12, 40, 1, 3, 8, 4), new HydrologyPlannerSettings.Hydraulics(12, 40, 1, 3, 8, 4),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
true, true,
@@ -357,8 +357,7 @@ public final class HydrologyPackProbeTest {
1337L, 1337L,
1L, 1L,
centerline, centerline,
thickChannel(centerline, 4, 32), thickChannel(centerline, 4, 32)
false
)); ));
assertTrue(result.gridLockedFraction() > 0.9D); assertTrue(result.gridLockedFraction() > 0.9D);
@@ -380,8 +379,7 @@ public final class HydrologyPackProbeTest {
1337L, 1337L,
2L, 2L,
centerline, centerline,
thickChannel(centerline, 4, 32), thickChannel(centerline, 4, 32)
false
)); ));
assertTrue(result.isolatedTurns() > 0); assertTrue(result.isolatedTurns() > 0);
@@ -390,126 +388,6 @@ public final class HydrologyPackProbeTest {
assertTrue(result.violations().contains("P95_TURN")); assertTrue(result.violations().contains("P95_TURN"));
} }
@Test
public void gradualSourceWidthRampPassesEndpointGate() {
List<HydrologyPoint> centerline = List.of(point(0, 0), point(80, 24));
HydrologyPackProbe.CourseMorphologyResult result =
HydrologyPackProbe.PublishedMorphologyMetrics.analyzeCourse(courseInput(
1337L,
3L,
centerline,
thickChannel(centerline, 4, 32),
false
));
assertEquals(0, result.hardEndpointRamps());
assertEquals(0, result.unexpectedLeaves());
assertFalse(result.violations().contains("HARD_ENDPOINT_RAMP"));
}
@Test
public void widenedSourceBasinFailsEndpointGate() {
List<HydrologyPoint> centerline = List.of(point(0, 0), point(80, 24));
HydrologyPackProbe.CourseMorphologyResult result =
HydrologyPackProbe.PublishedMorphologyMetrics.analyzeCourse(courseInput(
1337L,
7L,
centerline,
widenedSourceBasin(centerline, 4, 7, 32),
false
));
assertTrue(result.violations().contains("WIDENED_SOURCE_BASIN"));
}
@Test
public void fullWidthSourceCapFailsEndpointRampGate() {
List<HydrologyPoint> centerline = List.of(point(0, 0), point(80, 24));
HydrologyPackProbe.CourseMorphologyResult result =
HydrologyPackProbe.PublishedMorphologyMetrics.analyzeCourse(courseInput(
1337L,
4L,
centerline,
thickChannel(centerline, 4, 0),
false
));
assertTrue(result.hardEndpointRamps() > 0);
assertTrue(result.violations().contains("HARD_ENDPOINT_RAMP"));
}
@Test
public void fullWidthExposedTerminalFailsEndpointRampGate() {
List<HydrologyPoint> centerline = List.of(point(0, 0), point(80, 24));
HydrologyPackProbe.CourseMorphologyResult result =
HydrologyPackProbe.PublishedMorphologyMetrics.analyzeCourse(courseInput(
1337L,
5L,
centerline,
thickChannel(centerline, 4, 32),
true
));
assertEquals(1, result.hardEndpointRamps());
assertTrue(result.violations().contains("HARD_ENDPOINT_RAMP"));
}
@Test
public void clippedCourseDoesNotInventEndpointRampFailures() {
List<HydrologyPoint> centerline = List.of(point(0, 0), point(160, 48));
Set<Long> observedCells = thickChannel(
List.of(point(40, 12), point(120, 36)),
4,
0
);
HydrologyPackProbe.CourseMorphologyResult result =
HydrologyPackProbe.PublishedMorphologyMetrics.analyzeCourse(courseInput(
1337L,
8L,
centerline,
observedCells,
true
));
assertEquals(0, result.hardEndpointRamps());
assertFalse(result.violations().contains("HARD_ENDPOINT_RAMP"));
}
@Test
public void roundedExposedTerminalCapPassesCapGate() {
List<HydrologyPoint> centerline = List.of(point(0, 0), point(160, 0));
HydrologyPackProbe.CourseMorphologyResult result =
HydrologyPackProbe.PublishedMorphologyMetrics.analyzeCourse(courseInput(
1337L,
9L,
centerline,
terminalBasinChannel(centerline, 4, 7, 32, false),
true
));
assertEquals(0, result.clippedTerminalCaps());
assertTrue(result.terminalCapLength() >= 3D);
assertFalse(result.violations().contains("CLIPPED_TERMINAL_CAP"));
}
@Test
public void squareExposedTerminalCapFailsCapGateAfterGradualBasinRamp() {
List<HydrologyPoint> centerline = List.of(point(0, 0), point(160, 0));
HydrologyPackProbe.CourseMorphologyResult result =
HydrologyPackProbe.PublishedMorphologyMetrics.analyzeCourse(courseInput(
1337L,
10L,
centerline,
terminalBasinChannel(centerline, 4, 7, 32, true),
true
));
assertEquals(0, result.hardEndpointRamps());
assertEquals(1, result.clippedTerminalCaps());
assertEquals(0D, result.terminalCapLength(), 0D);
assertTrue(result.violations().contains("CLIPPED_TERMINAL_CAP"));
}
@Test @Test
public void configuredMinimumWidthRejectsNarrowOwnedInterior() { public void configuredMinimumWidthRejectsNarrowOwnedInterior() {
List<HydrologyPoint> centerline = List.of(point(0, 0), point(192, 0)); List<HydrologyPoint> centerline = List.of(point(0, 0), point(192, 0));
@@ -518,8 +396,7 @@ public final class HydrologyPackProbeTest {
1337L, 1337L,
11L, 11L,
centerline, centerline,
thickChannel(centerline, 1, 32), thickChannel(centerline, 1, 32)
false
)); ));
assertEquals(3, result.minimumInteriorWidth()); assertEquals(3, result.minimumInteriorWidth());
@@ -534,8 +411,7 @@ public final class HydrologyPackProbeTest {
1337L, 1337L,
12L, 12L,
centerline, centerline,
thickChannel(centerline, 2, 32), thickChannel(centerline, 2, 32)
false
)); ));
assertTrue(result.minimumInteriorWidth() >= 4); assertTrue(result.minimumInteriorWidth() >= 4);
@@ -544,32 +420,6 @@ public final class HydrologyPackProbeTest {
assertFalse(result.violations().contains("CONCAVE_WIDTH_TROUGH")); assertFalse(result.violations().contains("CONCAVE_WIDTH_TROUGH"));
} }
@Test
public void borePortalRunMarginsAreExcludedFromInteriorWidth() {
List<HydrologyPoint> centerline = List.of(point(0, 0), point(320, 0));
HashSet<Long> ownedCells = new HashSet<>(thickChannel(
List.of(point(0, 0), point(128, 0)),
2,
32
));
ownedCells.addAll(thickChannel(
List.of(point(192, 0), point(320, 0)),
2,
32
));
HydrologyPackProbe.CourseMorphologyResult result =
HydrologyPackProbe.PublishedMorphologyMetrics.analyzeCourse(courseInput(
1337L,
15L,
centerline,
ownedCells,
false
));
assertTrue(result.minimumInteriorWidth() >= 4);
assertFalse(result.violations().contains("NARROW_INTERIOR_WIDTH"));
}
@Test @Test
public void sustainedMidCourseWidthTroughFailsConcavityGate() { public void sustainedMidCourseWidthTroughFailsConcavityGate() {
List<HydrologyPoint> centerline = List.of(point(0, 0), point(256, 0)); List<HydrologyPoint> centerline = List.of(point(0, 0), point(256, 0));
@@ -578,8 +428,7 @@ public final class HydrologyPackProbeTest {
1337L, 1337L,
13L, 13L,
centerline, centerline,
troughChannel(centerline, 5, 2, 96, 160), troughChannel(centerline, 5, 2, 96, 160)
false
)); ));
assertTrue(result.maximumWidthTroughDepthRatio() >= 0.35D); assertTrue(result.maximumWidthTroughDepthRatio() >= 0.35D);
@@ -597,17 +446,14 @@ public final class HydrologyPackProbeTest {
384, 384,
0D, 0D,
false, false,
true,
centerline, centerline,
terminalBasinChannel(centerline, 1, 2, 32, true) terminalBasinChannel(centerline, 1, 2, 32, true)
); );
HydrologyPackProbe.CourseMorphologyResult result = HydrologyPackProbe.CourseMorphologyResult result =
HydrologyPackProbe.PublishedMorphologyMetrics.analyzeCourse(input); HydrologyPackProbe.PublishedMorphologyMetrics.analyzeCourse(input);
assertEquals(0, result.clippedTerminalCaps());
assertEquals(0, result.minimumInteriorWidth()); assertEquals(0, result.minimumInteriorWidth());
assertEquals(0D, result.maximumWidthTroughDepthRatio(), 0D); assertEquals(0D, result.maximumWidthTroughDepthRatio(), 0D);
assertFalse(result.violations().contains("CLIPPED_TERMINAL_CAP"));
assertFalse(result.violations().contains("NARROW_INTERIOR_WIDTH")); assertFalse(result.violations().contains("NARROW_INTERIOR_WIDTH"));
assertFalse(result.violations().contains("CONCAVE_WIDTH_TROUGH")); assertFalse(result.violations().contains("CONCAVE_WIDTH_TROUGH"));
} }
@@ -620,7 +466,7 @@ public final class HydrologyPackProbeTest {
HydrologyPackProbe.SeedMorphologyResult result = HydrologyPackProbe.SeedMorphologyResult result =
HydrologyPackProbe.PublishedMorphologyMetrics.analyzeSeed( HydrologyPackProbe.PublishedMorphologyMetrics.analyzeSeed(
1337L, 1337L,
List.of(courseInput(1337L, 6L, centerline, ownedCells, false)) List.of(courseInput(1337L, 6L, centerline, ownedCells))
); );
assertTrue(result.unexpectedLeaves() > 0); assertTrue(result.unexpectedLeaves() > 0);
@@ -638,8 +484,192 @@ public final class HydrologyPackProbeTest {
assertFalse(HydrologyPackProbe.ShapeMetrics.ownsOceanTerrain(apron)); assertFalse(HydrologyPackProbe.ShapeMetrics.ownsOceanTerrain(apron));
assertFalse(HydrologyPackProbe.ShapeMetrics.ownsOceanTerrain(inertLayer)); assertFalse(HydrologyPackProbe.ShapeMetrics.ownsOceanTerrain(inertLayer));
assertTrue(HydrologyPackProbe.ShapeMetrics.ownsOceanTerrain(ownedTerrain)); assertTrue(HydrologyPackProbe.ShapeMetrics.ownsOceanTerrain(ownedTerrain));
assertTrue(HydrologyPackProbe.ShapeMetrics.ownsOceanTerrain(gradedLayer(70)));
assertFalse(HydrologyPackProbe.ShapeMetrics.mutatesOceanTerrain(54, 54)); assertFalse(HydrologyPackProbe.ShapeMetrics.mutatesOceanTerrain(54, 54));
assertTrue(HydrologyPackProbe.ShapeMetrics.mutatesOceanTerrain(54, 48)); assertTrue(HydrologyPackProbe.ShapeMetrics.mutatesOceanTerrain(54, 48));
assertFalse(HydrologyPackProbe.ShapeMetrics.mutatesOceanTerrain(54, 60));
}
@Test
public void oceanIntegrityFlagsOwningSurfaceLayersAndCutTerrain() {
HydrologyColumnSample untouchedOcean = new HydrologyColumnSample(
0, 0, 48, 63, true, "parent", List.of(surfaceLayer(true, false)));
HydrologyColumnSample cutLand = new HydrologyColumnSample(
0, 0, 80, 63, false, "parent", List.of(gradedLayer(74)));
assertFalse(HydrologyPackProbe.ShapeMetrics.ownsSurfaceTerrain(untouchedOcean));
assertTrue(HydrologyPackProbe.ShapeMetrics.carriesOceanApron(untouchedOcean));
assertEquals(48, untouchedOcean.terrainHeight());
assertFalse(HydrologyPackProbe.ShapeMetrics.mutatesOceanTerrain(
untouchedOcean.naturalHeight(), untouchedOcean.terrainHeight()));
assertTrue(HydrologyPackProbe.ShapeMetrics.ownsSurfaceTerrain(cutLand));
assertTrue(HydrologyPackProbe.ShapeMetrics.mutatesOceanTerrain(
cutLand.naturalHeight(), cutLand.terrainHeight()));
assertThrows(IllegalArgumentException.class, () -> new HydrologyColumnSample(
0, 0, 60, 63, false, "parent", List.of(gradedLayer(55))));
}
@Test
public void landColumnsCarryingAnOceanApronAreDetected() {
HydrologyColumnSample land = new HydrologyColumnSample(
0, 0, 80, 63, false, "parent", List.of(surfaceLayer(true, false)));
assertTrue(HydrologyPackProbe.ShapeMetrics.carriesOceanApron(land));
assertFalse(HydrologyPackProbe.ShapeMetrics.ownsSurfaceTerrain(land));
}
@Test
public void bankContinuityAllowsSingleBlockStepsAndRejectsLedges() {
assertEquals(0, HydrologyPackProbe.ShapeMetrics.bankContinuityViolations(
gradedRow(70, 71, 72)));
assertEquals(1, HydrologyPackProbe.ShapeMetrics.bankContinuityViolations(
gradedRow(70, 71, 74)));
}
@Test
public void bankContinuityIgnoresPairsAcrossTheWaterEdge() {
HydrologyColumnSample west = gradedColumn(-1, 0, 71);
HydrologyColumnSample channel = channelColumn(0, 0, 66, 69);
HydrologyColumnSample east = gradedColumn(1, 0, 78);
assertEquals(0, HydrologyPackProbe.ShapeMetrics.bankContinuityViolations(
new RiverFootprint(Map.of(
RiverFootprint.pack(west.x(), west.z()), west,
RiverFootprint.pack(channel.x(), channel.z()), channel,
RiverFootprint.pack(east.x(), east.z()), east
))));
}
@Test
public void shapeMetricsEmitFormattedMachineAndGateLines() {
HydrologyPackProbe.ShapeMetrics metrics = new HydrologyPackProbe.ShapeMetrics();
String machineLine = metrics.machineLine();
assertTrue(machineLine.startsWith("IRIS_HYDROLOGY_PACK_SHAPE version=15 "));
assertTrue(machineLine.contains("shallow_incision_courses=0"));
assertTrue(machineLine.contains("ocean_apron_land_columns=0"));
assertTrue(machineLine.contains("bank_step_violations=0"));
}
@Test
public void emptyShapeMetricsReportOneLinePerGate() {
HydrologyPackProbe.ShapeMetrics metrics = new HydrologyPackProbe.ShapeMetrics();
List<HydrologyPackProbe.ShapeMetrics.Gate> gates = metrics.gates(List.of());
List<String> names = new ArrayList<>();
for (HydrologyPackProbe.ShapeMetrics.Gate gate : gates) {
names.add(gate.name());
}
assertEquals(
List.of(
"network_presence",
"channel_width",
"outlet_groups",
"terrain_support",
"ocean_integrity",
"bank_containment",
"bank_continuity",
"surface_incision",
"head_consistency",
"published_morphology"
),
names
);
for (HydrologyPackProbe.ShapeMetrics.Gate gate : gates) {
assertFalse(gate.counters().isBlank());
}
assertFalse(gate(gates, "network_presence").passed());
assertTrue(gate(gates, "ocean_integrity").passed());
assertTrue(gate(gates, "bank_continuity").passed());
assertTrue(gate(gates, "surface_incision").passed());
assertTrue(gate(gates, "ocean_integrity").counters().contains("ocean_apron_land_columns=0"));
assertTrue(gate(gates, "bank_continuity").counters().contains("bank_step_violations=0"));
assertTrue(gate(gates, "surface_incision").counters().contains("shallow_incision_courses=0"));
}
@Test
public void gateFailuresNameEveryFailedGate() {
HydrologyPackProbe.ShapeMetrics metrics = new HydrologyPackProbe.ShapeMetrics();
IllegalStateException failure = assertThrows(
IllegalStateException.class,
metrics::requireOrganicSurfaceNetwork
);
assertTrue(failure.getMessage().contains("network_presence"));
assertTrue(failure.getMessage().contains("published_morphology"));
}
private static HydrologyPackProbe.ShapeMetrics.Gate gate(
List<HydrologyPackProbe.ShapeMetrics.Gate> gates,
String name
) {
for (HydrologyPackProbe.ShapeMetrics.Gate gate : gates) {
if (gate.name().equals(name)) {
return gate;
}
}
throw new IllegalStateException("Missing gate " + name + ".");
}
@Test
public void channelIncisionAveragesNaturalMinusTerrainOverChannelColumns() {
HydrologyColumnSample deep = channelColumn(0, 0, 74, 77);
HydrologyColumnSample shallow = channelColumn(1, 0, 79, 79);
HydrologyColumnSample bank = gradedColumn(2, 0, 79);
RiverFootprint footprint = new RiverFootprint(Map.of(
RiverFootprint.pack(deep.x(), deep.z()), deep,
RiverFootprint.pack(shallow.x(), shallow.z()), shallow,
RiverFootprint.pack(bank.x(), bank.z()), bank
));
List<HydrologyPackProbe.ShapeMetrics.ChannelIncision> incisions =
HydrologyPackProbe.ShapeMetrics.channelIncisions(footprint, Set.of(42L), 2, 10);
assertEquals(1, incisions.size());
HydrologyPackProbe.ShapeMetrics.ChannelIncision incision = incisions.getFirst();
assertEquals(42L, incision.courseId());
assertEquals(2, incision.columns());
assertEquals(3.5D, incision.meanIncision(), 0D);
assertEquals(1, incision.shallowColumns());
assertEquals(0, incision.excessiveColumns());
assertFalse(HydrologyPackProbe.ShapeMetrics.shallowMeanIncision(incision.meanIncision()));
}
@Test
public void channelIncisionBelowOneBlockFailsTheShallowGate() {
HydrologyColumnSample flat = channelColumn(0, 0, 80, 80);
RiverFootprint footprint = new RiverFootprint(Map.of(
RiverFootprint.pack(flat.x(), flat.z()), flat
));
List<HydrologyPackProbe.ShapeMetrics.ChannelIncision> incisions =
HydrologyPackProbe.ShapeMetrics.channelIncisions(footprint, Set.of(42L), 2, 10);
assertEquals(0D, incisions.getFirst().meanIncision(), 0D);
assertTrue(HydrologyPackProbe.ShapeMetrics.shallowMeanIncision(incisions.getFirst().meanIncision()));
}
@Test
public void channelIncisionIgnoresCoursesOutsideTheScope() {
HydrologyColumnSample deep = channelColumn(0, 0, 74, 77);
RiverFootprint footprint = new RiverFootprint(Map.of(
RiverFootprint.pack(deep.x(), deep.z()), deep
));
assertEquals(
List.of(),
HydrologyPackProbe.ShapeMetrics.channelIncisions(footprint, Set.of(7L), 2, 10)
);
}
@Test
public void shallowMeanIncisionGateRequiresOneBlockOfCut() {
assertTrue(HydrologyPackProbe.ShapeMetrics.shallowMeanIncision(0.75D));
assertFalse(HydrologyPackProbe.ShapeMetrics.shallowMeanIncision(1D));
assertFalse(HydrologyPackProbe.ShapeMetrics.shallowMeanIncision(2.5D));
} }
@Test @Test
@@ -673,39 +703,31 @@ public final class HydrologyPackProbeTest {
public void seedsWithoutSurfaceCoursesDoNotMaskPublishedFailures() { public void seedsWithoutSurfaceCoursesDoNotMaskPublishedFailures() {
HydrologyPackProbe.CourseMorphologyResult acceptedCourse = HydrologyPackProbe.CourseMorphologyResult acceptedCourse =
new HydrologyPackProbe.CourseMorphologyResult( new HydrologyPackProbe.CourseMorphologyResult(
642L, 5L, 20, 80D, 80D, 0, 0D, 0D, 0, 0D, 0, 642L, 5L, 20, 80D, 80D, 0, 0D, 0D, 0, 0D, 0, 0D, 0, List.of());
0, 0, 0D, 0, 0, 0D, 0, 0D, 0, 0D, 0, List.of());
HydrologyPackProbe.SeedMorphologyResult accepted = HydrologyPackProbe.SeedMorphologyResult accepted =
new HydrologyPackProbe.SeedMorphologyResult(642L, List.of(acceptedCourse), 0, List.of()); new HydrologyPackProbe.SeedMorphologyResult(642L, List.of(acceptedCourse), 0, List.of());
HydrologyPackProbe.SeedMorphologyResult empty = HydrologyPackProbe.SeedMorphologyResult empty =
new HydrologyPackProbe.SeedMorphologyResult(1337L, List.of(), 0, List.of()); new HydrologyPackProbe.SeedMorphologyResult(1337L, List.of(), 0, List.of());
IllegalStateException perSeedFailure = assertThrows(
IllegalStateException.class,
() -> HydrologyPackProbe.PublishedMorphologyMetrics.requireValidResults(List.of(accepted, empty))
);
assertTrue(perSeedFailure.getMessage().contains("seed=1337"));
IllegalStateException emptyFailure = assertThrows(
IllegalStateException.class,
() -> HydrologyPackProbe.PublishedMorphologyMetrics.requireValidResults(List.of(empty))
);
assertTrue(emptyFailure.getMessage().contains("NO_SURFACE_COURSES"));
HydrologyPackProbe.CourseMorphologyResult rejectedCourse = HydrologyPackProbe.CourseMorphologyResult rejectedCourse =
new HydrologyPackProbe.CourseMorphologyResult( new HydrologyPackProbe.CourseMorphologyResult(
52L, 5L, 20, 80D, 80D, 0, 0D, 0D, 0, 0D, 0, 52L, 5L, 20, 80D, 80D, 0, 0D, 0D, 0, 0D, 2, 0D, 0,
0, 0, 0D, 0, 0, 0D, 0, 0D, 2, 0D, 0,
List.of("NARROW_INTERIOR_WIDTH")); List.of("NARROW_INTERIOR_WIDTH"));
HydrologyPackProbe.SeedMorphologyResult rejected = HydrologyPackProbe.SeedMorphologyResult rejected =
new HydrologyPackProbe.SeedMorphologyResult( new HydrologyPackProbe.SeedMorphologyResult(
52L, List.of(rejectedCourse), 0, List.of("5:NARROW_INTERIOR_WIDTH")); 52L, List.of(rejectedCourse), 0, List.of("5:NARROW_INTERIOR_WIDTH"));
IllegalStateException failure = assertThrows(
IllegalStateException.class,
() -> HydrologyPackProbe.PublishedMorphologyMetrics.requireValidResults(
List.of(accepted, empty, rejected))
);
assertTrue(failure.getMessage().contains("seed=52")); assertEquals(
List.of(),
HydrologyPackProbe.PublishedMorphologyMetrics.failures(List.of(accepted))
);
assertTrue(HydrologyPackProbe.PublishedMorphologyMetrics.failures(List.of(accepted, empty))
.toString().contains("seed=1337"));
assertTrue(HydrologyPackProbe.PublishedMorphologyMetrics.failures(List.of(empty))
.toString().contains("NO_SURFACE_COURSES"));
assertTrue(HydrologyPackProbe.PublishedMorphologyMetrics.failures(List.of())
.toString().contains("NO_SURFACE_COURSES"));
assertTrue(HydrologyPackProbe.PublishedMorphologyMetrics
.failures(List.of(accepted, empty, rejected)).toString().contains("seed=52"));
} }
@Test @Test
@@ -722,7 +744,6 @@ public final class HydrologyPackProbeTest {
384, 384,
40D, 40D,
true, true,
false,
centerline, centerline,
thickChannel(centerline, 4, 24) thickChannel(centerline, 4, 24)
); );
@@ -738,8 +759,7 @@ public final class HydrologyPackProbeTest {
File reportDirectory = temporaryFolder.newFolder("hydrology-reports"); File reportDirectory = temporaryFolder.newFolder("hydrology-reports");
HydrologyPackProbe.CourseMorphologyResult course = HydrologyPackProbe.CourseMorphologyResult course =
new HydrologyPackProbe.CourseMorphologyResult( new HydrologyPackProbe.CourseMorphologyResult(
1337L, 6L, 3, 2D, 2D, 0, 0D, 0D, 0, 0D, 0, 1337L, 6L, 3, 2D, 2D, 0, 0D, 0D, 0, 0D, 0, 0D, 0, List.of());
0, 0, 0D, 0, 0, 0D, 0, 0D, 0, 0D, 0, List.of());
HydrologyPackProbe.SeedMorphologyResult result = HydrologyPackProbe.SeedMorphologyResult result =
new HydrologyPackProbe.SeedMorphologyResult(1337L, List.of(course), 0, List.of()); new HydrologyPackProbe.SeedMorphologyResult(1337L, List.of(course), 0, List.of());
Map<Long, Long> courseByCell = Map.of( Map<Long, Long> courseByCell = Map.of(
@@ -763,7 +783,6 @@ public final class HydrologyPackProbeTest {
assertEquals(3, image.getWidth()); assertEquals(3, image.getWidth());
assertEquals(2, image.getHeight()); assertEquals(2, image.getHeight());
assertTrue(metrics.contains("\"seed\": 1337")); assertTrue(metrics.contains("\"seed\": 1337"));
assertTrue(metrics.contains("\"clippedTerminalCaps\": 0"));
assertTrue(metrics.contains("\"minimumInteriorWidth\": 0")); assertTrue(metrics.contains("\"minimumInteriorWidth\": 0"));
assertTrue(metrics.contains("\"maximumWidthTroughDepthRatio\": 0.000000")); assertTrue(metrics.contains("\"maximumWidthTroughDepthRatio\": 0.000000"));
assertTrue(metrics.contains("\"unexpectedLeaves\": 0")); assertTrue(metrics.contains("\"unexpectedLeaves\": 0"));
@@ -773,8 +792,7 @@ public final class HydrologyPackProbeTest {
long seed, long seed,
long courseId, long courseId,
List<HydrologyPoint> centerline, List<HydrologyPoint> centerline,
Set<Long> ownedCells, Set<Long> ownedCells
boolean terminalExposed
) { ) {
return new HydrologyPackProbe.CourseMorphologyInput( return new HydrologyPackProbe.CourseMorphologyInput(
seed, seed,
@@ -784,7 +802,6 @@ public final class HydrologyPackProbeTest {
0, 0,
0D, 0D,
true, true,
terminalExposed,
centerline, centerline,
ownedCells ownedCells
); );
@@ -802,22 +819,6 @@ public final class HydrologyPackProbeTest {
return Set.copyOf(cells); return Set.copyOf(cells);
} }
private static Set<Long> widenedSourceBasin(
List<HydrologyPoint> centerline,
int channelRadius,
int basinRadius,
int taperLength
) {
List<HydrologyPoint> raster = raster(centerline);
HashSet<Long> cells = new HashSet<>();
for (int index = 0; index < raster.size(); index++) {
double taper = Math.min(1D, index / (double) taperLength);
int radius = channelRadius + (int) StrictMath.round((basinRadius - channelRadius) * (1D - taper));
addDisc(cells, raster.get(index), radius);
}
return Set.copyOf(cells);
}
private static Set<Long> terminalBasinChannel( private static Set<Long> terminalBasinChannel(
List<HydrologyPoint> centerline, List<HydrologyPoint> centerline,
int channelRadius, int channelRadius,
@@ -955,6 +956,73 @@ public final class HydrologyPackProbeTest {
return new HydrologyColumnSample(x, z, naturalHeight, 63, false, "parent", List.of()); return new HydrologyColumnSample(x, z, naturalHeight, 63, false, "parent", List.of());
} }
private static RiverFootprint gradedRow(int westHeight, int centerHeight, int eastHeight) {
HydrologyColumnSample west = gradedColumn(-1, 0, westHeight);
HydrologyColumnSample center = gradedColumn(0, 0, centerHeight);
HydrologyColumnSample east = gradedColumn(1, 0, eastHeight);
return new RiverFootprint(Map.of(
RiverFootprint.pack(west.x(), west.z()), west,
RiverFootprint.pack(center.x(), center.z()), center,
RiverFootprint.pack(east.x(), east.z()), east
));
}
private static HydrologyColumnSample gradedColumn(int x, int z, int height) {
return new HydrologyColumnSample(x, z, 80, 63, false, "parent", List.of(gradedLayer(height)));
}
private static HydrologyColumnSample channelColumn(int x, int z, int bedY, int headY) {
return new HydrologyColumnSample(x, z, 80, 63, false, "parent", List.of(new HydrologyColumnLayer(
surfaceFeature(),
bedY,
headY,
headY,
true,
false,
false,
true,
false,
false,
true,
true,
false,
"water",
"surface",
"mouth",
"shore",
"bank",
"flooded"
)));
}
private static HydrologyColumnLayer gradedLayer(int height) {
return new HydrologyColumnLayer(
surfaceFeature(),
height,
height,
height,
false,
false,
true,
false,
false,
false,
true,
false,
false,
"water",
"surface",
"mouth",
"shore",
"bank",
"flooded"
);
}
private static HydrologyFeatureRef surfaceFeature() {
return new HydrologyFeatureRef(41L, HydrologyFeatureType.RIFFLE, 42L, 43L, 0, 70, 0, 1, 0, false);
}
private static List<HydrologyPoint> raster(List<HydrologyPoint> points) { private static List<HydrologyPoint> raster(List<HydrologyPoint> points) {
ArrayList<HydrologyPoint> raster = new ArrayList<>(); ArrayList<HydrologyPoint> raster = new ArrayList<>();
for (int pair = 0; pair < points.size() - 1; pair++) { for (int pair = 0; pair < points.size() - 1; pair++) {
@@ -8,16 +8,26 @@ import java.util.EnumSet;
import java.util.Map; import java.util.Map;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public final class HydrologyPlannerProbeTest { public final class HydrologyPlannerProbeTest {
@Test @Test
public void deterministicAcceptedCoveragePublishesEveryFeatureType() { public void deterministicAcceptedCoveragePublishesEveryReachableFeatureType() {
Map<HydrologyFeatureType, HydrologyFeatureRef> coverage = Map<HydrologyFeatureType, HydrologyFeatureRef> coverage =
HydrologyPlannerProbe.deterministicAcceptedCoverage(); HydrologyPlannerProbe.deterministicAcceptedCoverage();
assertEquals(EnumSet.allOf(HydrologyFeatureType.class), coverage.keySet()); assertEquals(HydrologyPlannerProbe.REQUIRED_FEATURE_TYPES, coverage.keySet());
for (Map.Entry<HydrologyFeatureType, HydrologyFeatureRef> entry : coverage.entrySet()) { for (Map.Entry<HydrologyFeatureType, HydrologyFeatureRef> entry : coverage.entrySet()) {
assertEquals(entry.getKey(), entry.getValue().type()); assertEquals(entry.getKey(), entry.getValue().type());
} }
} }
@Test
public void requiredCoverageExcludesTheRemovedRidgeBore() {
assertFalse(HydrologyPlannerProbe.REQUIRED_FEATURE_TYPES.contains(HydrologyFeatureType.RIDGE_BORE));
assertEquals(
EnumSet.complementOf(EnumSet.of(HydrologyFeatureType.RIDGE_BORE)),
HydrologyPlannerProbe.REQUIRED_FEATURE_TYPES
);
}
} }