package coast import ( "testing" "salty/terrain/internal/field" ) // paintedSeaFixture is a straight coast whose whole sea is painted at one depth, the way a planet's ocean // class is. The sea is made wide enough to hold the derived margin and a stretch of open water past it, so // the test can ask the question that matters: how much of what the author painted survives. func paintedSeaFixture(w, h, split int, cellM, backshoreM, paintedM float64) (*field.Field, []bool, []float32) { f, sea := coastFixture(w, h, split, cellM, backshoreM) abyss := make([]float32, w*h) for i := range abyss { if sea[i] { abyss[i] = float32(paintedM) } } return f, sea, abyss } // TestTheDerivedMarginDoesNotSwallowThePaintedOcean is the D-64 regression, stated as the property rather // than as the number that was wrong. // // The sea floor near a shore is derived and the sea floor away from it is the painting; the break depth is // what joins them. Set the break far shallower than the paint and the join stops being a join: the derived // profile is then a shallow bench that runs from the waterline out to the full reach of the margin, and on a // planet whose straits are narrower than twice that reach it *is* the ocean. That is not visible in a profile // test - the shape is monotone and correct at any break depth - so this measures the volume instead. // // 512 m is the first template's `ocean` class. 30 m was the inherited square-canvas break, 130 m is the // planet default. func TestTheDerivedMarginDoesNotSwallowThePaintedOcean(t *testing.T) { const w, h, split = 1400, 20, 1200 const cellM, painted = 8.0, 512.0 cfg := shelfOnlyCfg() measure := func(breakM float64) (shallow float64, atBreak, far float64) { f, sea, abyss := paintedSeaFixture(w, h, split, cellM, 5, painted) Build(Input{Height: f, Sea: sea, SeaLevelM: 0, BreakM: breakM, AbyssM: painted, Abyss: abyss, Seed: 7, Cfg: cfg}) y := h / 2 n, under50 := 0, 0 for x := 0; x < split; x++ { n++ if -float64(f.Data[y*w+x]) < 50 { under50++ } } // Just inside the widest shelf, and well past the shelf and the slope together. shelfCells := int(cfg.ShelfKm.Hi()*1000/cellM) - 2 reachCells := int((cfg.ShelfKm.Hi() + cfg.SlopeKm) * 1000 / cellM) return float64(under50) / float64(n), -float64(f.Data[y*w+split-1-shelfCells]), -float64(f.Data[y*w+split-1-reachCells-20]) } oldShallow, oldBreak, oldFar := measure(30) newShallow, newBreak, newFar := measure(130) t.Logf("break 30 m: %.0f%% of this sea shallower than 50 m, %.0f m at the break, %.0f m offshore", oldShallow*100, oldBreak, oldFar) t.Logf("break 130 m: %.0f%% of this sea shallower than 50 m, %.0f m at the break, %.0f m offshore", newShallow*100, newBreak, newFar) // Both must reach the painting in open water: the margin is a join, never a replacement. for _, c := range []struct { name string far float64 }{{"30 m", oldFar}, {"130 m", newFar}} { if c.far < painted-2 { t.Errorf("break %s: open water is %.0f m, want the painted %.0f m", c.name, c.far, painted) } } // The break is where it was asked for, which is what makes it the knob worth having. if newBreak < 110 || newBreak > 140 { t.Errorf("the shelf break is at %.0f m, want about 130 m", newBreak) } // And the shallow bench shrinks. This is the whole defect: at a 30 m break every cell of the derived // margin is shallower than 50 m by construction, so the bench is as wide as the margin reaches. if !(newShallow < oldShallow*0.75) { t.Errorf("shallow water is %.0f%% of the sea at a 130 m break against %.0f%% at 30 m; deepening the "+ "break has to shrink the bench or it is not doing anything", newShallow*100, oldShallow*100) } } // TestAPaintedShallowStraitIsStillShallow is the other half, and it is what stops the fix above from being a // blunt instrument: the break can never be deeper than the water it is a break in. An author who paints a // 20 m surf class gets 20 m of water, not a 130 m trench dug through it. func TestAPaintedShallowStraitIsStillShallow(t *testing.T) { const w, h, split = 1400, 20, 1200 const cellM, painted = 8.0, 20.0 f, sea, abyss := paintedSeaFixture(w, h, split, cellM, 5, painted) Build(Input{Height: f, Sea: sea, SeaLevelM: 0, BreakM: 130, AbyssM: painted, Abyss: abyss, Seed: 7, Cfg: shelfOnlyCfg()}) y := h / 2 for x := 0; x < split; x++ { if d := -float64(f.Data[y*w+x]); d > painted+1 { t.Fatalf("%.0f m offshore: %.1f m of water over a sea painted at %.0f m", float64(split-x)*cellM, d, painted) } } }