Surface settings v2

- Replace hydraulics, ridge tunnel, branching and inset ranges with channel, banks, flow and mouth configs
- Add routing cost weights, underground mouth leveling, bank biomes and bank multiplier
- Map the new schema into planner settings and derive refinement spacing
This commit is contained in:
Brian Neumann-Fopiano
2026-09-01 18:46:12 -04:00
parent 48afa0d93f
commit 915cc1a2f0
37 changed files with 528 additions and 291 deletions
@@ -48,7 +48,7 @@ final class PackHydrologyValidator {
"surfaceBiomes",
"mouthBiomes",
"shoreBiomes",
"dryBiomes",
"bankBiomes",
"floodedCaveBiomes"
);
private static final List<String> REGION_BIOME_FIELDS = List.of(
@@ -20,7 +20,7 @@ public record HydrologyColumnLayer(
String surfaceBiomeKey,
String mouthBiomeKey,
String shoreBiomeKey,
String dryBiomeKey,
String bankBiomeKey,
String floodedCaveBiomeKey
) {
public HydrologyColumnLayer {
@@ -29,7 +29,7 @@ public record HydrologyColumnLayer(
surfaceBiomeKey = requireKey(surfaceBiomeKey, "surfaceBiomeKey");
mouthBiomeKey = requireKey(mouthBiomeKey, "mouthBiomeKey");
shoreBiomeKey = requireKey(shoreBiomeKey, "shoreBiomeKey");
dryBiomeKey = requireKey(dryBiomeKey, "dryBiomeKey");
bankBiomeKey = requireKey(bankBiomeKey, "bankBiomeKey");
floodedCaveBiomeKey = requireKey(floodedCaveBiomeKey, "floodedCaveBiomeKey");
if (channel && bedY > fluidHeadY) {
throw new IllegalArgumentException("A channel bed cannot be above its fluid head.");
@@ -788,7 +788,7 @@ final class HydrologyFootprintCompiler {
layer.surfaceBiomeKey(),
layer.mouthBiomeKey(),
layer.shoreBiomeKey(),
layer.dryBiomeKey(),
layer.bankBiomeKey(),
layer.floodedCaveBiomeKey()
);
}
@@ -2121,7 +2121,7 @@ final class HydrologyFootprintCompiler {
layer.surfaceBiomeKey(),
layer.mouthBiomeKey(),
layer.shoreBiomeKey(),
layer.dryBiomeKey(),
layer.bankBiomeKey(),
layer.floodedCaveBiomeKey()
);
}
@@ -2232,7 +2232,7 @@ final class HydrologyFootprintCompiler {
terrain.surfaceBiomeKey(),
terrain.mouthBiomeKey(),
terrain.shoreBiomeKey(),
terrain.dryBiomeKey(),
terrain.bankBiomeKey(),
terrain.floodedCaveBiomeKey()
);
}
@@ -2443,7 +2443,7 @@ final class HydrologyFootprintCompiler {
terrain.surfaceBiomeKey(),
terrain.mouthBiomeKey(),
terrain.shoreBiomeKey(),
terrain.dryBiomeKey(),
terrain.bankBiomeKey(),
terrain.floodedCaveBiomeKey()
);
addLayer(columns, x, z, terrain, layer);
@@ -3655,7 +3655,7 @@ final class HydrologyFootprintCompiler {
first.surfaceBiomeKey(),
first.mouthBiomeKey(),
first.shoreBiomeKey(),
first.dryBiomeKey(),
first.bankBiomeKey(),
first.floodedCaveBiomeKey()
);
}
@@ -12,7 +12,7 @@ public record HydrologyPlannerSettings(
Geometry geometry,
List<DeepFluid> deepFluids
) {
private static final long PLAN_FORMAT_REVISION = 5L;
private static final long PLAN_FORMAT_REVISION = 6L;
private static final int MAXIMUM_CROSS_TILE_COLOR_PERIOD = 4;
public HydrologyPlannerSettings {
@@ -36,7 +36,7 @@ public record HydrologyPlannerSettings(
return new HydrologyPlannerSettings(
63,
new Routing(2048, 64, 8, 8192, 8192, new Branching(384, 192), 1.5D, 24D, 2D, 0.2D),
new Surface(true, surfaceSources, 4, 8, 1, 4, 3, 7, 6, 2, 10, 24, true, 192, 10),
new Surface(true, surfaceSources, 4, 8, 2, 4, 1, 1, 10, 1.5D, 4, 32, false, 1, 1, Banks.defaults()),
new Hydraulics(80, 180, 1, 7, 24, 8),
new Underground(true, undergroundSources, -48, 72, 3, 8, 1, 3, 6, 14, true),
new Outlets(
@@ -97,8 +97,9 @@ public record HydrologyPlannerSettings(
int radius = 0;
int routeDisplacement = Math.multiplyExact(routing.sampleSpacing(), 3);
if (surface.enabled() && surface.sources().enabled()) {
int blendWidth = Math.max(surface.maximumTerrainBlendWidth(), surface.banks().maximumBlendWidth());
int surfaceRadius = (int) StrictMath.ceil(
surface.maximumWidth() / 2D + surface.shoreWidth() + surface.maximumTerrainBlendWidth()
surface.maximumWidth() / 2D + surface.shoreWidth() + blendWidth
);
surfaceRadius = Math.max(
surfaceRadius,
@@ -228,11 +229,12 @@ public record HydrologyPlannerSettings(
int maximumTerrainBlendWidth,
boolean ridgeTunnelsEnabled,
int maximumRidgeTunnelLength,
int ridgeTunnelHeadroom
int ridgeTunnelHeadroom,
Banks banks
) {
public Surface {
if (sources == null) {
throw new IllegalArgumentException("Surface source settings are required.");
if (sources == null || banks == null) {
throw new IllegalArgumentException("Surface source and bank settings are required.");
}
requireRange(minimumWidth, maximumWidth, 1, 256, "surface width");
requireRange(minimumDepth, maximumDepth, 1, 64, "surface depth");
@@ -247,6 +249,37 @@ public record HydrologyPlannerSettings(
}
}
public record Banks(
int inset,
int freeboard,
double blendSlope,
int minimumBlendWidth,
int maximumBlendWidth,
double roughness,
int roughnessWavelength,
int cascadeRun,
int waterfallMinimumDrop,
double mouthFlareRatio,
boolean exposeCutStrata
) {
public Banks {
if (inset < 1 || inset > 16 || freeboard < 1 || freeboard > 8
|| !Double.isFinite(blendSlope) || blendSlope < 1D || blendSlope > 16D
|| minimumBlendWidth < 1 || maximumBlendWidth < minimumBlendWidth || maximumBlendWidth > 128
|| !Double.isFinite(roughness) || roughness < 0D || roughness > 1D
|| roughnessWavelength < 3 || roughnessWavelength > 128
|| cascadeRun < 1 || cascadeRun > 16
|| waterfallMinimumDrop < 2 || waterfallMinimumDrop > 24
|| !Double.isFinite(mouthFlareRatio) || mouthFlareRatio < 1D || mouthFlareRatio > 4D) {
throw new IllegalArgumentException("Surface bank settings are invalid.");
}
}
public static Banks defaults() {
return new Banks(1, 1, 3D, 4, 32, 0.25D, 16, 2, 6, 1.6D, true);
}
}
public record Hydraulics(
int minimumTargetPoolLength,
int maximumTargetPoolLength,
@@ -22,11 +22,12 @@ public record HydrologyTerrainSample(
double depthMultiplier,
double incisionMultiplier,
double routingMultiplier,
double bankMultiplier,
String parentBiomeKey,
String surfaceBiomeKey,
String mouthBiomeKey,
String shoreBiomeKey,
String dryBiomeKey,
String bankBiomeKey,
String floodedCaveBiomeKey,
List<String> preferredProfileKeys
) {
@@ -39,6 +40,7 @@ public record HydrologyTerrainSample(
requireFinitePositive(depthMultiplier, "depthMultiplier");
requireFiniteNonNegative(incisionMultiplier, "incisionMultiplier");
requireFiniteNonNegative(routingMultiplier, "routingMultiplier");
requireFinitePositive(bankMultiplier, "bankMultiplier");
if (caveAvailable && (caveFluidY <= caveFloorY || caveFluidY >= naturalHeight)) {
throw new IllegalArgumentException("Available cave fluid must be above its floor and below natural terrain.");
}
@@ -46,7 +48,7 @@ public record HydrologyTerrainSample(
surfaceBiomeKey = normalizeKey(surfaceBiomeKey, parentBiomeKey);
mouthBiomeKey = normalizeKey(mouthBiomeKey, surfaceBiomeKey);
shoreBiomeKey = normalizeKey(shoreBiomeKey, parentBiomeKey);
dryBiomeKey = normalizeKey(dryBiomeKey, parentBiomeKey);
bankBiomeKey = normalizeKey(bankBiomeKey, parentBiomeKey);
floodedCaveBiomeKey = normalizeKey(floodedCaveBiomeKey, surfaceBiomeKey);
preferredProfileKeys = normalizeProfiles(preferredProfileKeys);
}
@@ -72,6 +74,7 @@ public record HydrologyTerrainSample(
1D,
1D,
1D,
1D,
parentBiomeKey,
parentBiomeKey,
parentBiomeKey,
@@ -103,6 +106,7 @@ public record HydrologyTerrainSample(
1D,
1D,
1D,
1D,
parentBiomeKey,
parentBiomeKey,
parentBiomeKey,
@@ -134,11 +138,12 @@ public record HydrologyTerrainSample(
depthMultiplier,
incisionMultiplier,
routingMultiplier,
bankMultiplier,
parentBiomeKey,
surfaceBiomeKey,
mouthBiomeKey,
shoreBiomeKey,
dryBiomeKey,
bankBiomeKey,
floodedCaveBiomeKey,
preferredProfileKeys
);
@@ -14,12 +14,13 @@ public record EffectiveRiverPolicy(
List<String> surfaceBiomes,
List<String> mouthBiomes,
List<String> shoreBiomes,
List<String> dryBiomes,
List<String> bankBiomes,
List<String> floodedCaveBiomes,
double widthMultiplier,
double depthMultiplier,
double incisionMultiplier,
double routingMultiplier
double routingMultiplier,
double bankMultiplier
) {
public EffectiveRiverPolicy {
placement = Objects.requireNonNull(placement);
@@ -28,7 +29,7 @@ public record EffectiveRiverPolicy(
surfaceBiomes = List.copyOf(surfaceBiomes);
mouthBiomes = List.copyOf(mouthBiomes);
shoreBiomes = List.copyOf(shoreBiomes);
dryBiomes = List.copyOf(dryBiomes);
bankBiomes = List.copyOf(bankBiomes);
floodedCaveBiomes = List.copyOf(floodedCaveBiomes);
}
@@ -41,12 +41,13 @@ public final class RiverPolicyResolver {
private List<String> surfaceBiomes = List.of();
private List<String> mouthBiomes = List.of();
private List<String> shoreBiomes = List.of();
private List<String> dryBiomes = List.of();
private List<String> bankBiomes = List.of();
private List<String> floodedCaveBiomes = List.of();
private double widthMultiplier = 1D;
private double depthMultiplier = 1D;
private double incisionMultiplier = 1D;
private double routingMultiplier = 1D;
private double bankMultiplier = 1D;
private void apply(IrisRiverPolicy policy) {
if (policy == null) {
@@ -73,8 +74,8 @@ public final class RiverPolicyResolver {
if (policy.getShoreBiomes() != null) {
shoreBiomes = List.copyOf(policy.getShoreBiomes());
}
if (policy.getDryBiomes() != null) {
dryBiomes = List.copyOf(policy.getDryBiomes());
if (policy.getBankBiomes() != null) {
bankBiomes = List.copyOf(policy.getBankBiomes());
}
if (policy.getFloodedCaveBiomes() != null) {
floodedCaveBiomes = List.copyOf(policy.getFloodedCaveBiomes());
@@ -91,6 +92,9 @@ public final class RiverPolicyResolver {
if (policy.getRoutingMultiplier() != null) {
routingMultiplier = policy.getRoutingMultiplier();
}
if (policy.getBankMultiplier() != null) {
bankMultiplier = policy.getBankMultiplier();
}
}
private EffectiveRiverPolicy build() {
@@ -102,12 +106,13 @@ public final class RiverPolicyResolver {
surfaceBiomes,
mouthBiomes,
shoreBiomes,
dryBiomes,
bankBiomes,
floodedCaveBiomes,
widthMultiplier,
depthMultiplier,
incisionMultiplier,
routingMultiplier
routingMultiplier,
bankMultiplier
);
}
}
@@ -30,7 +30,6 @@ import art.arcane.iris.engine.object.IrisRiverGrottoConfig;
import art.arcane.iris.engine.object.IrisRiverChannelShapeConfig;
import art.arcane.iris.engine.object.IrisRiverDropShapeConfig;
import art.arcane.iris.engine.object.IrisRiverGeometryConfig;
import art.arcane.iris.engine.object.IrisRiverHydraulicsConfig;
import art.arcane.iris.engine.object.IrisRiverHydrology;
import art.arcane.iris.engine.object.IrisRiverInlandOutlet;
import art.arcane.iris.engine.object.IrisRiverMeanderConfig;
@@ -38,7 +37,9 @@ import art.arcane.iris.engine.object.IrisRiverProfile;
import art.arcane.iris.engine.object.IrisRiverRoutingConfig;
import art.arcane.iris.engine.object.IrisRiverRoutingMode;
import art.arcane.iris.engine.object.IrisStyledRange;
import art.arcane.iris.engine.object.IrisSurfaceRiverBankConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverChannelConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverFlowConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverSourceConfig;
import art.arcane.iris.engine.object.IrisUndergroundRiverConfig;
@@ -463,11 +464,12 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
policy.depthMultiplier(),
policy.incisionMultiplier(),
policy.routingMultiplier() * routingPreference,
policy.bankMultiplier(),
parentBiomeKey,
selectKey(policy.surfaceBiomes(), parentBiomeKey, biomePatchNoise, 1),
selectKey(policy.mouthBiomes(), parentBiomeKey, biomePatchNoise, 2),
selectKey(policy.shoreBiomes(), parentBiomeKey, biomePatchNoise, 3),
selectKey(policy.dryBiomes(), parentBiomeKey, biomePatchNoise, 4),
selectKey(policy.bankBiomes(), parentBiomeKey, biomePatchNoise, 4),
selectKey(policy.floodedCaveBiomes(), parentBiomeKey, biomePatchNoise, 5),
profiles
);
@@ -556,21 +558,13 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
IrisRiverHydrology rivers = hydrology.getRivers();
IrisSurfaceRiverConfig surface = rivers.getSurface();
IrisSurfaceRiverChannelConfig channel = surface.getChannel();
IrisRiverHydraulicsConfig hydraulics = surface.getHydraulics();
IrisSurfaceRiverBankConfig banks = surface.getBanks();
IrisUndergroundRiverConfig underground = rivers.getUnderground();
ProceduralStream<Double> surfaceWidth = styledStream(context, channel.getWidth(), SURFACE_WIDTH_SALT);
ProceduralStream<Double> surfaceDepth = styledStream(context, channel.getDepth(), SURFACE_DEPTH_SALT);
ProceduralStream<Double> surfaceInset = styledStream(context, channel.getSurfaceInset(), SURFACE_INSET_SALT);
ProceduralStream<Double> surfaceBlend = styledStream(
context,
channel.getTerrainBlendWidth(),
SURFACE_BLEND_SALT
);
ProceduralStream<Double> targetPool = styledStream(
context,
hydraulics.getTargetPoolLength(),
TARGET_POOL_SALT
);
ProceduralStream<Double> surfaceInset = ProceduralStream.ofDouble((x, z) -> (double) channel.getInset());
ProceduralStream<Double> surfaceBlend = ProceduralStream.ofDouble((x, z) -> (double) banks.getMaximumBlendWidth());
ProceduralStream<Double> targetPool = ProceduralStream.ofDouble((x, z) -> 128D);
ProceduralStream<Double> undergroundLevel = styledStream(
context,
underground.getFluidLevel(),
@@ -646,10 +640,12 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
IrisSurfaceRiverConfig surface = rivers.getSurface();
IrisSurfaceRiverSourceConfig surfaceSources = surface.getSources();
IrisSurfaceRiverChannelConfig channel = surface.getChannel();
IrisRiverHydraulicsConfig hydraulics = surface.getHydraulics();
IrisSurfaceRiverBankConfig banks = surface.getBanks();
IrisSurfaceRiverFlowConfig flow = surface.getFlow();
IrisUndergroundRiverConfig underground = rivers.getUnderground();
IrisUndergroundRiverSourceConfig undergroundSources = underground.getSources();
int routeNodes = maximumRouteNodes(routing);
int refinementSpacing = refinementSpacing(routing.getSampleSpacing());
int minimumWorldY = dimension.getMinHeight();
boolean riversEnabled = rivers.isEnabled();
boolean surfaceEnabled = riversEnabled && surface.isEnabled();
@@ -657,17 +653,17 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
HydrologyPlannerSettings.Routing plannerRouting = new HydrologyPlannerSettings.Routing(
routing.getTileSize(),
routing.getSampleSpacing(),
routing.getRefinementSpacing(),
refinementSpacing,
routeNodes,
routing.getMaximumRouteLength(),
new HydrologyPlannerSettings.Branching(
routing.getBranching().getMinimumSurfaceCourseLength(),
routing.getBranching().getMinimumUndergroundCourseLength()
routing.getMinimumSurfaceCourseLength(),
routing.getMinimumUndergroundCourseLength()
),
1.5D,
24D,
2D,
0.2D
routing.getValleyPreference(),
routing.getUphillPenalty(),
routing.getSlopePenalty(),
routing.getConfluenceAttraction()
);
HydrologyPlannerSettings.Source plannerSurfaceSources = new HydrologyPlannerSettings.Source(
surfaceEnabled,
@@ -684,23 +680,36 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
maximumInt(channel.getWidth()),
minimumInt(channel.getDepth()),
maximumInt(channel.getDepth()),
minimumInt(channel.getSurfaceInset()),
maximumInt(channel.getSurfaceInset()),
channel.getInset(),
channel.getInset(),
channel.getMaximumIncision(),
channel.getShoreWidth(),
minimumInt(channel.getTerrainBlendWidth()),
maximumInt(channel.getTerrainBlendWidth()),
surface.getRidgeTunnels().isEnabled(),
surface.getRidgeTunnels().getMaximumLength(),
surface.getRidgeTunnels().getHeadroom()
banks.getShoreWidth(),
banks.getMinimumBlendWidth(),
banks.getMaximumBlendWidth(),
false,
1,
1,
new HydrologyPlannerSettings.Banks(
channel.getInset(),
banks.getFreeboard(),
banks.getBlendSlope(),
banks.getMinimumBlendWidth(),
banks.getMaximumBlendWidth(),
channel.getRoughness(),
channel.getRoughnessWavelength(),
flow.getCascadeRun(),
flow.getWaterfallMinimumDrop(),
surface.getMouths().getFlareRatio(),
banks.isExposeCutStrata()
)
);
HydrologyPlannerSettings.Hydraulics plannerHydraulics = new HydrologyPlannerSettings.Hydraulics(
minimumInt(hydraulics.getTargetPoolLength()),
maximumInt(hydraulics.getTargetPoolLength()),
hydraulics.getRiffleDrop(),
hydraulics.getMaximumGradualDrop(),
hydraulics.getMaximumGradualLength(),
hydraulics.getWaterfallMinimumDrop()
80,
180,
1,
flow.getWaterfallMinimumDrop() - 1,
24,
flow.getWaterfallMinimumDrop()
);
HydrologyPlannerSettings.Source plannerUndergroundSources = new HydrologyPlannerSettings.Source(
undergroundEnabled,
@@ -736,11 +745,19 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
inland.getHeadroom(), inland.getMaximumVolume()),
inlandEnabled && inland.isConnectSurfaceRivers(),
Math.max(4, coastal.getVerticalRadius()),
surface.getMouths().getLevelingDistance(),
underground.getMouthLevelingDistance(),
surface.getMouths().getMaximumOceanApron(),
routing.getMaximumOutletsPerTile()
);
HydrologyPlannerSettings.Geometry plannerGeometry = geometry(rivers.getGeometry());
HydrologyPlannerSettings.Geometry plannerGeometry = geometry(
rivers.getGeometry(),
new HydrologyPlannerSettings.ChannelShape(
2D,
channel.getRoughness(),
channel.getRoughness(),
channel.getRoughnessWavelength()
)
);
return new HydrologyPlannerSettings(
dimension.getFluidHeight(),
plannerRouting,
@@ -749,11 +766,21 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
plannerUnderground,
plannerOutlets,
plannerGeometry,
deepFluids(hydrology.getDeepFluids(), minimumWorldY, routing)
deepFluids(hydrology.getDeepFluids(), minimumWorldY, routing, refinementSpacing)
);
}
private static HydrologyPlannerSettings.Geometry geometry(IrisRiverGeometryConfig geometry) {
static int refinementSpacing(int sampleSpacing) {
if (sampleSpacing % 4 == 0) {
return 4;
}
return sampleSpacing % 2 == 0 ? 2 : 1;
}
private static HydrologyPlannerSettings.Geometry geometry(
IrisRiverGeometryConfig geometry,
HydrologyPlannerSettings.ChannelShape surfaceShape
) {
IrisRiverMeanderConfig meanders = geometry.getMeanders();
IrisRiverDropShapeConfig drops = geometry.getDrops();
return new HydrologyPlannerSettings.Geometry(
@@ -766,7 +793,7 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
meanders.getSmoothingPasses(),
meanders.getMaximumTurnDegrees()
),
channelShape(geometry.getSurface()),
surfaceShape,
channelShape(geometry.getUnderground()),
channelShape(geometry.getGrottos()),
new HydrologyPlannerSettings.Drops(
@@ -809,7 +836,8 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
private static List<HydrologyPlannerSettings.DeepFluid> deepFluids(
List<IrisDeepFluidConfig> configurations,
int minimumWorldY,
IrisRiverRoutingConfig routing
IrisRiverRoutingConfig routing,
int refinementSpacing
) {
ArrayList<HydrologyPlannerSettings.DeepFluid> deepFluids = new ArrayList<>();
for (IrisDeepFluidConfig configuration : configurations) {
@@ -820,7 +848,7 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
int maximumChannelLength = maximumDeepChannelLength(configuration, routing);
int minimumChannelLength = maximumChannelLength == 0
? 0
: Math.min(maximumChannelLength, Math.max(routing.getRefinementSpacing(), radius));
: Math.min(maximumChannelLength, Math.max(refinementSpacing, radius));
deepFluids.add(new HydrologyPlannerSettings.DeepFluid(
configuration.getId(),
configuration.getDensity() > 0D
@@ -855,7 +883,7 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
return 0;
}
int configuredLength = Math.max(
routing.getRefinementSpacing(), configuration.getSpacing() / 3);
refinementSpacing(routing.getSampleSpacing()), configuration.getSpacing() / 3);
return Math.min(configuredLength, routing.getTileSize() / 2);
}
@@ -326,7 +326,7 @@ public final class MantleHydrologyComponent extends IrisMantleComponent {
+ position + ".");
}
String biomeKey = action == HydrologyCaveAction.DRY_AIR
? layer.dryBiomeKey()
? layer.bankBiomeKey()
: layer.floodedCaveBiomeKey();
PlannedCell candidate = new PlannedCell(
new HydrologyCaveCell(action, layer.profileKey(), biomeKey),
@@ -1,25 +0,0 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.MaxNumber;
import art.arcane.iris.engine.object.annotations.MinNumber;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Accessors(chain = true)
@NoArgsConstructor
@Desc("Controls minimum complete river lengths before a route is published.")
@Data
public class IrisRiverBranchingConfig {
@MinNumber(0)
@MaxNumber(32768)
@Desc("Minimum complete surface source-to-outlet route length in blocks.")
private int minimumSurfaceCourseLength = 384;
@MinNumber(0)
@MaxNumber(32768)
@Desc("Minimum complete underground source-to-outlet route length in blocks.")
private int minimumUndergroundCourseLength = 192;
}
@@ -7,21 +7,18 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
@NoArgsConstructor
@Desc("Controls organic river routing, beds, carved walls, and descending water geometry.")
@Desc("Controls organic river routing and contained cave geometry.")
@Data
public class IrisRiverGeometryConfig {
@Desc("Surface and underground centerline meanders.")
private IrisRiverMeanderConfig meanders = new IrisRiverMeanderConfig();
@Desc("Surface channel bed and bank shape.")
private IrisRiverChannelShapeConfig surface = new IrisRiverChannelShapeConfig();
@Desc("Underground channel bed and wall shape.")
private IrisRiverChannelShapeConfig underground = new IrisRiverChannelShapeConfig();
@Desc("Grotto pool bed and wall shape.")
private IrisRiverChannelShapeConfig grottos = new IrisRiverChannelShapeConfig();
@Desc("Cascade, cataract, waterfall, and sinkhole descent shape.")
@Desc("Underground drop, sinkhole and receiving basin shape.")
private IrisRiverDropShapeConfig drops = new IrisRiverDropShapeConfig();
}
@@ -1,41 +0,0 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.MaxNumber;
import art.arcane.iris.engine.object.annotations.MinNumber;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Accessors(chain = true)
@NoArgsConstructor
@Desc("Controls deterministic river head transitions and hydraulic segment classification.")
@Data
public class IrisRiverHydraulicsConfig {
@Desc("Target longitudinal length of level pools between head transitions.")
private IrisStyledRange targetPoolLength = range(80D, 180D, 1024D);
@MinNumber(0)
@MaxNumber(16)
@Desc("Normal one-step head loss represented by a riffle.")
private int riffleDrop = 1;
@MinNumber(0)
@MaxNumber(64)
@Desc("Largest head loss represented as a gradual cascade.")
private int maximumGradualDrop = 7;
@MinNumber(1)
@MaxNumber(1024)
@Desc("Largest longitudinal distance used for one gradual cascade transition.")
private int maximumGradualLength = 24;
@MinNumber(1)
@MaxNumber(128)
@Desc("Minimum required head loss represented by a waterfall.")
private int waterfallMinimumDrop = 8;
private static IrisStyledRange range(double min, double max, double zoom) {
return new IrisStyledRange(min, max, new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(zoom));
}
}
@@ -9,16 +9,16 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
@NoArgsConstructor
@Desc("Controls river leveling before a coastal reservoir and the final ocean footprint.")
@Desc("Controls how a surface river meets the sea.")
@Data
public class IrisRiverMouthConfig {
@MinNumber(0)
@MaxNumber(2048)
@Desc("Distance in blocks over which an ocean-bound river reaches sea level before the coastline.")
private int levelingDistance = 64;
@MinNumber(1)
@MaxNumber(4)
@Desc("Width multiplier reached at the coastline over the final stretch of the river.")
private double flareRatio = 1.6D;
@MinNumber(0)
@MaxNumber(64)
@Desc("Maximum number of blocks the river footprint may enter an ocean reservoir before terminating.")
@Desc("Maximum number of blocks the recorded mouth footprint may enter the ocean before terminating.")
private int maximumOceanApron = 8;
}
@@ -47,7 +47,7 @@ public class IrisRiverPolicy {
@RegistryListResource(IrisBiome.class)
@ArrayType(type = String.class)
@Desc("Dry river footprint biome selection. An empty list explicitly clears the inherited selection.")
private KList<String> dryBiomes = null;
private KList<String> bankBiomes = null;
@RegistryListResource(IrisBiome.class)
@ArrayType(type = String.class)
@@ -74,12 +74,17 @@ public class IrisRiverPolicy {
@Desc("Multiplier applied to terrain-guided routing cost.")
private Double routingMultiplier = null;
@MinNumber(0.0001)
@MaxNumber(16)
@Desc("Multiplier applied to the eroded bank width outside the shore band.")
private Double bankMultiplier = null;
public KSet<String> getAllBiomeIds() {
KSet<String> biomeIds = new KSet<>();
addAll(biomeIds, surfaceBiomes);
addAll(biomeIds, mouthBiomes);
addAll(biomeIds, shoreBiomes);
addAll(biomeIds, dryBiomes);
addAll(biomeIds, bankBiomes);
addAll(biomeIds, floodedCaveBiomes);
return biomeIds;
}
@@ -1,27 +0,0 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.MaxNumber;
import art.arcane.iris.engine.object.annotations.MinNumber;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Accessors(chain = true)
@NoArgsConstructor
@Desc("Controls short contained ridge bores on otherwise viable surface courses.")
@Data
public class IrisRiverRidgeTunnelConfig {
@Desc("Allow short ridge bores when they preserve the accepted drainage course.")
private boolean enabled = true;
@MinNumber(1)
@MaxNumber(4096)
@Desc("Maximum accepted ridge-bore length in blocks.")
private int maximumLength = 192;
@MinNumber(1)
@MaxNumber(128)
@Desc("Dry vertical clearance above the river head inside a ridge bore.")
private int headroom = 10;
}
@@ -24,22 +24,24 @@ public class IrisRiverRoutingConfig {
@Desc("Spacing in blocks between terrain samples used to build the drainage graph.")
private int sampleSpacing = 64;
@MinNumber(1)
@MaxNumber(64)
@Desc("Spacing in blocks used to refine accepted terrain-guided centerlines.")
private int refinementSpacing = 8;
@Desc("Minimum route and tributary lengths used to suppress chopped, clustered branches.")
private IrisRiverBranchingConfig branching = new IrisRiverBranchingConfig();
@MinNumber(256)
@MaxNumber(32768)
@Desc("Maximum bounded length in blocks of one accepted river route.")
private int maximumRouteLength = 16384;
@MinNumber(0)
@MaxNumber(32768)
@Desc("Minimum exposed source-to-outlet length in blocks of a published surface river.")
private int minimumSurfaceCourseLength = 384;
@MinNumber(0)
@MaxNumber(32768)
@Desc("Minimum source-to-outlet length in blocks of a published underground river.")
private int minimumUndergroundCourseLength = 192;
@MinNumber(1)
@MaxNumber(256)
@Desc("Maximum drainage roots selected per planning tile. Fewer roots produce longer, more strongly branching river trees.")
@Desc("Maximum drainage roots selected per planning tile.")
private int maximumOutletsPerTile = 4;
@Desc("Allow rivers to terminate at an ocean reservoir.")
@@ -48,4 +50,24 @@ public class IrisRiverRoutingConfig {
@ArrayType(type = IrisRiverInlandOutlet.class)
@Desc("Contained inland outlet kinds available when no legal ocean outlet is selected.")
private KList<IrisRiverInlandOutlet> inlandOutlets = new KList<>();
@MinNumber(0)
@MaxNumber(64)
@Desc("How strongly routes prefer lower ground.")
private double valleyPreference = 1.5D;
@MinNumber(0)
@MaxNumber(1024)
@Desc("Cost per block of rise along a route.")
private double uphillPenalty = 24D;
@MinNumber(0)
@MaxNumber(64)
@Desc("Cost per unit of terrain slope along a route.")
private double slopePenalty = 2D;
@MinNumber(0)
@MaxNumber(16)
@Desc("How strongly routes are drawn toward existing drainage.")
private double confluenceAttraction = 0.2D;
}
@@ -0,0 +1,42 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.MaxNumber;
import art.arcane.iris.engine.object.annotations.MinNumber;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Accessors(chain = true)
@NoArgsConstructor
@Desc("Controls the lip, shore band and eroded valley around a surface river.")
@Data
public class IrisSurfaceRiverBankConfig {
@MinNumber(1)
@MaxNumber(8)
@Desc("Blocks the bank top sits above the water surface at the channel edge.")
private int freeboard = 1;
@MinNumber(0)
@MaxNumber(8)
@Desc("Width in blocks of the shore band that receives shore biome content.")
private double shoreWidth = 1.5D;
@MinNumber(1)
@MaxNumber(16)
@Desc("Horizontal run in blocks of eroded bank per block of cut depth.")
private double blendSlope = 3D;
@MinNumber(1)
@MaxNumber(64)
@Desc("Narrowest eroded band outside the shore, in blocks.")
private int minimumBlendWidth = 4;
@MinNumber(1)
@MaxNumber(128)
@Desc("Widest eroded band outside the shore, in blocks.")
private int maximumBlendWidth = 32;
@Desc("Show the biome's deeper layers on eroded banks instead of the surface layer.")
private boolean exposeCutStrata = true;
}
@@ -9,39 +9,37 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
@NoArgsConstructor
@Desc("Controls the accepted surface-river terrain and biome footprint.")
@Desc("Controls the wet channel of a surface river.")
@Data
public class IrisSurfaceRiverChannelConfig {
@Desc("Wet channel width in blocks.")
private IrisStyledRange width = range(4D, 8D, 1024D);
@Desc("Wet bed depth in blocks.")
private IrisStyledRange depth = range(1D, 4D, 768D, 2D);
@Desc("Wet bed depth in blocks at the channel center.")
private IrisStyledRange depth = range(2D, 4D, 768D);
@Desc("Vertical distance from the natural terrain corridor to the solved water surface.")
private IrisStyledRange surfaceInset = range(3D, 7D, 768D, 2D);
@MinNumber(1)
@MaxNumber(16)
@Desc("Blocks the water surface sits below the lowest natural bank beside the channel.")
private int inset = 1;
@MinNumber(0)
@MaxNumber(64)
@Desc("Maximum permitted channel incision below natural terrain.")
private int maximumIncision = 6;
@Desc("Maximum cut below natural terrain at the channel center before a course is rejected.")
private int maximumIncision = 10;
@MinNumber(1)
@MaxNumber(2)
@Desc("Narrow biome-selection band immediately outside the wet channel.")
private double shoreWidth = 2D;
@MinNumber(0)
@MaxNumber(1)
@Desc("Coherent variation of the wet outline and bed as a fraction of the channel size.")
private double roughness = 0.25D;
@Desc("Wider terrain-grading distance outside the shore band.")
private IrisStyledRange terrainBlendWidth = range(10D, 24D, 1024D);
@MinNumber(3)
@MaxNumber(128)
@Desc("Wavelength in blocks of the outline and bed variation.")
private int roughnessWavelength = 16;
private static IrisStyledRange range(double min, double max, double zoom) {
return range(min, max, zoom, 1D);
}
private static IrisStyledRange range(double min, double max, double zoom, double exponent) {
IrisGeneratorStyle style = new IrisGeneratorStyle(NoiseStyle.IRIS)
.zoomed(zoom)
.setExponent(exponent);
IrisGeneratorStyle style = new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(zoom);
return new IrisStyledRange(min, max, style);
}
}
@@ -16,15 +16,15 @@ public class IrisSurfaceRiverConfig {
@Desc("Surface-river source allocation.")
private IrisSurfaceRiverSourceConfig sources = new IrisSurfaceRiverSourceConfig();
@Desc("Surface channel and surrounding terrain footprint.")
@Desc("Wet channel width, depth, inset and roughness.")
private IrisSurfaceRiverChannelConfig channel = new IrisSurfaceRiverChannelConfig();
@Desc("Pool, riffle, cascade, and waterfall classification thresholds.")
private IrisRiverHydraulicsConfig hydraulics = new IrisRiverHydraulicsConfig();
@Desc("Lip, shore band and eroded valley around the channel.")
private IrisSurfaceRiverBankConfig banks = new IrisSurfaceRiverBankConfig();
@Desc("Short ridge-bore configuration for otherwise viable surface routes.")
private IrisRiverRidgeTunnelConfig ridgeTunnels = new IrisRiverRidgeTunnelConfig();
@Desc("Cascade and waterfall thresholds.")
private IrisSurfaceRiverFlowConfig flow = new IrisSurfaceRiverFlowConfig();
@Desc("Coastal mouth leveling and ocean-apron limits.")
@Desc("Coastal mouth flare and ocean-apron limits.")
private IrisRiverMouthConfig mouths = new IrisRiverMouthConfig();
}
@@ -0,0 +1,24 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.MaxNumber;
import art.arcane.iris.engine.object.annotations.MinNumber;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Accessors(chain = true)
@NoArgsConstructor
@Desc("Controls how a surface river descends.")
@Data
public class IrisSurfaceRiverFlowConfig {
@MinNumber(1)
@MaxNumber(16)
@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)
@Desc("Smallest natural cliff in blocks that becomes a waterfall instead of a cascade.")
private int waterfallMinimumDrop = 6;
}
@@ -1,6 +1,8 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.MaxNumber;
import art.arcane.iris.engine.object.annotations.MinNumber;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@@ -31,6 +33,11 @@ public class IrisUndergroundRiverConfig {
@Desc("Allow accepted underground courses to connect transactionally to existing caves.")
private boolean connectToExistingCaves = true;
@MinNumber(0)
@MaxNumber(2048)
@Desc("Distance in blocks over which an ocean-bound underground river levels to sea level before its mouth.")
private int mouthLevelingDistance = 64;
private static IrisStyledRange range(double min, double max, double zoom) {
return new IrisStyledRange(min, max, new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(zoom));
}
@@ -50,7 +50,7 @@ public class PackHydrologyValidatorTest {
"derivative": "minecraft:river",
"riverPolicy": {
"profiles": ["default"],
"dryBiomes": ["river_dry"]
"bankBiomes": ["river_dry"]
}
}
""");
@@ -267,7 +267,7 @@ public class PackHydrologyValidatorTest {
"surfaceBiomes": ["missing"],
"mouthBiomes": "river",
"shoreBiomes": ["biome", "biome"],
"dryBiomes": [17],
"bankBiomes": [17],
"floodedCaveBiomes": ["../escape"],
"widthMultiplier": 0,
"depthMultiplier": 17,
@@ -286,7 +286,7 @@ public class PackHydrologyValidatorTest {
assertContains(result.errors(), "surfaceBiomes[0] references missing biome 'missing'");
assertContains(result.errors(), ".mouthBiomes must be an array or null");
assertContains(result.errors(), "shoreBiomes[1] duplicates biome 'biome'");
assertContains(result.errors(), "dryBiomes[0] must name a biome resource");
assertContains(result.errors(), "bankBiomes[0] must name a biome resource");
assertContains(result.errors(), "floodedCaveBiomes[0] must name a biome resource");
assertContains(result.errors(), "widthMultiplier must be at least");
assertContains(result.errors(), "depthMultiplier must be at most");
@@ -859,7 +859,7 @@ public class PackHydrologyValidatorTest {
"surfaceBiomes": ["river_surface"],
"mouthBiomes": ["river_surface"],
"shoreBiomes": ["river_shore"],
"dryBiomes": ["river_dry"],
"bankBiomes": ["river_dry"],
"floodedCaveBiomes": ["river_dry"],
"widthMultiplier": 1,
"depthMultiplier": 1,
@@ -164,7 +164,7 @@ public class IrisRiverSchemaTest {
assertEquals("array", properties.getJSONObject("surfaceBiomes").getString("type"));
assertEquals("array", properties.getJSONObject("mouthBiomes").getString("type"));
assertEquals("array", properties.getJSONObject("shoreBiomes").getString("type"));
assertEquals("array", properties.getJSONObject("dryBiomes").getString("type"));
assertEquals("array", properties.getJSONObject("bankBiomes").getString("type"));
assertEquals("array", properties.getJSONObject("floodedCaveBiomes").getString("type"));
assertEquals(16D, properties.getJSONObject("widthMultiplier").getDouble("maximum"), 0D);
assertEquals(0D, properties.getJSONObject("incisionMultiplier").getDouble("minimum"), 0D);
@@ -270,7 +270,8 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
true,
96,
10
),
,
HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(12, 40, 1, 3, 8, 4),
new HydrologyPlannerSettings.Underground(
true,
@@ -351,7 +352,8 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
baseSurface.ridgeTunnelsEnabled(),
baseSurface.maximumRidgeTunnelLength(),
baseSurface.ridgeTunnelHeadroom()
),
,
HydrologyPlannerSettings.Banks.defaults()),
base.hydraulics(),
new HydrologyPlannerSettings.Underground(
false,
@@ -412,6 +414,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -452,11 +455,12 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
sample.depthMultiplier(),
sample.incisionMultiplier(),
sample.routingMultiplier(),
1D,
sample.parentBiomeKey(),
sample.surfaceBiomeKey(),
sample.mouthBiomeKey(),
sample.shoreBiomeKey(),
sample.dryBiomeKey(),
sample.bankBiomeKey(),
sample.floodedCaveBiomeKey(),
sample.preferredProfileKeys()
);
@@ -484,6 +488,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
1D,
1D,
1D,
1D,
"ocean_parent",
"surface",
"mouth",
@@ -687,7 +687,8 @@ public class HydrologyPlannerGlobalInvariantTest {
false,
0,
0
),
,
HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 3, 8, 4),
new HydrologyPlannerSettings.Underground(
undergroundEnabled,
@@ -752,7 +753,8 @@ public class HydrologyPlannerGlobalInvariantTest {
surface.ridgeTunnelsEnabled(),
surface.maximumRidgeTunnelLength(),
surface.ridgeTunnelHeadroom()
),
,
HydrologyPlannerSettings.Banks.defaults()),
base.hydraulics(),
new HydrologyPlannerSettings.Underground(
underground.enabled(),
@@ -935,6 +937,7 @@ public class HydrologyPlannerGlobalInvariantTest {
1D,
1D,
1D,
1D,
ocean ? "ocean" : "land",
"surface",
"mouth",
@@ -985,6 +988,7 @@ public class HydrologyPlannerGlobalInvariantTest {
1D,
1D,
1D,
1D,
"land",
"surface",
"mouth",
@@ -1033,6 +1037,7 @@ public class HydrologyPlannerGlobalInvariantTest {
1D,
1D,
1D,
1D,
"land",
"surface",
"mouth",
@@ -1157,7 +1162,7 @@ public class HydrologyPlannerGlobalInvariantTest {
addString(terrain.surfaceBiomeKey());
addString(terrain.mouthBiomeKey());
addString(terrain.shoreBiomeKey());
addString(terrain.dryBiomeKey());
addString(terrain.bankBiomeKey());
addString(terrain.floodedCaveBiomeKey());
addInt(terrain.preferredProfileKeys().size());
for (String profile : terrain.preferredProfileKeys()) {
@@ -1287,7 +1292,7 @@ public class HydrologyPlannerGlobalInvariantTest {
addString(layer.surfaceBiomeKey());
addString(layer.mouthBiomeKey());
addString(layer.shoreBiomeKey());
addString(layer.dryBiomeKey());
addString(layer.bankBiomeKey());
addString(layer.floodedCaveBiomeKey());
}
@@ -89,7 +89,8 @@ public class HydrologyPlannerSettingsTest {
false,
0,
0
);
,
HydrologyPlannerSettings.Banks.defaults());
HydrologyPlannerSettings.Hydraulics hydraulics = new HydrologyPlannerSettings.Hydraulics(
8,
8,
@@ -139,6 +140,38 @@ public class HydrologyPlannerSettingsTest {
assertEquals(2730, deepFluid.maximumChannelLength());
}
@Test
public void bankSettingsRejectValuesOutsideTheirBounds() {
HydrologyPlannerSettings.Banks banks = HydrologyPlannerSettings.Banks.defaults();
assertEquals(1, banks.inset());
assertEquals(1, banks.freeboard());
assertEquals(3D, banks.blendSlope(), 0D);
assertEquals(4, banks.minimumBlendWidth());
assertEquals(32, banks.maximumBlendWidth());
assertEquals(6, banks.waterfallMinimumDrop());
assertTrue(banks.exposeCutStrata());
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks(
0, 1, 3D, 4, 32, 0.25D, 16, 2, 6, 1.6D, true));
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks(
1, 0, 3D, 4, 32, 0.25D, 16, 2, 6, 1.6D, true));
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks(
1, 1, 0.5D, 4, 32, 0.25D, 16, 2, 6, 1.6D, true));
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks(
1, 1, 3D, 40, 32, 0.25D, 16, 2, 6, 1.6D, true));
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks(
1, 1, 3D, 4, 32, 0.25D, 16, 2, 1, 1.6D, true));
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks(
1, 1, 3D, 4, 32, 0.25D, 16, 0, 6, 1.6D, true));
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks(
1, 1, 3D, 4, 32, 1.5D, 16, 2, 6, 1.6D, true));
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Surface(
true,
new HydrologyPlannerSettings.Source(true, 1D, 0, 0, 1, 0),
1, 1, 1, 1, 1, 1, 0, 1D, 0, 0, false, 0, 0,
null));
}
@Test
public void inconsistentHydraulicThresholdsAreRejected() {
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Hydraulics(
@@ -509,6 +509,7 @@ public class HydrologyPlannerTest {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -1124,7 +1125,7 @@ public class HydrologyPlannerTest {
assertEquals("surface", layer.surfaceBiomeKey());
assertEquals("mouth", layer.mouthBiomeKey());
assertEquals("shore", layer.shoreBiomeKey());
assertEquals("dry", layer.dryBiomeKey());
assertEquals("dry", layer.bankBiomeKey());
assertEquals("flooded", layer.floodedCaveBiomeKey());
if (layer.grading() && !layer.shore() && !layer.channel()) {
sawGrading = true;
@@ -1204,7 +1205,8 @@ public class HydrologyPlannerTest {
base.surface().ridgeTunnelsEnabled(),
base.surface().maximumRidgeTunnelLength(),
base.surface().ridgeTunnelHeadroom()
);
,
HydrologyPlannerSettings.Banks.defaults());
HydrologyPlannerSettings shaped = new HydrologyPlannerSettings(
base.seaLevel(),
base.routing(),
@@ -1498,7 +1500,8 @@ public class HydrologyPlannerTest {
surface.ridgeTunnelsEnabled(),
32,
surface.ridgeTunnelHeadroom()
),
,
HydrologyPlannerSettings.Banks.defaults()),
base.hydraulics(),
base.underground(),
base.outlets(),
@@ -1850,7 +1853,8 @@ public class HydrologyPlannerTest {
true,
64,
8
),
,
HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4),
new HydrologyPlannerSettings.Underground(
undergroundDensity > 0D,
@@ -1926,7 +1930,8 @@ public class HydrologyPlannerTest {
surface.ridgeTunnelsEnabled(),
192,
surface.ridgeTunnelHeadroom()
),
,
HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 4, 12, 5),
base.underground(),
new HydrologyPlannerSettings.Outlets(
@@ -2002,6 +2007,7 @@ public class HydrologyPlannerTest {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -2066,6 +2072,7 @@ public class HydrologyPlannerTest {
1D,
1D,
1D,
1D,
"ocean_parent",
"surface",
"mouth",
@@ -2097,6 +2104,7 @@ public class HydrologyPlannerTest {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -2137,6 +2145,7 @@ public class HydrologyPlannerTest {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -2168,6 +2177,7 @@ public class HydrologyPlannerTest {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -2199,6 +2209,7 @@ public class HydrologyPlannerTest {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -2233,7 +2244,8 @@ public class HydrologyPlannerTest {
surface.ridgeTunnelsEnabled(),
surface.maximumRidgeTunnelLength(),
surface.ridgeTunnelHeadroom()
),
,
HydrologyPlannerSettings.Banks.defaults()),
settings.hydraulics(),
settings.underground(),
settings.outlets(),
@@ -236,7 +236,8 @@ public class HydrologyStyledGeometryTest {
true,
96,
8
),
,
HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4),
new HydrologyPlannerSettings.Underground(
undergroundDensity > 0D,
@@ -288,6 +289,7 @@ public class HydrologyStyledGeometryTest {
1D,
1D,
1D,
1D,
"ocean",
"surface",
"mouth",
@@ -319,6 +321,7 @@ public class HydrologyStyledGeometryTest {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -70,6 +70,7 @@ public final class HydrologyTerrainSampleTest {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -245,7 +245,7 @@ public class HydrologyTileCacheTest {
63,
new HydrologyPlannerSettings.Routing(64, 16, 4, 128, 64,
new HydrologyPlannerSettings.Branching(0, 0), 0D, 0D, 0D, 0D),
new HydrologyPlannerSettings.Surface(false, disabled, 2, 4, 1, 2, 2, 4, 4, 1D, 2, 4, false, 0, 0),
new HydrologyPlannerSettings.Surface(false, disabled, 2, 4, 1, 2, 2, 4, 4, 1D, 2, 4, false, 0, 0, HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 2, 8, 3),
new HydrologyPlannerSettings.Underground(false, disabled, -32, 32, 2, 4, 1, 2, 3, 4, false),
new HydrologyPlannerSettings.Outlets(
@@ -284,7 +284,7 @@ public class HydrologyTileCacheTest {
63,
new HydrologyPlannerSettings.Routing(64, 16, 4, 128, 96,
new HydrologyPlannerSettings.Branching(16, 8), 0.5D, 12D, 0.5D, 0.1D),
new HydrologyPlannerSettings.Surface(true, surfaceSources, 4, 8, 2, 3, 4, 8, 20, 1.5D, 2, 4, true, 96, 8),
new HydrologyPlannerSettings.Surface(true, surfaceSources, 4, 8, 2, 3, 4, 8, 20, 1.5D, 2, 4, true, 96, 8, HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 2, 8, 3),
new HydrologyPlannerSettings.Underground(false, disabled, -32, 32, 2, 4, 1, 2, 3, 4, false),
new HydrologyPlannerSettings.Outlets(
@@ -327,6 +327,7 @@ public class HydrologyTileCacheTest {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -29,10 +29,12 @@ public class RiverPolicyResolverTest {
assertEquals(IrisRiverRoutingMode.ALLOW, policy.routing());
assertTrue(policy.outletAdmission());
assertTrue(policy.profiles().isEmpty());
assertTrue(policy.bankBiomes().isEmpty());
assertEquals(1D, policy.widthMultiplier(), 0D);
assertEquals(1D, policy.depthMultiplier(), 0D);
assertEquals(1D, policy.incisionMultiplier(), 0D);
assertEquals(1D, policy.routingMultiplier(), 0D);
assertEquals(1D, policy.bankMultiplier(), 0D);
assertTrue(policy.allowsSources());
assertTrue(policy.allowsTransit());
assertTrue(policy.allowsRouting());
@@ -49,13 +51,16 @@ public class RiverPolicyResolverTest {
.setProfiles(new KList<>("water"))
.setSurfaceBiomes(new KList<>("river/dimension"))
.setMouthBiomes(new KList<>("river/mouth"))
.setBankBiomes(new KList<>("river/bank-dimension"))
.setWidthMultiplier(1.5D)
.setDepthMultiplier(1.25D);
.setDepthMultiplier(1.25D)
.setBankMultiplier(2D);
IrisRiverPolicy region = new IrisRiverPolicy()
.setRouting(IrisRiverRoutingMode.PREFER)
.setProfiles(new KList<>())
.setSurfaceBiomes(new KList<>("river/region"))
.setShoreBiomes(new KList<>("river/shore"))
.setBankBiomes(new KList<>())
.setWidthMultiplier(0.8D)
.setRoutingMultiplier(2D);
IrisRiverPolicy biome = new IrisRiverPolicy()
@@ -63,10 +68,10 @@ public class RiverPolicyResolverTest {
.setRouting(IrisRiverRoutingMode.BLOCK)
.setOutletAdmission(true)
.setSurfaceBiomes(new KList<>())
.setDryBiomes(new KList<>("river/dry"))
.setFloodedCaveBiomes(new KList<>("river/flooded"))
.setDepthMultiplier(0.75D)
.setIncisionMultiplier(0.5D);
.setIncisionMultiplier(0.5D)
.setBankMultiplier(0.5D);
EffectiveRiverPolicy policy = RiverPolicyResolver.resolve(dimension, region, biome);
@@ -75,14 +80,15 @@ public class RiverPolicyResolverTest {
assertTrue(policy.outletAdmission());
assertTrue(policy.profiles().isEmpty());
assertTrue(policy.surfaceBiomes().isEmpty());
assertTrue(policy.bankBiomes().isEmpty());
assertEquals(List.of("river/mouth"), policy.mouthBiomes());
assertEquals(List.of("river/shore"), policy.shoreBiomes());
assertEquals(List.of("river/dry"), policy.dryBiomes());
assertEquals(List.of("river/flooded"), policy.floodedCaveBiomes());
assertEquals(0.8D, policy.widthMultiplier(), 0D);
assertEquals(0.75D, policy.depthMultiplier(), 0D);
assertEquals(0.5D, policy.incisionMultiplier(), 0D);
assertEquals(2D, policy.routingMultiplier(), 0D);
assertEquals(0.5D, policy.bankMultiplier(), 0D);
assertTrue(policy.allowsSources());
assertTrue(policy.allowsTransit());
assertFalse(policy.allowsRouting());
@@ -90,6 +96,17 @@ public class RiverPolicyResolverTest {
assertTrue(policy.requiresHeadwaters());
}
@Test
public void bankBiomesInheritUntilOverridden() {
IrisRiverPolicy dimension = new IrisRiverPolicy().setBankBiomes(new KList<>("river/bank"));
IrisRiverPolicy region = new IrisRiverPolicy().setRouting(IrisRiverRoutingMode.PREFER);
EffectiveRiverPolicy policy = RiverPolicyResolver.resolve(dimension, region, null);
assertEquals(List.of("river/bank"), policy.bankBiomes());
assertEquals(1D, policy.bankMultiplier(), 0D);
}
@Test
public void resolvesOwnerPoliciesAndSnapshotsMutableLists() {
KList<String> profiles = new KList<>("water");
@@ -19,8 +19,7 @@ public class IrisHydrologyRuntimeSettingsTest {
.setShortChannels(true)
.setSpacing(8192);
IrisRiverRoutingConfig routing = new IrisRiverRoutingConfig()
.setTileSize(256)
.setRefinementSpacing(8);
.setTileSize(256);
assertEquals(128, IrisHydrologyRuntime.maximumDeepChannelLength(deepFluid, routing));
}
@@ -31,8 +30,7 @@ public class IrisHydrologyRuntimeSettingsTest {
.setShortChannels(false)
.setSpacing(8192);
IrisRiverRoutingConfig routing = new IrisRiverRoutingConfig()
.setTileSize(256)
.setRefinementSpacing(8);
.setTileSize(256);
assertEquals(0, IrisHydrologyRuntime.maximumDeepChannelLength(deepFluid, routing));
}
@@ -75,6 +73,57 @@ public class IrisHydrologyRuntimeSettingsTest {
.underground().sources().minimumSpacing());
}
@Test
public void surfaceBankAndFlowConfigurationMapsIntoPlannerSettings() {
IrisDimension dimension = new IrisDimension();
IrisRiverHydrology rivers = dimension.getHydrology().getRivers();
rivers.getSurface().getChannel().setInset(2).setMaximumIncision(12);
rivers.getSurface().getBanks()
.setFreeboard(2)
.setShoreWidth(2.5D)
.setBlendSlope(4D)
.setMinimumBlendWidth(6)
.setMaximumBlendWidth(48)
.setExposeCutStrata(false);
rivers.getSurface().getFlow().setCascadeRun(3).setWaterfallMinimumDrop(8);
rivers.getSurface().getMouths().setFlareRatio(2.2D);
rivers.getUnderground().setMouthLevelingDistance(96);
rivers.getRouting()
.setSampleSpacing(32)
.setMinimumSurfaceCourseLength(500)
.setMinimumUndergroundCourseLength(250)
.setValleyPreference(2.5D)
.setUphillPenalty(30D)
.setSlopePenalty(3D)
.setConfluenceAttraction(0.4D);
HydrologyPlannerSettings settings = IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology());
HydrologyPlannerSettings.Banks banks = settings.surface().banks();
assertEquals(2, banks.inset());
assertEquals(2, banks.freeboard());
assertEquals(4D, banks.blendSlope(), 0D);
assertEquals(6, banks.minimumBlendWidth());
assertEquals(48, banks.maximumBlendWidth());
assertEquals(3, banks.cascadeRun());
assertEquals(8, banks.waterfallMinimumDrop());
assertEquals(2.2D, banks.mouthFlareRatio(), 0D);
assertFalse(banks.exposeCutStrata());
assertEquals(12, settings.surface().maximumIncision());
assertEquals(2.5D, settings.surface().shoreWidth(), 0D);
assertEquals(8, settings.hydraulics().waterfallMinimumDrop());
assertEquals(96, settings.outlets().mouthLevelingDistance());
assertEquals(4, settings.routing().refinementSpacing());
assertEquals(500, settings.routing().branching().minimumSurfaceCourseLength());
assertEquals(250, settings.routing().branching().minimumUndergroundCourseLength());
assertEquals(2.5D, settings.routing().valleyPreference(), 0D);
assertEquals(30D, settings.routing().uphillPenalty(), 0D);
assertEquals(3D, settings.routing().slopePenalty(), 0D);
assertEquals(0.4D, settings.routing().confluenceAttraction(), 0D);
assertEquals(2, IrisHydrologyRuntime.refinementSpacing(50));
assertEquals(1, IrisHydrologyRuntime.refinementSpacing(45));
}
@Test
public void routingOutletLimitControlsDrainageRootCount() {
IrisDimension dimension = new IrisDimension();
@@ -96,10 +145,8 @@ public class IrisHydrologyRuntimeSettingsTest {
.setMaximumOffsetRatio(0.44D)
.setSmoothingPasses(2)
.setMaximumTurnDegrees(76D);
rivers.getGeometry().getSurface()
.setBedRoundness(3.1D)
.setBedRoughness(0.37D)
.setWallRoughness(0.29D)
rivers.getSurface().getChannel()
.setRoughness(0.37D)
.setRoughnessWavelength(13);
rivers.getGeometry().getDrops()
.setCascadeRunPerBlock(4)
@@ -121,9 +168,8 @@ public class IrisHydrologyRuntimeSettingsTest {
assertEquals(0.44D, geometry.meanders().maximumOffsetRatio(), 0D);
assertEquals(2, geometry.meanders().smoothingPasses());
assertEquals(76D, geometry.meanders().maximumTurnDegrees(), 0D);
assertEquals(3.1D, geometry.surface().bedRoundness(), 0D);
assertEquals(0.37D, geometry.surface().bedRoughness(), 0D);
assertEquals(0.29D, geometry.surface().wallRoughness(), 0D);
assertEquals(0.37D, geometry.surface().wallRoughness(), 0D);
assertEquals(13, geometry.surface().roughnessWavelength());
assertEquals(4, geometry.drops().cascadeRunPerBlock());
assertEquals(2.2D, geometry.drops().cascadeExponent(), 0D);
@@ -1541,7 +1541,8 @@ public class MantleHydrologyComponentTest {
false,
0,
0
),
,
HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 3, 8, 4),
new HydrologyPlannerSettings.Underground(
false,
@@ -1597,6 +1598,7 @@ public class MantleHydrologyComponentTest {
1D,
1D,
1D,
1D,
ocean ? "ocean" : "land",
"surface",
"mouth",
@@ -148,7 +148,7 @@ public class IrisDimensionReachableBiomesTest {
.setChildren(new KList<>("dimension-child"));
IrisBiome dimensionChild = biome("dimension-child");
IrisBiome regionMouth = biome("region-mouth").setRiverPolicy(new IrisRiverPolicy()
.setDryBiomes(new KList<>("nested-dry")));
.setBankBiomes(new KList<>("nested-dry")));
IrisBiome biomeShore = biome("biome-shore");
IrisBiome nestedDry = biome("nested-dry");
@@ -222,7 +222,7 @@ public class IrisDimensionReachableBiomesTest {
IrisRegion reachable = new IrisRegion()
.setLandBiomes(new KList<>("root"))
.setRiverPolicy(new IrisRiverPolicy()
.setDryBiomes(new KList<>("region-dry")));
.setBankBiomes(new KList<>("region-dry")));
IrisBiome root = biome("root").setRiverPolicy(new IrisRiverPolicy()
.setFloodedCaveBiomes(new KList<>("biome-flooded")));
@@ -32,9 +32,14 @@ public class IrisRiverConfigurationTest {
IrisRiverRoutingConfig routing = dimension.getHydrology().getRivers().getRouting();
assertEquals(2048, routing.getTileSize());
assertEquals(64, routing.getSampleSpacing());
assertEquals(16384, routing.getMaximumRouteLength());
assertEquals(384, routing.getBranching().getMinimumSurfaceCourseLength());
assertEquals(192, routing.getBranching().getMinimumUndergroundCourseLength());
assertEquals(384, routing.getMinimumSurfaceCourseLength());
assertEquals(192, routing.getMinimumUndergroundCourseLength());
assertEquals(1.5D, routing.getValleyPreference(), 0D);
assertEquals(24D, routing.getUphillPenalty(), 0D);
assertEquals(2D, routing.getSlopePenalty(), 0D);
assertEquals(0.2D, routing.getConfluenceAttraction(), 0D);
IrisSurfaceRiverConfig surface = dimension.getHydrology().getRivers().getSurface();
assertTrue(surface.isEnabled());
@@ -43,19 +48,22 @@ public class IrisRiverConfigurationTest {
assertEquals(384, surface.getSources().getMinimumSpacing());
assertEquals(4D, surface.getChannel().getWidth().getMin(), 0D);
assertEquals(8D, surface.getChannel().getWidth().getMax(), 0D);
assertEquals(1D, surface.getChannel().getDepth().getMin(), 0D);
assertEquals(2D, surface.getChannel().getDepth().getMin(), 0D);
assertEquals(4D, surface.getChannel().getDepth().getMax(), 0D);
assertEquals(2D, surface.getChannel().getDepth().getStyle().getExponent(), 0D);
assertEquals(3D, surface.getChannel().getSurfaceInset().getMin(), 0D);
assertEquals(7D, surface.getChannel().getSurfaceInset().getMax(), 0D);
assertEquals(2D, surface.getChannel().getSurfaceInset().getStyle().getExponent(), 0D);
assertEquals(6, surface.getChannel().getMaximumIncision());
assertEquals(2D, surface.getChannel().getShoreWidth(), 0D);
assertEquals(64, surface.getMouths().getLevelingDistance());
assertEquals(1, surface.getChannel().getInset());
assertEquals(10, surface.getChannel().getMaximumIncision());
assertEquals(0.25D, surface.getChannel().getRoughness(), 0D);
assertEquals(16, surface.getChannel().getRoughnessWavelength());
assertEquals(1, surface.getBanks().getFreeboard());
assertEquals(1.5D, surface.getBanks().getShoreWidth(), 0D);
assertEquals(3D, surface.getBanks().getBlendSlope(), 0D);
assertEquals(4, surface.getBanks().getMinimumBlendWidth());
assertEquals(32, surface.getBanks().getMaximumBlendWidth());
assertTrue(surface.getBanks().isExposeCutStrata());
assertEquals(2, surface.getFlow().getCascadeRun());
assertEquals(6, surface.getFlow().getWaterfallMinimumDrop());
assertEquals(1.6D, surface.getMouths().getFlareRatio(), 0D);
assertEquals(8, surface.getMouths().getMaximumOceanApron());
assertEquals(80D, surface.getHydraulics().getTargetPoolLength().getMin(), 0D);
assertEquals(180D, surface.getHydraulics().getTargetPoolLength().getMax(), 0D);
assertEquals(8, surface.getHydraulics().getWaterfallMinimumDrop());
IrisRiverDropShapeConfig drops = dimension.getHydrology().getRivers().getGeometry().getDrops();
assertEquals(2, drops.getCascadeRunPerBlock());
assertEquals(1.4D, drops.getCascadeExponent(), 0D);
@@ -75,6 +83,7 @@ public class IrisRiverConfigurationTest {
assertEquals(3D, underground.getChannelWidth().getMin(), 0D);
assertEquals(8D, underground.getChannelWidth().getMax(), 0D);
assertTrue(underground.isConnectToExistingCaves());
assertEquals(64, underground.getMouthLevelingDistance());
IrisCoastalRiverGrottoConfig coastal = dimension.getHydrology().getRivers().getGrottos().getCoastal();
IrisInlandRiverGrottoConfig inland = dimension.getHydrology().getRivers().getGrottos().getInland();
@@ -101,6 +110,7 @@ public class IrisRiverConfigurationTest {
assertNull(dimension.getRiverPolicy().getPlacement());
assertNull(dimension.getRiverPolicy().getRouting());
assertNull(dimension.getRiverPolicy().getOutletAdmission());
assertNull(dimension.getRiverPolicy().getBankMultiplier());
assertNull(new IrisRegion().getRiverPolicy());
assertNull(new IrisBiome().getRiverPolicy());
}
@@ -116,15 +126,16 @@ public class IrisRiverConfigurationTest {
"routing": {
"tileSize": 4096,
"sampleSpacing": 96,
"refinementSpacing": 6,
"branching": {
"minimumSurfaceCourseLength": 640,
"minimumUndergroundCourseLength": 480
},
"maximumRouteLength": 32768,
"minimumSurfaceCourseLength": 640,
"minimumUndergroundCourseLength": 480,
"maximumOutletsPerTile": 3,
"oceanOutlets": true,
"inlandOutlets": ["SINKHOLE_GROTTO"]
"inlandOutlets": ["SINKHOLE_GROTTO"],
"valleyPreference": 2.5,
"uphillPenalty": 30,
"slopePenalty": 3,
"confluenceAttraction": 0.4
},
"geometry": {
"drops": {
@@ -143,20 +154,21 @@ public class IrisRiverConfigurationTest {
"channel": {
"width": {"min": 5, "max": 24},
"depth": {"min": 2, "max": 6},
"surfaceInset": {"min": 5, "max": 9},
"inset": 2,
"maximumIncision": 14,
"shoreWidth": 1.5,
"terrainBlendWidth": {"min": 8, "max": 10}
"roughness": 0.4,
"roughnessWavelength": 24
},
"hydraulics": {
"targetPoolLength": {"min": 64, "max": 192},
"riffleDrop": 1,
"maximumGradualDrop": 4,
"maximumGradualLength": 12,
"waterfallMinimumDrop": 5
"banks": {
"freeboard": 2,
"shoreWidth": 2.5,
"blendSlope": 4,
"minimumBlendWidth": 6,
"maximumBlendWidth": 48,
"exposeCutStrata": false
},
"ridgeTunnels": {"enabled": true, "maximumLength": 224, "headroom": 12},
"mouths": {"levelingDistance": 80, "maximumOceanApron": 1}
"flow": {"cascadeRun": 3, "waterfallMinimumDrop": 8},
"mouths": {"flareRatio": 2.2, "maximumOceanApron": 1}
},
"underground": {
"sources": {"density": 0.4, "minimumPerTile": 3, "minimumSpacing": 896},
@@ -164,7 +176,8 @@ public class IrisRiverConfigurationTest {
"channelWidth": {"min": 4, "max": 16},
"depth": {"min": 2, "max": 4},
"headroom": {"min": 7, "max": 15},
"connectToExistingCaves": false
"connectToExistingCaves": false,
"mouthLevelingDistance": 80
},
"grottos": {
"coastal": {
@@ -211,8 +224,10 @@ public class IrisRiverConfigurationTest {
"profiles": ["underworld"],
"surfaceBiomes": ["river/surface"],
"shoreBiomes": ["river/shore"],
"bankBiomes": ["river/bank"],
"widthMultiplier": 1.5,
"incisionMultiplier": 0.75
"incisionMultiplier": 0.75,
"bankMultiplier": 2
}
}
""", IrisDimension.class);
@@ -236,14 +251,31 @@ public class IrisRiverConfigurationTest {
IrisRiverHydrology rivers = dimension.getHydrology().getRivers();
assertTrue(rivers.isEnabled());
assertEquals(4096, rivers.getRouting().getTileSize());
assertEquals(96, rivers.getRouting().getSampleSpacing());
assertEquals(32768, rivers.getRouting().getMaximumRouteLength());
assertEquals(3, rivers.getRouting().getMaximumOutletsPerTile());
assertEquals(640, rivers.getRouting().getBranching().getMinimumSurfaceCourseLength());
assertEquals(480, rivers.getRouting().getBranching().getMinimumUndergroundCourseLength());
assertEquals(640, rivers.getRouting().getMinimumSurfaceCourseLength());
assertEquals(480, rivers.getRouting().getMinimumUndergroundCourseLength());
assertEquals(2.5D, rivers.getRouting().getValleyPreference(), 0D);
assertEquals(30D, rivers.getRouting().getUphillPenalty(), 0D);
assertEquals(3D, rivers.getRouting().getSlopePenalty(), 0D);
assertEquals(0.4D, rivers.getRouting().getConfluenceAttraction(), 0D);
assertEquals(new KList<>(IrisRiverInlandOutlet.SINKHOLE_GROTTO), rivers.getRouting().getInlandOutlets());
assertEquals(24D, rivers.getSurface().getChannel().getWidth().getMax(), 0D);
assertEquals(9D, rivers.getSurface().getChannel().getSurfaceInset().getMax(), 0D);
assertEquals(5, rivers.getSurface().getHydraulics().getWaterfallMinimumDrop());
assertEquals(2, rivers.getSurface().getChannel().getInset());
assertEquals(14, rivers.getSurface().getChannel().getMaximumIncision());
assertEquals(0.4D, rivers.getSurface().getChannel().getRoughness(), 0D);
assertEquals(24, rivers.getSurface().getChannel().getRoughnessWavelength());
assertEquals(2, rivers.getSurface().getBanks().getFreeboard());
assertEquals(2.5D, rivers.getSurface().getBanks().getShoreWidth(), 0D);
assertEquals(4D, rivers.getSurface().getBanks().getBlendSlope(), 0D);
assertEquals(6, rivers.getSurface().getBanks().getMinimumBlendWidth());
assertEquals(48, rivers.getSurface().getBanks().getMaximumBlendWidth());
assertFalse(rivers.getSurface().getBanks().isExposeCutStrata());
assertEquals(3, rivers.getSurface().getFlow().getCascadeRun());
assertEquals(8, rivers.getSurface().getFlow().getWaterfallMinimumDrop());
assertEquals(2.2D, rivers.getSurface().getMouths().getFlareRatio(), 0D);
assertEquals(1, rivers.getSurface().getMouths().getMaximumOceanApron());
assertEquals(768, rivers.getSurface().getSources().getMinimumSpacing());
assertEquals(5, rivers.getGeometry().getDrops().getCascadeRunPerBlock());
assertEquals(2.4D, rivers.getGeometry().getDrops().getCascadeExponent(), 0D);
@@ -252,10 +284,10 @@ public class IrisRiverConfigurationTest {
assertEquals(4, rivers.getGeometry().getDrops().getMaximumFlowDepth());
assertEquals(2.2D, rivers.getGeometry().getDrops().getBasinWidthRatio(), 0D);
assertEquals(11, rivers.getGeometry().getDrops().getMaximumBasinDepth());
assertEquals(224, rivers.getSurface().getRidgeTunnels().getMaximumLength());
assertEquals(-96D, rivers.getUnderground().getFluidLevel().getMin(), 0D);
assertEquals(896, rivers.getUnderground().getSources().getMinimumSpacing());
assertFalse(rivers.getUnderground().isConnectToExistingCaves());
assertEquals(80, rivers.getUnderground().getMouthLevelingDistance());
assertEquals(40, rivers.getGrottos().getCoastal().getHorizontalRadius());
assertTrue(rivers.getGrottos().getInland().isEnabled());
assertTrue(rivers.getGrottos().getInland().isConnectSurfaceRivers());
@@ -273,7 +305,8 @@ public class IrisRiverConfigurationTest {
assertEquals(IrisRiverPlacementMode.PREFERRED_HEADWATER, dimension.getRiverPolicy().getPlacement());
assertEquals(IrisRiverRoutingMode.PREFER, dimension.getRiverPolicy().getRouting());
assertFalse(dimension.getRiverPolicy().getOutletAdmission());
assertEquals(Set.of("river/surface", "river/shore"), dimension.getRiverPolicy().getAllBiomeIds());
assertEquals(2D, dimension.getRiverPolicy().getBankMultiplier(), 0D);
assertEquals(Set.of("river/surface", "river/shore", "river/bank"), dimension.getRiverPolicy().getAllBiomeIds());
assertEquals(IrisRiverPlacementMode.TRANSIT_ONLY, region.getRiverPolicy().getPlacement());
assertEquals(new KList<>("river/mouth"), region.getRiverPolicy().getMouthBiomes());
assertEquals(IrisRiverPlacementMode.REQUIRED_HEADWATER, biome.getRiverPolicy().getPlacement());
@@ -3862,7 +3862,7 @@ public final class HydrologyPackProbe {
position.z()
);
String expectedBiome = witness.action() == HydrologyCaveAction.DRY_AIR
? witness.layer().dryBiomeKey()
? witness.layer().bankBiomeKey()
: witness.layer().floodedCaveBiomeKey();
if (cell == null
|| cell.action() != witness.action()
@@ -231,7 +231,8 @@ public final class HydrologyPlannerProbe {
true,
96,
8
),
,
HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4),
new HydrologyPlannerSettings.Underground(
undergroundDensity > 0D,
@@ -289,6 +290,7 @@ public final class HydrologyPlannerProbe {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -396,7 +398,8 @@ public final class HydrologyPlannerProbe {
true,
96,
10
),
,
HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(12, 40, 1, 3, 8, 4),
new HydrologyPlannerSettings.Underground(
true,
@@ -460,6 +463,7 @@ public final class HydrologyPlannerProbe {
1D,
1D,
1D,
1D,
"parent",
"surface",
"mouth",
@@ -492,6 +496,7 @@ public final class HydrologyPlannerProbe {
1D,
1D,
1D,
1D,
"ocean_parent",
"surface",
"mouth",