Art Direction — Pour Decisions
The screen is about 80% simulated pixels. The art budget is spent only on: material palette, glow/post-processing, UI, icons, Steam capsule art. Hard rule: No generative AI images (triggers Steam disclosure); external assets only from the whitelist (
docs/research/03_assets_legal.md), and registered indocs/art/ASSET_LICENSES.md. Our strengths: shaders, procedural rendering, color math. Weakness: hand-drawn illustration → all decisions lean towards "compute it with code."
Extended references (read only when details are needed):
references/material-palette.md— Ramps, HEX, noise, and glow parameters for 14 materials (starting values)references/urp-postfx.md— URP Volume recipes, CRT shader specs, Pixel Perfect settingsreferences/ui-juice.md— Balatro UI breakdown, scoring animation timings, easing valuesreferences/steam-capsule.md— Capsule art specs, Valve rules, non-illustrator approach
1. Color Theory (Decide in This Order)
1.1 Value (Luminance) Before Hue
- Design the scene in grayscale first: convert to grayscale (
luma = 0.299R + 0.587G + 0.114B, or OKLab L), ensure background, materials, and UI have distinct layers. If grayscale doesn't separate them, changing hue won't help. - This game's luminance layers (target L, OKLab 0–1):
Layer L Range Content Background / Beaker interior 0.12–0.22 Dark, low saturation, cool purple Inert materials (stone, sand, gunpowder, seeds) 0.30–0.65 Mid luminance, mid-low saturation Fluids (water, oil, acid) 0.35–0.75 Mid luminance, distinguished by motion & transparency Reactions/glow (fire, lava, spark, score particles) 0.75–1.0 + HDR Brightest and most saturated on screen UI numbers/buttons 0.85–1.0 On separate panels, differentiated by panel background - Rule: The focal point must be the brightest, most saturated element. Focus = reaction location + score numbers. Nothing else should be brighter.
1.2 Hue Shifting
- A ramp (dark → light) does not just adjust luminance: light areas shift towards warm (yellow), dark areas shift towards cold (blue/purple). Shift 10–20° per step, with a 20° upper limit (Slynyrd Pixelblog 1).
- Do not use "add black" for shadows or "add white" for highlights → results in muddy, gray tones (pillow/muddy).
- Saturation curve: highest in the midtones, falling off at both ends but never reaching 0 or 100%; dark colors can be more saturated than light colors. Avoid simultaneous high saturation + high brightness (harsh), reserve that spot for emissive materials.
1.3 Saturation Hierarchy
- Saturation is the second attention magnet. Three tiers:
- High: Emissive materials, score effects, Mult numbers.
- Medium: Main colors of normal materials, interactive UI.
- Low: Background, beaker glass, panels, non-interactive text.
- Background saturation ≤ 25% (HSV S). The overall screen should be "mostly low saturation, with a few high saturation points" to create focus.
1.4 Limited Palette
- Use a single main palette; all materials, UI, and icons draw colors from it. Advantage: consistency is automatic, AI agents won't pick arbitrary colors.
- Starting point: Resurrect 64 (lospec.com/palette-list/resurrect-64), 64 colors, ramps already hue-shifted, dark tones lean purple, suitable for dark backgrounds + glow. Alternatives: Endesga 32, Apollo (46 colors), AAP-64. Palette values themselves are not copyrightable; Lospec page attribution is optional but low risk.
- Custom creation process (if needed):
- Pick a vivid midtone (S, B most important, H arbitrary).
- Expand 5–9 steps to both sides: B from dark to light (step size decreases near 100), S peaks at midtones, H +10~20° per step (shift warm towards light).
- Rotate the entire ramp by 45° H to create other color families.
- Duplicate the midtones, desaturate → neutral gray ramp (for UI/background).
- All color values in
Game/Assets/Art/Palette/palette.png(1px per color) + C#PaletteSO, code references only indices, never hardcoded HEX.
- Allowed exceptions: HDR glow values (>1.0) must be generated by multiplying palette color with intensity, not creating new colors.
2. Readability Rules
- Grayscale Test: Every color change: screenshot, convert to grayscale (QA checklist item #1).
- Adjacent materials must differ significantly in value or hue: ΔL (OKLab) ≥ 0.08 or Δhue ≥ 40°. Special handling for easily confused pairs:
- fire vs lava vs sand (all warm) → fire: brightest and moving, lava: dark red base with bright veins and slow, sand: non-emissive and mid luminance.
- acid vs plant (both green) → acid: yellow-green + slight glow + bubbles, plant: deep blue-green, non-emissive.
- steam vs smoke → steam: white, cool, rises fast; smoke: gray-purple, darker, spreads slowly.
- Color blindness: Cannot rely solely on red/green discrimination. Each material must have an additional non-color characteristic (motion, noise pattern, glow, transparency). Use Coblis or a shader to simulate deuteranopia.
- UI separated from game screen: Simulation area contains only material pixels; UI sits on panels outside the beaker, using neutral low-saturation colors + 1px border. UI never uses material colors as background (to avoid "Is this sand?"). Score numbers can float into the simulation area, but must have outline/shadow and exit within 0.6 seconds.
- Single focal point: Only one set of elements should "compete for attention" at a time. During mass reactions, reduce glow of secondary particles (see §4 glow budget).
- Silhouette: Beaker, buttons, icons must be recognizable in pure black silhouette.
3. Pixel Art Basics
- Fixed pixel scale: 1 simulation cell = 1 game pixel. UI pixel assets use the same scale (recommended sim resolution 320×180 or 384×216, integer scaled to 1080p/1440p/4K). Mixing different scales is forbidden.
- No mixels: No pixel asset may be scaled non-integer or rotated (rotation produces uneven pixels). Rotatable UI (card wobble) should use a high-resolution UI layer, or rotate in a low-resolution render texture then integer scale.
- Ramp: 3–5 colors per material (see references/material-palette.md). More than 5 colors are indistinguishable on 1px particles.
- Dithering correct usage: Only for (a) large area gradients (background, beaker glass reflections), (b) transparency illusion (smoke, steam using checkerboard/Bayer 4×4 alpha). Do not dither on small objects, icons, or text; do not use world-space dither on moving particles (it flickers); dither pattern must be locked to screen space or cell coordinates.
- Manual anti-aliasing: Only on diagonal/curved lines of UI icons and logos, place 1px of intermediate color between line and background. Do not AA outer contours against transparent backgrounds (dirty edges appear on different backgrounds).
- Banding: Avoid parallel, equal-width color bands following a contour (looks like topographic lines).
- Pillow shading forbidden: Light source fixed top-left; shadows are not "bright center, dark edges".
- Fonts: Pixel fonts only from whitelist (Cubic 11, Fusion Pixel Font, Kenney Fonts); display at integer multiples of native pixel size; TMP uses Point-sampled bitmap or high sampling SDF.
4. Coloring Falling Sand Particles
Observations from reference works (Noita, The Powder Toy, Sandspiel, Sandustry; some are inferred, marked "unverified"):
- Per-particle color noise: Store a random byte at particle creation (Sandspiel's
ra), use it during rendering to select a color or adjust brightness within the material's ramp → sand, stone have granularity. Noise fixed at creation, particle carries its color (do not re-randomize each frame, it flickers). - Solids use world-coordinate texture: Noita materials can specify
texture_file, solids sample texture based on world position; when turning into falling particles, retain the sampled color (inferred from wiki attributes, details unverified). Our stone/glass/ice can use cell-coordinate hash to sample ramp for stable texture. - Fluids rely on motion, not noise: water/oil noise amplitude small (±3% L), plus (a) a slightly brighter highlight on the surface cell, (b) subtle shimmer based on velocity/time.
- Emissive materials: Noita has
gfx_glow(0–255) andgfx_glow_color. Our approach: each cell has anemissionvalue, rendering outputs HDR color = palette color × (1 + emission × k), fed to URP Bloom. fire, lava, spark, acid (weak), glowing glass emit. - Temperature color: The Powder Toy has a heat display mode; we adopt the spirit: material temperature shifts along blackbody curve (dark red → orange → yellow → white) toward bright end, cools back to original ramp.
- Reaction flash: The cell where a reaction occurs gets maximum emission for that frame, decays over 2–4 frames → player "sees" the reaction. This is the most important readability feedback.
- Glow budget: If simultaneously glowing cells > N (starting 2,000), proportionally reduce each cell's emission to maintain overall brightness stability, avoid screen washout.
Implementation notes: sim data (material id, random byte, temperature, emission) packed into RGBA8 texture and uploaded; fragment shader queries a palette LUT (256×1 or materials×ramp small texture) to determine color. CPU does not calculate color.
5. Post-Processing (Unity 6 URP) Key Points
Full recipe in references/urp-postfx.md. Core:
- Camera HDR enabled; Bloom Threshold 1.0–1.1 (only HDR>1 emissive materials bloom, normal materials don't), Intensity 0.6–1.0 starting point, Scatter 0.6–0.75, High Quality Filtering on (reduces flickering).
- Tonemapping: Neutral (ACES changes hue, crushes palette). Or None + manual control.
- Color grading LUT: only subtle unification (cinematic "hearth glow" look), does not alter palette value structure;
Color Lookupcontribution ≤ 0.5. - CRT/scanline disabled by default, toggle & intensity in settings; Steam screenshots/trailers primarily "off", at most 1 screenshot with it on.
- Pixel Perfect Camera (built-in URP, not the old 2D Pixel Perfect package): Grid Snapping = Upscale Render Texture; Bloom applied at the upscaled resolution to avoid blocky halos.
6. UI Style (Balatro-style score-attack)
Full timing in references/ui-juice.md. Mandatory:
- Color is the scoring language: Chips = blue, Mult = red, Money = gold, ×Mult = red background white text. Fixed throughout the game, never repurposed.
- Itemized settlement: Reactions pop one by one (+chips / +mult), each with number pop (scale 1.0→1.3→1.0, overshoot easing), pitch ascending.
- Shake scales with magnitude: Larger scores = larger amplitude, longer duration; max 8px / 1°. Provide "reduce screen shake" option (accessibility).
- Numbers always readable: Thick outline (2px dark) + shadow; use scientific notation only after thousands separator.
- Juice must not obscure information: Particles and flashes must not cover chips×mult and target score.
7. Icons (16–32px)
- Create 16px version first, then upscale to 32px and add details (do not shrink 32px to 16px).
- One icon = one concept, one silhouette; 1px dark outline (palette's darkest color) ensures it works on any background.
- Each icon max 4–5 colors (outline, dark, mid, light, accent). Light source always top-left.
- Vial (material bottle) icons: uniform bottle shape, change only liquid color and one small symbol (flame, droplet, skull…) → players identify by color + symbol (colorblind friendly).
- Catalyst relics: shape based on rarity (circle / hexagon / star frame), frame color per rarity (gray, blue, purple, gold).
- Procedural generation: icons can be drawn using C# or Python with "shape + palette index" to output PNG, reproducible, batch-editable.
- Testing: view at actual UI background, 100% scale; at 50% scale, still distinguishable pairwise.
8. Steam Capsule Art
Full details in references/steam-capsule.md. Key points:
- Specs: Header 920×430, Small 462×174, Main 1232×706, Vertical 748×896, Library 600×900, Hero 3840×1240 (no text), Logo transparent PNG.
- Valve rules: Capsule can only contain game visuals/art and game name logo (and official subtitle); no ratings, awards, discount text, or other text.
- Our approach (non-illustrator friendly): Font-driven large logo + high-resolution simulation render (explosive reaction moment) + one clear focal point. Logo itself is falling sand: use simulation to "pour" letters or compose letters from different materials.
- Start with Small capsule (120×45 preview still must show logo clearly), then scale up.
9. Workflow Rules
- New color → add to palette file first, then reference; PR/commit description lists new indices.
- Any external asset (font, Kenney UI) → log into
docs/art/ASSET_LICENSES.md(filename, URL, author, license, date). - All procedural art (icons, logo, capsule art) keep generation scripts and parameters in
Game/Tools/ArtGen/ortools/, rerunnable. - Uncertain parameters marked "starting value", fill in references after prototype playtest.
10. Pre-Commit Visual QA Checklist
- Grayscale screenshot: background / materials / glow / UI four layers have distinct luminance
- Focal point (reaction site, score numbers) is the brightest and most saturated element on screen
- New colors all from palette (no hardcoded HEX)
- Adjacent easily confused materials (fire/lava/sand, acid/plant, steam/smoke) distinguishable at 100% and 50% scale
- Under colorblind simulation (deuteranopia, protanopia) materials and Chips/Mult still recognizable
- No mixels: pixel assets all integer scaled, not rotated
- Particle color noise does not flicker during movement
- Bloom: normal materials do not bleed; 500+ simultaneous reactions don't wash out screen (glow budget active)
- UI text has outline, remains readable above brightest explosions
- Screen shake ≤ 8px, "reduce shake" option off → no shake
- CRT filter off by default, toggle works
- Icons at native 16px size are distinguishable pairwise
- 1080p, 1440p, Steam Deck 1280×800 all have integer scaling or correct letterbox
- No generative AI output; new assets have registered licenses
- Screenshots saved to
docs/art/qa/<date>_<feature>.pngfor review
Sources
- Slynyrd Pixelblog 1 – Color Palettes (hue shift ≤20°, saturation curve): https://www.slynyrd.com/blog/2018/1/10/pixelblog-1-color-palettes
- Lospec Palette List: https://lospec.com/palette-list ; Resurrect 64: https://lospec.com/palette-list/resurrect-64 ; Endesga 32: https://lospec.com/palette-list/endesga-32 ; Apollo: https://lospec.com/palette-list/apollo ; AAP-64: https://lospec.com/palette-list/aap-64
- Lospec Pixel Art Tutorial Index: https://lospec.com/pixel-art-tutorials
- GameDev.net – Color Palettes: https://www.gamedev.net/articles/visual-arts/color-palettes-r4964/
- Noita wiki – Making a custom material (
texture_file,gfx_glow): https://noita.wiki.gg/wiki/Modding:_Making_a_custom_material - 80.lv – Noita falling sand simulation: https://80.lv/articles/noita-a-game-based-on-falling-sand-simulation/
- Making Sandspiel (per-particle
radetermines brightness, GPU coloring): https://maxbittker.com/making-sandspiel - The Powder Toy source code: https://github.com/The-Powder-Toy/The-Powder-Toy
- Unity URP Bloom: https://docs.unity3d.com/Packages/[email protected]/manual/post-processing-bloom.html
- Unity URP Pixel Perfect Camera: https://docs.unity3d.com/6000.0/Documentation/Manual/urp/2d-pixelperfect-ref.html
- Balatro feedback design breakdown: https://blakecrosley.com/guides/design/balatro
- Steamworks store image assets: https://partner.steamgames.com/doc/store/assets/standard
- presskit.gg Steam Capsule Art Guide: https://presskit.gg/field-guides/steam-capsule-art-guide
- Coblis color blindness simulator: https://www.color-blindness.com/coblis-color-blindness-simulator/