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) } }