planetsim/test_weather.cpp
Jonas Reith f84e06507a Live World weather: dynamic clouds & rain cycle (save v10)
A per-cell humidity/cloud/rain cycle advanced on the live clock (PlanetWeather.cpp,
raylib-free): evaporate over warm sunlit seas -> advect humidity & cloud along the prevailing
wind (upwind differencing) -> condense into cloud (saturation vs temperature + windward
orographic lift) -> rain out thick cloud -> dissipate. Bounded exp-rate forms keep it stable at
any timestep, so it runs cleanly from hours/sec up to a month/sec. initWeather() spins the
fields up from the moisture climatology; fully deterministic (no RNG).

Render: a translucent cloud shell over the 3D globe (white -> dark slate where it rains,
alpha = cover) plus a matching drawWeather2D layer on the 2D map (shared drawMapTris
rasterizer), toggled with K (default on). stepSim runs stepWeather each live frame at the
sim-hours added to liveTime (held when paused). Cell-info shows cloud/humidity/raining.

Saved as v10 (humidity/cloud/rain, flag-gated; pre-v10 saves spin weather up live). New
weather* config knobs. Reseed/regen now also drops out of Live World. test_weather.cpp:
fields in range, clouds form + rain falls, oceans moister than land, determinism, v10
round-trip; the other four suites still pass; GUI build clean. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:01:34 +02:00

93 lines
3.8 KiB
C++

// Headless test for the Live World weather cycle (humidity / cloud / rain). No display needed.
//
// g++ -std=c++17 -O2 -Isrc/sim test_weather.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \
// src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp src/sim/PlanetErosion.cpp \
// src/sim/PlanetHydrology.cpp src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp \
// src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.cpp \
// src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp src/sim/PlanetFaunaGen.cpp \
// src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp -o /tmp/tw && /tmp/tw
//
// Verifies: fields stay in range; oceans (the evaporation source) end up moister than land;
// clouds form and rain falls somewhere; the cycle is deterministic; and save v10 round-trips it.
#include "Planet.hpp"
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <sstream>
static int failures = 0;
static void check(bool cond, const char* what) {
std::printf(" [%s] %s\n", cond ? "PASS" : "FAIL", what);
if (!cond) ++failures;
}
// Run a fixed weather sequence on a planet (returns whether rain ever fell, max cloud seen).
static void runWeather(Planet& p, bool& everRained, double& maxCloud) {
p.initWeather();
everRained = false; maxCloud = 0.0;
for (int k = 0; k < 200; ++k) {
p.computeInsolation(0.25, std::fmod(0.3 + 0.01 * k, 1.0)); // sun advances
p.stepWeather(1.0); // 1-hour steps
const std::vector<double>& rn = p.rain();
const std::vector<double>& cl = p.cloud();
for (size_t i = 0; i < rn.size(); ++i) {
if (rn[i] > 0.0) everRained = true;
maxCloud = std::max(maxCloud, cl[i]);
}
}
}
int main() {
PlanetConfig cfg; cfg.subdivisions = 5; cfg.seed = 1337;
Planet p; p.generate(cfg);
const int n = (int)p.cells.size();
std::printf("Weather: cycle\n");
bool rained = false; double maxCloud = 0.0;
runWeather(p, rained, maxCloud);
bool inRange = true;
for (int i = 0; i < n; ++i) {
if (p.humidity()[i] < -1e-9) inRange = false;
if (p.cloud()[i] < -1e-9 || p.cloud()[i] > 1.5 + 1e-9) inRange = false;
if (p.rain()[i] < -1e-9) inRange = false;
}
check(inRange, "humidity/cloud/rain stay in range");
check(maxCloud > 0.05, "clouds form");
check(rained, "rain falls somewhere");
// Oceans are the moisture source -> moister than land on average.
double oh = 0, lh = 0; int oc = 0, lc = 0;
for (int i = 0; i < n; ++i) {
if (p.cells[i].elevation <= cfg.seaLevel) { oh += p.humidity()[i]; ++oc; }
else { lh += p.humidity()[i]; ++lc; }
}
oh /= std::max(1, oc); lh /= std::max(1, lc);
std::printf(" mean humidity: ocean %.3f, land %.3f\n", oh, lh);
check(oh > lh, "oceans end up moister than land");
std::printf("Weather: determinism\n");
Planet p2; p2.generate(cfg);
bool r2; double mc2; runWeather(p2, r2, mc2);
bool same = true;
for (int i = 0; i < n; ++i)
if (p2.humidity()[i] != p.humidity()[i] || p2.cloud()[i] != p.cloud()[i]
|| p2.rain()[i] != p.rain()[i]) same = false;
check(same, "same seed + sequence -> identical weather");
std::printf("Weather: save v10\n");
std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
p.writeState(ss);
Planet q;
bool ok = q.readState(ss, true, true, true, true);
bool rt = ok && (int)q.cloud().size() == n;
for (int i = 0; i < n && rt; ++i)
if (q.humidity()[i] != p.humidity()[i] || q.cloud()[i] != p.cloud()[i] || q.rain()[i] != p.rain()[i])
rt = false;
check(rt, "save v10 round-trips the weather state");
std::printf(failures ? "\nSOME WEATHER CHECKS FAILED (%d)\n" : "\nALL WEATHER CHECKS PASSED\n", failures);
return failures ? 1 : 0;
}