BlueMap Rendering Internals
How WN-BlueMap builds, colors, caches, and paces the four marker layers it pushes to BlueMap: nation territory, active wars, diplomacy lines, and villages. This is the deep internals reference for extending or debugging that rendering pipeline, not a player-facing guide.
Overview
All four layers are pushed to BlueMap by MarkerService, which owns one MarkerSet per layer per map.
The marker set id is part of BlueMap's own web API (bookmarkable/embeddable via its ?markers= URL query parameter), so it stays stable even when [labels] renames a layer's display name. wnbluemap.territory is the historical id and is deliberately not renamed to wnbluemap.claims to match the territory to claims config-key rename, so an existing saved link or embed never breaks.
Nations Geometry and Rendering
Each nation's claimed chunks, merged into clean polygons and colored with the nation's own color.
Claimed chunks are merged with RegionGrouper (4-connected BFS into disjoint connected regions) and then traced with ChunkPolygonBuilder (boundary edge tracing into outer rings plus holes, even-odd nested) - see Geometry Algorithms below. A nation whose land is split into several disjoint chunks of territory (scattered claims, land lost to war) gets one polygon set per region, not one polygon spanning gaps; an unclaimed pocket, or an enemy enclave fully surrounded by one nation's claims, becomes a real hole in that polygon rather than being silently painted over.
A chain of short vertical wall panels along the polygon's edge. Every borderSampleSpacing blocks, the wall resamples real ground height (first opaque block, or a fluid surface counted as ground so coastal borders trace the water line rather than the seabed; the median across nearby samples so one stray lamppost base can't drag a whole panel's height) and steps to match it, so the border reads correctly from BlueMap's angled 3D view. Consecutive same-height samples merge into one longer panel instead of paying for a marker per sample, so long flat runs cost nothing extra.
A flat, depth-tested fill just above sampled ground height across the whole claimed area, not just the border. From directly overhead it reads as solid filled territory; viewed at an angle, real terrain occludes it the way the ground would occlude a decal painted onto it - which is exactly why the border walls exist as a separate layer, since they stick up and stay visible from an angle on purpose.
tintHeightOffset needs real clearance above ground (0.1 blocks measured invisible from directly overhead in testing, not merely flickering) to reliably win BlueMap's depth test from a top-down camera.
Nations Coloring
Color comes from FTB Teams' TeamProperties.COLOR, the value /nation color sets.
If unset (FTB's default is pure white) or pure black - either one indistinguishable or useless on a colored map - a stable, vivid color is instead derived deterministically from the nation's UUID: hue = (uuid.hashCode() & 0x7FFFFFFF) % 360, at fixed saturation 0.65 / brightness 0.95. This means the same nation always renders the same color across restarts even without ever setting one explicitly, and every nation stays visually distinguishable from its neighbors. An at-war nation's territory color is blended toward the war color (see Active Wars below) so conflict is visible even with the war layer's own markers turned off.
Nations Popup
One popup marker per region, not per wall panel or tint shape, and not one per nation either - a nation with several disjoint regions gets a full card on each one, so a reader lands on complete information at every piece of land a nation holds.
Only WN nations draw - ordinary FTB personal/party claims (any FTB team that isn't a recognized WN nation) are filtered out entirely before any geometry work happens.
Active Wars Frontline
Not a whole territory repaint: only a nation's own claimed chunks that are adjacent to a chunk owned by a nation it's actively fighting.
Computed as a simple neighbor scan (frontlineCells) over the combined single-owner chunk map built for the whole rebuild pass. This deliberately narrow band keeps the layer readable as "where the fighting actually is" rather than repainting an entire territory red the moment any war starts anywhere on its border.
War state is read from WarringNationsAPI#activeWars(), not NationView#atWar() or the older areAtWar predicate - Warring Nations runs two parallel war implementations (the legacy model and file-defined composable war presets), and those two older accessors only ever saw the first one. A composable war fought entirely through the newer system used to render no frontline and no war-tint at all; activeWars() is the one accessor that sees both, so every war shows up regardless of which system declared it.
Active Wars Popup
A live scoreboard, not just "at war": one row per war this nation is currently fighting on this frontline.
Siege Rings, Keep, and Objectives
Real geometry, not just text, for wars using the composable scoring engines.
For a war using the composable siege-ring-breach scoring engine, WarringNationsAPI#siegeStatusOf exposes the engine's own per-chunk geometry (SiegeRingView#chunks, built from ComposableSiegeEngine's Chebyshev-distance ring grouping, plus SiegeStatusView#keep). Each ring and the keep draw as their own real, independently colored polygon over the defender's claimed chunks.
Uses [siege] ringColorRgb.
Stays drawn, not hidden - just visually demoted to ringFallenColorRgb, so a viewer can see overall siege progress at a glance, not merely what's left.
Ring 0 gets its own distinct keepColorRgb, since it's the attacker's actual win condition once every wall ring has fallen.
Each ring / the keep also carries its own small popup (fallen/standing, chunk count; the keep's popup additionally shows keep-hold duration once reached).
For a war using the composable objectives-capture scoring engine, WarringNationsAPI#objectiveProgressOf exposes each objective's live-bound site chunk (ObjectiveProgressView#siteChunk, from ComposableObjectivesEngine's per-objective claim binding). Each objective draws as a small square marker (objectiveMarkerRadiusBlocks half-width, kept well inside one 16-block chunk so it reads as a point, not a chunk fill) with its own popup showing fields relevant to its type - tier, and one of HP remaining (destructible), payload waypoint progress, or current holder (contested).
Both siege and objective geometry are built only from the defender's side of each war (building it again under the attacker's id would draw the identical shapes twice), and are strictly additive alongside the frontline overlay and the scoreboard's existing text summary - never a replacement for either.
Diplomacy
Lines between two nations that both hold land in the same dimension and share a standing diplomatic relation.
Drawn for ALLY or ENEMY relations, from WarringNationsAPI#relationsOf, anchored at each nation's own diplomacy anchor point (see below). Hidden by default ([features] diplomacyLinesDefaultHidden = true) - a viewer opts in from the web app's own layer toggles.
Use [war] allianceColorRgb (default green) and allianceLineWidth.
The ENEMY half of standing diplomacy (a declared enemy, not necessarily an active war) uses rivalryColorRgb (default amber) and the thinner rivalryLineWidth by default, reflecting that a rivalry is a lower-certainty posture than an alliance. Deliberately not the war/frontline color - a rivalry line and an active-war frontline must never read as the same thing from a distance. Gated by its own toggle, [features] rivalryLines, on top of diplomacyLines.
A cross-dimension pair is never drawn: both nations must hold land in the same dimension for a line to appear there.
Unlike every other layer, diplomacy lines are deliberately left uncached, and recomputed fresh on every single rebuild pass. Its cost is an O(nations squared) pairwise relation check per dimension, not O(claims) - it tracks how many nations hold land in a dimension, not how large any one claim burst is. Each nation's own relationsOf read is memoized for the pass, so a nation's relations are read once regardless of how many candidate pairs it appears in.
The anchor point. Each line's endpoint is the centroid of that nation's largest claimed region - computed as part of the Nations layer's own region-building pass and cached alongside it (anchorPosCache), not a separate lookup. This point is invisible on the map itself; nothing is drawn there, it only decides where a diplomacy line lands.
This anchor is deliberately not the player-set /nation home position. Warring Nations tracks a real home (/nation home set|go|clear) but hides other nations' home coordinates from /nation info as opsec, and the public API surface doesn't expose it at all. Since BlueMap markers are public to every viewer, broadcasting the true home would undo that opsec intent entirely, so this addon never reads or renders it. Using the largest-region centroid instead reveals nothing the Nations layer's own territory shading doesn't already show.
Villages
The territorial tier below a nation: either affiliated with one nation, or fully standalone.
A village's territory lives entirely in Warring Nations' own village claim store, not in FTB Chunks - WarringNationsAPI#claimsOfVillage is the only way to learn a village's shape, unlike nation territory there is no FTB-side claim data to fall back to - so it's read and rendered by an entirely separate code path (buildVillageTerritory) from nation territory.
Rendering. Always a flat, depth-tested decal - never the terrain-hugging border walls nation territory gets. This is a deliberate visual choice: a village never reading as a scaled-down nation at a glance is worth more than terrain-accurate walls here, and it comes with a real performance upside - village rendering does no per-block ground-height scanning at all.
Coloring. An affiliated village inherits its owning nation's color, pulled a third of the way toward white (ColorUtil.mix(nationColor, 0xFFFFFF, 1/3)) - close enough to visually associate the two, but always paler than the nation's own territory next to it. A standalone village gets its own hue, deterministically derived from its own UUID the same way a nation's fallback color is, but at lower saturation and higher brightness (0.40/0.90 vs. a nation's 0.65/0.95) - a muted pastel family a nation's own vivid fallback hue never lands in.
No war banner: villages have no war state of their own (only a nation can be a war belligerent), so that field is simply absent rather than always-false.
Pacing
The expensive part of a rebuild - reading real world blocks for border walls/tint, plus the polygon-tracing that feeds on it - is spread across server ticks at five independently-paced queues, rather than done in one synchronous burst, because each scales with a different thing.
War frontline, diplomacy lines, and the actual push to BlueMap's maps stay a single unpaced step (finalizePass) that runs once every queue above has fully drained for the pass: pure in-memory arithmetic and cache lookups, never the per-block world reads or per-group polygon-tracing that made everything else worth pacing.
Caching and Rebuild Triggers
Every rebuild is triggered by an actual Warring Nations event (WnEvents), never a polling timer.
Rebuilds are debounced (debounceMillis, default 1500ms of quiet) so a burst of rapid changes collapses into a single redraw instead of one per event, and incremental: only the nations/villages actually marked dirty recompute, everything else is reused verbatim from cache. A periodic full, uncached recompute (fullRebuildEvery, default every 500 passes) exists purely as insurance against an unknown cache-invalidation gap, not as the routine mechanism for catching anything - color changes in particular are now detected directly every pass at negligible cost (an O(1) per-nation comparison against the last-known color), rather than relying on this periodic sweep the way an earlier version did.
Geometry Algorithms
Both are pure JDK (no Minecraft or BlueMap types), unit-tested independently of a running server (ChunkPolygonBuilderTest, RegionGrouperTest).
4-connected BFS over a set of claimed chunk cells into disjoint connected components ("regions"). Also has a same-key variant used to group tint samples into same-height plateaus.
Boundary edge tracing. Every claimed cell's side whose neighbor isn't claimed is a directed boundary edge (emitted counter-clockwise so the claimed interior is always on the edge's left). Directed edges chain corner-to-corner into closed loops, taking the sharpest left turn at any "pinch" corner where two diagonally-touching regions meet (provably keeps the two loops separate and non-self-intersecting). Collinear vertices collapse into single edges.
Rings are then nested by even-odd containment (robust and winding-independent): a ring inside an even number of others is an outer ring, inside an odd number it's a hole, and each hole attaches to the smallest outer ring that contains it - correctly handling both a donut hole (an unclaimed pocket, or an enemy enclave, fully inside one nation's claim) and an island-in-lake (which becomes its own outer polygon nested inside the hole).
Example use case: a claim change becomes a rendered border
WN-BlueMap has no addon-facing extension API of its own to hook a custom marker or popup field into; the useful thing to understand instead is the real pipeline a claim change already runs through, end to end, from a nation's own claim event to pixels on the map.
A player runs /nation claim on a chunk bordering their existing territory. WN Core fires WnEvents.ClaimChanged. WN-BlueMap marks that nation's dimId|nationId territory and tint cache entries dirty (see Caching and Rebuild Triggers above), then waits out the debounceMillis quiet window (default 1500ms) in case more claims land in the same burst. Once the pass runs, RegionGrouper re-groups that nation's claimed chunks with 4-connected BFS into disjoint regions (see Geometry Algorithms), and ChunkPolygonBuilder traces the region containing the new chunk into outer rings and holes. The border-wall queue (wallRegionsPerTick, default 8) then samples real ground height every borderSampleSpacing blocks along that new edge and builds the wall panels, while the tint queues (tintCellsPerTick/tintGroupsPerTick) separately extend the ground-tint fill over the newly claimed chunk. Both are pushed to BlueMap by MarkerService as part of the same wnbluemap.territory marker set once their queues drain for the pass, and every other viewer of the web map sees the new border on their next marker refresh, with no page reload needed.
