00-art-direction.md · 71 requirements

Art Direction and Technical Foundation

This document fixes the visual identity, the team-colour authoring system, the canvas geometry, the file formats and the production pipeline for every piece of raster art in Every Last City. It is the document the other three art documents build on: they enumerate subjects and supply per-asset prompts, this one supplies the rules those prompts must obey and the pipeline that turns generator output into something the renderer can actually load. Nothing in the sibling documents overrides anything here. If you are about to generate your first image, read at least §2, §3, §4 and §7 first — every one of them describes a way to produce hundreds of plausible-looking files that are unusable.

Status: Draft v0.1 · Owner: unassigned · Depends on: docs/spec/04-ui-ux.md, docs/spec/07-modding-content.md, docs/spec/02-units-and-industry.md, docs/spec/01-game-rules.md

Decisions applied: D-1 to D-7 of docs/art/04-decisions.md — the interior-mark exemption for counting cues (ART-080), the move of §7's worked example from Infantry to Armour (ART-490, §7), <id> file naming (ART-390, ART-400), masters re-rendered and never upscaled (ART-280), Chip and Strategic drawn at runtime (ART-140, ART-145), the labelled atlas bootstrap (ART-415), and the split of linear detail from area detail with its narrow-projection contour exception and its prohibition on surface marking (ART-157, ART-158, ART-159, and ART-050, ART-155, ART-225, ART-255, §7, §8 following them).

Consumed by: docs/art/01-units.md (the 23 producible units plus Militia, Administrative Cadre and Forward Depot), docs/art/02-terrain.md (the 11 terrain types, the Road / Fort / Airfield installations, the River and Ford edge features, the City), docs/art/03-ui-effects.md (badges, overlay glyphs, LOD markers and the UI chrome this document reserves space for).


0.1 The one-paragraph version

Ownership is rendered by substituting each player's foreground and background colours into a team-colour mask carried by the art itself (UX-440). Everything else follows from one consequence of that. UX-440 constrains the mask by an outcome, not by a technique or a colour count — ownership must be unmistakable from the silhouette alone at the Chip tier, an 8 CSS px sprite — and a design that tints only insignia or trim fails it. So the mask has to be most of the sprite, and the tint-mask base of every unit is authored as a flat two-tone neutral stencil: a printed wargame counter, not an illustration. What UX-440 does not do is cap the number of tones. A Close-tier sprite MAY therefore ship a second, optional detail layer of authored colour that composites over the tinted base and is never recoloured — tracks, a gun barrel, a canopy, a rotor disc, a wake, a shadow the subject casts on itself — bought at one price: the sprite MUST still be complete, legible and unmistakably owned with that layer deleted. Ownership additionally must not be carried by hue alone at any zoom tier (UX-1310), so silhouette and interior mark have to do work that colour is not permitted to do. Image generators will fight the flat base, because they are trained to produce rich shaded colour art; §7 is about defeating that tendency, splitting the result into two layers, and §2.6 is about proving you did.


0.2 Units of measurement — read this before any number in this document

Three different pixel grids appear in this specification, and confusing them is a 4× error in every stroke width — the single most expensive mistake available here. They are named distinctly throughout:

Term Grid Where it lives
CSS px the browser's layout pixel what the player's display shows: a Close-tier tile is 32 CSS px
cell px 1/128 of the Close-tier cell the delivered sprite. Every silhouette, stroke, contour, gap and reserve figure in this document is stated in cell px
master px 1/512 of the master file the authored artwork of record, before reduction

The conversions are exact and worth memorising: 1 CSS px = 4 cell px = 16 master px at the Close tier. So ART-050's 8 cell px contour is 2 CSS px on screen, and is drawn 32 px wide in the 512 master.

Rationale: an earlier draft used "master pixel" for both the 1/128 and the 1/512 grid — the requirements meant the delivered cell, while the worked example in §7 meant the authoring file. An author who read the requirements and then drew at 512 would have made every contour and stroke a quarter of its intended weight, and the error stays invisible until the sprite is reduced and the silhouette falls apart. The terms are now distinct, and the requirements are stated in cell px because that is the grid the legibility argument is actually about.

1. The visual identity

1.1 The reference object

ART-010 The tint-mask base of all authored raster art (§2) MUST read as a printed two-colour wargame counter: flat screen-printed ink on a flat field, hard edges, no rendering of light. The reference objects are the die-cut cardboard counter, the stencil, the two-colour silkscreen poster, and the printed military map symbol. The reference objects are not the painted box-art illustration, the isometric strategy-game unit render, the icon with a soft drop shadow, or the "flat design" vector icon with three or four tints of one hue. The optional detail layer of ART-125 sits over that counter and is a restrained material break in it, never a repainting of it: with the detail layer deleted, what remains MUST be a complete and correct counter under every requirement of this section.

Rationale, restated because the reason previously given here was wrong. This document used to claim the counter style was "the only style the tinting pipeline of UX-440 can express". That was never true of a GPU, and it is not true of the current UX-440, which explicitly frees art outside the mask to carry shading, material and mechanical detail. The style is an art-direction choice and it is made here on the merits.

At the Close tier a tile is 32 CSS px with friendly and enemy units adjacent constantly, and the only encoding of ownership that survives that — and survives the reduction to the 8 CSS px Chip sprite that UX-440's outcome test measures (ART-1135 of docs/art/02-terrain.md) — is a large flat mass changing colour. Hard edges and no rendering of light are what keep that mass reading as one mass rather than as a lit object whose shading competes with the seat colour. The counter is also the cheapest style to hold consistent across two dozen separate generations (§6.2), which matters far more on a generated set than it would on a hand-drawn one. None of that is inherited; all of it is checkable, and §2.6 checks it.

1.2 Projection

ART-020 Ground and naval subjects MUST be drawn in flat side elevation: orthographic, zero perspective, the camera exactly level with the subject, no vanishing point, no foreshortening, no ground plane, no cast shadow. Air subjects MUST be drawn in plan view: seen from directly overhead, orthographic, no perspective.

ART-030 Every subject in a given projection MUST face the same direction. Ground and naval subjects face right. Air subjects have their nose pointing right. The renderer MUST NOT mirror or rotate a unit sprite to indicate direction of travel — mirroring flips the interior marks and destroys the identity read, and the simulation carries no facing to mirror. Direction of intent is carried by the intent arrow of UX-395 and by the order preview, not by the sprite.

Rationale: two decisions here, both load-bearing.

Side elevation rather than a three-quarter overhead view is chosen partly because it is the counter idiom, and partly for a blunt production reason: a fixed side elevation is the tightest framing an image model can hold across dozens of separate generations. "Camera exactly level, no perspective" is a target a model either hits or visibly misses. "Sixty degrees from horizontal, subject facing the top of the frame" is a target a model drifts across, and twenty-six sprites at twenty-six slightly different camera heights is a set that looks wrong and cannot be fixed by any amount of colour work. Consistency is the hardest problem in this whole pipeline (§7.4); spending the projection choice on it is a good trade.

Air units in plan view is a deliberate break, and it earns its keep twice: aircraft are only identifiable from above, and the change of projection is itself a non-colour cue that separates the air domain from everything else, which is a UX-1310 obligation discharged for free. Printed wargames have mixed elevation and planform on the same map for seventy years and nobody has ever been confused by it.

1.3 Silhouette

ART-040 The silhouette MUST be a single connected mass. No part of it may be thinner than 8 cell px (2 CSS px at the Close tier — see §3 for the pixel budget). At most one disconnected accent island is permitted per sprite, and it MUST be at least 12 cell px across in both dimensions; a sprite whose identity depends on that island is a defect, because it is the first thing to vanish at the Mid tier.

ART-050 The silhouette MUST carry a closed ink contour of 8 cell px (2 CSS px) around its entire outer boundary, unbroken. There is exactly one permitted variation in its weight: a narrow linear projection qualifying under ART-157 MAY carry 2 cell px on each exposed side, under ART-158. The contour there is narrower, never absent — "closed and unbroken" admits no exception at all.

Rationale: the contour is not decoration. A seat's background colour can land arbitrarily close to the colour of the terrain underneath — the player chooses their colours (UX-210) and can re-map them mid-game (UX-1310) — so the only thing guaranteeing the unit separates from the map is a band drawn in the seat's other colour. It also makes the alpha-bleed rule of §2.5 exact rather than approximate, because it means every pixel at the silhouette boundary is known to be ink — a guarantee about the value at the boundary, which ART-157's narrower band leaves intact.

ART-060 Silhouettes MUST be blunt, chunky and closed, built from a small vocabulary of geometric primitives: rectangles, trapezoids, chamfered rectangles, circles, half-circles, and straight ink strokes of uniform width. Organic contour, taper, wobble, calligraphic line-weight variation and hand-drawn irregularity are forbidden. Convex forms are preferred; a concavity narrower than 12 cell px MUST be closed up.

ART-070 Each movement class MUST have a distinguishable base footprint, established here and elaborated per unit in docs/art/01-units.md:

Movement class Aspect (W:H) Base footprint The tell at a glance
foot ~3:4, taller than wide upright figure mass, squared shoulders, rounded head the only tall-narrow silhouette
wheeled ~4:3 low horizontal body over separated circular wheel discs discs with gaps between them
tracked ~4:3 low horizontal body over one continuous rounded track band an unbroken band, no gaps
naval ~5:2, wide and low elongated hull, raked prow at the right, superstructure block cut off at a flat waterline
air ~1:1 plan-view planform, swept or straight wing, nose right the only plan-view subject

Rationale: UX-1310 forbids ownership being carried by hue alone at any tier, and the Chip and Strategic tiers reduce a unit to a shape and a label. If the shape families are not separable in pure silhouette, the low tiers carry no information and the requirement is not met. Deciding the families at the foundation, rather than per unit, is what stops twenty-six independently prompted sprites from converging on twenty-six variations of "vaguely military box".

1.4 Interior detail

ART-080 The interior of a sprite MUST carry at most four ink shapes, not counting the contour of ART-050. Every interior shape MUST be at least 12 cell px (3 CSS px) in its smaller dimension. Every ink stroke MUST be 4 to 8 cell px wide. Every gap between two ink shapes, and between an ink shape and the contour, MUST be at least 4 cell px.

A repeated identity group counts as one shape. Where a run of marks is drawn so that its number is the cue — five road wheels in a track band, seven, a battery of barrels, a row of gun ports — the whole run counts as one semantic feature against the cap above, not as one shape per element. The per-element floors are unchanged and still apply to every mark in the run: none may be under 12 cell px in its smaller dimension, every stroke stays in the 4–8 cell px band, and every gap stays at or above the minimum.

The exemption applies only where the group actually reads as a group, and all four of these MUST hold:

  1. the elements are uniform in size and shape;
  2. they are evenly spaced;
  3. they are aligned on a common axis; and
  4. the group survives reduction to 8 CSS px as a texture — at the Chip tier it must read as a band of regular repetition, not as noise.

An irregular scatter of similar marks is clutter wearing a counting cue's clothes: it is not a group, it takes the full four-shape cap, and each mark counts separately. The counting cues themselves are enumerated per family in docs/art/01-units.md §3.4, which owns them; this requirement only says what they cost against the cap.

Rationale: this is the whole detail budget and it is smaller than it feels. The Close-tier cell is 128 cell px wide and is displayed at 32 CSS px; four marks at three CSS pixels each is a busy sprite already. Hatching, panel lines, rivets, tread detail and grille slots all reduce to an even mid-grey mush at the Mid tier and to nothing at all below it, while costing real texture memory — and, worse, they defeat the two-tone check in §2.6 by producing a broad intermediate band in the histogram that is indistinguishable from genuine shading.

Rationale for the exemption, which is not a loosening of that budget. The cap exists to stop a 128 px cell turning to mush, and what causes mush is four unrelated marks competing for attention. A regular repeated run does not compete with itself: the eye reads "tracked vehicle, many wheels" as a single percept and only then counts, which is exactly the identity cue docs/art/01-units.md is buying with it. Counting each wheel as a shape would forbid the cue that distinguishes Armour from Heavy Armour and the one that separates Corvette, Destroyer and Cruiser — a trade this cap was never meant to make, and the reason the two documents were in conflict until this was written down.

Rationale for the guard, added rather than assumed. The four conditions are what separate a cue from an excuse. Uniformity, even spacing and a common axis are what make the run resolve as one percept instead of four; without them the exemption would launder any amount of clutter through the word "repeated". Condition 4 is the honest one: if the group reads as noise at 8 CSS px then the count was never legible at the sizes this game actually draws, and the complexity was bought for nothing.

ART-090 Interior marks MAY draw on the logic of map symbology — a closed field bearing an abstract mark that names a role — but the mark set is original to this game and is defined per unit in docs/art/01-units.md. Art MUST NOT reproduce any real-world national insignia, service emblem, roundel, flag, or standardised military symbol set.

ART-100 Art MUST NOT contain text, numerals, letterforms, or anything that reads as lettering, at any tier. Chip-tier short labels (UX-390) are drawn from the label glyph atlas at runtime and are never baked into art.

Rationale: image generators cannot render small text and never will within this project's lifetime; every attempt produces convincing-looking garbage that a reviewer's eye skips over. Banning it outright removes an entire class of asset that would have to be rejected one at a time.

1.5 Correct and incorrect, side by side

ART-110 A reviewer MUST be able to reject a sprite on any row of this table without opening an image editor.

Correct Plausible but wrong
Base: two flat tones, hard boundary between them A mid-grey body with a darker rim and a lighter top edge — three tones in the base, reads as "shaded"
Base: perfectly flat field, no rendering of light Any gradient, any specular highlight, any ambient occlusion in the corners of the base
Base: neutral grey throughout; colour lives only in the detail layer Any hue at all in the base, including a faint warm or cool cast
Contour closed all the way round, 2 CSS px everywhere except a qualifying narrow projection at 0.5 (ART-158), and untouched by detail Contour that thins to nothing at the top, opens where two forms meet, is overpainted by a detail region, or is thinned on something that is not a projection
Camera exactly level, no perspective A slight three-quarter view — you can see the top plane of the hull
Sits on transparency, nothing behind it A soft drop shadow, a ground line, a circular base, a card, a border
One connected mass A silhouette with three separated pieces "floating" in formation
Four or fewer interior marks, all blunt Panel lines, rivets, tread links, grille hatching
Detail: area regions ≥ 12 cell px, or a projection meeting ART-157's three linear tests — either way, deletable without loss A detail layer carrying the identifying mark; a soft airbrushed shade across the whole body; a seam or panel line claiming to be "linear detail" (ART-159)
Fills the identity core, respects the frame band Subject small and centred with a wide empty margin, or bleeding into the corners
One subject Three infantry figures arranged as a squad

2. The team-colour channel

This is the section that determines whether the asset set works at all.

2.1 Two layers: the tint-mask base and the detail layer

ART-120 Every authored raster asset that is tinted per player — all unit art, all city art, all markers, all overlay glyphs — MUST deliver a tint-mask base consisting of exactly two tone values plus an alpha channel, and nothing else. The two values are:

Name Role in the artwork Substituted at runtime with
INK the closed outer contour (ART-050) and every interior mark (ART-080) the seat's foreground colour (UX-210)
FIELD the body mass enclosed by the contour the seat's background colour (UX-210)

So INK carries the silhouette boundary and the identifying marks; FIELD carries the silhouette's area. Both tones are used, both are load-bearing, and neither is "the background of the image" — outside the silhouette there is no image at all, only alpha. Every opaque pixel of the base is masked. There is no unmasked region in the base, which is what makes the base by itself the strongest available answer to UX-440's Chip-tier outcome test: the whole shape changes colour with the owner.

An asset MAY additionally deliver a detail layer: a straight-alpha RGBA image at the same cell size and the same cell coordinates, carrying authored colour that the renderer never recolours. It composites over the tinted base, and its alpha is therefore the complement of the tint maskdetail.a = 0 means the pixel takes the owner's colour, detail.a = 1 means it renders as authored, and values between are a boundary. The base's alpha, unchanged, remains the silhouette's coverage. The full composite is ART-185.

ART-125 Mask delivery MUST follow one of the two conventions ART-605 of docs/art/02-terrain.md defines, and the gen.json sidecar of ART-420 MUST record which — with, for an explicit-mask asset, the art-direction reason it earned one, because UX-440 makes the tone count a per-asset choice and a choice without a recorded reason is how the rule this replaced got written in the first place.

Convention Delivery for a unit What the packer does
Implicit mask One file, <id>.png — the tint-mask base. Every opaque pixel is masked, no mask file, no companion atlas. This is the whole of the format as it stood, and it remains the default. Normalises [20, 235] → [0, 255] (ART-190) and packs one page.
Explicit mask Two files at identical pixel dimensions: <id>.png, the tint-mask base, plus <id>_detail.png, the detail layer. Packs the detail layer into a companion atlas at identical cell coordinates, so one UV samples both (ART-455).

Units extend ART-605's explicit convention in exactly one respect and MUST NOT extend it in any other: the mask channel is carried in the detail layer's alpha, inverted — opaque detail is ART-605's black, "renders as authored" — rather than in a separate single-channel <name>_mask.png. Everything else is ART-605 unmodified: two files, identical pixel dimensions, a companion atlas at identical cell coordinates, one UV sampling both, masked pixels substituted per UX-440 and unmasked pixels rendering as authored. Both conventions are equally conforming under either renderer path UX-440 permits — the shader of ART-185, or atlases pre-tinted per seat at load time under the Canvas fallback of UX-385 (ART-460).

Rationale for folding the mask into the detail layer's alpha rather than shipping ART-605's third file: for a unit the mask is exactly the complement of detail coverage — a pixel renders as authored if and only if there is authored colour there to render. A separate mask file would be a second copy of information the detail layer already carries, and two copies of one fact can disagree, silently, in a way no check catches until a seat colour appears through a gun barrel. Terrain's assets do not have that property, which is why ART-605 is right to keep the file: its explicit convention has to serve an asset whose masked region is itself painted in real colour that the mask then substitutes away, and there the mask genuinely is independent information. The delivery shape is identical either way; units simply have one fewer channel to ship.

Rationale for over rather than the other serious candidate — one image carrying authored colour throughout, lerped toward the seat tint by a separate mask. Both encode the same thing. over was chosen because it keeps the base a complete, valid, standalone counter underneath whatever the detail layer does, and four things fall out of that which the single-image encoding cannot offer.

  1. Every rule already in this section — the palette, the normalisation, the anti-aliasing budget, the bleed, all seven checks of §2.6 — applies to the base verbatim and unchanged. Adding a detail layer changed no requirement that was already right.
  2. An asset with no detail ships no second file and is byte-identical to what this document specified before. That is what makes "two tones is still the right answer for this sprite" a zero-cost choice rather than a special case — and per UX-440 it will often be the right answer, exactly as docs/art/02-terrain.md ART-615 found for all seventeen of its owner-coloured assets.
  3. The detail atlas is droppable. A target under the texture budget of UX-400 — where the mobile WebView is named as the tightest of the six — can decline to load it and still render a correct, fully-owned, fully-legible unit, because the base was never a hole. Under the lerped encoding there is no base to fall back to.
  4. It makes the production failure mode benign, which matters more than it sounds. The layer split is a hand step at the 512 master (§7.3), and §7.2 is an honest account of a pipeline where takes are imperfect and hand-work is the norm. Under over, a mis-cut detail region leaves a correct two-tone counter showing through. Under the lerped encoding it leaves a patch of arbitrary colour where the artwork should be. An encoding whose mistakes are cosmetic is worth a texture page.

The remaining alternatives were weighed and rejected on the merits. A fourth channel does not exist: the base is already (m, m, m, a), and although G and B are free, authored colour needs three channels and there is then nowhere to put coverage. A second "exclude from tint" mask says which pixels are not tinted without saying what to draw instead, so it still needs authored colour delivered somewhere and collapses into one of the other two options with an extra file. A packed encoding that reserves particular pixel values to mean "not tinted" is precisely the chroma-keying MOD-420 forbids and ART-170 repeats, and it fails silently on a resampled asset — which every asset here is, twice.

ART-130 Terrain art (docs/art/02-terrain.md) is not player-tinted and is exempt from ART-120; ART-600 of that document is the governing requirement and this one defers to it. Terrain is authored in full colour within the value band that document defines. The exemption exists so that units in any two seat colours are legible against every one of the 11 terrain types. The owner-coloured assets that document does own — Fort, Airfield, City, city badges (ART-610) — carry a team-colour channel under this section's contract, and record their delivery convention in their own register.

ART-135 A detail layer MAY be delivered for the Close tier only. The Mid tier MUST ship the tint-mask base alone; Chip and Strategic are drawn at runtime (ART-140, ART-540) and have no layers at all.

Rationale: the Mid cell is 64 px displayed at 16 CSS px, so ART-155 rule 4's 12 cell px minimum area region lands at 1.5 CSS px there — below the size at which that same rule already excludes a region from the Close tier. ART-157's linear detail fares worse still and argues the same way: its 4 cell px of visible width is 0.5 CSS px at Mid, and ART-158's 2 cell px contour is 0.25, which is not a thing a display can draw. Mid art is hand-redrawn rather than downsampled (ART-300) precisely because the Close encoding does not survive the reduction; docs/art/02-terrain.md ART-1145 reaches the same finding for its own mid variants and gives the mechanical reason, which applies here in full. Confining detail to one tier also bounds the entire cost of this feature at one optional atlas page, which is what makes the droppability argument of ART-125 cheap enough to be true.

ART-140 The Chip and Strategic tiers are drawn at runtime, not authored. There MUST be no authored, generated or hand-placed Chip or Strategic raster in the shipped art set: the client constructs both tiers from the roster, the active locale's short labels and the seat's colours, on the canvas, at the size it needs them (ART-540). They use the full foreground / background / text trio of UX-210, and are exempt from ART-120 because nothing is being tinted — the shapes and the label are constructed in the seat's colours directly.

Rationale: both tiers carry short text labels and demand exact placement at 32 px and 16 px, which are the two things generated and hand-placed raster art gets wrong most reliably. Drawing them is also the only way they can be correct by construction for a modded unit set and for a translated label — neither of which a shipped raster can be, and both of which MOD-430 requires. This is ART-540's argument stated as a tier property rather than as a production ban, because the ban is not the point: the point is that the geometry and the label are computed from the roster at the moment they are drawn.

Note, because the two art documents are not yet reconciled on it. docs/art/01-units.md §10 and §11 specify the chip and marker construction — the plate shapes of ART-920, the label table of ART-930, the modifier marks of ART-940 and ART-950, the marker shapes of ART-980 — and those are the drawing code this requirement means. That document currently describes it as a build step emitting an atlas page, and its cell coordinates, atlas geometry and frame-table checks are written to that shape. The construction is the same either way; where it runs is not settled here, and is open question 17 of that document.

ART-145 Sequencing. Chip and Strategic rasters that already exist in art/ MUST remain there, labelled as bootstrap under ART-415, until the drawing code of ART-140 exists and runs. They MUST then be removed in the same change that lands that code — not before it, and not in a later tidy-up.

Rationale: deleting a working asset ahead of its replacement trades a recorded deviation for a hole, and a map that draws nothing at 8 CSS px is a worse state than a map drawing a raster somebody has written down as temporary. Removing them afterwards, as a separate change, is the other failure: for however long that gap lasts there are two sources for one tier and no rule saying which wins, which is exactly the condition under which somebody edits the raster. One change, both halves.

2.2 The authoring palette

ART-150 Every tint-mask base MUST be authored, at every stage, in exactly these two values:

INK    #141414   sRGB (20, 20, 20)    — contour and interior marks
FIELD  #EBEBEB   sRGB (235, 235, 235) — body mass

Any pixel of the base that is opaque and is neither of these two values, and is not an anti-aliasing intermediate between them under §2.4, is a defect. This requirement governs the base only; the detail layer is chromatic by definition and is governed by ART-155.

Rationale, four reasons, because this looks arbitrary and is not:

  1. Neutral grey means any chroma in the base is a bug. Because no legitimate base pixel has any saturation whatsoever, "maximum saturation across the image is zero" stays a one-line automated test that catches every case of the generator's colour leaking through a desaturation step. If the palette had a hue, that test would not exist and colour contamination would have to be found by eye. The test is narrower than it was — it is now a statement about one of two files rather than about every file in the set — but it is not weaker, because the base is still where every ownership guarantee lives, and a chroma leak into it is exactly as fatal as it ever was. What changed is that "the set contains no colour" is no longer a proxy for it; ART-155 and ART-255 say what proves the other file instead.
  2. Stepping in from the ends makes tonal damage self-announcing. Nothing in a correct file is ever at 0 or 255. A histogram spike at either extreme therefore proves that something crushed the blacks or blew out the whites — a levels operation, a JPEG round-trip, an over-eager contrast filter — and you learn it from the histogram instead of from a sprite that looks slightly wrong three weeks later.
  3. Pure #000000 is what an alpha-compositing mistake produces. Un-premultiplying, or compositing straight alpha over an implicit black matte, leaves a black fringe. Reserving 0 as "never a legitimate authored value" makes that specific and very common bug visible instead of invisible.
  4. Pure #FFFFFF is what image generators add. A white card, a white margin, a white vignette. If FIELD were #FFFFFF you could not tell the artwork from the paper the model drew it on.

ART-155 The detail layer is authored in full sRGB colour and is NOT subject to ART-150. It is bound instead by five rules, which together are what replaces "any chroma is a bug" as its gate:

  1. It is never load-bearing. With the detail layer deleted, the sprite MUST still pass every Identity and Geometry box of §8 — silhouette, contour, movement-class footprint, identity core, occlusion test. The feature that distinguishes this unit from every other unit in the roster MUST live in the base (ART-340). Verified by ART-255 check 11.
  2. It stays inside the silhouette and off the ink contour. ART-225.
  3. It is bounded in area, so that the mask remains most of the sprite. ART-255 check 9, which is the ≥ 60 % mask-coverage floor of ART-1135 in docs/art/02-terrain.md, expressed on this encoding.
  4. Every connected detail region MUST clear the floor for its kind. An area region MUST be at least 12 cell px (3 CSS px) in its smaller dimension — the same floor ART-080 sets for an interior shape. A linear region is measured differently and is governed by ART-157, which also fixes which regions are entitled to be measured that way.
  5. Close tier only. ART-135.

Within those, the layer is free, and UX-440 names what it is for: shading, material and mechanical detail. In practice that means track bands, gun barrels and muzzles, canopies and glazing, rotor and propeller discs, a hull's wake, and a shadow the subject casts on itself. A shadow cast onto a ground plane remains forbidden: ART-020 is unchanged, there is no ground plane, and a sprite still sits on transparency with nothing behind it (ART-110).

Rationale for rule 4, which does the most work of the five. An area detail region below 12 cell px is under one CSS pixel at the Close tier and is gone entirely at Mid. It costs a texture page and buys a smudge. This is the same argument ART-080 makes about interior marks, and it is deliberately the same number: panel lines, rivets, tread links and grille slots were not readmitted by UX-440's change. They are still mush at every tier this game draws, and the detail layer is not the door they come back through. ART-157 does not reopen that door either — it is a rule about projections, and it names those surface marks as excluded by prohibition rather than by arithmetic.

Rationale for rule 1, which is the whole safety property, and the reason the detail layer needs no palette of its own. Seat colours are chosen by the player and may be re-mapped mid-game (UX-210, UX-1310), so no author can guarantee that a given detail colour contrasts with a given seat's background — a brown track band on a brown seat is invisible, and there is no palette rule that prevents it, because the palette is not ours to set. The only defence that actually works is to put nothing in the detail layer that the player needs. That is also what makes the layer honestly optional, which is what licenses dropping the whole detail atlas under memory pressure (ART-125) and what lets the Mid tier ignore it (ART-135). A rule that looks like a restriction on art is doing three structural jobs at once.

ART-157 A detail region is either area detail or linear detail, and the two are measured differently. Every figure below is cell px (§0.2); the master equivalents are given because this is a rule an author applies at 512.

Area detail — a region whose legibility is carried by the size of its face: a hatch, a canopy, a panel of material, a shaded plate. It is governed by ART-155 rule 4 unchanged: at least 12 cell px (48 master px) in its smaller dimension.

Linear detail — a region whose legibility is carried by its length, and whose thinness is the point rather than a compromise. A region qualifies as linear detail only where all three of these hold:

Test Floor Master Measured on
Visible width 4 cell px 16 master px the detail region itself — the authored colour left between the contours, not the projection's outside thickness
Length 24 cell px 96 master px the region's long axis
Aspect ratio 4 : 1 length : visible width

A region that fails any one of the three is not linear detail and takes ART-155 rule 4's 12 cell px floor like anything else. There is no partial qualification and no rounding: 23 cell px of length is a fail.

ART-158 The narrow-linear-projection exception to the contour. A projection — a part of the silhouette that reaches out from the mass with void on the far side of its contour — that qualifies as linear detail under ART-157 MAY carry a 2 cell px (8 master px) ink contour on each exposed side in place of ART-050's 8, subject to a minimum total silhouette thickness of 8 cell px (32 master px) for the projection. At the floor:

2 px contour  +  4 px minimum visible detail  +  2 px contour  =  8 cell px total thickness

The 8 cell px total is ART-040's silhouette minimum, not a new number. ART-040 already forbids any part of a silhouette thinner than 8 cell px, and this exception is bounded by exactly that figure: a qualifying projection meets the general rule rather than carving underneath it. That is what makes the exception defensible and what bounds it — an author who needs a 6 cell px projection is not asking for this exception, they are asking for a hole in ART-040, and that is a different argument made in a different place.

Three things this exception does not do:

  1. It does not break the contour. ART-050's "closed and unbroken" is untouched. The band is narrower along the projection's exposed sides and returns to 8 cell px where the projection meets the mass; it is never absent anywhere.
  2. It does not relax ART-155 rule 1. The base MUST remain a complete and identifiable counter with the detail layer deleted — ART-255 check 11, and the existing test for whether a detail layer is legitimate at all. A projection that only reads as itself once its authored colour is present has not earned a thinner contour; it has failed the prior question. Delete the layer, and a tube must still be a tube.
  3. It does not change the coverage or containment rules. ART-155 rule 3 and ART-225 apply unchanged; the detail region still stops at the contour, whatever the contour's width there.

ART-159 The exception is not available to surface marking. A decorative line, a panel seam, a panel line, a rivet, a tread link, a hatch outline, a grille slot, a bore line drawn as a mark on a surface, or any similar marking MUST NOT be authored as linear detail and MUST NOT take ART-158's narrower contour, whatever its measured width, length and aspect ratio. Those features remain governed by ART-080 and ART-155 rule 4 exactly as before. ART-158 reaches a projection and nothing else.

Rationale, and this is stated as a prohibition rather than as guidance on purpose. ART-080 and ART-155 rule 4 have held one line since the first draft of this document: fine surface detail is mush at every tier this game draws, it costs a texture page and buys a smudge, and UX-440's change did not readmit it. ART-157 hands an author a 4 cell px minimum width. If that width were available to anything that could be measured at 4 × 24, then every rivet run, every panel seam and every tread line in the roster is drawable as "linear detail", the four-shape cap stops being enforceable, and the silhouette discipline this entire style rests on is gone in one revision — not by anybody deciding to abandon it, but by an exception quietly generalising. The width relaxation buys exactly one thing: a projection thin enough to be a gun tube. It buys nothing else, and the boundary is drawn by what the feature is rather than by what it measures, because a measurement can always be arranged.

Rationale for the categories themselves. ART-155 rule 4's 12 cell px was written for an area feature, where the smaller dimension is the thing a reader has to resolve and 12 cell px is honestly the floor of resolvability. A linear feature is a different object: its thinness is constitutive — a gun tube that is not thin is not a gun tube — and what makes it legible is length and straightness, which the eye reads well before it resolves width. Measured against the area test, a tube contoured on both sides needs 8 + 12 + 8 = 28 cell px of thickness before it may carry any detail at all, which is 7 CSS px on a subject 70–78 cell px wide and is not a barrel anyone would draw. That result was not a strict rule doing its job; it was the wrong quantity being measured, and it returned "forbidden" for every instance of the class — all four detail layers in the roster. See D-7 of docs/art/04-decisions.md for the full record.

ART-158 and the alpha-bleed rule of §2.5: the rule is unaffected, and it is worth answering here because every reader of that exception will wonder. ART-230 floods the base's transparent RGB with INK and §2.5 calls that exactly correct rather than a tolerable approximation. The claim rests on ART-050: because the contour is closed, every pixel at the silhouette boundary is known to be ink, so continuing ink outward continues the artwork instead of guessing at it. A 2 cell px contour is still ink. The boundary pixel along a qualifying projection is an ink pixel exactly as it was, the guarantee is about the value at the boundary rather than the width of the band behind it, and nothing in ART-230, ART-310's extrusion ring or ART-330's alpha-weighted mipmaps changes. What would have broken the flood is a contour that was absent somewhere, and ART-158 does not permit that anywhere.

A known risk in ART-158, recorded rather than solved. A 2 cell px contour is 0.5 CSS px at the Close tier — the 128 cell px cell is displayed at 32 CSS px — so on the wire it is half a device pixel at DPR 1, one at DPR 2, and one and a half at DPR 3. At DPR 1 the dark edge may alias away entirely, leaving the projection's authored colour floating on the terrain with nothing separating it, which is precisely the failure ART-050 exists to prevent, arriving at the one place the contour was thinned. This is not claimed to be solved. It is checked, by name, in §8's Set boxes: render a qualifying projection at DPR 1, 2 and 3, over the lightest and the darkest terrain, and confirm the contour survives at each. Do that on the first qualifying sprite. If it fails at DPR 1, the honest responses are to thicken the projection until its contour can be wider, or to accept that target rendering the tube untinted — which ART-125 already permits and already has a clean fallback for.

ART-160 The chroma-key background used during generation MUST be magenta #FF00FF, and MUST be flat, uniform and edge-to-edge.

Rationale: it is safe precisely because of ART-150 and of what §7.3 does with the keyed image. Legitimate base art is neutral by definition, so the key colour is maximally distant from every legitimate base pixel in every direction, and a key with a generous tolerance cannot eat any part of the subject. The detail layer is cut from the same keyed image after the key is gone (§7.3 step 4b), so it inherits that alpha rather than needing a key of its own — but it is chromatic, so a detail region deliberately authored near magenta would be at risk if the key were ever re-run on it. It MUST NOT be: the key runs exactly once, at generation resolution, before the split.

ART-170 Chroma-keying is a generation-time alpha-extraction aid only. It MUST NOT survive into any delivered asset, and no delivered asset may reserve any pixel value as meaning "player colour". MOD-420 forbids that runtime technique explicitly and is right to: it makes the reserved values unusable in ordinary art and fails silently on a resampled asset. The runtime mechanism is the mask channel and companion layer declared in the atlas metadata (ART-450), which is a different thing — and the reason it is a different thing is that no value is reserved anywhere: the base's whole RGB plane is a mask, and the detail layer's exclusion lives in its alpha. Both survive resampling with a defined meaning, which is exactly what a reserved sentinel value does not.

2.3 Which tone becomes which seat colour, and why there is no inversion step

ART-180 The delivered mask value m is the base's authored luminance unchanged and un-inverted, normalised so that INK maps to 0.0 and FIELD maps to 1.0. The tint MUST be computed:

vec4 b      = texture2D(uBase, vUv);                     // b.rgb = m, b.a = coverage
vec3 tinted = mix(seatForeground, seatBackground, b.r);  // m = b.r

Rationale: there is a design choice hidden here and it is worth naming. The obvious encoding is "mask = 1 means foreground", which reads well in the shader and requires the build to invert the authored image — dark ink becomes a bright mask. That inversion is a step someone forgets, and when they forget it every seat's two colours swap, which looks deliberate. Defining m as the luminance itself means the delivered file is pixel-identical in appearance to the file the artist approved, a reviewer can open it in any image viewer and see the artwork, and there is no inversion anywhere in the pipeline to get wrong. The cost is a mix(fg, bg, m) that reads backwards for about five seconds. Adding a detail layer changed none of this: the argument is about the base's encoding, the base's encoding is untouched, and the same un-inverted convention is what lets the tint step of §2.6 and ART-255 be written as -level-colors "$fg","$bg" with no negate.

ART-185 The renderer MUST composite the two layers as over with straight alpha, and MUST take the output alpha from the base, never from the detail layer:

vec4 b = texture2D(uBase, vUv);                          // b.r = m (ART-180), b.a = coverage
vec4 d = uHasDetail ? texture2D(uDetail, vUv) : vec4(0.0);

vec3 tinted = mix(seatForeground, seatBackground, b.r);
vec3 rgb    = mix(tinted, d.rgb, d.a);                   // over(); d.a is the mask complement

gl_FragColor = vec4(rgb, b.a) * tintAlpha;

uHasDetail MUST be driven by the atlas's declared frame list (ART-455), and a frame that declares no detail MUST NOT sample the detail atlas. Where the detail atlas is not loaded at all — which ART-125 permits under memory pressure — uHasDetail is false for every frame and the pass reduces exactly to ART-180, with no other change and no visual defect.

Rationale for taking the output alpha from the base: the detail layer's alpha is a mask complement, not coverage, and confusing the two would be catastrophic in a way that is easy to miss. d.a = 1 in the middle of a hull means "this pixel is authored colour", not "this pixel is more opaque". If the detail alpha reached gl_FragColor.a, a unit would grow a solid patch wherever it had a gun barrel and a hole wherever it did not — and it would look plausible on the one sprite you tested. ART-225 states the corresponding authoring rule that keeps the two consistent, and ART-255 check 10 measures it.

ART-190 The build MUST apply exactly one value transform to the base's authored tones: a linear normalisation of the input range [20, 235] to the output range [0, 255], so the runtime endpoints are mathematically exact seat foreground and seat background. This is the only permitted value transform between master/ and src/. In ImageMagick terms it is -level 7.84%,92.16%. The detail layer MUST receive no value transform at all: its RGB is a colour, not a mix factor, and normalising it would silently shift every material in the set.

2.4 Anti-aliasing: why a boundary is fine and a third colour is not

ART-200 In the base, anti-aliased intermediate values between INK and FIELD are permitted along the boundary where the two tones meet. Values outside the closed range [INK, FIELD] are forbidden. A plateau of intermediate values — a region, rather than a boundary, holding a value between the two tones — is forbidden.

The reason is mechanical, not aesthetic. The base has exactly two independent channels: m, the mix factor between the seat's two colours, and a, coverage. An anti-aliased pixel halfway between INK and FIELD has an exact and correct meaning — it is half of the seat's foreground and half of its background, which is precisely what a half-covered boundary pixel should be. A third colour has no representation in the base: there is nowhere in (m, a) to put "and also this other colour", so the shader will render it as some mixture of the two seat colours, chosen by an accident of its luminance, and the result will be different for every seat.

That argument is unchanged by the detail layer and it is why ART-150 and every check in §2.6 survive untouched. What the detail layer changes is only this: a third colour now has a representation somewhere, and putting it there is a deliberate act with its own rules (ART-155, ART-225) rather than a tone that leaked into the base. A third tone in the base is still a defect, and it is still the single most common one this pipeline produces.

The detail layer's own anti-aliasing is an ordinary RGBA edge and needs no equivalent rule: it composites with over (ART-185), where a partially opaque pixel means "partly authored colour, partly seat colour", which is exactly what a boundary pixel between a masked and an unmasked region should be.

This is also why linear texture filtering and alpha-weighted mipmaps are correct for these textures (ART-330) rather than merely tolerable: a filtered mask value is a valid mask value.

ART-210 Anti-aliasing is a perimeter phenomenon, and its pixel count MUST scale with the silhouette's perimeter, not its area. For a correct Close-tier cell, intermediate values MUST account for no more than 12% of opaque pixels, and no single intermediate histogram bin may hold more than 0.5% of opaque pixels.

2.5 Alpha, and the bleed rule

ART-220 The base's alpha MUST be straight (non-premultiplied), 8-bit, and MUST carry silhouette coverage only. Alpha MUST NOT be used to fake a tone: a 50%-alpha INK pixel over terrain is not a mid grey, it is a hole in the unit.

ART-225 The detail layer's alpha is a mask complement, not coverage (ART-185), and MUST obey all three of:

  1. It MUST NOT extend the silhouette. detail.a > 0 is permitted only where base.a = 255. A detail pixel outside the base's opaque region is invisible under ART-185 and is a defect, because it means the artist drew something the renderer will never show and believed it was there.
  2. It MUST NOT overpaint the closed ink contour of ART-050. The contour band MUST be detail.a = 0 all the way round, at whatever weight applies to that stretch of boundary — 8 cell px (32 master px) in general, and 2 cell px (8 master px) along a narrow linear projection qualifying under ART-158. The rule is "the detail layer stops at the contour", not "the detail layer stops 8 cell px inside the silhouette", and the difference now matters: on a projection at ART-158's floor the two readings differ by the whole of the detail region.
  3. It MUST be straight (non-premultiplied), 8-bit, like the base (ART-330).

Rationale for rule 2, which is the load-bearing one. ART-050's contour exists because a seat's background colour can land arbitrarily close to the terrain beneath it, so the band of the seat's other colour around the whole silhouette is the only thing guaranteeing that the unit separates from the map. A detail region allowed onto the contour would replace that band with a fixed authored colour that no longer relates to either seat colour, and would break the guarantee in exactly the case it was built for. Keeping detail off the contour also discharges, by construction, the clause of docs/art/02-terrain.md ART-1135 requiring an explicit-mask asset's mask to reach the outer boundary of the silhouette on all four sides: there is no way to author a conforming unit that fails it.

Rationale for rule 1: the silhouette is the base's business entirely, and keeping it there is what holds the bleed of ART-230, the extrusion ring of ART-310 and the alpha-weighted mipmaps of ART-330 to a single layer each. A wake, a rotor disc, or any other feature that changes the outline is a change to the base's shape — the base carries it, with its contour, under ART-040 and ART-050 — and the detail layer only colours it. That ordering is not a formality: it is what keeps ART-040's "one connected mass" and ART-050's closed contour enforceable on one file.

ART-230 Outside the silhouette, where the base's alpha is zero or near-zero, the base's RGB channels MUST be flooded with INK, not left black, not left white, and not left as whatever the key-removal step happened to leave behind. Delivered assets MUST have their mask values bled outward by at least 4 pixels past the last opaque pixel. The detail layer MUST be bled the same distance past its last a > 0 pixel, but with its own nearest authored colour — not with INK, which is a mask value and means nothing in a layer whose RGB is a colour.

Rationale: RGB under transparent pixels is not "don't care". Bilinear filtering, mipmap generation and atlas packing all read it, and a wrong value there produces a visible fringe — the classic dark halo, or in this pipeline a much stranger artefact where the halo takes one seat colour instead of the other. Flooding with INK is not a compromise here, it is exactly correct, and it is exactly correct as a direct consequence of ART-050: because the outer contour is always ink, every pixel at the silhouette boundary is ink, so continuing ink outward is continuing the artwork rather than guessing at it. This is a case where a rule made for legibility reasons paid for itself somewhere else entirely.

And it survives ART-158 intact, which is the first question anyone asks on reading that exception. A 2 cell px contour is still ink; the premise this flood rests on is that the boundary pixel is ink, not that the band behind it is any particular width. A contour that was permitted to be absent somewhere would break the flood, and ART-158 permits no such thing. ART-158 answers this at length.

2.6 Verification: proving the base really is two-tone

ART-240 No asset may enter art/master/ or art/src/ until its tint-mask base has passed all seven checks below, and — where it ships one — its detail layer has passed the four of ART-255. The normative gate is a committed CLI, art-verify, which MUST implement checks 1 to 7 here and the mechanical part of checks 8 to 10 in ART-255, MUST name the check that failed and the pixel coordinates that failed it, and MUST be runnable offline over a directory. Check 11, and the human confirmation inside check 8, are not mechanisable and are read by a person against §8. The one-liners here are what you run while you are working; the CLI is what the packer runs before it will pack anything (ART-550).

Checks 1 to 7 run on <id>.png and on nothing else. They are unchanged from the version of this document that mandated two-tone art everywhere, because the base is still exactly what they were written to prove.

What a correct histogram looks like

Two spikes and nothing else worth mentioning. Concretely, over the opaque pixels of a correct Close-tier cell:

count
  |                                                                    ####
  |    ####                                                            ####
  |    ####                                                            ####
  |    ####                                                            ####
  |    ####  .                                                    .    ####
  |    ####  ..                                                  ..    ####
  +----+----+---------------------------------------------------+----+-----> value
  0   20   40                                                  215  235   255
       ^INK                                                          ^FIELD
       |                    <- thin, low, smooth ramp ->              |
       |                       AA only: <=12% of pixels,              |
       |                       no bin above 0.5%                      |
    nothing at 0                                              nothing at 255

Two things to read off it. First, the two spikes hold at least 88% of opaque pixels between them. Second, there is no third spike and no plateau — the ramp between them is low and smooth, with no step, shelf, or bump. A shelf in the middle is the signature failure of this entire pipeline and it means the generator gave you a shaded illustration that a desaturation step turned into a three-value image.

The checks

# Check How Pass
1 Neutral — no hue anywhere image differenced against its own greyscale maximum difference is 0
2 No clipping — nothing at the extremes histogram zero pixels at 0 and at 255
3 Two peaks — the tones are where they should be histogram peaks within ±3 of 20 and of 235
4 No third tone — no plateau histogram no bin in (24, 231) above 0.5% of opaque pixels
5 AA is a boundary histogram opaque pixels in (24, 231) are ≤12% of opaque pixels
6 Alpha is binary-ish alpha histogram ≥85% of pixels are alpha 0 or alpha 255
7 Bleed sample RGB 4 px outside the silhouette equals INK
# 1. Neutral: differencing the image against its own desaturation must give exactly zero.
magick sprite.png -alpha off \( +clone -colorspace Gray \) \
       -compose difference -composite -format "%[fx:maxima*255]" info:
# expect: 0

# 2, 3, 4, 5. Tone histogram. Sorted descending, the top two lines are your two spikes.
magick sprite.png -alpha off -colorspace Gray -format "%c" histogram:info:- \
  | sort -rn | head -12
# expect: two dominant bins at gray(20) and gray(235), then a long tail of
#         small counts. A third line with a large count is the failure.

# 6. Alpha distribution.
magick sprite.png -alpha extract -format "%c" histogram:info:- | sort -rn | head -6
# expect: two dominant bins at 0 and 255.

# How many distinct values exist at all — DIAGNOSTIC ONLY, see the warning below.
magick sprite.png -alpha off -format "%k" info:

ART-250 A unique-colour count MUST NOT be used as a pass/fail two-tone test.

Rationale: it is the test everyone reaches for first and it is wrong in both directions. Correct anti-aliased art at the 512 px master has somewhere between 60 and 250 distinct values and passes nothing. A file that returns exactly 2 has no anti-aliasing at all, which means either it has not been through the downsample of §7.3 or somebody "fixed" a delivered file by posterizing it, which produces the jagged edges that make the whole set look cheap. The count is useful as a smell — a delivered 128 px cell returning 800 values is definitely wrong — and useless as a gate.

This is worth reading beside docs/art/02-terrain.md ART-1140, which does gate on a colour count — exactly three values — and is right to, because its assets are hand-drawn at target size with no anti-aliasing and are never resampled (ART-1145). Ours are downsampled from a 512 master and are sampled by a continuously zooming camera under linear filtering (ART-330), so anti-aliasing is mandatory here and a count gate would reject every correct file. Both documents are enforcing the same property on assets with different resampling stories; ART-1140 is scoped to that document's register and does not reach ours.

The detail layer's checks

ART-255 An asset that ships a detail layer MUST additionally pass all four of these. Checks 8 and 11 are the requirements — the ownership outcome UX-440 states, and the "delete it and nothing is lost" property everything else in this document leans on. Checks 9 and 10 are cheap mechanical screens for the two ways those usually fail.

# Check How Pass
8 Ownership readdocs/art/02-terrain.md ART-1135 substitute two seat pairs into the composite, reduce to 8 CSS px, composite over a Chip-tier terrain colour, measure the difference between the owners RMSE > 0.25, then confirmed by a person
9 Coverage — the mask is still most of the sprite alpha-weighted mean of the detail layer over the base's opaque pixels ≤ 40 %, i.e. mask coverage ≥ 60 % (ART-1135)
10 Containment — detail inside the silhouette, off the contour detail alpha where base alpha < 255, and over the contour band at the weight that applies to that stretch of boundary — 32 master px in general, 8 master px along an ART-158 projection zero in both (ART-225)
11 Non-load-bearing — the base alone is a complete counter delete the detail layer, re-run §8's Identity and Geometry boxes all pass (ART-155 rule 1)
K=armour
B=art/src/close/$K.png
D=art/src/close/${K}_detail.png

# 8. Ownership read. Substitute a seat pair into the base (m=0 -> foreground, m=1 ->
#    background, ART-180, so -level-colors takes fg first and needs no negate), draw
#    the detail layer over it, and reduce to the Chip tier: 8 CSS px.
tint () {   # $1 base  $2 detail  $3 foreground  $4 background  $5 out.png
  magick "$1" \
    \( +clone -alpha off -colorspace Gray -level-colors "$3","$4" \) \
    -compose CopyRGB -composite \
    "$2" -compose over -composite \
    -filter Lanczos -resize 8x8 "$5"
}
tint "$B" "$D" "#B3372E" "#F2E4CE" /tmp/chip_a.png     # substitute real seat pairs
tint "$B" "$D" "#2E5FB3" "#E8EDF2" /tmp/chip_b.png

magick compare -metric RMSE \
  \( -size 8x8 xc:"#7E9C5E" /tmp/chip_a.png -composite \) \
  \( -size 8x8 xc:"#7E9C5E" /tmp/chip_b.png -composite \) null:
# expect: > 0.25. Then do what ART-1135 actually requires: print the sprite at
# 8 CSS px in four seat pairs, shuffled, and ask a reader who has never seen the
# sheet which piece belongs to which player. The number is a screen; that is the test.

# 9. Coverage: mean detail alpha over the base's opaque area.
magick "$D" -alpha extract \( "$B" -alpha extract \) \
       -compose multiply -composite \
       -format "detail covers %[fx:round(100*mean)]%%\n" info:
# expect: <= 40

# 10. Containment: any detail alpha outside the base's opaque area is a defect.
magick "$D" -alpha extract \( "$B" -alpha extract -negate \) \
       -compose multiply -composite -format "%[fx:round(255*maxima)]\n" info:
# expect: 0

# 11. Non-load-bearing: this is the base on its own. Look at it. It must be a
#     finished sprite, not a sprite with its material missing.
magick "$B" -filter Lanczos -resize 32x32 /tmp/base_only.png

Rationale for citing ART-1135 rather than writing a second read test: it is the same requirement — the mechanical form of UX-440's outcome — it already has a number, a method and a human confirmation step, and two documents specifying one measurement differently is how a project acquires two answers to one question. The only thing this document adds is which image a unit's test runs on: the composite, base tinted with the detail layer over it, not the base alone. A detail layer can only ever reduce the difference between two owners, so measuring the base would measure the wrong image and would always pass. That is the whole failure mode ART-1135 exists to catch, and running it on the pre-composite image would reintroduce it.

Rationale for check 11 being a human check with no number: it is the requirement ART-155 rule 1 states, and no metric expresses "still reads as this unit". It is also nearly free — you already have the base as a separate file, which is the practical dividend of choosing over in ART-125. Under an encoding where the two were one image there would be nothing to look at.

Note on implementing check 10 now that ART-158 exists, because the obvious implementation is wrong — and it is now implemented. The cheap way to find the contour band is to erode the silhouette by the contour weight and call the difference the band. That is correct only while the weight is one number for the whole silhouette, and it stops being correct the moment a cell carries a qualifying projection: eroding by 8 cell px consumes an 8-cell-px-thick projection entirely, so the whole tube is read as band and its legitimate detail region is reported as detail on the contour.

art-verify therefore derives the band from the boundary — an L1 distance transform, so every opaque pixel knows how far it is from the outside — and measures each stretch at the weight that applies there: 8 cell px in general, 2 cell px inside a projection declared under ART-585 of docs/art/01-units.md and validated against ART-445's geometry. The 97% ink tolerance is applied to each required band separately rather than to the result of one erosion, and the 2 cell px contour is now checked to be present, which nothing checked before. docs/art/01-units.md ART-590 carries the same algorithm on the roster side.

The four unit cells this note used to say were unchecked are not, and never were, failing. Measured against the shipped bundle they pass at 100.0% ink, because their gun tubes are drawn as interior markings rather than as projections — the detail regions sit 8–20 cell px inside the silhouette, nowhere near a contour. No cell in the currently delivered bundle exercises ART-158 — the normative geometry requires all four to, once they are redrawn. That leaves a live question about the artwork rather than the checker, and open question 4 of docs/art/04-decisions.md carries it.

Note for this project's Windows machines

convert on Windows is the operating system's disk-format tool, not ImageMagick, and running it with ImageMagick arguments is a bad afternoon. Always invoke magick. ImageMagick 7 is not currently installed on the development machine; install it, or use the Node implementation of art-verify (ART-240), which is the normative gate anyway.

2.7 Fixing an image that fails

ART-260 The correct repair for a three-tone or shaded base is a hard threshold at the master resolution followed by a filtered downsample, never a posterize at the delivered resolution.

Note what this does not mean any more. Thresholding is how you produce the base; it is no longer how you dispose of the shading. Where a take's shading is genuinely worth keeping — a track band, a canopy, a barrel — the threshold still runs, and the shading is lifted into the detail layer from the pre-threshold master instead (§7.3 step 4b, ART-155). The threshold and the split read the same file and neither destroys the other.

# Threshold to exactly two values at 512, then let the downsample to 128
# regenerate correct anti-aliasing between exactly those two values.
magick master-512.png \
  -channel RGB -colorspace Gray -threshold 55% \
  -level-colors "#141414","#EBEBEB" +channel \
  -background "#141414" -alpha background \
  -filter Lanczos -resize 128x128 \
  -strip  src-128.png

Three things in that command are not optional:

ART-270 If a thresholded image no longer reads as the subject — the threshold cut along a shading boundary rather than along the form — the image MUST be regenerated, not repaired. A silhouette recovered from bad shading is a silhouette shaped by where the model put its light source.


3. Canvas, grid and resolution

3.1 Deriving the master and delivered sizes

The Close tier draws a tile at 32 CSS px (UX-390). Everything follows from that number and from four pressures on it:

Pressure Demand
Device pixel ratio up to 3 (phones, high-DPI laptops), which the client must re-render for (UX-385) 32 × 3 = 96 device px at zoom 1.0
Close tier is defined as ≥ 1.0×, so the camera can zoom in past 1.0 while still on this atlas headroom above 96
Mipmap quality — the tier atlases want to be exact halvings of each other a power of two
DOM icons: the same art appears in the Dispatch, production panels and unit tables at ~24 px (MOD-420) a size that reduces cleanly to 24

ART-280 The master resolution MUST be 512 × 512 per sprite, one sprite per file. The delivered Close-tier cell MUST be 128 × 128.

A 512 px master MUST carry information the 128 px delivered cell does not. It is therefore produced in exactly one of two ways:

  1. Re-rendered at 512 px from the source that constructed it — the script, the vector or the geometry that drew the sprite. This is a real master: it has true curves, and a later edit made at 512 reduces cleanly.
  2. Kept from generation, where the sprite came from an image model — the accepted 1024 px take, reduced to 512 and hand-cleaned during cleanup (ART-290, §7.3 step 3).

A master MUST NOT be produced by upscaling a 128 px raster. Where neither route is available — the construction is not reproducible and no larger original survives — the 128 px file is the artwork of record, art/master/ legitimately has no entry for that sprite, and art/README.md MUST record the deviation and name that sprite. The deviation covers only what already exists: every sprite made from now on still owes a master under this requirement, by route 1 or route 2.

Rationale, because "just upscale it, a master is required" is the obvious shortcut and it is the worst available outcome. An upscaled master is worse than an absent one. It has the size, the name and the location of an artwork of record while containing not one pixel of information the delivered cell did not already have, so every downstream decision that assumes headroom — a hand edit at 512, a higher-DPI tier, a print asset, a re-cut after a geometry change — silently gets a blurred 128 px image instead, and gets it without a single warning anywhere in the pipeline. The absence of a master is a fact somebody can act on: they can re-render it, or regenerate the sprite, or decide the 128 px file is good enough and write that down. A fake master is a fact that lies, and it lies in the one direction nobody checks. Naming the sprite in art/README.md is what turns the absence into that actionable fact rather than a hole somebody discovers.

Rationale for 128: it is the next power of two above the 96 device pixels that a DPR-3 phone demands, which means at DPR 3 the camera can zoom to 1.33× and at DPR 2 to 2.0× before the sprite is being magnified past its own pixel density — comfortably covering the zoom range inside the Close tier. It is exactly 4× the 32 CSS px logical tile, so every lower tier is an exact halving and the mipmap chain is clean. And it reduces to the 24 px DOM icon with plenty of information left.

Rationale for a 512 master rather than authoring at 128: two reasons. Generators produce mush below about 512 — asking for a 128 px image gets you a blurry upscale of nothing, because the model has no 128 px training distribution to draw on. And the two-tone reduction of §2.7 depends on thresholding at high resolution and downsampling, which is what produces correct anti-aliasing; there is no room to do that at the delivered size.

ART-290 Generation MUST be requested at 1024 × 1024 square and reduced to the 512 master during cleanup. Requesting the master size directly wastes the model's best resolution band.

3.2 The per-tier cell table

ART-300 Cell geometry per LOD tier (UX-390):

Tier Tile (CSS px) Cell (px) Extrude Gutter Slot stride Detail layer Source
Close 32 128 2 2 136 permitted (ART-135) downsampled from the 512 master
Mid 16 64 2 2 72 none separately authored simplification (see below)
Chip 8 32 2 2 40 none drawn at runtime, no authored file (ART-140)
Strategic 4 16 2 2 24 none drawn at runtime, no authored file (ART-140)

The Mid-tier cell is not a naive downscale of the Close cell. UX-390 calls for "simplified sprites" at Mid, and a 64 px cell holds roughly one CSS-pixel of ink per stroke: the correct Mid asset drops interior marks down to the one that identifies the unit, thickens the contour to hold at half size, and simplifies the outline. The workflow is: downscale the master to 64, then hand-simplify in a pixel editor. Per-unit Mid simplifications are specified in docs/art/01-units.md.

3.3 Slot layout inside the atlas

ART-310 Each cell occupies a square slot of cell + 8 pixels. Within a slot, from the outside in: a 2 px fully transparent gutter, then a 2 px extrusion ring carrying a copy of the art box's outermost pixel row/column, then the art box itself.

slot origin = (col * stride, row * stride)

  +-------------------------------------------+   <- slot, stride x stride
  |  gutter, 2 px, alpha 0, mask flooded      |
  |  +-------------------------------------+  |
  |  |  extrusion ring, 2 px               |  |
  |  |  +-------------------------------+  |  |
  |  |  |                               |  |  |
  |  |  |          ART BOX              |  |  |   frame.x = col*stride + 4
  |  |  |        cell x cell            |  |  |   frame.y = row*stride + 4
  |  |  |                               |  |  |   frame.w = frame.h = cell
  |  |  +-------------------------------+  |  |
  |  +-------------------------------------+  |
  +-------------------------------------------+

Rationale: the extrusion ring is what prevents the seam artefacts that appear when a texture atlas is sampled with linear filtering at a non-integer scale — the sampler reaches half a texel past the frame edge and picks up the neighbour. Terrain tiles need it absolutely, because they are sampled right to their edge. Unit sprites mostly do not, because their edges are already transparent — but emitting the ring for every asset means one packer rule instead of two, and it costs 8 px per slot.

ART-320 Atlas pages MUST be power-of-two square (MOD-505) and MUST NOT exceed 2048 × 2048. When a tier's assets do not fit, the packer MUST spill onto an additional page and MUST NOT downscale art to fit (MOD-505). Capacity at 2048:

Tier Slot Cells per 2048² page
Close 136 225
Mid 72 784
Chip 40 2 601
Strategic 24 7 225

Rationale for 2048 rather than 4096: 2048 is the maximum texture dimension guaranteed by the oldest mobile GPUs the client targets, and the mobile WebView case is described in UX-400 as the tightest of the six targets. 225 Close-tier cells covers the 26 units of the stock roster across their presentation states (MOD-430) with room left, so the constraint costs nothing.

ART-330 Textures MUST be uploaded with linear minification and magnification filtering and with alpha-weighted mipmaps — each mipmap texel's mask value is the average of its children weighted by their alpha. Nearest-neighbour filtering MUST NOT be used; the camera zooms continuously and nearest sampling produces crawling edges at non-integer scale. Premultiplied alpha MUST be disabled on upload (Phaser's render.premultipliedAlpha), because premultiplying destroys the mask value in partially-transparent pixels. All of this applies identically to a companion detail page (ART-455): it is sampled by the same UV at the same scale, so any difference in filter or mipmap policy between the two pages would show up as the detail sliding against the art it belongs to.


4. Overlay reservations

The client draws a condition badge (UX-1125), a Stance glyph, a Posture glyph, an encirclement mark (UX-1135) and an out-of-supply mark on top of unit sprites, and UX-430 fixes their priority when they collide: encircled, out of supply, condition, Stance, Posture. Art that does not leave room for them produces a map where the marks are unreadable and the units are unreadable.

ART-340 The Close-tier art box is divided as follows. All coordinates are in the 128 px art box, origin top-left. Lower tiers use the same proportions.

     0        8            44                    84          120  128
   0 +-----------------------------------------------------------+
     |                     F R A M E   B A N D                   |
   8 |   +--------+                              +--------+      |
     |   |  COND  |                              | STANCE |      |
     |   |  BADGE |                              | GLYPH  |      |
     |   | 36x36  |                              | 36x36  |      |
  44 |   +--------+     +--------------------+   +--------+      |
     |                  |                    |                   |
     |                  |   IDENTITY CORE    |                   |
     |                  |      64 x 64       |                   |
     |                  |                    |                   |
  84 |   +--------+     +--------------------+   +--------+      |
     |   | SUPPLY |                              |POSTURE |      |
     |   |  MARK  |                              | GLYPH  |      |
     |   | 36x36  |                              | 36x36  |      |
 120 |   +--------+                              +--------+      |
     |                     F R A M E   B A N D                   |
 128 +-----------------------------------------------------------+

     ground baseline .......... y = 104   (lowest ink pixel, foot/wheeled/tracked)
     naval waterline .......... y = 100   (hull cut-off)
     tallest permitted body ... y =  24
     ground unit max width .... 88 px, centred on x = 64
     naval unit max width ..... 112 px (may use the full live box)
Region Extent Rule for art
Frame band outer 8 px on all four sides Art MUST NOT enter it at all. Reserved for the encirclement bracket (UX-1135) and the anchor of the out-of-supply hatch — the two highest-priority marks, and the only two that are full-cell.
Live box (8,8)(120,120), 112 × 112 Art lives here.
Corner reservations four 36 × 36 squares at the corners of the live box Art MAY extend into them but MUST NOT place anything load-bearing there.
Identity core (32,32)(96,96), 64 × 64 The feature that distinguishes this unit from every other unit in the roster MUST fall inside this box.

ART-350 Corner assignment: top-left condition badge (UX-1125); top-right Stance glyph; bottom-right Posture glyph; bottom-left supply grade, and the secondary marks of UX-430 that the client chooses to surface on the map.

Rationale for putting condition top-left: it is the highest-priority of the three per-unit badges in UX-430's ordering, and left-to-right, top-to-bottom reading order puts it first for the majority of players. Stance and Posture pair on the right because they are the two order-state glyphs and reading them as a vertical pair is faster than hunting diagonally.

ART-360 Every sprite MUST pass the occlusion test: with all four corner reservations filled with opaque blocks and the frame band overdrawn, the unit MUST still be identifiable. The test MUST be run on the tint-mask base alone, with any detail layer deleted (ART-155 rule 1). A sprite that fails is one whose identity is living in the corners, and it will be unreadable on the only units the player cares about — the ones with something wrong with them, which is exactly when all four badges are lit.

Rationale: this is the rule that most often gets broken, because a sprite that fails it looks perfectly good in isolation. Naval units are the usual offender: the raked prow at the right is often the most identifying feature, and it sits under the Stance glyph.


5. File format, naming and layout

5.1 Format

ART-370 Delivered assets MUST be PNG, 8 bits per channel, RGBA (PNG-32), straight alpha, sRGB, with no iCCP, gAMA, cHRM, sRGB or eXIf chunks. Indexed/palette PNG MUST NOT be used. Interlacing MUST NOT be used.

Rationale for stripping colour management, which is a correctness requirement and not a size optimisation: the RGB of a delivered base is not a colour, it is a mix factor (ART-180). An embedded gamma or profile chunk invites the browser to transform it, and a browser that gamma-decodes a mix factor shifts the blend between every seat's two colours — by different amounts in different browsers. The symptom is "the units look slightly different in Firefox", which is a maddening bug to chase and a trivial one to prevent. The detail layer is stripped too, and the requirement is the same even though the reason inverts: its RGB genuinely is a colour, untagged PNG is sRGB by every browser's default, and shipping it untagged is what guarantees it lands unchanged alongside a base that has been stripped. A profile on one file and not the other is the worst of both.

ART-380 Authoring MAY use SVG (MOD-410 accepts it), and SVG is genuinely the better authoring form for the geometric primitives of ART-060. But image-generator output is raster, so the mainline pipeline is raster; SVG's benefit here is resolution-independent authoring, not runtime scaling, because the WebGL map layer cannot consume vector art.

5.2 Naming — the filename is the asset id

ART-390 Asset file names MUST be exactly the asset's id plus .png. Lowercase ASCII, digits and _. No spaces, no hyphens, no version suffixes, no _final, and no domain prefix — the domain is carried by the directory the file sits in and by the MOD-460 registry, and does not get repeated in every filename.

<id>.png                 the tint-mask base — the asset itself
<id>__<variant>.png      a variant that earns its own art, double underscore
<id>_detail.png          the detail layer of <id>; not an id of its own

id       the unit `id` of MOD-210, or the registry key of MOD-460
variant  a disambiguator the owning register defines — for units, one of the
         presentation states of MOD-430 that earns distinct art under
         `docs/art/01-units.md` ART-460; for terrain, an edge name
infantry.png
armour.png
armour_detail.png                  the detail layer of armour; not an id itself
heavy_armour.png
submarine__submerged.png
forward_depot__emplaced.png

Every state in MOD-430's vocabulary that does not earn distinct art is an overlay and has no file at all (docs/art/01-units.md ART-460, ART-480), which is why there is no .normal segment and no infantry__damaged.png: a segment meaning "no variant" is a value that exists only to avoid being absent. The sibling documents name the ids in their own registers, in this same shape.

A detail layer (ART-125) is the id plus _detail.png. That is the only permitted suffix, and it is deliberately the shape docs/art/02-terrain.md ART-605 already uses for its companion <name>_mask.png, so that a directory listing reads the same way in both documents. It is not itself an id: it MUST NOT appear in sprites.json or in the MOD-460 registry, and the packer MUST resolve it as the second file belonging to <id> rather than rejecting it as an unparseable id (ART-550, guarantee 8). A _detail.png with no matching base is a blocking error, not an orphan to skip.

ART-400 A file whose <variant> segment is not in the owning register's closed vocabulary — for a unit, one of MOD-430's presentation states — MUST be rejected by the packer, naming the offending variant. Every unit MUST ship its base <id>.png; its absence is a blocking error under MOD-350.

Rationale for making the filename the key rather than carrying a mapping table: MOD-460 requires a published, versioned asset-key registry and MOD-430 binds sprites by key in sprites.json. Deriving the key from the path means the registry can be validated against the filesystem by listing it, an asset can never be silently orphaned by a typo in a mapping file, and a reviewer looking at a directory listing is looking at the registry.

5.3 Directory layout

docs/art/
  00-art-direction.md          this document
  01-units.md                  the 23 producible units + Militia, Administrative Cadre, Forward Depot
  02-terrain.md                11 terrain types, Road / Fort / Airfield, River and Ford edges, the City
  03-ui-effects.md             badges, overlay glyphs, LOD markers, UI chrome

art/
  README.md                  names, by sprite, every master that does not exist and why (ART-280)
  style/
    style-plate.png            the APPROVED Infantry exemplar — image reference for every
                               generation. Infantry is BASE-ONLY, so there is deliberately no
                               style-plate_detail.png: the plate teaches the flat counter, and
                               §7's worked example (Armour) teaches the layer split (ART-490)
    style-plate.md             the prompt that made it, the model, the seed, and why it was accepted
  gen/                         raw generator output. Never edited. Never shipped. Committed.
    armour/
      001.png 002.png 003.png ...
      choice.json              which take was accepted and why the others were not
  master/                      512 px, alpha cut, hand-cleaned. THE ARTWORK OF RECORD (ART-280).
    infantry.png                      two-tone tint-mask base
    armour.png
    armour_detail.png                 authored-colour detail layer, where one exists (ART-125)
    armour.gen.json
  src/
    close/  infantry.png                        128 px, tint-mask base, packer input
            armour_detail.png                   128 px, detail layer, optional (ART-125)
    mid/    infantry.png                         64 px, hand-simplified, base only (ART-135)
            submarine__submerged.png             a variant: <id>__<variant>.png
  chips/
    shapes.json                Chip and Strategic tiers are DRAWN AT RUNTIME, not authored
                               (ART-140). This file is drawing input, not art
  atlas/                       BOOTSTRAP ONLY, and labelled as such (ART-415). Packed pages are
    README.md                  committed here because elc-atlaspack does not exist yet. Generated
    units.close.0.png          output: MUST NOT be hand-edited, MUST NOT be treated as a source of
    units.close.0.json         truth. Moves to build/atlas/ — derived and gitignored — in the change
    units.chip.0.png           that lands the packer (ART-550). The Chip and Strategic pages are
    markers.strategic.0.png    here for the same reason and are deleted in the change that lands
                               the drawing code of ART-140, not before and not after (ART-145)

build/                         derived, .gitignore'd, reproducible from art/master/ alone
  atlas/units.close.0.png         base atlas page
  atlas/units.close.detail.0.png  companion detail page, identical cell coordinates (ART-455)
  atlas/units.close.0.json        Phaser 3 atlas metadata — covers both pages
  atlas/units.mid.0.png

ART-410 art/gen/ and art/master/ MUST be committed. build/ MUST NOT be, and MUST be reproducible from art/master/ alone (MOD-505: the derived bundle is derived, cacheable and disposable, and enters no hash).

Rationale for committing the raw generator output, which feels like clutter: it is the only record of what the model actually produced, and it is what makes a rejection reviewable. It is also cheap insurance against the failure mode where somebody hand-fixes a master, the fix is wrong, and the original is gone.

ART-415 Until elc-atlaspack (ART-550) exists, packed atlas pages and their metadata are committed at art/atlas/ as a labelled bootstrap. art/atlas/README.md MUST state that the files there are generated output committed as a bootstrap, that they MUST NOT be hand-edited, and that they MUST NOT be treated as a source of truth. When the packer lands, the directory moves to build/atlas/ in that same change, becomes derived and gitignored under ART-410, and MUST be reproducible from art/src/ alone.

Rationale for committing them at all: the alternative is gitignoring artefacts nobody can currently regenerate, which leaves a teammate unable to load anything — correct in principle, useless in practice, and this project's shared state is the server rather than a local build.

Rationale for insisting on the label rather than on the intention: "these are temporary" is a fact that lives in somebody's head, and a generated file sitting in a source tree with nothing saying so is one that gets hand-patched by whoever is fixing a sprite in a hurry. That patch is then lost the first time the packer runs, silently, and the sprite regresses to a state no one edited. The README is the whole difference between a bootstrap and a second source of truth.

ART-420 Every master MUST carry a sidecar <id>.gen.json (ART-390) recording enough to reproduce it. A sprite that has no master under ART-280 MUST still carry its sidecar, recording "master": null and everything else that is known:

{
  "id": "armour",
  "master": "rerendered",
  "generator": { "model": "<exact model id>", "version": "<exact version string>" },
  "prompt": "<the full prompt text, verbatim, including the boilerplate blocks>",
  "negative": "<the full negative prompt, verbatim, or null>",
  "styleReference": { "file": "art/style/style-plate.png", "weight": "<tool's setting>" },
  "seed": 1274419,
  "settings": { "size": "1024x1024", "guidance": 7.0, "steps": 40 },
  "takes": 7,
  "accepted": "art/gen/armour/004.png",
  "handEdits": ["closed the contour under the rear sprocket", "restored the fifth road wheel"],
  "maskDelivery": {
    "convention": "explicit",
    "reason": "the gun tube is machinery, not livery: it projects into void, reads by shape rather than by contrast against a seat colour, and separates Armour from Heavy Armour by length (ART-247 of 01-units.md)",
    "detailRegions": ["gun tube, machined steel #6A6E72, 144 x 48 master px"],
    "coverage": 0.15
  },
  "verifiedBy": "art-verify 1.0.0",
  "verifiedAt": "2026-09-02"
}

The "id" field is the filename without its extension (ART-390) — armour, not a dotted key, and not armour_detail, which is a second file belonging to this id rather than an id of its own. The "master" field records which of ART-280's two routes produced the 512 px master: "rerendered" or "generated". There is no third value. A sprite with no master carries "master": null and MUST be named in art/README.md.

ART-425 The maskDelivery block is mandatory on every master and MUST record "convention" as "implicit" or "explicit" (ART-125). Where it is "explicit" it MUST additionally record the art-direction reason the asset earned a detail layer, in a sentence, and the regions taken into it.

Rationale: UX-440 makes the number of tones a per-asset choice rather than a rule, and a choice without a recorded reason is exactly how the rule this document used to carry got written — cited nowhere, defended nowhere, and impossible to find by searching for its source. docs/art/02-terrain.md ART-615 records the opposite choice, two values fully masked, for all seventeen of its owner-coloured assets, with its reasons, and that is the standard to match. A reviewer looking at a gen.json should be able to see not only how the asset was made but why it is shaped the way it is, and a future reviewer should be able to overturn the decision by arguing with the recorded reason rather than by guessing at one.

Rationale: this project already treats reproducibility as a first-class property everywhere else — the determinism gate, the committed reference hash, the specification cross-reference checker. Art is the one place where a year from now nobody will remember which model made the set, and the model in question may no longer exist. Recording the generation is not bureaucracy; it is what makes "add one more unit that matches" a possible request rather than a re-do of the whole roster.

5.4 Atlas metadata

ART-430 Atlas metadata MUST be TexturePacker JSON (Hash), which Phaser 3 loads directly via this.load.atlas(key, pngUrl, jsonUrl); multi-page tiers use this.load.multiatlas. Frame keys MUST be the asset ids of ART-390 — the delivered filename without its extension, so armour and submarine__submerged, never armour_detail.

ART-440 Every frame MUST have "trimmed": false and "rotated": false, on the base page and on any companion detail page alike.

Rationale, and this is the single easiest thing in the whole pipeline to get wrong: every sprite packer trims transparent margins by default, and it is normally the right thing to do. Here it is fatal. The entire overlay reservation system of §4 is expressed in cell coordinates — the client places the condition badge at (8,8) within the cell — and a trimmed frame has thrown the cell origin away. Rotation is banned for the same reason plus one more: it makes the extrusion ring meaningless. A companion detail page raises the stakes rather than changing the argument: a detail layer is mostly transparent by construction (ART-255 check 9 caps it at 40 %), so it is exactly the kind of page a trimming packer mangles most, and a trimmed detail frame does not fail loudly — it slides the material a few pixels off the art it belongs to.

{
  "frames": {
    "armour": {
      "frame":            { "x": 4, "y": 4, "w": 128, "h": 128 },
      "rotated":          false,
      "trimmed":          false,
      "spriteSourceSize": { "x": 0, "y": 0, "w": 128, "h": 128 },
      "sourceSize":       { "w": 128, "h": 128 }
    },
    "infantry": {
      "frame":            { "x": 140, "y": 4, "w": 128, "h": 128 },
      "rotated":          false,
      "trimmed":          false,
      "spriteSourceSize": { "x": 0, "y": 0, "w": 128, "h": 128 },
      "sourceSize":       { "w": 128, "h": 128 }
    }
  },
  "meta": {
    "app":     "elc-atlaspack",
    "version": "1.0",
    "image":   "units.close.0.png",
    "format":  "RGBA8888",
    "size":    { "w": 2048, "h": 2048 },
    "scale":   "1",

    "elc": {
      "pipelineVersion": 1,
      "tier":            "close",
      "cell":            128,
      "stride":          136,
      "extrude":         2,
      "gutter":          2,
      "premultiplied":   false,
      "colorManaged":    false,
      "channels":        { "rgb": "tintMask", "a": "coverage" },
      "tintMask": {
        "encoding": "linear",
        "zero":     "seat.foreground",
        "one":      "seat.background"
      },
      "detail": {
        "convention":  "ART-605 explicit; mask carried in detail alpha (ART-125)",
        "image":       "units.close.detail.0.png",
        "channels":    { "rgb": "authoredColor", "a": "tintExclusion" },
        "composite":   "over",
        "coordinates": "identical",
        "frames":      [ "armour" ]
      },
      "reservations": {
        "frameBand":    8,
        "corner":       36,
        "identityCore": { "x": 32, "y": 32, "w": 64, "h": 64 },
        "assign": {
          "topLeft": "condition", "topRight": "stance",
          "bottomLeft": "supply", "bottomRight": "posture"
        }
      }
    }
  }
}

ART-450 The elc.tintMask and elc.detail blocks together are the declaration that satisfies MOD-420's requirement for "a dedicated tint-mask channel or companion mask layer, authored as such" — tintMask is the channel, detail is the companion layer, and MOD-420 asks for either. The whole RGB plane of the base is a mask; no particular value is reserved as a sentinel; every value in [0, 255] is a valid mix factor with a defined meaning. That is what makes it a mask channel and not the chroma-keying MOD-420 forbids, and the distinction MUST be preserved by anyone changing this format — including in the detail layer, whose RGB is authored colour with no reserved value meaning "tint me". Its exclusion lives in its alpha, where it cannot collide with any colour an artist might legitimately want to use. An encoding that reserved, say, pure magenta in the detail layer to mean "take the seat colour here" would be the forbidden technique wearing a new file name, and it would fail exactly as MOD-420 predicts: silently, on the first resample.

ART-455 A companion detail page MUST have identical slot geometry, identical slot stride and identical slot assignment to its base page, so that a single UV samples both (ART-605), and MUST spill in lockstep with it, so that page n of the detail atlas holds exactly the frames of page n of the base (ART-320). Cells whose frame declares no detail MUST be fully transparent on the detail page.

The elc.detail.frames list is normative and is this tier's register in the sense docs/art/02-terrain.md §13 means: a frame in it uses ART-605's explicit convention, a frame absent from it uses the implicit convention. Where no frame in a tier declares a detail layer the elc.detail block MUST be absent and no companion page emitted — which is the state every tier ships in until an asset earns otherwise.

Rationale for a frame list under meta.elc rather than a flag on each frame object: TexturePacker JSON (Hash) is a format other tools read, and adding unknown keys inside individual frames entries is the kind of thing a loader is entitled to reject or drop. Everything project-specific already lives under meta.elc and this belongs there with it. It is also the shorter and more readable diff when one sprite gains a detail layer, which is the change this list will actually see — one line, naming the sprite, rather than a scattered edit across a frame table.

ART-460 The same two layers MUST feed both consumers MOD-420 names: the WebGL shader path of ART-185, and the pre-baked tinted rasters the DOM UI needs for the Dispatch, production panels, unit tables, the encyclopedia and the victory Board. Both MUST produce visually identical output. A pre-baked raster MUST be produced by running ART-185's composite in that order — tint the base, then draw the detail over it — and MUST NOT be produced by tinting a pre-flattened image, which would recolour the detail and is the one implementation of this that looks correct on a grey unit and wrong on every other. The pre-baked set is bounded by seat count, not palette, and MUST be regenerated when a seat's colours change, which UX-1310 allows a player to do at any time, in game.

The bake is a per-pixel pass and needs no shader, no canvas compositing mode, and no second code path for an asset with no detail layer:

// base, detail: ImageData at the delivered cell size; detail may be null.
// fg, bg:       the seat's [r, g, b] (UX-210).
function bake(base, detail, fg, bg) {
  const out = new ImageData(base.width, base.height);

  for (let i = 0; i < base.data.length; i += 4) {
    const m = base.data[i] / 255;                          // ART-180: m = R, un-inverted
    for (let k = 0; k < 3; k++)
      out.data[i + k] = fg[k] + (bg[k] - fg[k]) * m;       // mix(fg, bg, m)
    out.data[i + 3] = base.data[i + 3];                    // ART-185: alpha from the base
  }

  if (detail) {
    for (let i = 0; i < out.data.length; i += 4) {
      const a = detail.data[i + 3] / 255;                  // mask complement, not coverage
      for (let k = 0; k < 3; k++)
        out.data[i + k] += (detail.data[i + k] - out.data[i + k]) * a;   // over()
    }
  }
  return out;
}

Rationale for specifying the bake as arithmetic rather than as canvas operations: "visually identical output" is a requirement, and the way to meet it is for both paths to run the same expression. globalCompositeOperation has no gradient-map mode, so every canvas-native approach to mix(fg, bg, m) is an approximation — usually "fill fg, then fill bg masked by luminance", which is close and is not equal, and whose error shows up precisely in the anti-aliased boundary pixels §2.4 went to some trouble to make meaningful. Twelve lines of ImageData are exact, are visibly the same arithmetic as ART-185, and cost one pass over 128 × 128 pixels per seat per asset — trivial, once, off the main thread with the rest of the bundle build (MOD-505).


6. The production pipeline, honestly

6.1 What image generators are bad at

Not a disclaimer — a list of things this pipeline is built around.

They are bad at What that means here The workaround
Exact pixel grids Asking for "a 4×4 sprite sheet, 128 px cells" returns an image with five and a half wobbly cells of varying size Never ask for a grid. One sprite per image, pack programmatically (ART-470)
True alpha Almost no generator emits a real alpha channel; the ones that claim to produce soft, wrong, fringed alpha Flat magenta chroma-key ground, cut in post (ART-160)
Style consistency across separate generations Twenty-six sprites prompted identically will still be twenty-six styles Style-reference image on every generation (ART-490), plus the two-tone reduction, which is itself a powerful style normaliser
Small text Convincing-looking garbage, every time Banned outright (ART-100); Chip labels come from a glyph atlas at runtime
Precise repetition "The same turret as the last one" is not a thing you can ask for Design the roster so shared parts are few, and hand-copy shared parts in a pixel editor
Seamless tiling Edges never match; "seamless" in a prompt is aspirational Terrain tiles must be edge-matched by hand or generated as a large field and cut — see docs/art/02-terrain.md
Flat, unshaded fills The strongest bias of all. Models add light because their training data has light §2.7's threshold, and accepting that a fraction of takes are unrecoverable
Consistent camera height Drift across generations Side elevation, which has no camera height to drift (ART-020)
Layers A generator emits one flat image and has no concept of a layer. Asking for "the base and the detail as two files" gets you one image, or two unrelated ones Never ask for two. Prompt for the flat counter only (ART-520), then cut the detail layer out of the same accepted take, from the pre-threshold master (§7.3 step 4b). Both layers are the same pixels, so they register exactly — which no two generations ever would

ART-470 Art MUST be generated one sprite per image, and sheets MUST be assembled by the packer. A sheet MUST NOT be requested from a generator under any circumstances.

Rationale: a model has no representation of a grid. What it has is a representation of "an image that looks like a sprite sheet", which is a different thing and is worthless — the cells drift, the subjects vary in scale, and half of them bleed into their neighbours. Even if a model produced a perfect grid, packing it by hand would forfeit the extrusion, the gutter and the frame-coordinate guarantees of ART-440, which the renderer depends on.

ART-480 Generation MUST be at 1024², reduced to the 512 master, reduced again to the delivered cell. Never generate at the delivered size.

6.2 The style plate — the technique that actually buys consistency

ART-490 Before any other sprite is generated, one exemplar MUST be produced, reviewed and approved as art/style/style-plate.png. That approved image — the tint-mask base, never the tinted or detail-composited version — MUST then be supplied as an image reference on every subsequent generation in the set, alongside the prompt.

The plate is Infantry. The worked example of §7 is Armour. These are deliberately two different sprites, and an earlier draft of this document conflated them.

Infantry is the plate, and it is base-only: it ships no detail layer, so there is no art/style/style-plate_detail.png and there is nothing to attach beside the plate. A style reference wants the simplest, most typical silhouette in the roster and it wants it flat, because whatever the plate shows, every subsequent generation will try to reproduce. docs/art/01-units.md records Infantry as implicit in its own register (ART-247 #2) and makes the same argument from the roster side (ART-530).

Armour is the worked example (§7), because the plate and the teaching case are answering different questions. The plate answers "what does this set look like"; §7 answers "how is one sprite actually made, including the layer split". Infantry is the wrong sprite for the second question — a shaded rifle teaches nothing and the sprite is better without it — and Armour is the right one, so the two jobs are given to the two sprites that suit them. §7.5 says how the two relate in practice.

Rationale, stated plainly because it is the most important operational fact in this document: prompt text alone will not produce a consistent set. It is not a matter of writing a better prompt. Two generations from the same model with the same prompt and different seeds differ in line weight, in silhouette mass, in how much interior detail the model felt like adding, and in how literally it took "flat". Across twenty-six units those differences compound into a set that looks like it was assembled from four different games. The image-reference mechanism — Midjourney's style reference, an IP-Adapter or ControlNet conditioning in a Stable Diffusion pipeline, or simply attaching the plate and saying "match this exactly" to a conversational image model — is what closes that gap, and it closes most of it.

There is a second consistency mechanism, and it is free: the two-tone reduction of §2.7 is itself a style normaliser. Once every sprite's base has been thresholded to the same two values, the differences that survive are differences of silhouette and mark, which are the ones ART-020 through ART-080 have already constrained. Sets that look hopelessly mismatched as raw generator output frequently look like one set after reduction. Judge coherence on the reduced images, not on the takes.

Note the cost of the detail layer against that mechanism, because it is real and it is the strongest argument for using it sparingly. Detail colour is the one part of a sprite the threshold does not normalise, so it is the one part where twenty-six generations can drift apart unchecked — a set of brown-ish tracks in twenty-six brown-ish browns looks exactly as mismatched as the unreduced takes did. Two defences, both cheap. The first is that a detail region is hand-authored, not generated, so its colour is a decision rather than a model's whim: pick the value, write it in the gen.json (ART-425), and reuse it across every unit that has the same material. The second is that the last two Set boxes of §8 are read on the composite, so a drifting palette shows up in the same review that catches a drifting silhouette.

ART-500 Every generation in a set MUST use the same model, the same model version and the same settings, recorded per ART-420, and the roster SHOULD be generated in one sitting. Hosted models change under their version strings; a set half-generated before a silent update and half after will not match, and there is no way to detect that except by looking.

6.3 What to expect, in hours

ART-510 Plan for 4 to 8 takes per sprite and 10 to 20 minutes of hand cleanup per accepted take in a pixel editor, plus a further 5 to 10 minutes for any sprite that ships a detail layer. Cleanup is closing the contour where the threshold opened it, removing a fifth interior mark, squaring a corner the model rounded, and enforcing the occlusion test of ART-360; the detail pass on top of it is a selection, a delete-to-transparent, and a check against ART-225. Budget both. A pipeline plan that assumes one prompt yields one shippable sprite will be wrong by an order of magnitude and the project will conclude the art direction is at fault.

A note on that second figure, because it is the number most likely to be used as an argument against the detail layer. It is small only because the split reuses a file the pipeline already produces (§7.3). If a detail layer were instead a second generation, or a hand-painted overlay authored from nothing, it would be an hour a sprite and twenty-six sprites would not be worth it. That is a real constraint on how many assets should take one, and it is the practical half of the argument ART-155 rule 1 makes on legibility grounds — see open question 8.

6.4 The prompt template

ART-520 Every per-asset prompt in the sibling documents MUST be built from this template. The four boilerplate blocks — RENDERING, DETAIL BUDGET, BACKGROUND, FRAMING — MUST be copied verbatim. Only {{SUBJECT}}, {{SILHOUETTE}} and {{MARKS}} vary.

The template produces the tint-mask base, and only that. A prompt MUST NOT ask a generator for a detail layer, a second file, a layered result, or a version "with the material kept" — see the Layers row of §6.1. The detail layer is cut from the accepted take of this prompt, after the key and before the threshold (§7.3 step 4b), which is the only way the two layers register.

A single military map counter icon of {{SUBJECT}}, drawn as flat two-tone stencil
art in the style of a printed cardboard wargame counter.

FRAMING: one icon, centred, filling about 75% of a perfectly square frame. Flat
side elevation: orthographic, no perspective, the camera exactly level with the
subject, no vanishing point, no foreshortening. The subject faces right. Nothing
is cropped by the frame edge.

SUBJECT: {{SILHOUETTE}} Chunky, blunt and blocky, built only from simple geometric
masses -- rectangles, trapezoids, circles and chamfered rectangles. A symbol, not
an illustration.

RENDERING: exactly two flat colours and nothing else. Every surface is either solid
light grey #EBEBEB or solid near-black #141414. Solid light grey fills the body
mass. Near-black draws one heavy unbroken outline all the way around the silhouette,
about 6% of the icon's width, and a small number of interior marks inside the body.
No third grey. No shading, no gradients, no highlights, no rim light, no ambient
occlusion, no cast shadow, no drop shadow, no texture, no grain, no noise, no
hatching, no cross-hatching, no dithering, no line weight variation, no colour of
any kind. Flat two-colour screen print, like a stencil or a silkscreen poster.

DETAIL BUDGET: at most four separate near-black marks inside the body. {{MARKS}}
Every mark is thick and blunt; nothing is thinner than one twentieth of the icon's
width. No fine lines, no panel lines, no rivets, no bolts, no tread links, no
grille slots, no small text, no numbers, no letters, no insignia, no national
markings, no roundels, no flags.

BACKGROUND: completely flat uniform pure magenta #FF00FF, edge to edge, with no
shading, no vignette, no gradient, no ground plane, no horizon, no terrain, no
base, no pedestal, no platform, no frame, no border, no card, no label bar, and
no shadow of any kind touching it.

Square 1:1 image, 1024x1024.

ART-530 Negative constraints MUST be expressed in whatever way the target generator actually supports, and the sibling documents MUST supply all three forms:

Generator family Mechanism Form
Midjourney and similar --no parameter + style reference --no shading, gradient, highlight, shadow, texture, colour, text, border, background, perspective --sref <plate> --ar 1:1
Stable Diffusion, Flux, local pipelines true negative prompt + IP-Adapter / ControlNet on the plate shading, gradient, soft light, ambient occlusion, drop shadow, texture, noise, colour, saturated, text, letters, numbers, border, frame, 3d render, photorealistic, perspective, three-quarter view
Conversational image models (no negative field) prose only fold every exclusion into the positive prompt as a stated requirement, exactly as the template above does — this is why the template's RENDERING and BACKGROUND blocks are written as long "no X, no Y" prose rather than as a separate list

Rationale: a spec that supplies only a Midjourney --no string is useless to someone on a conversational model, and vice versa. The template is deliberately written so that a model with no negative-prompt facility still receives every constraint.

6.5 What the generator must never be asked to make

ART-540 The following MUST be produced programmatically and MUST NOT be image-generated:

Asset Why
Chip-tier cells (32 px, bearing the unit's short label — UX-390) They carry text. ART-100. They are also a rounded rectangle and a glyph, which is thirty lines of canvas code. Drawn at runtime, not baked: ART-140
Strategic-tier markers (16 px, owner-coloured, with a count) Same, plus they are literally three primitives. Drawn at runtime: ART-140
Condition badge, Stance glyph, Posture glyph, encirclement mark, out-of-supply mark Precise geometry at 9 CSS px; a generator cannot hit it and a <canvas> can, exactly, every time
Pattern overlays for UX-1310's non-hue ownership encoding They must tile exactly and align across a whole map. Generators cannot tile
Fidelity, age and count decorations (UX-420) Geometry and text

Rationale: this is not a limitation, it is a saving. These assets are geometric primitives; drawing them in code is faster than prompting them, produces exact results, scales to any DPR without an asset, and — for the Chip tier specifically — is the only way to honour MOD-430's requirement that labels come from the active locale, since a pre-baked Latin atlas is insufficient for a translated or modded unit set.

6.6 The packer

ART-550 elc-atlaspack is a committed CLI, runnable offline (MOD-505), that reads art/src/<tier>/ and emits build/atlas/. It MUST guarantee, and MUST fail loudly rather than silently degrade on any of:

  1. Fixed cell geometry. Every frame is exactly cell × cell at slot offset +4, trimmed: false, rotated: false (ART-440).
  2. Extrusion. The 2 px ring is a copy of the art box's border row/column, per side (ART-310).
  3. Gutter. The outer 2 px of every slot is alpha 0 with the mask flooded, not garbage.
  4. Bleed. Base mask values are dilated at least 4 px past the last opaque pixel with INK; a detail layer is dilated the same distance with its own nearest authored colour (ART-230). Both done before any resample.
  5. Normalisation. [20, 235] → [0, 255], once, on the base, and it is the only value transform (ART-190). A detail layer receives none.
  6. Chunk stripping. No colour-management chunks survive into a page (ART-370).
  7. Verification. Every input passes art-verify (ART-240), and every input declaring a detail layer additionally passes ART-255; a failing input aborts the pack and is named, with which check it failed.
  8. Id validation. Every filename parses as <id>[__<variant>].png or <id>_detail.png (ART-390), and every <variant> is in the owning register's closed vocabulary — for a unit, MOD-430's; unknown variants abort and are named (ART-400). A filename carrying a domain prefix or a .normal segment is rejected as the old convention rather than accepted as a new id. <id>_detail.png resolves as the detail layer of <id> and is never treated as an id of its own; a _detail file with no matching base aborts the pack and is named (ART-390).
  9. Companion atlas. Where any input in a tier declares a detail layer, the packer emits <tier>.detail.<n>.png with identical slot geometry, identical slot assignment and lockstep spill, transparent in every cell that declares none, and records the declaring frames in meta.elc.detail.frames (ART-455). Where none does, no detail page and no elc.detail block are emitted. A detail layer offered for any tier but Close aborts the pack (ART-135).
  10. Determinism. Same inputs, same pipeline version, byte-identical outputs. Slot assignment is by sorted key, not by packing heuristic — and the same sorted keys drive both pages, which is what makes "identical slot assignment" free rather than a second thing to keep in sync.
  11. Spill, never shrink. Overflowing 2048² adds a page (ART-320, MOD-505).

Rationale for point 10, which a normal sprite packer would consider a strange demand: a heuristic bin-packer reorders frames when one asset changes size, which turns a one-sprite edit into a whole-page diff and makes it impossible to see in review what actually changed. Sorted-key placement wastes a few percent of a texture page and buys a readable diff. This project's whole architecture is built on the position that a reproducible artifact is worth more than a marginally optimal one. Point 9 collects a second dividend from it that was not foreseen when it was written: because placement is a pure function of the sorted key set, the base page and the detail page arrive at identical coordinates by construction rather than by a synchronisation step, and there is no state in which the two pages can drift apart.


7. Worked example, end to end: armour

This section walks one sprite from prompt to packed cell, because every rule above is easier to follow once you have seen all of them applied to the same image in order. The sprite is Armour.

Armour is the worked example. Infantry is the style plate. They are deliberately two different sprites, and an earlier draft of this document conflated them — it made Infantry the plate and gave it a detail layer so that the plate could demonstrate the split. That was a bad trade in both directions, and ART-490 now separates the two jobs:

Sprite What it is for Layers
Style plate infantry The image reference attached to every generation in the set (ART-490). It wants the simplest, most typical silhouette in the roster, and it wants it flat, because whatever it shows every later take will try to reproduce base only — no style-plate_detail.png exists
Worked example armour This section: how one sprite is actually made, including the layer split base and detail (ART-125 explicit)

Why Infantry is the wrong teaching case, stated once so nobody restores it: Infantry is the smallest and simplest subject in the roster and it is exactly the "two tones is still the right answer" asset UX-440 names. A shaded rifle teaches nothing about material and the sprite is better without it — worse, the one region a determined author would reach for is the rifle, which is the identifying mark and is forbidden the layer outright by ART-155 rule 1. An exemplar whose only available demonstration is the one thing you must never do is not an exemplar.

Why Armour is the right one: it is an explicit-mask asset on the merits rather than by exception, recorded as such in docs/art/01-units.md ART-247 #10, and what it takes into the layer — a machined gun tube — is the clearest case in the roster of material that is genuinely not identity. Delete the layer and what is left is a correct, complete, unmistakably owned medium tank with a slightly duller barrel. That is exactly the test ART-125 sets for whether a detail layer is legitimate at all, and Armour is the sprite that passes it most visibly.

One thing about Armour is a trap, and it is the reason this example is worth reading. The obvious guess is that the track band and the five road wheels are the detail layer — they are machinery, they articulate, and "tracks are not identity" sounds right. It is wrong on this roster, for reasons that are written down and are not matters of taste, and §7.3 works through why. Getting that one backwards produces a sprite that passes every automated check in this document and fails the only question the art exists to answer.

7.1 The prompt

Copy-pasteable, filled in from the ART-520 template. {{SUBJECT}}, {{SILHOUETTE}} and {{MARKS}} are the only substitutions; the FRAMING, RENDERING, DETAIL BUDGET and BACKGROUND blocks are verbatim.

Note what the prompt does not ask for: the detail layer. It asks for a flat two-tone counter and nothing else, because a generator has no concept of a layer (§6.1) — the split happens afterwards, in §7.3, out of this same take.

A single military map counter icon of a medium battle tank, drawn as flat two-tone
stencil art in the style of a printed cardboard wargame counter.

FRAMING: one icon, centred, filling about 75% of a perfectly square frame. Flat
side elevation: orthographic, no perspective, the camera exactly level with the
subject, no vanishing point, no foreshortening. The subject faces right. Nothing
is cropped by the frame edge.

SUBJECT: one medium battle tank reduced to a symbol -- a long low hull with a
sloped front glacis plate, sitting on one continuous unbroken track band that
touches the ground along its whole length with no gap underneath it, and a single
squat turret box mounted centrally on top with one straight gun barrel pointing
right and level. Wider than it is tall, roughly four units wide by three tall.
Chunky, blunt and blocky, built only from simple geometric masses -- rectangles,
trapezoids, circles and chamfered rectangles. A symbol, not an illustration. Not a
model kit. Not a realistic tank. No crew, no stowage, no aerials, no tow cables.

RENDERING: exactly two flat colours and nothing else. Every surface is either solid
light grey #EBEBEB or solid near-black #141414. Solid light grey fills the body
mass. Near-black draws one heavy unbroken outline all the way around the silhouette,
about 6% of the icon's width, and a small number of interior marks inside the body.
No third grey. No shading, no gradients, no highlights, no rim light, no ambient
occlusion, no cast shadow, no drop shadow, no texture, no grain, no noise, no
hatching, no cross-hatching, no dithering, no line weight variation, no colour of
any kind. Flat two-colour screen print, like a stencil or a silkscreen poster.

DETAIL BUDGET: at most four separate near-black marks inside the body. The marks
are: exactly five identical circular road wheels in one evenly spaced row inside
the track band, the track link line running along the top of the track run, and the
join between the hull and the turret. Every mark is thick and blunt; nothing is
thinner than one twentieth of the icon's width. No fine lines, no panel lines, no
rivets, no bolts, no tread links, no grille slots, no small text, no numbers, no
letters, no insignia, no national markings, no roundels, no flags.

BACKGROUND: completely flat uniform pure magenta #FF00FF, edge to edge, with no
shading, no vignette, no gradient, no ground plane, no horizon, no terrain, no base,
no pedestal, no platform, no frame, no border, no card, no label bar, and no shadow
of any kind touching it.

Square 1:1 image, 1024x1024.

Negative constraints, per ART-530:

--no shading, gradients, highlights, shadow, texture, noise, colour, text, letters,
numbers, border, frame, background scenery, perspective, three-quarter view,
photorealism, 3d render, tread links, rivets  --ar 1:1

Count the marks against ART-080 before you send it. The DETAIL BUDGET block asks for five road wheels, a track link line and a hull/turret join. That is seven objects and three shapes: the five wheels are a repeated identity group and count as one under ART-080's exemption, because they are uniform, evenly spaced, on a common axis, and they hold together as a band of repetition at 8 CSS px. Three of four is inside the cap with one mark to spare — which is where it should be, because the hand pass usually wants that spare.

Not yet reconciled, and a producer will hit this today. docs/art/01-units.md carries its own prompt template (its §6) and its own literal Armour prompt (its §8.3.2), and for the four explicit-mask cells it uses a three-ink COLOUR-DETAIL stanza: the untinted region is painted by the generator in pure cyan #00FFFF and the layer split is then derived from that region (its ART-515, ART-565) rather than hand-cut as in §7.3 below. That method is one generation, so it does not violate the "never ask for two" rule of §6.1, and it has a real advantage — a barrel that moves takes its layer boundary with it. But the two templates are differently shaped and are not built from each other, which ART-520 requires. The prompt of record for shipping armour is that document's §8.3.2; the prompt above is this document's template made concrete, and the two want merging in one pass. See open question 10.

7.2 What actually comes back

Honestly, across eight takes, expect roughly this distribution. None of it is a sign the prompt is wrong; this is the normal yield.

Takes What you get Verdict
2–3 Soft shading down the hull side, a mid-grey plateau across the glacis, a slight drop shadow under the track. The form is right Recoverable — §7.3's threshold fixes it, hand-erase the shadow
1–2 Six or four road wheels, or five wheels of visibly different sizes, or wheels merged into a smear Recoverable at the master, but check it first. Re-cut the row by hand at 512; if the band itself is wrong, re-roll
1 A three-quarter view — you can see the top plane of the hull and the far track Reject. Re-roll
1 A photorealistic or 3D-rendered tank that ignored "flat" entirely Reject. Re-roll
1 Correct two tones, but tread links drawn as a row of individual plates around the whole track run Recoverable — erase them at master resolution; they are the ART-080 violation this subject attracts most
1–2 Close to correct: two tones, flat, level camera, unbroken band, five wheels Accept, then clean

The two things to check before anything else, because they are unrecoverable: is the camera level (any visible top surface or far track means three-quarter view crept in, reject), and does the silhouette read as a tank with the interior erased (fill the whole shape black and look at it — a long low mass on an unbroken bar, with a box and a stick on top; if that reads as a truck or a ship, no amount of interior work will save it).

Then check the two things that are specific to a tracked subject, and check them in this order:

  1. Is the track band continuous, with no void underneath it anywhere? That is the tracked-class footprint of ART-070, and it is the single cue separating this sprite from a wheeled one at the Mid tier. A model that has drawn daylight under the hull has drawn the wrong movement class.
  2. Are there exactly five road wheels, evenly spaced and the same size? Five is Armour and seven is Heavy Armour (docs/art/01-units.md §3.4). Even spacing and uniform size are not cosmetic here: they are two of the four conditions ART-080's group exemption requires, and a row that fails them stops being one shape and starts being five, which puts the sprite over the cap.

Notice what did not change in this table now that a detail layer exists. Row 1's soft shading is still recoverable by the same threshold, and it is still shading you throw away — not a windfall to harvest into the detail layer. A model's shading describes a light source it invented; ART-155's layer is for material, which is a different fact about the subject. The one honest exception is the reason step 4b reads the pre-threshold master rather than a fresh file: where the model has separated a genuinely different material — here, the barrel — its colour there is worth keeping, and it is already in register. Take that; leave the ambient occlusion under the sponson.

7.3 Reduction and the layer split — the whole pipeline as commands

First, the decision the commands implement. Every feature of the sprite goes to exactly one layer, and here is where each one lands:

Feature Layer Value Why
Outer contour, all the way round base INK ART-050, and it is the one thing detail may never touch (ART-225 rule 2)
Hull, glacis, turret box base FIELD this is the mass that changes colour with the owner, and it is what docs/art/02-terrain.md ART-1135 measures
Track band envelope base FIELD, with its own contour in INK it is the tracked-class footprint of ART-070 and it reaches the ground baseline, which is the bottom boundary of the ownership read (docs/art/01-units.md ART-215.3)
The five road wheels base INK the counting cue that separates Armour from Heavy Armour. One shape under ART-080's group exemption, and INK because a count is read by contrast before it is read by shape
Track link line along the top run base INK interior mark, ART-080
Hull / turret join base INK interior mark, ART-080
Gun tube, between its contours detail machined steel #6A6E72, 36 × 8 cell px (144 × 32 master px), inside a 12 cell px tube contoured at 2 cell px a side material, not identity: delete it and the sprite is still a medium tank, still owned, still passes §8. Linear detail under ART-157, with ART-158's narrower contour

The track band and the road wheels are the instructive case, and they are the mistake to expect. The obvious move once a detail layer exists is to render the running gear in a track colour, because tracks are machinery and the layer is for material. It is wrong here, for two reasons that have nothing to do with taste and everything to do with what the base is guaranteeing.

The band is load-bearing geometry. It is the movement-class footprint of ART-070 — an unbroken bar at the baseline, against a wheeled unit's separated discs — and it is the lowest painted row of the sprite. docs/art/01-units.md ART-215.3 requires ground contact to be masked precisely because at the Mid tier the bottom two rows of a land cell are often all a crowded stack shows of it. Untinting the band takes the bottom out of the ownership read and leaves the sprite claiming a movement class in a colour that belongs to no seat.

The wheels are load-bearing contrast. Their count is the identity cue, and a count is read by contrast before it is read by shape. The seat's foreground and background pair is guaranteed to contrast, because UX-210 chose it and UX-1310 constrains it; an authored steel is guaranteed to contrast with neither, because the seat pair is remappable at any time. Five INK discs in a FIELD band are countable in every palette a player can pick. Five gunmetal discs in that band disappear the moment somebody chooses a dark background, and the cue that distinguishes Armour from Heavy Armour goes with them. That is ART-155 rule 1 exactly: nothing the player needs may live in the layer.

The gun tube is safe under the same test, and safe for the mirror-image reasons: it projects into void, so it reads by shape rather than by contrast against a seat colour; nothing about the tank's identity or class is carried by its colour; and the base still separates it from the turret with an ink contour on every side. What is lost when the detail atlas is dropped (ART-125) is that the barrel stops looking like steel. Nothing else.

The tube has to be thick enough to hold a layer at all, and this is the number to check before you draw it. The tube is part of the silhouette, so ART-050's contour runs along both its long edges — and what the tube has to carry between them is a linear detail region, not an area one. ART-157 and ART-158 are the requirements, and this is what they come to on this sprite:

                        contour   detail   contour     total
ART-158 floor:            2   +     4    +   2     =    8 cell px   (= ART-040's silhouette minimum)
armour, as drawn:         2   +     8    +   2     =   12 cell px   (3 CSS px)

the detail region: 8 x 36 cell px  ->  4.5 : 1     clears 4 px wide, 24 px long, 4:1

This was the arithmetic that nearly killed the whole feature, and it is worth seeing what it used to say. Measured as area detail — ART-050's 8 cell px contour on each side plus ART-155 rule 4's 12 cell px minimum between them — a tube needed 8 + 12 + 8 = 28 cell px of thickness before it could carry any detail at all. That is 7 CSS px on a tank whose whole painted width is 70–78 px: roughly a third the height of the hull, a barrel nobody would draw and nobody would accept. Every detail layer in the roster is a gun tube, so the two requirements between them had forbidden all four — not as a judgement anyone made, but as a consequence neither requirement noticed it had. D-7 of docs/art/04-decisions.md records the resolution: the 12 cell px floor is an area test, a gun tube is a linear feature, and thinness is the point of it rather than a corner being cut.

A tube still has to be drawn deliberately. It MUST clear all three of ART-157's tests — 4 cell px of visible authored colour, 24 cell px of length, 4 : 1 — and its total silhouette thickness MUST be at least 8 cell px, which is nothing more than ART-040 applying to it as it applies to everything else. Armour is drawn at 12 rather than at the 8 px floor because the floor leaves no margin for the reduction of step 6 to eat, and because 3 CSS px is what a medium tank's gun should look like. docs/art/01-units.md ART-445 gives Heavy Armour's tube the same 12 px and lets its extra length carry the comparison, for a reason worth knowing before you reach for a thicker barrel here: a tube thick enough to look heavy stops clearing ART-157's 4 : 1 aspect test and falls back to the area floor, which is the 28 px dead end again. On a gun tube, thickness is the axis with a ceiling on it.

docs/art/01-units.md ART-445 carries the same geometry at the drawing board and checks all four explicit cells against it. What is still open there is question 11's other half — whether four barrels are worth a companion atlas page at all, which is a budget question rather than a draughtsmanship one (open question 9 below). If that decides against the page, this section becomes a base-only worked example and steps 4b and 6b drop out of the commands below. Nothing else in §7 changes.

K=armour
RAW=art/gen/$K/004.png          # 1024x1024, the accepted take

# --- 1. Cut alpha from the chroma key, at full generation resolution ---------
# Generous fuzz is safe: legitimate art is neutral (ART-150), so nothing in the
# subject is anywhere near magenta.
magick "$RAW" -fuzz 20% -transparent "#FF00FF" /tmp/01-keyed.png

# --- 2. Erode the key fringe by one pixel ------------------------------------
# The magenta halo left at the boundary would survive desaturation as a mid grey
# and would then threshold into a ring of ink -- a halo around every unit.
magick /tmp/01-keyed.png \
  -channel A -morphology Erode Octagon:1 +channel /tmp/02-deringed.png

# --- 3. Down to the 512 master ----------------------------------------------
magick /tmp/02-deringed.png -filter Lanczos -resize 512x512 /tmp/03-master-raw.png

#   >>> HAND STEP. Open 03-master-raw.png in a pixel editor. Erase the drop
#   >>> shadow, the card, any tread-link detail the model added. Close the
#   >>> contour where the model opened it -- under the rear sprocket is where it
#   >>> opens on this subject. Square the corners it rounded. RE-CUT THE ROAD
#   >>> WHEELS to five identical discs at equal spacing on one axis: that is
#   >>> what makes them one shape under ART-080 rather than five, and the
#   >>> reduction in step 6 will not fix an uneven row, it will smear it.
#   >>> 10-20 minutes (ART-510).
#   >>> Save as 03-master-clean.png. DO NOT DISCARD IT after step 4 -- it is
#   >>> the input to step 4b as well, and it is the only thing that still
#   >>> carries the model's own colour.

# --- 4. Threshold to exactly two tones: this is the BASE ---------------------
# The -channel RGB / +channel guard is mandatory: without it the threshold also
# hits alpha and the silhouette goes hard-edged and aliased.
magick /tmp/03-master-clean.png \
  -channel RGB -colorspace Gray -threshold 55% \
  -level-colors "#141414","#EBEBEB" +channel \
  -strip  art/master/$K.png

# --- 4b. Cut the DETAIL layer from the same take, before the threshold -------
# 03-master-clean.png is the accepted take with its alpha cut and its defects
# fixed, and it still carries the model's own colour and material. Everything
# the detail layer wants is already in it and already in register with the base,
# because it is literally the same pixels. Do NOT generate a second image for
# this: a second generation is a different silhouette and will not register.
#
#   >>> HAND STEP. Duplicate 03-master-clean.png. Keep the regions that stay
#   >>> authored -- for Armour, the gun tube BETWEEN its two contours, from the
#   >>> turret face out to the start of the muzzle contour -- and delete
#   >>> everything else to FULL transparency. Keep NOTHING on the track band and
#   >>> NOTHING on the road wheels; that is the whole judgement above.
#   >>> Then check four things, which are the whole of ART-225, ART-155 rule 4
#   >>> and ART-157. Every figure here is MASTER px: the file is 512 (§0.2).
#   >>>   - nothing you kept touches the ink contour, anywhere. On this subject
#   >>>     that is still the constraint that bites, but the band is now the
#   >>>     tube's own: the contour runs down both sides of the tube at 8 master
#   >>>     px each (2 cell px, ART-158), not the 32 master px it carries
#   >>>     everywhere else on the silhouette;
#   >>>   - nothing you kept lies outside the silhouette;
#   >>>   - the tube region is LINEAR detail and clears all three of ART-157:
#   >>>     at least 16 master px of visible width (4 cell px), at least 96
#   >>>     master px long (24 cell px), and at least 4:1. On armour as drawn it
#   >>>     is 32 x 144 master px, which is 4.5:1;
#   >>>   - any region that is NOT a projection -- a seam, a line, a plate face
#   >>>     -- is AREA detail and needs 48 master px in its smaller dimension
#   >>>     (12 cell px, ART-155 rule 4). ART-159 forbids calling it linear to
#   >>>     get under that. On this sprite you kept no such region: erase it.
#   >>> 5-10 minutes on top of step 3's 10-20 (ART-510).
#
# No threshold, no level, no colourspace change: the detail layer is a colour
# and ART-190 forbids transforming it.
magick /tmp/04-master-detail.png -strip art/master/${K}_detail.png

# --- 5. Verify the base (ART-240, checks 1-7) -------------------------------
# These run on the BASE and on nothing else. The detail layer is chromatic by
# design and would fail check 1 by construction; ART-255 is its gate.
magick art/master/$K.png -alpha off \( +clone -colorspace Gray \) \
       -compose difference -composite -format "%[fx:maxima*255]" info:   # expect 0
magick art/master/$K.png -alpha off -colorspace Gray -format "%c" \
       histogram:info:- | sort -rn | head -6      # expect two dominant bins, 20 and 235

# --- 6. Deliver both 128 px Close cells --------------------------------------
# The bleed (ART-230) goes on BEFORE each resize. The base's downsample is what
# regenerates correct anti-aliasing between exactly the two tones (ART-200).
magick art/master/$K.png \
  -background "#141414" -alpha background \
  -filter Lanczos -resize 128x128 \
  -strip  art/src/close/$K.png

#   >>> LOOK AT THE WHEELS. The 4:1 reduction is where a five-wheel row becomes
#   >>> a four-wheel row, or a smear. Count them in the delivered cell, every
#   >>> time, and re-cut at 512 and re-run rather than patching at 128.

# The detail layer bleeds with its OWN nearest colour, not with INK (ART-230):
# INK is a mask value and means nothing in a layer whose RGB is a colour. A single
# flood colour is a cheap approximation, and here it is the tube's darker body
# value rather than its lighter upper edge (`docs/art/01-units.md` ART-245.3);
# a multi-region layer needs a true per-pixel dilate, which is what the packer
# does anyway (ART-550, guarantee 4).
magick art/master/${K}_detail.png \
  -background "#6A6E72" -alpha background \
  -filter Lanczos -resize 128x128 \
  -strip  art/src/close/${K}_detail.png

# --- 6b. Verify the detail layer and the composite (ART-255, checks 8-11) ----
# Ownership read, coverage, containment, and the base standing on its own.
# The commands are in ART-255; run all four before packing. Check 11 on this
# sprite is the one to take seriously: delete the layer, look at the cell, and
# confirm it is a finished tank rather than a tank with its barrel missing.

# --- 7. Pack -----------------------------------------------------------------
# The packer picks up _detail.png automatically and emits the companion page
# (ART-455). Nothing on the command line changes.
node scripts/atlaspack.mjs --tier close --in art/src/close --out build/atlas

A note on step 4's 55% rather than 50%: generator output skews light, because models light their subjects from above and the "unlit" regions are still fairly bright. Thresholding at the midpoint therefore tends to eat the contour. Start at 55%, look at the result, and adjust per sprite — this is one of very few numbers in this document that is a starting point rather than a rule. On this subject watch the road wheels while you adjust it: they are INK discs surrounded by FIELD, so they are the first marks a low threshold thickens into each other. Record the value you used in the gen.json.

7.4 Cell placement

The 128 px delivered cell drops into the Close atlas at slot (col, row), art box origin (col*136 + 4, row*136 + 4). Against the reservations of §4, the accepted Armour cell should measure roughly:

Feature Layer Where it lands Rule
Turret crown base y ≈ 52 above y = 24 forbidden (ART-340); a tank has no reason to go near it
Hull / turret join base inside (32,32)(96,96) interior mark, ART-080
Gun tube, silhouette base 12 px thick, contoured at 2 px a side a narrow linear projection under ART-158. 12 px clears ART-040's 8 px floor with margin rather than sitting on it
Gun tube detail region detail ≈ 36 × 8 px in the art box, on the tube's long axis linear detail: 8 px visible width, 36 px long, 4.5 : 1 — all three of ART-157 cleared. Clear of the ink contour on all four sides, at the 2 px weight that applies there (ART-225 rule 2)
Track band envelope base reaching the ground baseline the tracked footprint (ART-070); geometry is docs/art/01-units.md §3.2's, adopted not re-derived
Five road wheels base in one evenly spaced row inside the band the counting cue, one shape under ART-080; geometry is docs/art/01-units.md §3.4's
Lowest painted row base y = 104 the ground baseline, shared with every foot/wheeled/tracked unit
Overall width base ≈ 76 px, centred on x = 64 under the 88 px ground maximum (ART-340)
Nothing at all either in the outer 8 px the frame band (ART-340)

The identity core is the box to think about hardest on this sprite, because Armour's identity is distributed rather than concentrated. What distinguishes it from Heavy Armour is a count along the bottom of the vehicle and a length along the top, and neither is a single mark that sits neatly in the middle. ART-340 requires the distinguishing feature to fall inside (32,32)(96,96), and the way this sprite satisfies it is that the middle three of the five road wheels and the hull/turret join are all inside the core. Centre the vehicle so that they are. A tank pushed left to make room for its barrel puts its counting cue under the bottom-left supply reserve, which is where the occlusion test finds it.

Coverage comes out at roughly 13 % of opaque pixels, well inside ART-255's 40 % ceiling and so leaving mask coverage near 87 %. That is not a target; it is what one tube-shaped material region on a wide, low subject costs. It is a point or two lower than the figure this section used to carry, because ART-158's tube is 12 px thick rather than the 28 px the area floor had demanded — the exception moved this number in the safe direction, which is worth noticing but is not why it was made. A sprite approaching 40 % is one to look at hard, because ART-1135 stops being comfortable long before the arithmetic stops passing.

Run the occlusion test (ART-360): black out all four 36 × 36 corners and the frame band. Armour is a wide low subject centred in the cell, so the corners were never carrying it — but the bottom-left corner is the one to check, because it eats the front of the track run and the first road wheel, and the count has to survive with four visible. Run it on the base alone, which is also ART-255 check 11: if the sprite needs its barrel colour to survive four lit badges, the detail layer has become load-bearing and ART-155 rule 1 has been broken somewhere upstream.

7.5 What happens to this sprite — and what happens to the plate

Armour does not become the style plate. art/style/style-plate.png is Infantry, base only, approved before any other sprite is generated, and attached as an image reference on every generation in the set (ART-490). Nothing in this section changes that, and the accepted Armour master does not go into art/style/ under that name.

Armour does become the second reference. docs/art/01-units.md ART-530 requires it: Armour is the first cell in the roster to carry a detail layer, it MUST be accepted before the other three explicit-mask cells are generated, and it is then supplied alongside the plate for field_gun, heavy_artillery and heavy_armour — and withheld from every implicit-mask cell.

Two things follow, and both are the same argument seen from opposite ends.

Withhold the coloured reference from flat cells. A reference image carrying authored colour invites every subsequent take to invent its own material, which is the ransom-note failure §6.1 describes arriving through the door this document opened. Nineteen of the twenty-five cells have no untinted element at all and must never be shown one.

Give it to the three cells that need it. A flat reference cannot teach a three-ink form, and a model shown only the plate and then handed a material instruction tends either to ignore it or to spread it into the nearest large mass — which on those subjects is the carriage or the track band, exactly the mass that must stay masked.

Then write the sidecar. art/master/armour.gen.json per ART-420 and ART-425: the convention (explicit), the art-direction reason the tube earned a layer, the region and its exact authored colour, the measured coverage, and which of ART-280's two routes produced the 512 px master. The colour matters beyond this one sprite — docs/art/01-units.md ART-245.2 requires all four explicit cells to use the same authored steel, so that four barrels generated weeks apart read as four instances of one material rather than four decisions. Armour is the first of the four, so the value written in this file is the value the other three inherit.


8. Acceptance criteria

ART-560 An asset MUST NOT enter art/src/ until every box below is ticked. The seven Tint-mask base boxes, the coverage and containment boxes under Detail layer, and the first File box are automated by art-verify (ART-240, ART-255); the ownership read is half a number and half a person; every other box is read by a person, and the five Set boxes can only be read by a person looking at the whole set at once — or, for the last of them, at the same sprite on three different displays.

Every Identity and Geometry box below MUST be ticked against the base alone, with the detail layer deleted. That is ART-155 rule 1 stated as a procedure, and it is the only way the checklist enforces it.

Identity

Tint-mask base — run on <id>.png and on nothing else

Detail layer — only where one ships; an implicit-mask asset skips this block entirely (ART-125)

Geometry

File

Set


Open questions

  1. Sibling document scope, now that the filenames are known. The siblings landed as 01-units.md, 02-terrain.md and 03-ui-effects.md, and the paths in §0, §5.3 and every citation of ART-605 and ART-1135 have been corrected to match. What has not been reconciled is scope: this document described 03-* as owning "city states under UX-450", and the City in fact landed in 02-terrain.md (§9 there, ART-950 onward). The §5.3 one-line descriptions are updated to match the filenames but not audited against the documents' actual contents. The cross-reference checker (pnpm check-spec) should be extended to cover docs/art/ so this class of drift is caught rather than discovered — and it should check ART IDs too, because the four art documents currently reuse ID ranges across files: ART-600 is defined in both 01-units.md and 02-terrain.md, ART-1000 in three of the four, and so on. This document's own range, ART-010ART-560, does not collide with 02-terrain.md, but it collides heavily with 01-units.md. Every cross-document citation here is therefore written with its filename attached, and every one elsewhere should be. Whether the fix is per-document prefixes or a single allocated range is a decision somebody has to take before the first tool tries to resolve an ART ID on its own.

  2. Mid-tier authoring cost. §3.2 says the Mid cell is a hand-simplified variant, not a downscale. That doubles the hand-work for the whole roster. The alternative — ship only the Close art and let mipmapping produce Mid — is cheaper and worse, and how much worse has not been measured. Someone should build both for four units across the five movement classes and look at them at 16 CSS px before the roster is committed to.

  3. Threshold point per movement class. §7.3 uses 55% and says it is a starting point. If it turns out to cluster by movement class (naval subjects are broader and flatter and may want a different point from foot subjects), the sibling documents should carry a per-class default rather than leaving it to each operator's eye.

  4. Damaged and disordered states. MOD-430's vocabulary has ten presentation states. Whether damaged and disordered are separate authored sprites or the same sprite with a programmatically drawn overlay is a real cost decision — ten states × 26 units is 260 Close cells if authored, versus 26 if overlaid — and it interacts with the corner reservations of §4, which are already fully allocated. docs/art/01-units.md should settle it; this document has left room for either.

  5. Pattern overlays for UX-1310. ART-540 assigns them to code, but nobody has designed the pattern set. It must tile exactly, survive down to the Strategic tier's 4 CSS px, remain distinguishable in at least eight variants, and not destroy the ownership read it is drawn over. That is a hard visual problem and it is currently nobody's. The detail layer does not help with it and should not be pressed into service: a pattern must vary per seat, and the detail layer is by definition the part of the sprite that does not.

  6. Whether the Canvas fallback needs the same art. UX-385 permits a Canvas fallback at Chip and Strategic only, with pre-tinted atlases replacing the shader. Since both those tiers are drawn programmatically (ART-540), the fallback may need no baked art at all — which would be a pleasant result and should be confirmed before anyone pre-bakes anything. Note that this is a separate question from the DOM pre-bake of ART-460, which is not a fallback, is definitely needed, and does have to composite both layers.

  7. ImageMagick is not installed on the development machine. The recipes in §2.6, §2.7, ART-255 and §7.3 are written against ImageMagick 7 and have not been executed. They should be run once, end to end, on a real generator take before this document is taken off Draft — and then folded into the art-verify and elc-atlaspack Node implementations, which are the normative tools and which do not exist yet either. The ART-255 tint() function is the one to run first: it is the newest, it is the only one that composites two files, and its -compose CopyRGB step is the kind of thing ImageMagick has more than one opinion about.

  8. Which units should actually carry a detail layer. Closed by D-2 of 04-decisions.md and by ART-247 of docs/art/01-units.md. Struck rather than deleted so that a reader of the previous draft can see it was answered and not forgotten. Two halves, answered from two directions:

    • The exemplar. This document used to give Infantry a detail layer "for the narrow reason that it is the style plate". D-2 removes that exception. Infantry is base-only and remains the style plate; Armour is the worked example of §7, and the two are deliberately different sprites (ART-490).
    • The roster. The per-asset choices were always this document's to delegate and docs/art/01-units.md ART-247 has now made all twenty-five of them with their reasons: 21 implicit cells and 4 explicit ones — field_gun, armour, heavy_artillery, heavy_armour — each carrying a single untinted gun tube and nothing else. The expectation this question recorded, that most units should not take one, held.

    What did not survive contact with the roster, and is worth keeping visible: this question named a tracked hull's track band, an aircraft canopy and a submarine's waterline as "the cases with a real argument". All three were argued asset by asset and all three were declined, on evidence this document did not have — the band is the movement-class footprint and the bottom of the ownership read, the Fighter's canopy is 8 × 6 px, and the waterline is the widest ownership signal a sea unit has. §7.3 works the band case through in full, because it is the mistake this document's own reasoning invited. Whether the four that remain are worth a companion page is a separate question and is still open: question 9 below. Whether they could be drawn at all was a third question, and it is now closed by D-7 of docs/art/04-decisions.md — ART-157 and ART-158, and question 11 of 01-units.md, which had all but decided against them on the old arithmetic.

  9. What the detail atlas actually costs, and whether the droppability claim holds. ART-125 argues that the companion page is droppable under memory pressure and that the base degrades cleanly to ART-180. Both halves are unmeasured. A second 2048² Close page is 16 MiB uncompressed, which is not nothing against the mobile WebView budget UX-400 calls the tightest of the six targets, and the claim that dropping it is visually acceptable is exactly the kind of claim that is obviously true until someone looks at a map. Build the Close set both ways, load it on the tightest target, and look — before 01-units.md commits a single unit to an explicit mask.

  10. Two prompt templates, and neither is built from the other. ART-520 requires every per-asset prompt in the sibling documents to be built from this document's template, with four verbatim boilerplate blocks and three substitutions. docs/art/01-units.md §6 carries a differently shaped template — VIEW / COLOUR / LIGHTING / FRAMING / STYLE / NEGATIVE / OUTPUT against this one's FRAMING / SUBJECT / RENDERING / DETAIL BUDGET / BACKGROUND — and for the four explicit cells it adds a three-ink COLOUR-DETAIL stanza whose cyan region lets the layer split be derived (its ART-565) instead of hand-cut as in §7.3 step 4b. The derived split is the better method and it does not breach §6.1's "never ask for two", because it is still one generation of one image. But two templates means two Armour prompts in two documents, and §7.1 has had to say which one ships. Merge them in one pass, in whichever direction the art index prefers, and delete the loser rather than leaving both. This was not settled by 04-decisions.md.

  11. Chip and Strategic: runtime drawing versus a build-time page. ART-140 now states, per D-5, that both tiers are drawn at runtime and that no Chip or Strategic raster ships. docs/art/01-units.md §10 and §11 specify the same construction — ART-920's plate shapes, ART-930's label table, ART-940 and ART-950's modifier marks, ART-980's markers — but describe it as a build step emitting an atlas page (its ART-900, ART-970), and its §12.1 tree, its Chip cell coordinates in §12.2, its atlas geometry in ART-1030 and its frame-table check in ART-1040 are all written to that shape. The drawing code is the same either way; where it runs is not. D-5's own open question 2 assigns the ownership question to 04-ui-ux.md, and that is where the answer should come from, because a build-time page cannot carry a translated label and that is half of D-5's argument. Whoever resolves it must edit that document's §10, §11 and §12 in one pass; this document's ART-140 and ART-145 are written to the runtime answer and will need nothing further.


Built from source 7f764a6c1ff9 · VERSION.json