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>
93 lines
3.8 KiB
C++
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;
|
|
}
|