Align schema bounds with the hydrology validator

Studio schema annotations now match the validator ranges; mouthLevelingDistance must fit inside maximumRouteLength.
This commit is contained in:
Brian Neumann-Fopiano
2026-09-01 21:15:36 -04:00
parent 00f5a16c20
commit 124bcd9372
9 changed files with 44 additions and 25 deletions
@@ -186,6 +186,10 @@ final class PackHydrologyValidator {
SourceBudget undergroundBudget = underground == null
? SourceBudget.defaults()
: validateUnderground(path + ".underground", underground, dimension, errors, warnings);
if (underground != null
&& integerValue(underground, "mouthLevelingDistance", 64) > routingValues.maximumRouteLength()) {
errors.add(path + ".underground.mouthLevelingDistance must not exceed routing.maximumRouteLength.");
}
if (grottos != null) {
validateGrottos(path + ".grottos", grottos, routingValues, errors);
}
@@ -18,7 +18,7 @@ public class IrisRiverMouthConfig {
private double flareRatio = 1.6D;
@MinNumber(0)
@MaxNumber(64)
@MaxNumber(32)
@Desc("Maximum number of blocks the recorded mouth footprint may enter the ocean before terminating.")
private int maximumOceanApron = 8;
}
@@ -74,8 +74,8 @@ public class IrisRiverPolicy {
@Desc("Multiplier applied to terrain-guided routing cost.")
private Double routingMultiplier = null;
@MinNumber(0.0001)
@MaxNumber(16)
@MinNumber(0)
@MaxNumber(4)
@Desc("Multiplier applied to the eroded bank width outside the shore band.")
private Double bankMultiplier = null;
@@ -20,7 +20,7 @@ public class IrisRiverRoutingConfig {
private int tileSize = 2048;
@MinNumber(8)
@MaxNumber(512)
@MaxNumber(64)
@Desc("Spacing in blocks between terrain samples used to build the drainage graph.")
private int sampleSpacing = 64;
@@ -52,22 +52,22 @@ public class IrisRiverRoutingConfig {
private KList<IrisRiverInlandOutlet> inlandOutlets = new KList<>();
@MinNumber(0)
@MaxNumber(64)
@MaxNumber(8)
@Desc("How strongly routes prefer lower ground.")
private double valleyPreference = 1.5D;
@MinNumber(0)
@MaxNumber(1024)
@MaxNumber(128)
@Desc("Cost per block of rise along a route.")
private double uphillPenalty = 24D;
@MinNumber(0)
@MaxNumber(64)
@MaxNumber(16)
@Desc("Cost per unit of terrain slope along a route.")
private double slopePenalty = 2D;
@MinNumber(0)
@MaxNumber(16)
@MaxNumber(1)
@Desc("How strongly routes are drawn toward existing drainage.")
private double confluenceAttraction = 0.2D;
}
@@ -12,18 +12,18 @@ import lombok.experimental.Accessors;
@Desc("Controls the lip, shore band and eroded valley around a surface river.")
@Data
public class IrisSurfaceRiverBankConfig {
@MinNumber(1)
@MaxNumber(8)
@MinNumber(0)
@MaxNumber(4)
@Desc("Blocks the bank top sits above the water surface at the channel edge.")
private int freeboard = 1;
@MinNumber(0)
@MaxNumber(8)
@MinNumber(0.5)
@MaxNumber(6)
@Desc("Width in blocks of the shore band that receives shore biome content.")
private double shoreWidth = 1.5D;
@MinNumber(1)
@MaxNumber(16)
@MinNumber(0.5)
@MaxNumber(12)
@Desc("Horizontal run in blocks of eroded bank per block of cut depth.")
private double blendSlope = 3D;
@@ -33,7 +33,7 @@ public class IrisSurfaceRiverBankConfig {
private int minimumBlendWidth = 4;
@MinNumber(1)
@MaxNumber(128)
@MaxNumber(64)
@Desc("Widest eroded band outside the shore, in blocks.")
private int maximumBlendWidth = 32;
@@ -18,13 +18,13 @@ public class IrisSurfaceRiverChannelConfig {
@Desc("Wet bed depth in blocks at the channel center.")
private IrisStyledRange depth = range(2D, 4D, 768D);
@MinNumber(1)
@MaxNumber(16)
@MinNumber(0)
@MaxNumber(3)
@Desc("Blocks the water surface sits below the lowest natural bank beside the channel.")
private int inset = 1;
@MinNumber(0)
@MaxNumber(64)
@MinNumber(1)
@MaxNumber(32)
@Desc("Maximum cut below natural terrain at the channel center before a course is rejected.")
private int maximumIncision = 10;
@@ -33,8 +33,8 @@ public class IrisSurfaceRiverChannelConfig {
@Desc("Coherent variation of the wet outline and bed as a fraction of the channel size.")
private double roughness = 0.25D;
@MinNumber(3)
@MaxNumber(128)
@MinNumber(4)
@MaxNumber(64)
@Desc("Wavelength in blocks of the outline and bed variation.")
private int roughnessWavelength = 16;
@@ -13,12 +13,12 @@ import lombok.experimental.Accessors;
@Data
public class IrisSurfaceRiverFlowConfig {
@MinNumber(1)
@MaxNumber(16)
@MaxNumber(8)
@Desc("Shortest level run in blocks between one-block steps before consecutive steps are labelled a cascade.")
private int cascadeRun = 2;
@MinNumber(2)
@MaxNumber(24)
@MaxNumber(32)
@Desc("Smallest natural cliff in blocks that becomes a waterfall instead of a cascade.")
private int waterfallMinimumDrop = 6;
}
@@ -33,8 +33,8 @@ public class IrisUndergroundRiverConfig {
@Desc("Allow accepted underground courses to connect transactionally to existing caves.")
private boolean connectToExistingCaves = true;
@MinNumber(0)
@MaxNumber(2048)
@MinNumber(16)
@MaxNumber(512)
@Desc("Distance in blocks over which an ocean-bound underground river levels to sea level before its mouth.")
private int mouthLevelingDistance = 64;
@@ -113,6 +113,21 @@ public class PackHydrologyValidatorTest {
assertTrue(result.errors().toString(), result.errors().isEmpty());
}
@Test
public void rejectsMouthLevelingDistanceBeyondTheRouteLength() throws Exception {
File pack = pack(riversDimension("""
{
"enabled": true,
"routing": {"tileSize": 1024, "sampleSpacing": 64, "maximumRouteLength": 256, "oceanOutlets": true},
"underground": {"mouthLevelingDistance": 512}
}
""", ""));
PackHydrologyValidator.Validation result = validate(pack);
assertContains(result.errors(), "mouthLevelingDistance must not exceed routing.maximumRouteLength");
}
@Test
public void acceptsEverySchemaBoundAtItsMaximum() throws Exception {
File pack = pack(riversDimension("""