03-ui-effects.md · 86 requirements

Every Last City — Art Production 03: UI, Overlay and Effects

This document specifies every mark drawn on top of the map: the overlay glyph set that reports unit state, the selection and targeting furniture, order previews and path art, the attack-odds marker, the colourblind-safe pattern system that carries every graded encoding without hue, the combat and Cascade effects, the Formation and Group markers of Command View, the cursor set, and the minimum DOM iconography the HTML layer needs. It does not cover unit bodies, terrain, or cities. Its central claim is that most of this set must not be generated at all — a sixteen-pixel symbol that must be unmistakable is a vector-authoring job, and an image generator is the wrong tool for it. Where a generator is the right tool — effect masks, textures, decorative marks — this document says so and gives the prompt.

Status: Draft v0.2 · Owner: unassigned · Depends on: docs/spec/04-ui-ux.md, docs/spec/10-turn-model.md, docs/spec/11-combat.md, docs/spec/12-economy.md, docs/spec/13-command.md, docs/art/00-* (pipeline, palette, the UI ink pair, and the team-colour mask authoring rules UX-440 requires), docs/art/01-* (unit sprites — footprints and clear areas), docs/art/02-* (terrain and cities — the backgrounds every glyph here must survive)

Sibling documents. Three companion art documents are being written concurrently: the foundation/pipeline document (docs/art/00-*), the unit sprite document (docs/art/01-*) and the terrain and city document (docs/art/02-*). Where this document says "the seat foreground colour", "the team-colour mask" or "the unit clear area", the binding definition lives in those files. If a number here disagrees with a number there, theirs wins for anything inside the sprite and this one wins for anything drawn over it.

What changed in v0.2. UX-440 was rewritten. Unit art now carries a per-pixel team-colour mask rather than a mandated two-tone palette, and art outside that mask is explicitly free to carry shading, material and mechanical detail. Nothing about the glyph set's shapes changes — they were never seat-tinted — but the field a glyph lands on is no longer guaranteed to be one of two flat values, and §1.1 is new because of it. §10's seat patterns are now scoped to the masked region, §12's markers are simplified by the change, and ART-1640 has been replaced.


1. Production method policy

ART-900 Every asset in this document MUST be labelled with exactly one production method:

Method Meaning Delivered as
V — vector-authored Drawn by hand in a vector tool against the geometry given here, then rasterised into an atlas at build time .svg source + generated atlas
P — procedural Generated at runtime by the renderer from parameters (shader, particle system, geometry) — no art file exists code
G — generated Produced by an image model from the prompt given here, then cleaned and converted .png (single-channel luminance mask + alpha — this document commissions no colour art at all)

ART-910 Every glyph, badge, mark, ring, arrowhead, waypoint, pin, cursor and DOM icon in this document MUST be vector-authored (V). Generating them with an image model MUST NOT be attempted.

Rationale, and it is the most important sentence in this file. A symbol set has exactly one job: any two members of it must be impossible to confuse at 16 device pixels, in greyscale, in peripheral vision, over unpredictable terrain. That requires identical stroke weight across the set, identical optical size, pixel-grid alignment, and deliberate silhouette separation between every pair. Image models cannot deliver any of the four. They produce anti-aliased mush below about 48 px; they drift in stroke weight between generations, so glyph 7 arrives visibly heavier than glyph 3; they cannot hold a shared construction grid across separate calls; and they will happily give you two glyphs that are 90% similar because "chevron" and "arrow" occupy nearby ground in their training. A symbol set is exactly the artefact that cannot tolerate any of that. Sixty-odd glyphs is perhaps two days of work for a competent person in Figma, Illustrator or Inkscape, and the result is resolution independent, diffable, and editable when playtesting says glyph 7 is being misread.

ART-920 The glyph set MUST be authored as SVG on a shared construction grid (§2), MUST be kept in version control as source, and MUST be rasterised into the overlay atlas by a build step, not by hand. Hand-exported PNGs MUST NOT be committed as the source of truth.

ART-930 An image generator MAY be used for, and only for: effect luminance masks (§11), tiling noise and grain textures, and large decorative marks that are never smaller than 96 device px. Everything else in this document is V or P.

ART-940 Overlay glyphs MUST NOT be drawn in seat colours. They MUST be drawn in the neutral UI ink pair — one near-black ink and one near-white ink, defined in docs/art/00-* — regardless of which seat owns the unit beneath them.

Rationale: ownership is carried by the unit body's team-colour region (UX-440), and UX-440's outcome test — ownership unmistakable from the silhouette alone at the Chip tier — guarantees that region is large enough to do the job unaided. That guarantee is exactly what buys the overlay set the right to stay neutral. If state glyphs were also tinted per seat, a badge would change appearance between seats and a player would have to learn it eight times, and on a dark seat colour half the set would vanish. Neutral ink also removes these glyphs from the tint pipeline entirely, which is a real simplification: the overlay atlas needs no per-seat variants and no mask channel.

ART-950 Because an overlay glyph can land on any of the eleven terrains, on any seat colour, and on any part of any unit sprite, every glyph in this document MUST carry a halo of 2 authoring units in the opposite ink to its body — dark glyphs get a light halo, light glyphs get a dark halo — drawn as an outward dilation of the glyph's alpha. Two authoring units is 1 device px at the 12 px ship size and scales with the raster: 2 px at 24 px, 4 px at 48 px. A glyph without a halo MUST be treated as unfinished. The halo MUST be produced by the build step from the SVG alpha, not drawn by hand, so that it is exactly uniform.


1.1 Reading over unit art (UX-440)

ART-1690 No mark in this document may depend on the value of anything beneath it. Every mark MUST be legible over an arbitrary field of arbitrary luminance and arbitrary local frequency, and MUST reach that legibility using only ink it carries within its own footprint.

Rationale, and this is the requirement UX-440's rewrite actually forced. The old requirement mandated two-tone unit art, which had a side effect nobody wrote down: a glyph landing on a unit was landing on one of two known, flat values, and a 1 px halo was plenty against that. UX-440 now lets art outside the team-colour mask carry shading, material breaks, tracks, gun barrels, canopies, rotor discs, wakes and cast shadow — deliberately, because those carry information. The accidental guarantee is gone with it. The tempting fix is to ask the sprite documents for a promise: "nothing high-contrast under the badge anchor". That promise is worthless here. It cannot be checked at build time, because this layer never sees the sprites; and it cannot be checked at run time, because the sprite under a given badge is whichever unit the player moved onto that tile a moment ago. So the overlay layer assumes nothing and carries its own contrast. This is also cheaper than it sounds — most of the set is open strokes, and open strokes are already handled by the halo they have.

ART-1700 Every mark in this document MUST declare exactly one protection class, and the class MUST follow from the mark's own construction by the rules below — it is a derivation, not a taste judgement:

Class Mechanism Assigned to
HALO the ART-950 halo alone any mark all of whose ink lies within one stroke width of its own alpha edge, and whose encoding does not turn on filled-versus-unfilled
HALO-2 a halo in both inks, 2 authoring units each — outer light, inner dark, or the reverse — so the mark carries a complete value sandwich at its own edge marks that must be found in peripheral vision without being read: the Idle ring (ART-1080), the accessible map cursor (ART-1230), the Alarm pulse (ART-1410)
PLATE an opaque backing plate (ART-1720) under the whole mark, plus the ART-950 halo around the plate any mark whose encoding turns on filled-versus-unfilled, or that contains a solid area more than 4 device px across in its shortest dimension at the 32 px tile

Applying the derivation to the existing set gives:

Class Members
HALO Stance glyphs (§4), Posture glyphs and handles (§5), encirclement ring (ART-1100), supply cord (ART-1110), grade chevrons (ART-1120), dig-in works (ART-1130), Forming Up square (ART-1150), cargo tabs (ART-1170, 4 px exactly — not over), endurance pips and banked-movement square (ART-1180), all selection and targeting furniture (§7), all path, waypoint-line and geometry art (§8), the CRP (ART-1220), the Trace marker and cluster marker (ART-1410), and the Formation and Group marker outlines (ART-1480) — the last being the one halo in this document whose body ink is a seat colour rather than a UI ink, because the outline's outer edge still lands on open terrain
HALO-2 Idle ring, accessible map cursor, Alarm pulse
PLATE condition badge (§3), reaction-capacity diamond (ART-1160), waypoint diamonds (ART-1270), odds-marker glyphs (ART-1300), Formation strength bar (ART-1490), Dispatch pin head (ART-1510 — the head is solid ink with its bars knocked out of it, so it is its own plate and needs no second one)

ART-1710 The halo is a value break, not a colour break, and it only works if the two UI inks of docs/art/00-* are far enough apart that whichever one is doing the work still separates from a mid-value backdrop. This document assumes the ink pair is separated by at least 4.5:1. If it is not, every halo specified here is decoration and the whole set fails ART-1640. This is stated as an assumption rather than a rule because the ink pair belongs to docs/art/00-*; ART-1640 is what catches the violation.

ART-1720 A backing plate is an opaque rectangle in the ink opposite to the mark's body ink, filling the mark's anchor footprint, carrying its own ART-950 halo in the body ink around its outer edge. Plates MUST be generated by the build step from the mark's own geometry — no plate is a separate authored asset and the asset count does not change.

Rationale: a halo protects a stroke, because every pixel of a stroke is next to the halo. It does not protect a fill. A solid diamond over a dark hull still reads as a diamond — the halo outlines it — but it stops reading as a solid one, and for ART-1160 solid-versus-hollow is the whole message. The same argument covers the condition ladder's filled rungs, the odds ramp from solid square to empty outline, and the Formation strength bar. These are precisely the marks where the reader must judge how much ink is inside an outline, and that judgement is impossible against an unknown backdrop. Everything else in the set is counting or shape-matching, which the halo already serves.

ART-1730 Plates occlude unit art, and under UX-440 unit art now carries information worth seeing. Total opaque plate area on one tile at the Close tier MUST NOT exceed 20% of the tile — 205 of 1024 device px² — and every plate MUST lie within an ART-990 anchor, all of which touch a tile edge. The current set spends 180 px² (17.6%) on the condition badge and nothing else on the tile; the remaining headroom is not a budget to be filled. Where ART-980's drop order and this budget disagree, this budget binds and marks MUST be dropped from the bottom of that order until it is met.

Two PLATE-class marks are excluded from the tile budget because they do not sit on a tile:


2. The glyph construction grid

ART-960 All overlay glyphs MUST be authored on a 24 × 24 unit grid with a 2-unit stroke, and MUST be designed to rasterise correctly at 12 × 12 device px (the 1× map case) by an even 1:2 downscale. Authoring at 24 and shipping at 12 keeps every stroke landing on a whole pixel.

Property Value Note
Author grid 24 × 24 units one grid for the whole set
Stroke weight 2 units (→ 1 px at 12 px) uniform across the set; no optical thinning
Corner radius 0 or 2 units the set is square-cornered; 2 only on the condition frame
Safe area 20 × 20 (2 unit margin all round) the ART-950 halo lives in the margin; at 24 units a 1-unit margin is half a device pixel at the 12 px ship size and cannot hold it
Minimum feature 2 units nothing thinner than the stroke; no 1-unit details
Minimum gap 2 units two strokes never closer than the stroke width
Alignment every edge on a whole unit; diagonals at 45° only no arbitrary angles
Ship sizes 12, 16, 24, 32, 48 device px rasterised from the same SVG

ART-970 The Close tier tile is 32 CSS px (UX-390). Every glyph in this document MUST be verified legible and mutually distinguishable at that tile size, at device pixel ratio 1, in greyscale. DPR 2 is not the test case; DPR 1 is.

ART-980 No more than four state glyphs may be drawn on one unit at the Close tier and no more than two at the Mid tier. Where more marks apply than fit, the renderer MUST drop them in the priority order UX-430 fixes — encircled, out of supply, condition, Stance, Posture — dropping from the bottom, and MUST leave the dropped marks reachable on the unit panel. Art MUST be designed on the assumption that Posture is the first thing to disappear.

ART-990 Glyph anchors on the 32 px tile MUST be exactly these, so that a player learns where to look before they learn what the mark means:

      ┌───────────────────────────────┐
      │ TL: encirclement (drawn as a  │
      │     ring around the whole     │
   C  │     tile, not in a corner)    │  TR: grade chevrons
   O  │                               │      + Forming Up
   N  │                               │
   D  │          unit body            │  RE: supply cord
   I  │                               │      (right edge, centred)
   T  │                               │
   I  │                               │
   O  │ BL: Posture glyph             │  BR: Stance glyph
      └───────────────────────────────┘
        bottom edge: dig-in works ticks
Anchor Content Footprint at 32 px tile Offset
Left edge, vertically centred condition badge 12 × 20 x = 0, y = 6
Around the tile encirclement ring 30 × 30 inset 1
Top-right grade chevrons, then Forming Up below them 10 × 12 x = 21, y = 1
Right edge, vertically centred supply cord 10 × 6 x = 21, y = 13
Bottom-left Posture glyph 12 × 12 x = 1, y = 19
Bottom-right Stance glyph 12 × 12 x = 19, y = 19
Bottom edge, centred dig-in works ticks 14 × 4 x = 9, y = 28
Above the tile Idle mark, reaction mark, odds marker — in that anchor's priority order, odds first while an attack preview is open (ART-1730) 12 × 12 x = 10, y = −6

ART-1000 No Stance glyph and no Posture glyph may share a silhouette, and the two families MUST be drawn from disjoint shape vocabularies: Stance glyphs are open strokes — chevrons and bars, no enclosed area; Posture glyphs are closed outlines — shapes with an interior.

Rationale: "Hold" is both a Stance (TM-630) and a Posture (CM-330), and they mean different things. Position alone will not save a player who glances at the wrong corner. Making one family open and one family closed means the two Holds cannot be confused even when a player has forgotten which corner is which.


3. The condition badge (UX-1125, CB-190)

The badge is the hardest single problem in this document: one mark, two independent 0–100 quantities, readable at a glance, without colour. strength is how much of the unit is left; disorder is accumulated shock (CB-100).

ART-1010 The condition badge MUST be one composite mark, not two adjacent widgets. Its design is the filled ladder in a frame: the rungs carry strength and the integrity of the frame carries disorder. Production method V.

Condition badge — construction, in device px at the 32 px tile (author at 2× on the 24-grid)

 total footprint 12 × 20, including 1 px halo
 ┌────────────┐   halo (1 px, opposite ink)
 │ ┌────────┐ │   frame (1 px stroke) ......... carries DISORDER
 │ │ ▓▓▓▓▓▓ │ │   rung 5  (6 × 2)  = strength 81–100
 │ │        │ │   gap 1
 │ │ ▓▓▓▓▓▓ │ │   rung 4  (6 × 2)  = strength 61–80
 │ │        │ │
 │ │ ▓▓▓▓▓▓ │ │   rung 3  (6 × 2)  = strength 41–60
 │ │        │ │
 │ │ ░░░░░░ │ │   rung 2  (6 × 2)  = strength 21–40   (empty = outline only)
 │ │        │ │
 │ │ ░░░░░░ │ │   rung 1  (6 × 2)  = strength  1–20
 │ └────────┘ │
 └────────────┘
   ladder 6 × 14 · frame inset 1 · badge 10 × 18 · +1 halo = 12 × 20

The badge is PLATE class (ART-1700): the 10 × 18 area inside the halo is an opaque plate in the ink opposite to the frame and rungs, and the ladder is drawn on it. The filled-versus-unfilled rung distinction is the badge's entire strength encoding, and under UX-440 the sprite beneath may be a shaded hull, a track run or a cast shadow rather than one of two flat tones, so that distinction cannot be left to depend on it. The plate is 180 device px², which is the whole of ART-1730's tile budget that this document spends.

ART-1020 Rung count MUST be ceil(strength / 20), filled from the bottom. An unfilled rung MUST be drawn as a 1 px outline, not omitted — the ladder's full height must always be visible so the filled fraction reads as a fraction. Exact strength is a panel figure (UX-530), never a map figure.

ART-1030 disorder MUST be carried by the frame in exactly four states, ordered so that "more broken" always means "more shock":

Band disorder Frame treatment Silhouette
0 — steady 0–9 continuous 1 px rectangle closed box
1 — shaken 10–39 rectangle broken by a 2 px gap at the midpoint of each of the four sides dashed box
2 — disordered 40–69 frame reduced to four 4 px corner brackets corners only
3 — wrecked 70–100 corner brackets plus a 1 px slash from the ladder's lower-left to its upper-right, with its own halo corners + slash

Rationale: line style is a recognised non-colour channel and it is the only one still available — count is taken by the ladder, position is taken by the anchor, and hue is forbidden. Four states, each with a different silhouette, is as much resolution as 10 × 18 px will carry honestly. Two of the obvious alternatives were rejected: a second stack of pips beside the bar, because it reads as two widgets and UX-1125 explicitly forbids that; and a colour ramp on the fill, because UX-1310 forbids it. The slash at band 3 is deliberately alarming — a unit at 70+ disorder is about to break, and this is the one condition state that should catch the eye from across the map.

ART-1040 The badge MUST also appear, unchanged in construction, in the DOM at 16 and 24 px — in the unit panel (UX-530), the Roster, the Dispatch and the after-action report — so the mark a player learns on the map is the same mark everywhere. The DOM instance MUST be an inline <svg> using currentColor, MUST carry an aria-label giving both numbers in words ("strength 64, disorder 31"), and MUST NOT be an <img> or a CSS background (UX-1310 forced-colours clause).

Mid tier. At ~0.5× the badge is still required (UX-390). The Mid variant MUST be the same badge with the frame dropped to bands {steady, shaken+disordered merged, wrecked} — three states — and the ladder reduced to 3 rungs (ceil(strength / 34)). Ship it as a separate 8 × 12 SVG, not as a downscale of the Close badge.


4. Stance glyphs (TM-630, TM-640, UX-430)

Four values, always exactly four (adding a fifth is a rules change). Open-stroke family, bottom-right anchor, 12 × 12 px. Production method V.

Stance Glyph Construction (24-unit grid) Reads as
March double chevron, pointing up two nested ∧, stroke 2, apex at (12,4) and (12,12), arms to (4,12)/(20,12) and (4,20)/(20,20) two strokes = fastest
Advance single chevron, pointing up one ∧, apex (12,7), arms to (4,17) and (20,17) one stroke = the reference case
Hold horizontal bar rectangle (4,11)–(20,15), solid a line you stand on
Entrench bar with two legs the Hold bar, plus legs (7,15)–(9,21) and (15,15)–(17,21) the bar dug into the ground

ART-1050 The Stance set MUST be built as above: count separates March from Advance (two strokes against one), orientation separates the chevrons from the bars (vertical thrust against horizontal rest), and descenders separate Entrench from Hold. All three are non-colour channels and all three survive a 1 px blur.

ART-1060 Where a Reflex or Sanction has replaced the committed Stance with a different effective Stance (TM-760, TM-770, CM-1400), the effective Stance glyph MUST be drawn and MUST carry a 2 px dot at its lower-right corner marking it as derived. The committed value belongs on the panel (UX-530), not on the map.


5. Posture glyphs (CM-330, UX-430)

Six Postures plus Idle. Closed-outline family, bottom-left anchor, 12 × 12 px. Production method V.

Posture Glyph Construction (24-unit grid) Reads as
Hold boxed dot square outline (4,4)–(20,20) stroke 2, plus a solid 4 × 4 dot at centre stay on this tile
Garrison crenellated block square outline (4,6)–(20,20), with two 4-wide notches cut from the top edge at x=8 and x=16 a work you occupy
Screen dashed frontage three solid bars 4 × 4 at y=10, x = 3, 10, 17, plus one 4 × 4 facing tick above the centre bar a line you cover
Survey hollow diamond diamond outline, vertices (12,3)(21,12)(12,21)(3,12), stroke 2 a region you search
March arrow to a stop horizontal shaft (3,12)–(15,12) stroke 2, 6 × 6 solid head at x=15, terminal bar (21,5)–(21,19) travel to the end of this route
Attach chain link two 8 × 8 ring outlines centred (9,12) and (15,12), stroke 2, overlapping take orders from that formation
Idle see ART-1080 nothing assigned

ART-1070 Nothing in the Posture set may be a chevron and nothing may be an unenclosed stroke, per ART-1000. The Screen glyph is the near miss — three bars are strokes — and MUST therefore carry its facing tick at all times, giving it a distinctive T-shaped silhouette no Stance glyph has.

ART-1080 Idle MUST be conspicuous (UX-430, CM-330). The Idle mark MUST be drawn above the tile, at 1.5× the normal glyph size (18 × 18 px), as a bold ring — 18 px outer, 4 px stroke, hollow centre — carrying a double halo (2 px, both inks). It MUST animate as a 1 Hz opacity pulse between 100% and 55%. When animation is off or set to instant (UX-480, UX-345) the static form MUST substitute a second concentric ring at 12 px so the mark is still visibly louder than every other glyph on the map.

Rationale: an idle unit is a wasted activation and the report of UX-355 exists largely to surface them. On the map, the mark has to be loud enough to be noticed without being read, and it is the only glyph in the set permitted to break the size and animation rules.

ART-1090 Posture geometry authoring handles — the vertices a player drags when editing a Screen line, Survey region or March route (UX-735) — MUST use a separate handle set, not the Posture glyphs:

Handle Shape Size State variants
Vertex, idle hollow square, 2 px stroke 10 × 10
Vertex, hover hollow square + 1 px outer ring 12 × 12
Vertex, dragging solid square 10 × 10
Insertion point (midpoint) hollow square at 60% size, 1 px stroke 6 × 6 appears on segment hover only
Vertex, invalid position hollow square with an internal 45° slash 10 × 10 CM-350 rejection preview

6. State marks

Every remaining row of UX-430. All V, all on the 24-grid, all haloed.

ART-1100 Encirclement (CB-1410, UX-1135) MUST be drawn as a ring of eight ticks around the whole tile — one tick at each of the eight compass points, each 4 × 2 px, inset 1 px from the tile bounds, 1 px halo. It MUST be drawn for both owners whenever the marked unit is visible to the viewer.

Rationale: eight ticks for eight blocked neighbours is not decoration — the game is Chebyshev (GR-170) and "no retreat candidate" means literally that all eight are unavailable. The mark teaches the rule. It also sits around the unit rather than on it, so it can be the highest-priority mark (ART-980) without evicting anything.

ART-1110 Supply state (EC-1880, UX-1010) MUST be drawn as a cord at the right edge, 10 × 6 px, in exactly three states:

State Mark Construction
Supplied no mark absence is the quiet default
Extended cord with one break two 4 × 2 bars at y=2, x=0 and x=6; 2 px gap
Isolated cord severed, ends drooping two 3 × 2 bars at x=0 and x=7, each with a 2 × 2 downward tick at its inner end; 4 px gap

Note: the field is supplyState with values Supplied / Extended / Isolated (12-economy.md naming note). Documents that say "supply grade" are citing the same thing under its old name.

ART-1120 Grade (CB-170, CB-1600) MUST be drawn as 0–3 stacked chevrons at the top-right, each 8 × 3 px with 1 px gaps, pointing up: Green 0, Trained 1, Veteran 2, Elite 3. Count encoding only; no colour, no ornament, no wreath.

ART-1130 Dig-in (digIn 0–4, CB-100) MUST be drawn as a works mark that grows along the bottom edge: a 14 × 1 px baseline plus digIn downward ticks of 2 × 3 px, evenly spaced. Established entrenchment (TM-700) MUST thicken the baseline to 2 px and close the ticks into a continuous 14 × 3 px band. Two visually distinct end states, one growth animation between them.

ART-1140 Shaken (shaken 1–2, CB-100, CB-1130) MUST be drawn as a 1 px offset ghost of the unit's own silhouette, displaced 1 px up-left, at 45% alpha, redrawn each frame from the unit's alpha channel. Production method P — it is a shader effect over the existing sprite, not an art asset. It MUST persist for exactly the remaining shaken turns and MUST be visibly different from condition band 3's slash.

ART-1150 Forming Up (age 0–3, TM-950, TM-960) MUST be a mark that visibly expires: an 8 × 8 hollow square at top-right below the grade chevrons, with 4 − age of its four sides drawn. At age 3 a single side remains; on completion the mark disappears. The countdown is the point.

ART-1160 Reaction capacity (TM-1530, TM-1740) MUST be drawn above the tile as a 6 × 6 solid diamond when armed and a 6 × 6 hollow diamond with a 45° slash when spent. It MUST be drawn for own units always, and for enemy units only where TM-1740's disclosure permits — the renderer, not the art, enforces that, but the asset list MUST NOT contain an "enemy reaction" variant that could be mistakenly wired up.

ART-1170 Carried cargo (GR-1060) MUST be drawn as a 4 × 8 px solid tab on the unit's lower edge, offset left, with one tab per occupied cargo slot up to 4. Contents are shown only at the fidelity GR-1060 admits, and the tab MUST carry no class information whatsoever.

ART-1180 Air endurance (US-2000, US-2040) MUST be a countdown mark: endurance remaining drawn as that many 2 × 4 px vertical pips at the top-left, and the final turn of endurance MUST add a 1 px outline box around the pips. Banked movement (CM-1550, CM-1590) MUST be a single 3 × 3 px open square at the bottom-right of the Stance glyph's cell; the amount is a panel figure.


7. Selection, hover and targeting

ART-1190 Every radius this document draws MUST be drawn as a square (UX-380, GR-170). A circular range indicator is a defect: it misstates the rule at exactly the corners where the player is deciding. This applies to Leash radii, cohesion radii, vision, reaction reach, bombard range, Depot Reach, and the endurance envelope, without exception. Production method P for all of them — they are generated geometry at arbitrary radius, not sprites.

ART-1200 The selection furniture MUST be exactly this set, and the four members MUST be distinguishable from each other by silhouette alone:

Mark Construction Animation Method
Selection (primary) four L-shaped corner brackets, arms 9 px, stroke 2 px, inset 1 px from tile bounds 0.5 Hz "breathing" — inset oscillates 1 px ↔ 2 px P
Selection (secondary, multi-select member) same brackets, stroke 1 px, arms 7 px none P
Hover continuous 1 px outline of the full tile, inset 1 px none P
Attack target full tile outline, 2 px, plus four inward 4 px ticks at the edge midpoints 4 Hz single-pulse on acquisition, then static P

ART-1210 The drag-box selection rectangle (UX-840) MUST be a 1 px outline with a 2-2 dashed stroke, drawn in screen space, with a 12% fill of the near-white ink. The dash MUST NOT march — marching ants at 60 fps over a moving map is nausea, not affordance.

ART-1220 The Command Reference Point (CM-950) MUST be a crosshair: a 20 × 20 open cross, stroke 2, with a 6 px hollow ring at its centre and a 3 px gap between the ring and each arm. It MUST be distinguishable from the attack reticle at 32 px — the reticle is a bounded square, the CRP is an unbounded cross.

ART-1230 The accessible map cursor (UX-1320.4) MUST have a visible on-canvas marker distinct from both hover and selection: a double outline, 1 px + 1 px with a 1 px gap, full tile. It MUST be visible whenever the accessible map view has focus, and MUST meet the focus-indicator contrast obligation of UX-1325 against every terrain — which is why it is a double outline in both inks rather than a single stroke in one.


8. Order previews: paths, waypoints and geometry

ART-1240 The movement path MUST be drawn as a centre-line polyline through tile centres, 3 px wide with a 1 px keyline on both sides, corners mitred, no curve fitting. Production method P. The path MUST come from the core's pathfinder (UX-670) and the art MUST NOT smooth it — a drawn curve that does not match the tiles the unit will actually enter is a lie about a Chebyshev grid.

ART-1250 Path segment styles MUST encode reachability by line style, never by hue alone:

Segment Style Meaning
Reachable this turn solid 3 px will be traversed this activation
Beyond this turn's allowance 4-2 dash later turns
Illegal / not enterable 2-2 dash at 1 px, plus a 45° slash at the first illegal tile the route does not exist
Retreat / involuntary (playback) 3 px solid with a 1 px gap down its centre (railroad) not a player order

ART-1260 Turn boundaries on a path MUST be marked with a cross-tick: a 9 px bar perpendicular to the path at the tile where the allowance runs out. The turn number MUST be rendered in the map-anchored DOM layer (UX-016), not on the canvas.

Rationale: small text on a WebGL canvas at DPR 1 is unreadable, and every honest attempt to fix it ends in a bitmap font with a hinting problem. The DOM is already anchored to map locations by UX-016, it gets the browser's text rasteriser and the player's font-size preference for free (UX-1330), and it is the only route by which a screen reader can ever read the number (UX-1320). This rule applies to every numeral in this document: no numerals in canvas art, ever.

ART-1270 Waypoints (March route, 1–16 waypoints, CM-330) MUST be hollow diamonds, 10 × 10, stroke 2 — matching the Survey/Posture diamond family so the shape means "a place you told it about". The ordinal number is DOM (ART-1260). The final waypoint MUST be a solid diamond. Loop mode MUST add a 12 × 12 open arc with an arrowhead, drawn once at the final waypoint.

ART-1280 Posture geometry overlays (UX-460 "Postures") MUST be:

Geometry Rendering Method
Screen line 3 px polyline, 6-3 dash, with 5 px perpendicular facing ticks every 2 tiles on the covered side P
Survey region 45° hatch fill, 2 px on / 4 px off, plus a 1 px region boundary P
March route the path art of ART-1240 with waypoint diamonds P
Leash radius Chebyshev square, 1 px, 8-4 dash P
Cohesion radius Chebyshev square, 1 px, 2-2 dash P

The Leash and cohesion squares MUST use different dash periods, because a Formation member under Attach will frequently display both at once.

ART-1290 The transit trail used in Cascade playback (UX-340, TM-2090) MUST be a fading polyline in the path geometry, 3 px, with alpha ramping 100% → 0% over the last 6 tiles behind the moving unit. A unit that crossed an observed corridor MUST be seen to cross it. Rendering only endpoints is called out in UX-340 as the one failure that makes playback a lie; the art budget for this trail is therefore not optional.


9. The attack-preview odds marker (CB-2430, UX-1130, UX-1110)

ART-1300 The odds marker MUST encode break probability in five bands, and the bands MUST be carried by an ink-density ramp — a pattern set ordered by how much ink it puts on the tile — so that the scale reads correctly even when the individual textures are too small to resolve.

Band Break probability Pattern Ink coverage Marker glyph (12 × 12)
A ≥ 900‰ solid 100% solid square
B 700–899‰ 2 px checker 50% square, half-filled from the bottom
C 400–699‰ 45° hatch, 2 on / 4 off 33% square, 45° hatch
D 150–399‰ 1 px dots on a 3 px pitch 17% square, four corner dots
E < 150‰ none 0% empty square outline

Rationale: this is the whole colourblind trick in one table, and it is worth stating why it works. Individual small patterns are unreliable — 2 px checker and 2/4 hatch are genuinely hard to tell apart at 12 px. But their average darkness is not: 100 / 50 / 33 / 17 / 0 is a monotone ramp, so even when the texture dissolves into grey the ordering survives. The pattern is the fine reading and the density is the coarse one, and the coarse one never fails. Any replacement pattern set MUST preserve a monotone density ordering with at least a 12-point gap between adjacent bands.

ART-1310 The tile-level odds overlay (UX-460 "Attack odds") MUST use the same five patterns as the marker, generated procedurally in the shader in tile space — not sampled from a texture, and not screen-space. Method P. A screen-space pattern shimmers and moirés under continuous zoom (UX-380); a texture-sampled one aliases at every zoom that is not a power of two.

ART-1320 The odds marker MUST NOT display a number on the canvas. The per-mille figure, the reliability figure, the loss bands and the verdict all belong to the DOM attack preview, in the order UX-1110 fixes. Art MUST NOT provide a "quick odds number" affordance that would tempt an implementation to violate UX-1112's ban on a single expected value.


10. Colourblind-safe encoding (UX-1310, UX-440)

ART-1330 Ownership MUST carry two non-colour channels, not one. Seat identity is a hue pair (UX-210) plus a seat pattern at the Close/Mid tiers plus a seat shape at the Chip/Strategic tiers. Two channels, because a pattern is sub-pixel at Strategic zoom and a shape is redundant clutter at Close zoom, so each covers where the other fails.

Seat Pattern (Close/Mid, applied to the seat background inside the team-colour mask) Shape (Chip/Strategic marker outline) Ink coverage
1 solid circle 100%
2 45° hatch, 2 on / 3 off square 40%
3 135° hatch, 2 on / 3 off diamond 40%
4 cross-hatch, 2 on / 3 off both ways hexagon (flat-top) 64%
5 horizontal bars, 2 on / 3 off triangle, apex up 40%
6 vertical bars, 2 on / 3 off triangle, apex down 40%
7 2 px dots on a 5 px pitch pentagon 13%
8 2 px checker plus / cross 50%
Neutral / unowned 1 px outline only, no fill circle, 1 px stroke 0%

Note the deliberate difference from ART-1300: here the patterns are chosen to be maximally distinct in orientation, not ordered by density, because seat identity is nominal rather than ordinal. 45° against 135° is the single most reliable pair of textures at small size, so those go to seats 2 and 3, which are the most common opponents in a duel.

ART-1340 Seat patterns MUST be procedural (P) and MUST be applied only inside the team-colour mask of UX-440, as a modulation of the seat background colour. Three consequences, all binding:

  1. The seat foreground MUST stay solid. Patterning it dissolves the silhouette, which is the one thing UX-440's Chip-tier outcome test will not tolerate.
  2. Pixels outside the mask MUST NOT be patterned at all. They carry the shading, material and mechanical detail UX-440 exists to permit — tracks, barrels, canopies, rotor discs, wakes, cast shadow — and hatching over them destroys exactly the information they were left untinted to convey. A shader that patterns the whole sprite and relies on the mask only for hue is a defect.
  3. Because the mask need not cover a whole sprite, the visible pattern area is now per-asset and can be small. Each pattern MUST therefore be verified on the smallest masked background region any sprite in docs/art/01-* presents, not on a full-tile swatch. A 5 px pitch dot pattern (seat 7) in a 9 px-wide masked strip is two dots, and two dots is not a texture.

Patterns MUST be generated in tile space so they hold still under pan and zoom.

ART-1350 Every graded encoding in the game MUST declare its non-colour channel. This table is normative and MUST be kept complete:

Encoding Values Non-colour channel Where specified
Ownership per seat pattern (Close/Mid) + shape (Chip/Strategic) ART-1330
Condition — strength 0–100 count (rungs, 5 levels) ART-1020
Condition — disorder 0–100 line style (4 states) ART-1030
Fidelity grade (UX-420) Trace / Sighting / Identification / Assessment border line style: dotted / dashed / thin solid / thick solid, plus corner-tick count 0/1/2/3 ART-1360
Observation age (GR-1170) turns DOM numeral + border alpha ramp ART-1360
Supply state (UX-1010) Supplied / Extended / Isolated cord break count 0/1/2 + drooping ends ART-1110
Attack odds (CB-2430) 5 bands ink density ramp ART-1300
Grade (CB-1600) 4 grades chevron count 0–3 ART-1120
Stance (TM-630) 4 shape + stroke count ART-1050
Posture (CM-330) 6 + Idle shape + size (Idle) §5
Victory bands (UX-1155) per Track DOM: position on a labelled scale + pattern fill in the bar ART-1370
Chart series (UX-1145) n DOM/SVG: dash pattern + point marker shape, per series ART-1370
Dispatch severity (CM-1720) S/T/A composite pin bar count 1/2/3 + doubled outline for non-adaptable ART-1510

ART-1360 Fidelity rendering MUST make remembered never mistakable for observed (UX-420.1). The four fidelity grades MUST be drawn as border treatments on the Contact's region or marker:

Grade Border Corner ticks Contents shown
Trace 1 px dotted (1 on / 2 off) 0 domain only, unattributed
Sighting 1 px dashed (3 on / 3 off) 1 (top-left) owner colour + count band, no identity
Identification 1 px solid 2 (top-left, top-right) exact count and classes
Assessment 2 px solid 3 strength, Stance, condition

Contact regions (GR-1330, GR-1340) MUST be drawn as the Chebyshev box they are (ART-1190), with excluded tiles (GR-1350) drawn as a 1 px cross-hatch knock-out — a patrol that found nothing must visibly have narrowed the search. A tracked region MUST carry a 2 px inner border inset 2 px; an untracked one MUST NOT. A Contact approaching a grade drop (GR-1300, GR-1320) MUST have its border switch to a 1 Hz alternation between its own style and the next-worse style, so decay is legible.

ART-1370 DOM-layer encodings (charts, victory bands, the Watch, tables) MUST use SVG with currentColor and dash/marker differentiation, MUST honour forced-colours mode without losing information, and MUST NOT carry meaning in a CSS background image (UX-1310). Chart series MUST be distinguishable by dash pattern and point-marker shape with the colour channel removed entirely.

ART-1380 When a platform high-contrast preference is detected, the client MUST enable the patterns of ART-1330 and the ink-density ramp of ART-1300 by default (UX-1310). The art set MUST therefore contain no asset that only works with patterns off.


11. Effects

Be blunt with the reader about this section. Image generators cannot produce coherent animation frames. Asked for "8 frames of an explosion", a model returns eight unrelated explosions: different scale, different light direction, different debris, different centre of mass. Played back at 14 fps that is a strobe, not an animation. There are three ways out, in order of preference:

  1. Do it in code. Most of the effects below are particle systems and shader ramps. A particle system is coherent by construction, costs no texture memory, scales with zoom for free, and can be tuned by changing a number instead of regenerating art. This is the right answer for the majority of this list, and the honest recommendation is to ship with zero effect sprite sheets and add them only where playtesting says something feels thin.
  2. Generate one still, animate it procedurally. A single 256 × 256 luminance mask of a smoke puff, scaled/rotated/faded over time by the particle system, beats eight generated frames every time. This is where an image model earns its place here.
  3. Generate key frames, interpolate by hand. Only for the detonation. Generate frames 1, 4 and 8 with the same seed and image-to-image chaining, then fill 2–3 and 5–7 in a pixel editor. Expect this to take longer than drawing all eight.

ART-1390 Effect art MUST be authored as single-channel luminance masks on transparency — white shape, black nothing, alpha carrying the shape — and MUST be tinted at runtime. No effect asset may ship with baked colour.

Rationale: this sidesteps the generator's strongest bad habit in one move. You do not have to fight a model into producing a limited palette; you take the luminance of whatever it produces and throw the colour away. It also lets one detonation mask serve every seat and every effect colour, and it keeps effects outside the team-colour mask pipeline of UX-440 entirely: an effect has no owner, so it has no mask and needs no mask channel, and the tint it takes at run time is the effect's own rather than a seat's.

ART-1400 Every effect MUST respect the global animation-speed setting including instant (UX-480, UX-345), MUST be skippable at any point, and MUST NOT alter any outcome. The Cascade is computed to completion before the first frame is drawn (UX-335, TM-2240); effects are presentation and nothing else. An effect that cannot be cancelled mid-flight is a defect.

ART-1410 The effect list, with production method and timing:

Effect Method Construction Duration Notes
Clash (combat resolution) P expanding Chebyshev square ring: 8 px → 40 px, stroke 3 → 1, alpha 1 → 0; plus 8 shard particles, 3 × 1 px, ejected 12–20 px with gravity 380 ms (ring 180 ms, shards 320 ms) one per engagement after clustering (UX-350)
Bombardment P arc tracer, 2 px, apex at 40% of span height, head travels the arc; impact reuses the Clash ring at 60% scale 420 ms no break check happens (CB-1020); effect must not imply one
Falter / break P the retreating stack's transit trail (ART-1290) drawn in railroad style, plus a 24 px chevron burst pointing along the retreat 500 ms reuses the retreat path style of ART-1250
Unit destruction P + one G mask threshold dissolve: the sprite's alpha is cut by a tiling blue-noise mask with a rising threshold uniform, plus 6 debris particles 300 ms see ART-1420
City capture P three concentric expanding Chebyshev square rings (offset 0/80/160 ms) + the city sprite's masked region cross-fading from the old seat's colours to the new (UX-440); pixels outside the mask MUST NOT cross-fade 600 ms the tint cross-fade is the payload; the rings are punctuation. Under the Canvas fallback's pre-tinted atlases (UX-385) there is nothing to interpolate, so the cross-fade MUST degrade to a hard swap on the middle ring rather than to nothing
Missile launch P + one G mask 2 px tracer with a bright head travelling the flight line, plus a plume of 12 particles using the smoke mask, emitted at the origin over 400 ms flight at ~60 tiles/s, so ≤ 400 ms for reach 24 US-1550: reach 24, expendable
Missile detonation G sheet (see ART-1440) 8-frame sheet, 64 × 64 cells 570 ms @ 14 fps the one place a sheet earns its keep
Interception (Fighter / Air Defence) P short 2 px tracer from interceptor to the flight line, then the Clash ring at 50% scale on the flight line 300 ms must read as "stopped short of the target"
Transit P ART-1290 trail per movement UX-340 requires it
Cascade cluster marker V + DOM 20 × 20 open square, stroke 2, with a second ring at 26 px whose stroke thickens 1 → 3 px with clustered event count; count numeral in DOM static until drilled UX-350
Perceptible-event Trace V 16 × 16 dotted circle, 1 on / 2 off, no owner colour, no class mark, no direction 1 s fade-in, persists UX-425 — the client MUST NOT decorate it with an inference
Attention / Alarm pulse P the Dispatch pin (ART-1510) scales 100% → 115% → 100% 400 ms, twice UX-1165

ART-1420 The destruction dissolve MUST use one 128 × 128 tiling blue-noise mask, greyscale, single channel. It SHOULD be generated procedurally (a void-and-cluster or Bayer-plus-jitter generator is a few dozen lines) rather than by an image model — a model will produce a noise field that does not tile and whose frequency content is uneven, both of which are visible as banding. If generated anyway, it MUST be verified to tile seamlessly by the 2 × 2 tiling test of ART-1650.

ART-1430 Combat, capture, retreat and interception MUST be P. No sprite sheets may be commissioned for them. Rationale: each is a ring, a tracer or a particle burst — geometry a shader draws better than a texture, at any zoom, with no atlas cost, and tunable without a round trip through an image generator. Commissioning art for these is spending the scarcest resource in the project on the part of the problem that code solves for free.

ART-1440 Exactly two generated effect assets are commissioned by this document:

Asset File Grid Cells Cell size Method
Missile detonation fx_detonation_8.png 8 × 1 strip 8 64 × 64 G
Smoke / dust puff fx_puff_mask.png single 1 256 × 256 G

Everything else in §11 is P.

11.1 Generation prompt — missile detonation strip

Generate one cell at a time at 512 × 512, not the strip in one image. A model asked for a strip produces eight unrelated explosions at eight different scales. Generate frame 1, then use it as an image prompt for frame 2, and so on, keeping the seed fixed. Downsample each to 64 × 64 with a box filter, take the luminance, threshold the alpha, and assemble the strip in a pixel editor.

A single frame of a stylised explosion, rendered as a pure white shape on a solid
flat black background. Top-down orthographic view, looking straight down from
directly above — no perspective, no horizon, no ground plane, no shadow.

Frame 1 of 8: a small tight circular flash, roughly 20 percent of the frame width,
centred exactly in the frame, with six or seven short radial spikes of even length
radiating from it. Hard edges. No gradient falloff, no soft glow, no bloom.

The image must contain exactly two values: pure white (255,255,255) for the
explosion and pure black (0,0,0) for everything else. No greys, no anti-aliasing
softness beyond one pixel, no colour of any kind — not orange, not yellow, not red.

Graphic, poster-like, stencil-like. Think a printed silkscreen of an explosion,
or an explosion symbol from a technical manual, not a photograph and not a
rendered fireball.

Do not include: colour, gradients, smoke wisps, fire, embers, sparks, lens flare,
camera shake blur, a ground surface, debris silhouettes of objects, text, numbers,
a border, a frame, a drop shadow, multiple explosions, or any 3/4 or side view.

Frames 2–8 use the same prompt with the frame line replaced:

Frame Frame line to substitute
2 Frame 2 of 8: the flash has expanded to roughly 40 percent of frame width; the radial spikes are longer and beginning to separate from the core.
3 Frame 3 of 8: expanded to roughly 60 percent; the core has become a ring with a hollow centre; spikes now reach the frame's mid-radius.
4 Frame 4 of 8: expanded to roughly 80 percent; a clear hollow ring with a ragged outer edge; the centre is fully black.
5 Frame 5 of 8: the ring has reached the frame edge and thinned; its outer edge is breaking into separate blobs.
6 Frame 6 of 8: the ring has broken into eight to twelve separate white blobs arranged on a circle near the frame edge; the centre is empty.
7 Frame 7 of 8: the blobs are smaller and fewer, six or so, still on the same circle.
8 Frame 8 of 8: three or four very small white specks near the frame edge; almost entirely black.

Correct result: eight frames whose white area grows monotonically to frame 4–5 then shrinks; all centred on the same point; all with the same edge hardness; assembled and played at 14 fps they read as one expanding, breaking ring. Wrong result, and you will get these: an orange fireball (colour); a soft glowing blob (no hard edge, will not threshold); a 3/4-view mushroom cloud (perspective — it will look wrong drawn flat on a top-down map); eight explosions at eight different scales (why you chain the seed); a "cartoon explosion" with a comic-book outline (that outline becomes a black ring in the mask).

11.2 Generation prompt — smoke / dust puff mask

One image, 1024 × 1024, downsampled to 256 × 256. This single mask serves the missile plume, the destruction debris, city-capture dust, and any future ground effect — it is scaled, rotated, faded and re-tinted by the particle system, so it must be a generic puff and not a dramatic one.

A single soft cloud puff shape, pure white on a solid flat black background.
Top-down view. Roughly circular but irregular, with three or four rounded lobes,
filling about 70 percent of the square frame and centred in it.

The white is solid and opaque at the centre and fades to black only in the outer
15 percent of the shape's radius. Fine-grained internal texture — small tonal
variation within the white — but no dark holes and no separate detached pieces.

Greyscale only. No colour whatsoever. No outline, no rim light, no drop shadow,
no ground plane, no horizon, no sky.

Do not include: colour, a second puff, a smoke trail or tail, fire, an object
emitting the smoke, a character, text, a watermark, a border, or a vignette
darkening the frame corners.

Correct result: one centred blob, opaque core, soft edge, no hard rim. Wrong result: a detailed volumetric cloud render with directional lighting (the lighting will fight the map's flat lighting from every angle the particle rotates to); a smoke column with a tail (it will only look right at one rotation); a vignette (it darkens the mask's corners and shows up as a square when the particle is scaled up).

ART-1450 Both generated masks MUST be converted to single-channel alpha before use: take the luminance, discard RGB, and store as an 8-bit alpha-only PNG or a single-channel texture. The build step MUST reject any effect asset containing chroma with a per-pixel saturation above 2%.

ART-1460 Playback clustering (UX-350) means the effect system MUST support one marker representing many events. Every effect above MUST therefore accept a magnitude parameter scaling its radius and particle count, and the cluster marker of ART-1410 MUST be drawable in place of the individual effects entirely. Art that only works one-event-at-a-time will fail on a large Cascade.

ART-1470 No effect may draw anything the observation stream withholds (UX-340). Concretely, the effect system MUST NOT have assets for: a gap marker, a "something happened over there" placeholder, an off-screen event indicator, or an unattributed direction hint. Their absence from this document is deliberate and MUST NOT be treated as an oversight to be filled in.


12. Formation and Group markers (Command View — CM-1880, UX-395)

At Strategic zoom individual unit icons stop meaning anything. These markers replace them and must carry, per CM-1880, a Posture glyph, a strength bar, an intent arrow, and any Dispatch pins in that area. All V except the arrow and bar, which are P because they are parameterised.

ART-1740 Formation and Group markers replace unit sprites at Command View (UX-395) and at the Strategic tier (UX-390), so they are the sole exception to ART-940: the marker's outline MUST be drawn in the owning seat's foreground colour and its interior MUST be an opaque fill in the seat's background colour (UX-210).

A marker MUST NOT carry a team-colour mask and MUST NOT be authored per seat. The SVG is authored once, with outline and interior as two separate paths, and the renderer supplies the seat pair.

Rationale, and this is a place the UX-440 rewrite made things simpler rather than harder. UX-440's mask exists to let a sprite mix owned pixels with unowned ones — to keep a track run, a barrel or a canopy out of the tint while still putting enough seat colour on the silhouette to read at the Chip tier. A marker has no unowned pixels: every pixel of it is ownership, by construction, because the thing it is drawing is "these units belong to that seat". The mask would be all-ones and the outcome test UX-440 imposes is satisfied in its maximal case. So the correct answer is not to author a degenerate mask but to author none, and the marker set loses a channel, a build step and eight variants it would otherwise have carried.

ART-1750 The marker's contents — the Posture glyph, the strength bar, the CRP crosshair — remain neutral UI ink under ART-940 and MUST keep their ART-950 halos. UX-1310 lets a player remap any seat's colours for their own view, so the marker interior a glyph sits on is not a value this document can predict; the halo is what makes that safe, and it is the same ART-1690 argument as for a glyph over a unit sprite. The strength bar is PLATE class per ART-1700, and the marker's own opaque interior fill is its plate — no second plate may be drawn.

ART-1480 The marker MUST be 40 × 40 CSS px and MUST distinguish Formation from Group by outline shape, not by colour:

Kind Outline Stroke
Formation rounded rectangle, 40 × 32, corner radius 4 2 px
Group flat-top hexagon, 40 × 36 2 px

ART-1490 Marker contents MUST be laid out exactly as follows:

Formation marker, 40 × 32 (+ intent arrow outside the outline)

  ┌──────────────────────────────┐
  │                              │      Posture glyph: the §5 glyph
  │        ╔════════╗            │      at 16 × 16, centred, top-weighted
  │        ║ POSTURE║            │
  │        ╚════════╝            │      Strength bar: 5 horizontal segments,
  │                              │      6 × 4 px each, 1 px gaps, filled
  │  ▓▓▓ ▓▓▓ ▓▓▓ ░░░ ░░░         │      left-to-right = ceil(agg/20)
  └──────────────────────────────┘
                              ╲          Intent arrow: 2 px shaft + 8 px head,
                               ╲         projecting OUTSIDE the outline in the
                                ➤        direction of intent; shaft length
                                         12 px (near) / 18 px (mid) / 24 px (far)
Element Construction Method
Posture glyph the §5 glyph scaled to 16 × 16, centred, y-offset −3, in neutral ink with its halo (ART-1750) V
Strength bar 5 segments 6 × 4 px, 1 px gaps, filled = ceil(aggregateStrength / 20); unfilled drawn as outline; plated by the marker interior (ART-1750) P
Intent arrow 2 px shaft, 8 px solid triangular head, one of 8 compass directions (Chebyshev), origin at the outline edge P
Cohesion radius Chebyshev square, 2-2 dash (ART-1280) P
CRP the crosshair of ART-1220 V
Member count DOM numeral, anchored bottom-right of the marker DOM

ART-1500 Formation health states MUST be carried by the outline, not by hue:

State Outline treatment Source
Normal continuous 2 px
Under strength outline broken by a 3 px gap at each corner FormationUnderStrength, CM-1720
Strung out (cohesion exceeded) outline switched to a 4-2 dash FormationStrung, CM-1720
Commander dead, no successor activated outline doubled (2 px + 1 px, 1 px gap) CM-980

ART-1510 Dispatch pins (CM-1890) MUST be teardrop pins, 14 × 20 px, tip at the map location, with severity carried by bar count in the pin head:

Severity Head content Outline
Low (composite < 60) 1 bar, 6 × 2 single 1 px
Medium (60–119) 2 bars, 6 × 2, 2 px apart single 1 px
High (≥ 120) 3 bars, 6 × 2 single 1 px
Non-adaptable class (CM-1790) as above for its band doubled outline, 1 px + 1 px

Rationale for the doubled outline: CityThreatened is exempt from Dispatch adaptation precisely because the game must never learn to stop telling you your capital is about to fall. The mark that says "this one will not go away" should be visible on the map, and an outline treatment reads without colour and without reading the pin's text.

ART-1520 Command View MUST be usable to issue every Formation, Group and Posting order without leaving it (CM-1900), so the marker MUST have hover and selected states — the same corner-bracket and outline vocabulary of ART-1200, scaled to the marker's bounding box rather than to a tile.


13. Cursors

ART-1530 Cursors MUST be vector-authored (V) and exported at 32 × 32 (1×) and 64 × 64 (2×) PNG with declared hotspots. Every cursor MUST be drawn as a near-black shape with a 2 px near-white outline so that it survives both Icefield and Open Ocean without a readability toggle. Every cursor MUST declare a CSS fallback keyword for the case where the custom image fails to load.

Cursor Shape Size Hotspot (1×) CSS fallback
Default standard arrow 32 1, 1 default
Pan (idle) open hand 32 16, 16 grab
Pan (active) closed hand 32 16, 16 grabbing
Move-to arrow with three trailing motion ticks behind it 32 1, 1 pointer
Attack the ART-1200 attack reticle at 24 px, centred 32 16, 16 crosshair
Bombard reticle with an arc over its top edge 32 16, 16 crosshair
Load / embark 8 px down-chevron entering an open bracket 32 16, 20 pointer
Unload / disembark 8 px up-chevron leaving an open bracket 32 16, 12 pointer
Geometry draw crosshair with a 6 px pen tick at upper-right 32 16, 16 crosshair
Waypoint add crosshair with a 6 px + at upper-right 32 16, 16 copy
Waypoint remove crosshair with a 6 px at upper-right 32 16, 16 crosshair
Invalid arrow with a 14 px slashed circle at lower-right 32 1, 1 not-allowed
Measure arrow with a small Chebyshev square at lower-right 32 1, 1 crosshair

ART-1540 The cursor MUST NOT be the only channel for any of these states. Every cursor above MUST be accompanied by an on-map affordance — the target reticle, the path preview, the handle highlight — because a keyboard or gamepad player (UX-1240, UX-1340) never sees a cursor at all, and because touch users do not have hover.

ART-1550 Touch targets MUST be at least 44 × 44 CSS px at 100% scale (UX-1330). Where a glyph in this document is smaller than that and is interactive — waypoint handles, Dispatch pins, Formation markers — the hit area MUST be padded to 44 × 44 without changing the drawn size.


14. DOM iconography

Everything in this section is V, delivered as one SVG sprite sheet of <symbol> elements, 24 × 24 viewBox, 2 px stroke, square caps, no fill except where noted.

ART-1560 DOM icons MUST use currentColor for every stroke and fill. They MUST NOT be delivered as PNG, as an icon font, or as CSS background images — all three lose their meaning in forced-colours mode, which UX-1310 forbids.

ART-1570 Every icon MUST be paired with a text label or an aria-label; no control may carry its meaning in the icon alone (UX-1320.1). A disabled control's icon MUST NOT be the only indication that it is disabled, and the reason MUST be exposed (UX-560).

ART-1580 The minimum DOM icon set, one row per icon:

Group Icon Construction sketch (24 grid)
Overlays (UX-460) Supply (Reach) three nested Chebyshev squares, decreasing stroke
Projected supply as Supply, outer square dashed
Attack odds the ART-1300 band-C hatched square
Intelligence dotted square with one solid corner tick
Industry four ascending bars, heights 6/10/14/18
Integration and Unrest a square split diagonally, one half hatched
Victory sites five-pointed star outline, 2 px
Formations the ART-1480 rounded rectangle at 18 px, containing 3 dots
Postures the Screen glyph at 18 px
Reaction cover the ART-1160 diamond over a Chebyshev square
Endurance a clock-free arc with an arrowhead returning to its start
Grid a 3 × 3 lattice of 1 px lines
Playback (UX-345) Play solid right triangle
Step forward right triangle + terminal bar
Skip to end double right triangle + terminal bar
Instant double right triangle, no bar
Replay circular arrow, 270°, arrowhead at start
Phase (UX-540) Orders phase pen over a line
Cascade phase three right chevrons
Commit square with a check stroke
Panels Pin pushpin, 2 px
Close 16 px ×
Collapse / expand single chevron, rotated by state
Minimap rounded square with an inner viewport rectangle
Data (UX-045) Sort ascending up chevron + three bars, ascending
Sort descending down chevron + three bars, descending
Filter funnel outline
Copy two offset rounded rectangles
Table view 3 × 3 grid with a heavier top row
Chart view axis pair + polyline
Reference Encyclopedia open book, 2 px
Key bindings keycap outline containing a small square
Settings slider track with two handles (not a gear — a gear at 24 px with 2 px strokes turns to mush)
Status Alarm (UX-1165) triangle with a 2 × 8 bar and a 2 × 2 dot
Dispatch item the ART-1510 pin at 18 px
Idle unit the ART-1080 ring at 18 px
Stalled production a bar with a 4 px gap and a 2 px cross over the gap

ART-1590 Where a DOM icon has a map counterpart — the condition badge, the Posture glyphs, the Idle ring, the Dispatch pin, the supply cord — the DOM version MUST be the same SVG source scaled, never a redrawn approximation. A player who learns a mark on the map must find the identical mark in the panel.


15. File layout and delivery

ART-1600 Sources and outputs MUST be laid out as follows. The client package does not exist in this repository yet; assets/ is proposed at the repository root and the build step is expected to emit into whichever package eventually owns the renderer.

assets/
  ui/
    src/                          # authored SVG — the source of truth (V)
      glyph/
        condition/
          condition-ladder.svg          # the 5-rung ladder
          condition-frame-0.svg         # steady
          condition-frame-1.svg         # shaken
          condition-frame-2.svg         # disordered
          condition-frame-3.svg         # wrecked
          condition-mid.svg             # 3-rung Mid-tier variant
        stance/
          stance-march.svg  stance-advance.svg
          stance-hold.svg   stance-entrench.svg
          stance-derived-dot.svg
        posture/
          posture-hold.svg      posture-garrison.svg
          posture-screen.svg    posture-survey.svg
          posture-march.svg     posture-attach.svg
          posture-idle.svg      posture-idle-static.svg
        state/
          encirclement-ring.svg
          supply-extended.svg   supply-isolated.svg
          grade-chevron.svg                 # one chevron, stacked 0-3 by the renderer
          digin-tick.svg        digin-established.svg
          formingup-0.svg .. formingup-3.svg
          reaction-armed.svg    reaction-spent.svg
          cargo-tab.svg         endurance-pip.svg
          banked-movement.svg
        handle/
          vertex-idle.svg  vertex-hover.svg  vertex-drag.svg
          vertex-insert.svg  vertex-invalid.svg
          waypoint-diamond.svg  waypoint-final.svg  waypoint-loop.svg
        marker/
          formation-outline.svg  group-outline.svg
          dispatch-pin-1.svg  dispatch-pin-2.svg  dispatch-pin-3.svg
          crp-crosshair.svg
          cluster-marker.svg
          trace-marker.svg
        odds/
          odds-band-a.svg .. odds-band-e.svg
      cursor/
        cur-default.svg  cur-pan.svg  cur-pan-active.svg
        cur-moveto.svg   cur-attack.svg  cur-bombard.svg
        cur-load.svg     cur-unload.svg
        cur-geometry.svg cur-waypoint-add.svg  cur-waypoint-remove.svg
        cur-invalid.svg  cur-measure.svg
      icon/
        icons.svg                        # the DOM <symbol> sheet, all of ART-1580
    gen/                          # generated assets (G)
      fx_detonation_8.png                # 512 × 64, 8 cells of 64 × 64, alpha only
      fx_puff_mask.png                   # 256 × 256, alpha only
    build/                        # emitted, never hand-edited, never committed
      overlay@1x.png  overlay@1x.json    # atlas + frame data
      overlay@2x.png  overlay@2x.json
      cursors/*.png                      # 1× and 2×

ART-1610 Atlas parameters for the overlay atlas:

Parameter Value
Padding between frames 2 px (transparent)
Extrude 1 px edge repeat, into the padding
Power-of-two not required; max dimension 2048
Format PNG-32, straight (non-premultiplied) alpha
Colour the two UI inks only; the build step MUST reject any other colour
Rasterisation from SVG at each ship size in ART-960, never by downscaling a larger raster

Rationale for the extrude: at non-integer zoom the sampler reaches half a texel outside the frame, and without an extruded border a glyph picks up its neighbour's pixels along one edge. It shows up as a faint line on one side of every badge and it is invariably diagnosed as "the artist left a stray pixel".

ART-1620 Overlay glyphs MUST be rasterised at each ship size from the SVG, and the renderer MUST pick the nearest size at or above the current on-screen size. Scaling a 24 px raster down to 12 px produces exactly the blurred, uneven-weight result that ART-910 exists to avoid.


16. Verification

ART-1630 The confusion test. Render the complete glyph set — Stance, Posture, state marks, handles, odds bands — at 12 device px, in greyscale, over each of the eleven terrains and over the adversarial backdrop sheet of ART-1640, and apply a 1 px Gaussian blur. Show the sheet to a person who has not memorised it and ask them to name each mark. Any pair confused more than 10% of the time fails, and the fix is to change a silhouette, never to change a colour. This test MUST be re-run whenever a glyph is added.

ART-1640 The overlay contrast test. This replaces the two-tone posterise test the previous draft carried. That test asserted that anything this document draws over was flat two-tone art; UX-440 no longer says so, and a test that fails a correctly authored shaded hull is worse than no test at all. What must be verified now is the opposite property: that the marks survive art the specification permits.

The backdrop sheet. Composite every mark in this document over a sheet containing, at minimum:

Backdrop Why it is in the sheet
Each of the eleven terrains (GR-260) at its authored value the original ART-950 case, unchanged
A masked seat-background field in each of the eight seat colours, patterned per ART-1330 the pattern is high-frequency and can beat a 1 px halo on its own
A masked seat-foreground field in each of the eight seat colours the solid case; the darkest and lightest seats are the two that matter
An unmasked sprite region at the worst the mask permits: a specular edge at the near-white ink's own value, a cast shadow at the near-black ink's own value, a 1 px repeating detail line at the halo's own width, and a material break running diagonally under the mark this is the case UX-440's rewrite introduced and the only one the old test never covered
A 50% grey field the value that defeats a single ink and forces the halo to do the work

Pass criterion, per mark, per backdrop. Take the mark's outer boundary after compositing. At every point on it, at least one of the two inks present in the mark's outer 2 px — body or halo — MUST reach 3:1 against the composited pixel immediately outside. A mark that passes over some backdrops and not others fails; there is no partial credit, because the player does not choose which unit the badge lands on.

Fill criterion, PLATE class only. Render each PLATE-class mark in its filled and its empty state over the same sheet and ask a viewer who has not been told the answer which is which. Any error fails. This is the criterion a halo cannot pass and the sole reason ART-1720 exists; if the plates are ever proposed for removal, this is the test that decides it.

Rationale for keeping a posterise step nowhere in this document: whether a sprite is two-tone is no longer a property anything is entitled to assert, and asserting it here would put this file back in conflict with UX-440 through the side door of a verification rule. Tone count is an art-direction choice per asset (UX-440) and docs/art/01-* owns it.

ART-1650 The tiling test for the noise mask of ART-1420 and for every seat pattern: lay the texture out 2 × 2 and inspect the seams at 400%. A visible seam, or a visible brightness gradient across the tile, fails.

ART-1660 The density-ramp test for ART-1300 and any replacement: render the five bands at 12 px, convert to greyscale, and measure mean luminance. The five means MUST be monotone and MUST differ from their neighbours by at least 12 points on 0–255. If they do not, the ramp will not read when the pattern dissolves, and the whole colourblind guarantee for attack odds is gone.

ART-1670 The DPR-1 test. Every legibility claim in this document is at device pixel ratio 1 (ART-970). Verification on a 2× display only is not verification. At least one member of the review matrix MUST be a 1× display at 100% browser zoom.

ART-1680 The forced-colours test. Load the client with the platform's forced-colours / high contrast mode active and confirm that no DOM icon, chart series, victory band or status indicator has lost information (UX-1310, ART-1370). The canvas cannot participate; confirm instead that the patterns of ART-1330 and the ramp of ART-1300 have auto-enabled (ART-1380).


Asset count

Category Assets Method
Condition badge parts 6 V
Stance glyphs (+ derived dot) 5 V
Posture glyphs (+ Idle static) 8 V
State marks 14 V
Geometry handles and waypoints 8 V
Markers, pins, CRP, cluster, Trace 8 V
Odds band glyphs 5 V
Cursors 13 (× 2 export sizes) V
DOM icons 34 (one <symbol> sheet) V
Generated effect masks 2 files, 9 cells (8 detonation + 1 puff) G
Procedural effects and overlays 24 named effects/overlays, no files P

Total individual sprite cells specified: 110 — 101 vector-authored cells, 9 generated cells. Cursors are counted once each, not once per export size.


Open questions

  1. Does the condition badge actually survive playtesting? Four disorder bands carried by frame integrity is the least-tested idea in this document. Band 1 (dashed) against band 2 (corners only) is the pair most likely to fail ART-1630, and if it does, the honest fix is to drop to three disorder bands rather than to reach for hue.
  2. Eight seats, or more? ART-1330 gives eight patterns and eight shapes. UX-260 names an eight-player Vast game as a preset, but no requirement in the specification set fixes a maximum seat count. If the maximum exceeds eight, the pattern set needs extending and the ninth pattern onward will be materially harder to keep distinct.
  3. Is the Stance/Posture "Hold" collision fully resolved by ART-1000? Two families and two anchors is the mitigation. Whether players actually read them as different is a question for the confusion test with real players, not for this document.
  4. How much of the overlay set survives the Mid tier? ART-980 caps Mid at two marks and ART-1030 specifies a Mid condition badge, but which two marks win at ~0.5× is a legibility judgement nobody has made yet. It should be made against the Vast fixture, not against a screenshot of six units.
  5. Should the Idle mark's pulse be on by default? ART-1080 makes it conspicuous by size, halo and motion. On a map with forty idle units, forty pulsing rings may be worse than useless. A count in the Dispatch might do the job better, in which case the map mark can be static.
  6. Does the detonation sheet earn its 570 ms? It is the only commissioned animation in this document. If the procedural Clash ring at 150% scale reads well enough for a missile strike, the sheet should be cut and §11.1 deleted — that would take the generated asset count to one.
  7. Where does the odds overlay's shader live relative to the team-colour mask pass? ART-1310 wants a tile-space procedural pattern; UX-440 wants a mask-driven substitution pass over the sprite. Whether they compose in one pass or need two is a renderer question this document cannot answer, and the answer changes whether ART-1340's seat patterns and ART-1310's odds patterns can be visible simultaneously without turning the map into moiré. The mask makes this narrower than it was — ART-1340's pattern now touches only masked pixels, so there is less surface for the two patterns to collide on — but it does not settle it.
  8. Are the plates of ART-1720 needed, or is the halo enough? The argument in ART-1690 is sound in principle and the fill criterion of ART-1640 is designed to settle it in practice, but nobody has run it, and 180 px² of opaque plate on a 32 px tile is a real cost paid against a hypothetical sprite. If the unit sprites docs/art/01-* actually ships are quiet under the left-edge anchor, the condition badge could drop to HALO class and buy that area back. The honest sequence is: ship the plates, run ART-1640 against real sprites, and remove them if they turn out to be insurance against a case the art direction never produces.
  9. Does the anchor set of ART-990 still sit in the right places? The anchors were chosen against flat counters, where every part of a sprite was equally uninformative. That is no longer true: under UX-440 a sprite's untinted detail — the barrel, the rotor disc, the wake — is likely to be concentrated somewhere specific, and the left edge that the condition badge occupies may be exactly where a naval unit's wake wants to be. This document cannot resolve it alone; it needs one pass over the finished unit set with docs/art/01-* to check that the eight anchors and the sprites' informative regions do not want the same pixels.
  10. How much team-colour does the Chip tier's flat chip actually need? UX-390 puts a short class label on the Chip-tier chip and UX-440 requires ownership to read from the silhouette at that tier. The chip is not this document's asset, but the Chip-tier seat shapes of ART-1330 are, and whether a shape outline plus a labelled chip is one channel too many at 8 CSS px is a question for the same pass as question 9.

Built from source 7f764a6c1ff9 · VERSION.json