Add realm names to the 2D map/exports; make diplomacy arcs a separate toggle

Kingdom/empire capital labels only ever drew on the 3D globe -- neither the
on-screen 2D map nor exportMapImage()/exportAtlasImage() showed them.
drawMapOverlays() now has a 2D realm-label block mirroring the existing 3D
one; exportAtlasImage() adds nation names as label candidates in its
decluttered pass (offset above the capital's own settlement-name anchor so
the two don't collide), shown regardless of the current colour mode since
the atlas is a reference, not "what you're looking at".

Also split the Territory view's political borders from its alliance/rivalry
diplomacy arcs, which previously always showed together with no way to hide
just the arcs. New independent showDiplomacy toggle (key A, default on, a
toolbar checkbox in Overlays) gates only the ally/rival arc draw calls.

Verified end to end: exported both Map and Atlas images from a live world
with several realms and confirmed realm names render correctly (cleanly
decluttered in the Atlas, alongside geography and settlement names).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMfyZv91tonDnPJqbaTVJE
This commit is contained in:
Jonas Reith 2026-08-30 19:23:41 +02:00
parent 7cc44cda84
commit a5b0d76c9c
6 changed files with 68 additions and 8 deletions

View File

@ -57,7 +57,8 @@ the full ~2.8x speedup; the default uses all cores for no extra gain:
E ecoregions colour view (names ecological provinces on first use; Eco tab)
I habitability heat map (where civilization can thrive)
U settlements: the dawn of civilization on first press, then toggle markers (Civ tab)
P territory / realms view + borders + war fronts + alliance/rivalry arcs (Realms tab)
P territory / realms view + borders + war fronts + realm-name labels (Realms tab)
A toggle alliance/rivalry diplomacy arcs on the Territory view (on by default)
X culture / faiths view + cultural borders (peoples/ethos/religion in the Cultures tab)
Z wealth / trade view + trade routes (sea/river/overland; prosperity feeds city growth)
Y follow-cam: cycle the 3D camera through active storms (Live World; off after last)

View File

@ -406,6 +406,22 @@ on the Live World clock). **Steps 17 of the roadmap are done (plus a derived
etc., now zero-arg methods on `Viewer`, e.g. `mode == ColorMode::Territory`) instead of stored,
independently-mutable state — the two can no longer drift apart because there's only one source of
truth. `src/render` only, no save-format or `src/sim` change; ctest unaffected (`src/sim` untouched).
- **Realm names in the 2D map/exports + a diplomacy-arc toggle independent of Territory** *(done)*
two follow-up requests after the above fix. (1) Kingdom/empire capital labels ("Kingdom of X") were
3D-globe-only (`renderFrame()`'s "3D realm labels" block); neither the on-screen 2D map nor
`exportMapImage()`/`exportAtlasImage()` ever drew them. `drawMapOverlays()` now has a matching 2D
block (same `showNationBorders() && drawLabels` gating, `projLonLat` instead of the 3D camera
projection; city-states skipped same as the 3D version) — fixes the on-screen map and
`exportMapImage()` in one place. `exportAtlasImage()` separately adds nation names as label
candidates in its decluttered pass (priority tier alongside cities; skipped for city-states), offset
above the capital's own settlement-name anchor so the two don't fight over one box; shown regardless
of the current colour mode, consistent with the atlas being a reference rather than "what you're
looking at". (2) `showNationBorders()` (Territory view) used to unconditionally show the political
borders **and** the Step-6 alliance/rivalry arcs together — no way to see realm borders without the
diplomacy lines. Added an independent `showDiplomacy` bool (default on, key **`A`**, a toolbar
"Alliances" checkbox in Overlays), `&&`-ed into just the ally/rival arc draw calls (3D + 2D) so
political borders/war fronts still show under Territory regardless — Territory now toggles cleanly
with or without the arcs. `src/render` only, no save-format change.
## Current state
@ -930,7 +946,9 @@ all in 3D + 2D) · `N` day/night terminator (Live World) · `T` tide-coloured co
`V` volcano markers (Live World) · `M` place-name labels (the atlas; names the world on first use) ·
`E` ecoregions colour view (names ecology on first use) · `I` habitability heat map ·
`U` settlements (the dawn of civilization on first press; toggles markers after) ·
`P` territory / realms view + political borders (Realms tab lists nations) ·
`P` territory / realms view + political borders + realm-name labels (Realms tab lists nations) ·
`A` toggle the alliance/rivalry diplomacy arcs on the Territory view (on by default; political
borders/war fronts stay regardless) ·
`X` culture / faiths view + cultural borders (Cultures tab lists peoples, ethos & religion) ·
`Z` wealth / trade view + trade routes (sea/river/overland; prosperity feeds city growth) ·
`SPACE` or on-screen button pause ·

View File

@ -130,12 +130,13 @@ void drawToolbar(Viewer& v) {
{"Grid (G)", &v.showGrat}, {"Rivers (J)", &v.showRivers},
{"Day/night (N)", &v.dayNightOn}, {"Tides (T)", &v.showTides},
{"Currents (O)", &v.showCurrents}, {"Clouds (K)", &v.showClouds},
{"Volcanoes (V)", &v.showVolcanoes},
{"Volcanoes (V)", &v.showVolcanoes}, {"Alliances (A)", &v.showDiplomacy},
};
const int overlayCount = 10;
float halfW = (cw - GAP) * 0.5f;
for (int i = 0; i < 9; i += 2) {
for (int i = 0; i < overlayCount; i += 2) {
guiCheckRow(Rectangle{ cx, cy, halfW, ROWH }, overlays[i].label, overlays[i].flag);
if (i + 1 < 9) guiCheckRow(Rectangle{ cx + halfW + GAP, cy, halfW, ROWH }, overlays[i + 1].label, overlays[i + 1].flag);
if (i + 1 < overlayCount) guiCheckRow(Rectangle{ cx + halfW + GAP, cy, halfW, ROWH }, overlays[i + 1].label, overlays[i + 1].flag);
cy += ROWH + GAP;
}
{

View File

@ -110,6 +110,8 @@ struct Viewer {
std::vector<Vector3> cultureBorders; // cultural-region border segments (civ Step 4, rebuilt with territory)
std::vector<Vector3> warFrontier; // red frontier segments between realms currently at war (civ Step 5)
std::vector<Vector3> allyLinks, rivalLinks; // civ Step 6: capital-to-capital arcs (allies green, rivals red)
bool showDiplomacy = true; // draw the alliance/rivalry arcs above with the Territory view (key A) --
// independent of showNationBorders() so Territory can be shown without them
std::vector<Vector3> tradeSea, tradeLand; // civ Step 7: trade routes (sea cyan / river+land amber)
long lastTerritoryYear = -1; // sim year territory was last recomputed (recompute when it ticks)
// Whether to draw the political/cultural/trade overlays is *derived* from `mode` -- deliberately

View File

@ -197,6 +197,7 @@ void Viewer::handleInput() {
if (IsKeyPressed(KEY_D)) showDrift = !showDrift;
if (IsKeyPressed(KEY_G)) showGrat = !showGrat;
if (IsKeyPressed(KEY_J)) showRivers = !showRivers;
if (IsKeyPressed(KEY_A)) showDiplomacy = !showDiplomacy; // alliance/rivalry arcs with the Territory view
if (IsKeyPressed(KEY_H)) toggleHydrology();
if (IsKeyPressed(KEY_L)) generateOrRegenerateBiota();
if (IsKeyPressed(KEY_E)) toggleEcoregionView();

View File

@ -108,7 +108,7 @@ void Viewer::renderGlobe3D() {
}
rlEnd(); rlSetLineWidth(1.0f);
}
if (showNationBorders()) { // civ Step 6: diplomacy arcs (allies green, rivals dark red)
if (showNationBorders() && showDiplomacy) { // civ Step 6: diplomacy arcs (allies green, rivals dark red)
rlSetLineWidth(2.0f); rlBegin(RL_LINES);
rlColor4ub(70, 220, 120, 200);
for (size_t i = 0; i + 1 < allyLinks.size(); i += 2) {
@ -394,8 +394,8 @@ void Viewer::drawMapOverlays(Rectangle vr, double lonOffset, float scale, float
if (showNationBorders() && !nationBorders.empty()) drawSegments2D(nationBorders, Color{18, 18, 26, 235}, 2.0f * scale, vr, lonOffset);
if (showCultureBorders() && !cultureBorders.empty()) drawSegments2D(cultureBorders, Color{245, 240, 220, 230}, 2.5f * scale, vr, lonOffset);
if (showNationBorders() && !warFrontier.empty()) drawSegments2D(warFrontier, Color{235, 40, 30, 255}, 2.5f * scale, vr, lonOffset);
if (showNationBorders() && !allyLinks.empty()) drawSegments2D(allyLinks, Color{70, 220, 120, 220}, 1.5f * scale, vr, lonOffset);
if (showNationBorders() && !rivalLinks.empty()) drawSegments2D(rivalLinks, Color{150, 40, 60, 220}, 1.5f * scale, vr, lonOffset);
if (showNationBorders() && showDiplomacy && !allyLinks.empty()) drawSegments2D(allyLinks, Color{70, 220, 120, 220}, 1.5f * scale, vr, lonOffset);
if (showNationBorders() && showDiplomacy && !rivalLinks.empty()) drawSegments2D(rivalLinks, Color{150, 40, 60, 220}, 1.5f * scale, vr, lonOffset);
if (showTradeRoutes() && !tradeSea.empty()) drawSegments2D(tradeSea, Color{90, 200, 235, 220}, 1.2f * scale, vr, lonOffset);
if (showTradeRoutes() && !tradeLand.empty()) drawSegments2D(tradeLand, Color{230, 180, 90, 220}, 1.2f * scale, vr, lonOffset);
if (showDrift && !driftArrows.empty()) drawSegments2D(driftArrows, Color{90, 230, 255, 255}, 2.0f * scale, vr, lonOffset);
@ -464,6 +464,25 @@ void Viewer::drawMapOverlays(Rectangle vr, double lonOffset, float scale, float
DrawText(txt, (int)(lp.x + 4 * scale), (int)(lp.y - 8 * scale), fs, RAYWHITE);
}
}
// Realm labels (with the territory view): name kingdoms/empires at their capital. Mirrors the
// 3D realm-label block in renderFrame() -- previously 2D-only had none, so neither the on-screen
// map nor exportMapImage() ever showed realm names, only the 3D globe did.
if (drawLabels && showNationBorders() && !planet.nationList().empty()) {
for (const Nation& nat : planet.nationList()) {
if (nat.tier == NationTier::CityState) continue; // declutter: only multi-settlement realms
if (nat.capital < 0 || nat.capital >= (int)planet.settlements.size()) continue;
int cell = planet.settlements[nat.capital].cell;
if (cell < 0 || cell >= (int)planet.cells.size()) continue;
int font = (int)std::round((nat.tier == NationTier::Empire ? 16 : 14) * scale);
if (font <= 0) continue;
double lon, lat; dirToLonLat(planet.cells[cell].unit, lon, lat);
Vector2 lp = projLonLat(lon, lat, lonOffset, vr);
if (!CheckCollisionPointRec(lp, vr)) continue;
int w = MeasureText(nat.name.c_str(), font);
DrawText(nat.name.c_str(), (int)lp.x - w / 2 + 1, (int)lp.y - font - 7, font, Color{0, 0, 0, 205});
DrawText(nat.name.c_str(), (int)lp.x - w / 2, (int)lp.y - font - 8, font, Color{245, 235, 210, 255});
}
}
// Place-name labels (the atlas). Minor features only when the map is zoomed in (on-screen) or
// always for a high-res export, where there's ample room for the full detail. Skipped entirely
// when the caller (exportAtlasImage) does its own decluttered label pass instead.
@ -582,6 +601,24 @@ void Viewer::exportAtlasImage() {
labels.push_back({ lp, &f.name, (int)std::round(font * labelScale), col, prio, (float)f.size, dot });
}
}
// Realm names (kingdoms/empires), regardless of the current colour mode -- an atlas is a
// reference, not "what you're currently looking at". Offset above the capital's own marker/name
// (rather than sharing its exact anchor point) so the two don't fight over the same box in the
// decluttering pass below; city-states are skipped (bare settlement name already covers them).
if (!planet.nationList().empty()) {
for (const Nation& nat : planet.nationList()) {
if (nat.tier == NationTier::CityState || nat.name.empty()) continue;
if (nat.capital < 0 || nat.capital >= (int)planet.settlements.size()) continue;
int cell = planet.settlements[nat.capital].cell;
if (cell < 0 || cell >= (int)planet.cells.size()) continue;
int font = (int)std::round((nat.tier == NationTier::Empire ? 16 : 14) * labelScale);
int prio = nat.tier == NationTier::Empire ? 0 : 1;
double lon, lat; dirToLonLat(planet.cells[cell].unit, lon, lat);
Vector2 lp = projLonLat(lon, lat, mapLon, er);
lp.y -= font * 0.9f;
labels.push_back({ lp, &nat.name, font, Color{245, 235, 210, 255}, prio, (float)nat.totalPop, false });
}
}
if (!planet.settlements.empty()) {
const double townP = planet.cfg.civTownPop, cityP = planet.cfg.civCityPop, abP = planet.cfg.civAbandonPop;
for (const Settlement& s : planet.settlements) {