Cap tides on small enclosed seas (inland lakes stay calm)

A small closed-off ocean body cannot build a real tidal range -- the equilibrium tide
assumes a connected global ocean. computeTides now flood-fills connected ocean bodies and
caps any body under 10 cells to 0.01 m/cell + 0.03 m (a one-cell sea ~0.04 m); open oceans
(>=10 cells) keep the full equilibrium tide. The Sky & tides panel reads the capped level so
it stays consistent with the cell-info panel. test_ocean.cpp covers both cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jonas Reith 2026-06-28 13:42:17 +02:00
parent aa32e38dad
commit d4b46afe00
4 changed files with 52 additions and 5 deletions

View File

@ -356,7 +356,10 @@ Working and verified (logic tested headless):
v9** appends the moons block (`writeState`/`readState(...,hasMoons)`; pre-v9 saves synthesize
moons from the seed). `test_ocean.cpp`: moon count/determinism + RNG isolation, unit sweeping
sky dirs, zero-mean two-bulge tide (high under moon + antipode, low at 90°, moves with time),
save v9 round-trip.
save v9 round-trip. **Enclosed-sea cap:** `computeTides` flood-fills connected ocean bodies and
caps the amplitude of any body under 10 cells to `0.01·cells + 0.03` m (a one-cell sea ≈ 0.04 m,
an inland saltwater lake stays calm) — a small closed basin can't build a real tidal range;
open oceans (≥10 cells) keep the full equilibrium tide.
- **Live World — ocean currents + climate feedback:** `Planet::computeOceanCurrents()`
(PlanetOcean.cpp) builds a per-ocean-cell tangent velocity `sCurrent` (derived/not saved):
wind stress (`sWind`) rotated by a **Coriolis** deflection (right N / left S about the cell

View File

@ -290,11 +290,13 @@ void Viewer::renderLiveInfo() {
h += planet.cfg.tideSunFactor * (cs * cs - 1.0 / 3.0);
return h * planet.cfg.tideAmplitude;
};
double t0 = cellTide(doy, tod, days), t1 = cellTide(doy2, tod2, days2);
bool rising = t1 >= t0;
bool rising = cellTide(doy2, tod2, days2) >= cellTide(doy, tod, days); // direction
// Level from the actual tide field (so the enclosed-sea cap is reflected here too).
double lvl = ((int)planet.tide().size() == (int)planet.cells.size()) ? planet.tide()[selectedCell]
: cellTide(doy, tod, days);
DrawText(TextFormat("coastal cell #%d", selectedCell), x, y, 15, Color{160, 170, 185, 255}); y += 21;
DrawText(TextFormat("%+.2f m %s, %s", t0, t0 >= 0.0 ? "high" : "low", rising ? "rising" : "falling"),
x, y, 17, tideColor(t0, std::max(0.05, std::fabs(t0)))); y += 24;
DrawText(TextFormat("%+.2f m %s, %s", lvl, lvl >= 0.0 ? "high" : "low", rising ? "rising" : "falling"),
x, y, 17, tideColor(lvl, std::max(0.05, std::fabs(lvl)))); y += 24;
DrawText("(equilibrium model - placeholder)", x, y, 13, Color{120, 125, 140, 255});
} else {
DrawText("selected tile is inland", x, y, 15, Color{150, 155, 170, 255});

View File

@ -149,4 +149,29 @@ void Planet::computeTides(double dayOfYear01, double timeOfDay01, double timeDay
}
sTide[i] = amp * h;
}
// Enclosed-sea correction: a small, closed-off ocean body (an inland saltwater lake or a
// one-tile sea) cannot build a real tidal range -- the equilibrium tide assumes a connected
// global ocean. Cap the amplitude of any ocean body under `enclosedMax` cells to
// 0.01 m/cell + 0.03 m (1 cell -> ~0.04 m, scaling up to the threshold); larger / open
// oceans keep the full equilibrium tide. Components are flood-filled over the fixed grid.
const int enclosedMax = 10;
const double sea = cfg.seaLevel;
std::vector<char> seen(n, 0);
std::vector<int> stack, members;
for (int i = 0; i < n; ++i) {
if (cells[i].elevation > sea || seen[i]) continue;
stack.clear(); members.clear();
stack.push_back(i); seen[i] = 1;
while (!stack.empty()) {
int c = stack.back(); stack.pop_back(); members.push_back(c);
for (int nb : cells[c].neighbors)
if (cells[nb].elevation <= sea && !seen[nb]) { seen[nb] = 1; stack.push_back(nb); }
}
int sz = (int)members.size();
if (sz < enclosedMax) {
double cap = 0.01 * sz + 0.03;
for (int m : members) sTide[m] = std::clamp(sTide[m], -cap, cap);
}
}
}

View File

@ -128,6 +128,23 @@ int main() {
}
check(det, "currents + feedback deterministic for a seed");
std::printf("Ocean: enclosed-sea tide cap\n");
{
// A single isolated ocean cell (closed-off sea) must have a tiny tidal range.
Planet e; e.generate(cfg);
for (auto& cc : e.cells) cc.elevation = 100.0; // all land
e.cells[0].elevation = -100.0; // one-cell sea
e.computeTides(0.0, 0.3, 4.0);
check(std::fabs(e.tide()[0]) <= 0.04 + 1e-9, "a one-cell sea is capped to ~0.04 m");
// A fully open ocean (one giant body) keeps the full equilibrium tidal range.
Planet g; g.generate(cfg);
for (auto& cc : g.cells) cc.elevation = -100.0; // all ocean
g.computeTides(0.0, 0.3, 4.0);
double gmax = 0.0; for (int i = 0; i < n; ++i) gmax = std::max(gmax, std::fabs(g.tide()[i]));
check(gmax > 0.1, "a large open ocean keeps the full tidal range");
}
std::printf("Ocean: save v9\n");
std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
planet.writeState(ss);