Fix atlas capital labels: realm name/culture block was colliding with the settlement's own name

Capitals systematically lost their own settlement-name label in the atlas
export to the higher-priority realm name at the same point -- the previous
offsets (font*0.9 up for the realm name, font*0.62 back down for the culture
line) weren't nearly enough clearance, so the two boxes still overlapped and
the settlement's own label got dropped by the collision check every time.

Replaced the ad-hoc offsets with an explicit bottom-up stack: settlement's
own label box (unshifted, drawn separately) -> culture line positioned
strictly above its top edge with a real gap -> realm name positioned
strictly above the culture line. Uses a conservative worst-case settlement
font (City tier) as the clearance baseline so it holds regardless of which
tier the capital actually is.

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 21:38:43 +02:00
parent dae6981dc9
commit f18ff8f254

View File

@ -619,10 +619,17 @@ void Viewer::exportAtlasImage() {
}
}
// 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).
// reference, not "what you're currently looking at". Stacked *cleanly above* the capital's own
// settlement-name box (drawn separately below, unshifted at this same point) with a guaranteed
// gap -- not just "offset a bit", which previously still let the realm-name/culture block
// overlap the settlement's own label at the same point often enough that the (higher-priority)
// realm name would win the collision check and the capital's own name would silently vanish, so
// a capital never showed its own identity, only "Duchy of X" (built from that same name).
// City-states are skipped (their bare settlement name already covers them -- there's no separate
// realm identity to show).
if (!planet.nationList().empty()) {
const float pad = 3.0f; // matches the collision-box padding below
const float settleFontMax = 17.0f * labelScale; // worst case: a City-tier capital's own label
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;
@ -632,20 +639,22 @@ void Viewer::exportAtlasImage() {
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 });
// The realm's dominant culture, as a smaller "(the Velmar)" label anchored just below the
// realm name (still above the raw capital point) -- a lower-priority candidate in the same
// greedy pass, so on a dense world it simply drops out on its own if it doesn't fit,
// without any special-case logic.
if (nat.cultureId >= 0 && nat.cultureId < (int)planet.cultureList().size()) {
float cultureFont = std::max(8.0f, std::round(font * 0.72f));
bool hasCulture = nat.cultureId >= 0 && nat.cultureId < (int)planet.cultureList().size()
&& !planet.cultureList()[nat.cultureId].name.empty();
float stackTop = lp.y - settleFontMax * 0.5f - pad; // top edge of the settlement's own box
float cultureCenterY = stackTop - pad - cultureFont * 0.5f;
float realmCenterY = (hasCulture ? cultureCenterY - cultureFont * 0.5f - pad : stackTop - pad)
- pad - font * 0.5f;
labels.push_back({ Vector2{lp.x, realmCenterY}, nat.name, font, Color{245, 235, 210, 255},
prio, (float)nat.totalPop, false });
// The realm's dominant culture, as a smaller "(the Velmar)" label right above the realm
// name -- a lower-priority candidate in the same greedy pass, so on a dense world it
// simply drops out on its own if it doesn't fit, without any special-case logic.
if (hasCulture) {
const std::string& cname = planet.cultureList()[nat.cultureId].name;
if (!cname.empty()) {
int cfont = std::max(8, (int)std::round(font * 0.72f));
Vector2 clp = lp; clp.y += font * 0.62f;
labels.push_back({ clp, "(" + cname + ")", cfont, Color{215, 205, 180, 235},
prio + 1, (float)nat.totalPop, false });
}
labels.push_back({ Vector2{lp.x, cultureCenterY}, "(" + cname + ")", (int)cultureFont,
Color{215, 205, 180, 235}, prio + 1, (float)nat.totalPop, false });
}
}
}