Files
2026-09-25 17:02:24 +03:00

112 lines
3.9 KiB
Go

package uplift
import (
"math"
"testing"
"salty/terrain/internal/manifest"
"salty/terrain/internal/world"
)
// For n = 1 the uplift rate alone fixes the hillslope angle, so a uniformly painted island sits at the angle
// of repose right down to the water. The coastal plain is what puts the range inland: the rate ramps from a
// low value at the shore up to the class rate over a stated distance.
//
// The distinction from D-52, which removed a coastal taper, is that this one is asked for and does not go to
// zero - the waterline keeps a real rate, so the strip the surf works in is not flattened.
func TestTheCoastalPlainRampsInlandAndNeverToZero(t *testing.T) {
// A 64 m cell so that a four-kilometre plain is 62 cells rather than 500: the grid has to be comfortably
// wider than the thing being measured, which the first version of this test was not.
const w, h, cellM = 256, 32, 64.0
p := world.Planet{CellM: cellM, W: w, H: h, PadY: 0, NoisePeriodM: float64(w) * cellM}
if err := p.Validate(); err != nil {
t.Fatal(err)
}
f := world.Whole(p)
class := make([]uint8, w*h)
land := make([]bool, w*h)
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
// Land from x = 20 rightwards, so distance to the shore is x - 20 cells.
if x >= 20 {
class[y*w+x], land[y*w+x] = 1, true
}
}
}
const rate = 0.0009 // 0.9 mm/yr
const floor = 0.00006 // 0.06 mm/yr
const plainM = 4000.0 // 4 km
m := manifest.Defaults()
m.Source.Seed = 7
up := FromTemplate(Paint{
Frame: f, Class: class, Land: land,
Rates: []float32{0, rate},
Ks: []float32{1, 1},
PlainM: []float64{0, plainM},
PlainFloor: []float32{0, floor},
Variation: 0, // the swell off, so the ramp is the only thing being measured
}, m)
y := h / 2
at := func(cellsInland int) float64 { return float64(up.Rate.Data[y*w+20+cellsInland]) }
// Four kilometres is 62.5 cells at 64 m.
const full = 63
// The first land cell is one cell from the water, not zero, so it has already climbed a sliver of the
// ramp. A couple of per cent is that sliver; anything more would mean the ramp is not anchored at the
// floor where it should be.
if got := at(0); math.Abs(got-floor) > 0.02*floor {
t.Errorf("at the waterline the rate is %.5f mm/yr, want the floor %.3f", got*1000, floor*1000)
}
if at(0) <= 0 {
t.Error("the rate at the waterline is zero; that is D-52's mistake, not this feature")
}
if got := at(full); math.Abs(got-rate) > 1e-7 {
t.Errorf("at 4 km inland the rate is %.4f mm/yr, want the class rate %.2f", got*1000, rate*1000)
}
if got := at(150); math.Abs(got-rate) > 1e-7 {
t.Errorf("well inland the rate is %.4f mm/yr, want the class rate %.2f", got*1000, rate*1000)
}
// Monotonic in between, and genuinely below the class rate at the halfway mark.
prev := 0.0
for d := 0; d <= full; d++ {
v := at(d)
if v < prev-1e-12 {
t.Fatalf("the ramp dips at %d cells inland", d)
}
prev = v
}
if got := at(full / 2); got >= rate*0.9 {
t.Errorf("halfway across the plain the rate is already %.3f mm/yr of %.2f; that is not a plain",
got*1000, rate*1000)
}
}
// Without the knob nothing changes, which is what keeps every existing world and every existing test where it
// was.
func TestNoCoastalPlainMeansTheRateReachesTheSea(t *testing.T) {
const w, h = 64, 32
p := world.Planet{CellM: 8, W: w, H: h, PadY: 0, NoisePeriodM: float64(w) * 8}
f := world.Whole(p)
class := make([]uint8, w*h)
land := make([]bool, w*h)
for i := range land {
if i%w >= 8 {
class[i], land[i] = 1, true
}
}
m := manifest.Defaults()
up := FromTemplate(Paint{
Frame: f, Class: class, Land: land,
Rates: []float32{0, 0.0009}, Ks: []float32{1, 1},
PlainM: []float64{0, 0}, PlainFloor: []float32{0, 0}, Variation: 0,
}, m)
y := h / 2
if got := float64(up.Rate.Data[y*w+8]); math.Abs(got-0.0009) > 1e-9 {
t.Errorf("the first land cell is at %.4f mm/yr, want the full 0.9", got*1000)
}
}