package check import ( "math" "os" "strconv" "testing" "salty/terrain/internal/fluvial" "salty/terrain/internal/manifest" "salty/terrain/internal/thermal" "salty/terrain/internal/uplift" ) // TestTalusHoldsInThePipeline is the gap between a unit test and a system. thermal.Apply demonstrably cuts a // cone to the angle of repose, and yet a full run with the angle set to 22 degrees reports a median land slope // of 37. One of those two things is wrong and the difference matters: the angle of repose is the only direct // lever on how steep hill country looks. func TestTalusHoldsInThePipeline(t *testing.T) { const ( size = 300 deg = 22.0 steps = 300 ) passesUnderTest := 3 if v := os.Getenv("TALUS_PASSES"); v != "" { passesUnderTest, _ = strconv.Atoi(v) } m := manifest.Defaults() m.Source.Seed = 7 cellM := m.SideM() / float64(size-1) up := uplift.Build(size, cellM, m) h := up.Height.Clone() g := fluvial.NewGrid(size, size, cellM, up.Base) g.SetElevationRange(-2000, 4000) talus := thermal.TalusFromDegrees(deg) g.Run(h.Data, up.Rate.Data, up.K.Data, fluvial.Params{ K: m.Pipeline.Fluvial.K, M: 0.5, N: 1, DtYr: 1500, Steps: steps, Diffusion: 0.02, FillEvery: 1, TalusSlope: talus, ThermalEvery: 1, ThermalPasses: passesUnderTest, }, nil) if os.Getenv("CLAMP_AFTER") != "" { t.Logf("removed %.2f m mean by a final clamp", g.ClampToRepose(h.Data, talus)) } // The steepest neighbour drop anywhere on land, in the same units the angle of repose is written in. worst, count := 0.0, 0 wx, wy := 0, 0 worstOther := float32(0) worstOtherBase := false for y := 1; y < size-1; y++ { for x := 1; x < size-1; x++ { i := y*size + x if up.Base[i] { continue } for _, d := range [][2]int{{1, 0}, {0, 1}, {1, 1}, {1, -1}} { ni := (y+d[1])*size + (x + d[0]) if up.Base[ni] { continue } dist := cellM if d[0] != 0 && d[1] != 0 { dist = cellM * math.Sqrt2 } if s := math.Abs(float64(h.Data[i]-h.Data[ni])) / dist; s > worst { worst = s wx, wy = x, y worstOther = h.Data[ni] worstOtherBase = up.Base[ni] } } if count++; false { _ = count } } } t.Logf("worst pair at (%d,%d): h=%.1f vs h=%.1f, base=%v/%v", wx, wy, h.Data[wy*size+wx], worstOther, up.Base[wy*size+wx], worstOtherBase) gotDeg := math.Atan(worst) * 180 / math.Pi t.Logf("steepest land slope after %d steps: %.1f deg (repose %.1f, cell %.0f m)", steps, gotDeg, deg, cellM) if gotDeg > deg+5 { t.Errorf("steepest land slope is %.1f deg with the angle of repose at %.1f: landsliding is not binding", gotDeg, deg) } }