Raster unit art handoff

Status: active visual experiment only. The checked-in tank PNG rig path is enabled for a pass-11 white-painted Tiger I no-track hull/turret/barrel experiment, Rifleman pass 02 is enabled as a full-frame PNG strip, and Artillery uses the modular A-19 pass-02 alignment-review atlas. The generated images are not final game art. This note records what worked, what failed, and how to reproduce the next experiment without rediscovering the same traps.

Goal

The intended workflow is:

  1. Keep authoring unit rigs as SVG, because the SVG rig already carries anchors, part ids, draw order, tint slots, and animation bindings.
  2. Render one contact sheet containing the complete unit plus every component needed by the runtime rig.
  3. Feed that single sheet to an image generation pass so all components are restyled together.
  4. Slice the output back into a PNG atlas and metadata.
  5. Let Pixi render the PNG components while the SVG rig remains authoritative for animation, pivots, recoil, facing, selection anchors, and route split.

The current tank prototype proves the shape of this pipeline but does not yet produce an acceptable asset. Pass 11 is enabled only as a local visual experiment; it repaints the pass-10 no-guide Tiger I sheet as weathered matte white, bakes 30% lower brightness and 20% lower saturation, keeps the separate turret/barrel structure, maps sprite.barrel to the original barrel animation, removes visible guide boxes from the imagegen input, and relies on visible-alpha postprocessing for runtime sizing. Runtime owner color intentionally tints the dimmed white source art through the semantic atlas tint slots. It still needs component cleanup and alignment review before it should be treated as accepted art.

Rifleman pass 02 deliberately does not use component slicing yet. It ships as a compact six-frame full-body strip because the useful experiment is testing whether a generated infantry token reads at RTS scale in-game. Runtime frame 0 is the idle standing frame. Frames 1-4 cycle at 12 FPS only while the authoritative client entity state is move. Frame 5 is retained from the generated post-shot source but is not wired; firing and recoil remain future work. The old SVG rifleman rig still provides the shadow route and fallback while the PNG texture loads. Frame-strip art uses the shared default FRAME_STRIP_TARGET_COLOR_ADJUSTMENT in client/src/renderer/rigs/frame_strip_color_profile.js (brightness: 170, saturation: 118, hue: 100) and the brighter team-light tint slot so blue and orange owners read on dark terrain. Rifleman pass 02 already has that baseline baked into its checked-in runtime strip; raw frame strips such as Machine Gunner pass 01 receive the missing delta once at client texture-load time, preserving the original generated source sheets. A strip can override the target when that generated unit needs its own brightness match.

Browser-facing rig textures are 8-bit PNGs with neither dimension above 2048 pixels. Generated full-resolution sources stay beside their production derivatives when the runtime texture is the only reproducible source. The deploy-asset contract enforces both limits. Production derivatives may be downsampled independently as long as atlas frames, origins, pixels-per-unit, and frame-strip world scale preserve the authored world-space presentation.

Default color strategy for future generated PNG unit sprites: generate team-colorable paint, uniform, and armor regions as weathered matte white or off-white source art, not blue, gray-blue, or any final owner color. Keep dark outlines, panel seams, equipment details, and light gray shading so the white base stays readable before tinting. Runtime tint slots, color-profile adjustments, or future masks then apply owner color over the neutral white base. Fixed non-team materials such as rubber, dark weapon metal, wood, skin, and transparent chroma-key background can keep their normal material colors; the #ff00ff key remains only the background key, never the unit paint.

Current files

Current no-guide semantic sheet

The current tank sheet uses a 2x3 layout with no visible guide boxes, subgrid lines, center marks, or dividers. The cell order is metadata-driven and the model sees only the magenta background plus the component art:

  1. reference.full - assembled no-track tank reference, with the SVG drop shadow, tracks, fuel cue, and muzzle-flash effect parts removed.
  2. sprite.track - empty no-track placeholder used only so transparent PNG sprites can cover the SVG track/tread parts and suppress fallback track rendering.
  3. sprite.hull - hull, nose, hull shading parts, and nose tick.
  4. sprite.turret - turret and coax barrel, excluding the main barrel.
  5. sprite.barrel - separate main barrel mapped to part.barrel, preserving the original SVG barrel recoil scale.
  6. unused.blank - empty zone to keep the contact sheet rectangular.

The runtime atlas metadata still exposes sprite.track.left and sprite.track.right, but both sprites point at the same sprite.track source cell with different rig origins. That cell is intentionally blank in pass 11: it preserves PNG coverage for track source parts while suppressing both generated tracks and fallback SVG tracks. The fuel/no-oil cue is intentionally omitted from the PNG atlas and remains SVG-only through a separate overlay route.

The SVG is annotated with stable part.* ids, but those ids are not enough for image generation. Many individual SVG parts are rectangles, lines, or tiny treads with no independent semantic meaning. The important lesson is that each unit needs a semantic grouping layer before imagegen: turret assembly, hull assembly, left track assembly, right track assembly, weapon assembly, crew assembly, and so on. The grouping can be derived from existing ids for simple rigs, but it cannot be assumed from raw SVG layers alone.

Reproduce the current prototype

Requirements:

Generate the semantic source sheet:

node scripts/art/tank-raster-pipeline.mjs make-sheet \
  --scale 3 \
  --columns 2 \
  --layout tight \
  --profile semantic \
  --guides none

Refresh the base prompt:

node scripts/art/tank-raster-pipeline.mjs write-prompt

Before any image generation pass, present the exact contact sheet image to the user and wait for explicit confirmation. Do not start imagegen from a new, edited, cropped, or regenerated sheet until the user has seen that specific sheet and approved it as the input.

Use client/assets/rigs/tank-ps1/tank-contact-sheet.png as the input image for a new structural generation pass. For a repaint pass like pass11-white, use the prior generated sheet (client/assets/rigs/tank-ps1/generated/tank-tiger-i-pass-10-noguide-ref.png) as the edit target and constrain the model to preserve the existing 2x3 layout. Start from the latest short prompt direction rather than the most detailed prompt: strict top-down Tiger I, no shadows, very simple low-end 3D raster rendering, anti-aliased raster shapes, not pixel art, and much less detail than concept art.

Save each generated candidate with a pass number, for example:

client/assets/rigs/tank-ps1/generated/tank-tiger-i-pass-11-white.png
client/assets/rigs/tank-ps1/metadata/prompt-tiger-i-pass-11-white.md
client/assets/rigs/tank-ps1/metadata/tiger-i-pass-11-white.json

Convert the chroma-key background to alpha before atlas wiring:

python "${CODEX_HOME:-$HOME/.codex}/skills/.system/imagegen/scripts/remove_chroma_key.py" \
  --input client/assets/rigs/tank-ps1/generated/tank-tiger-i-pass-11-white.png \
  --out client/assets/rigs/tank-ps1/generated/tank-tiger-i-pass-11-white-alpha.png \
  --key-color '#ff00ff' \
  --auto-key none \
  --soft-matte \
  --transparent-threshold 58 \
  --opaque-threshold 220 \
  --despill

Use an explicit #ff00ff key when the generated background has mild magenta variation. Wider thresholds may be needed because the model sometimes adds subtle background gradients even when asked for a flat chroma key.

Write atlas metadata disabled while evaluating. For no-guide sheets, use visible-alpha normalization so runtime scale comes from the generated component bounds rather than the whole cell:

node scripts/art/tank-raster-pipeline.mjs write-atlas \
  --sheet client/assets/rigs/tank-ps1/generated/tank-tiger-i-pass-11-white-alpha.png \
  --columns 2 \
  --layout tight \
  --profile semantic \
  --normalize-visible-bounds \
  --clear-cell-edge-alpha 0 \
  --visible-padding 0 \
  --disabled \
  --model "built-in image generation" \
  --notes "Candidate Tiger I raster pass; disabled pending alignment and component consistency review."

For a local no-track experiment like pass 11, also blank the reference, track-placeholder, and unused cells, then add an image version so a browser reload fetches the updated atlas:

node scripts/art/tank-raster-pipeline.mjs write-atlas \
  --sheet client/assets/rigs/tank-ps1/generated/tank-tiger-i-pass-11-white-alpha.png \
  --columns 2 \
  --layout tight \
  --profile semantic \
  --blank-cells reference.full,sprite.track,unused.blank \
  --normalize-visible-bounds \
  --clear-cell-edge-alpha 0 \
  --visible-padding 0 \
  --world-scale 1.2 \
  --brightness 70 \
  --saturation 80 \
  --image-version pass11-white-dim30 \
  --prompt-file client/assets/rigs/tank-ps1/metadata/prompt-tiger-i-pass-11-white.md \
  --model "built-in image generation + chroma-key cleanup + ImageMagick dimming pass" \
  --notes "Experimental Tiger I white-painted no-track no-guide raster pass 11 with 1.2x world-scale compensation, 30% lower brightness, and 20% lower saturation, using runtime team tint over the white base."

Only omit --disabled for a local experiment after the validation checklist passes. Omit --semantic-paint-tint-slot when the white source art should still receive owner team tint; reserve the option for a future pass that deliberately bakes final paint colors into the generated image. Do not commit an enabled atlas unless the component cells can actually reconstruct the tank.

Prompt lessons

Use concise prompts. Over-specified realism prompts pushed the model toward tiny hatches, grilles, scratches, bolts, and independent redesigns. The better direction is:

The prompt must say that the complete tank cell is a reference only. Runtime truth should come from the component cells. A generated complete tank that does not match the generated components should be rejected even if it looks good.

What failed

Raw exploded SVG layers were technically faithful but visually useless for imagegen. The treads and small pieces looked like unrelated rectangles, so the model had no context for what they were.

A compact/reused sheet was worse. Because it did not show every real component with context, the model invented sprockets, gears, road wheels, and other exposed mechanisms that were not present in the source SVG.

The original full-tank reference included a drop shadow. That made the whole sheet read like a rendered object on a floor and encouraged shadow artifacts. The current semantic reference removes the drop shadow and the fuel cue.

The model treated the complete tank and the components as separately designed objects. Several passes had a plausible full tank in the top-left cell, but the generated component cells would not assemble into that tank. This is the main unsolved issue.

Visible guide boxes helped layout but hurt the art. They repeatedly came back as baked guide-color slivers, black frame remnants, or grid-inspired seams. Pass 10 removes visible guides from the imagegen input and lets atlas postprocessing do the sizing.

Detailed prompts produced too much detail. Tiger I pass 01 and pass 03 had a better tank read, but they were closer to detailed concept sprites than the requested simple low-end raster look.

The best style direction so far was pass 04: simpler, lower-detail, broad shapes. It still was not usable because orientation and component consistency drifted.

The current prototype does not have automatic validation. Human inspection caught layout, orientation, detail, shadow, and component consistency failures after generation. Future work needs cheap checks before any generated atlas can be enabled.

Candidate pass log

These candidates are useful references for what to avoid. None should be treated as accepted art.

Validation checklist before activation

Next work