# Godot 4 — Color & Mood

> **Purpose**: Reference document for Claude Code. Specific palettes, color-shifting techniques, and shader values that create mood in 2D games. The marinade — not color theory basics.

---

## 1. Limited Palette Techniques

### The "3 + Accent" Rule

Every scene should have **3 base colors + 1 accent**. The base colors define the environment's mood. The accent screams "look here" — interactive elements, danger, collectibles.

**Distribution rule**: never use colors in equal amounts. One color dominates (60-70% of pixels), a second supports (20-25%), a third adds depth (10-15%), and the accent punctuates (< 5%).

Think of it as voices in a room. One voice speaks, others murmur, and one shouts when it matters.

**Game examples**:
- **Downwell**: Black background, white outlines, red accent for gems/enemies/important objects. 3 colors total. Readability is instant because the accent ratio is tiny
- **Inside**: Desaturated grays everywhere, controlled lighting, with subtle red touches. The accent is so rare that when it appears, you physically notice
- **Ori and the Blind Forest**: Analogous palette (greens + blues) for nature/safety, pink accent for danger. The accent breaks the harmony intentionally

### The Pico-8 Constraint (16 Colors)

Why 16 colors produces better art than unlimited: every pixel stored as a 4-bit value forces artists to choose from a shared vocabulary. The result — automatic cohesion. Colors you mix from the same limited set are "automatically related" and harmonious.

The Pico-8 palette hex values:

| Index | Hex | Role |
|-------|-----|------|
| 0 | `#000000` | Black — void, background |
| 1 | `#1D2B53` | Dark blue — night, deep water |
| 2 | `#7E2553` | Dark magenta — corruption, menace |
| 3 | `#008751` | Dark green — nature, health |
| 4 | `#AB5236` | Brown — earth, wood, warmth |
| 5 | `#5F574F` | Dark gray — stone, neutrality |
| 6 | `#C2C3C7` | Light gray — mist, ghosts |
| 7 | `#FFF1E8` | Off-white — purity, highlights |
| 8 | `#FF004D` | Red — danger, blood, urgency |
| 9 | `#FFA300` | Orange — fire, energy, warmth |
| 10 | `#FFEC27` | Yellow — gold, electricity, joy |
| 11 | `#00E436` | Green — life, healing, go |
| 12 | `#29ADFF` | Blue — water, UI, calm |
| 13 | `#83769C` | Lavender — magic, mystery |
| 14 | `#FF77A8` | Pink — love, friendly, soft |
| 15 | `#FFCCAA` | Peach — skin, warmth, comfort |

**Practical workflow**: start with a Pico-8-style constraint. Pick 8-12 colors max. Build your entire scene. Add colors only when you can justify why the existing ones fail.

### Dead Cells: Saturation as Mood Engine

Dead Cells contradicts the "dark game = desaturated" orthodoxy. The key technique: **saturate the midtones, not the highlights or shadows**.

- Backgrounds: low contrast, colors fuse together — no sharp ruptures
- Interactive elements (ladders, platforms): medium contrast
- Threats (enemies, hazards): high saturation + high brightness + high contrast

Per-biome palette strategy:
- **Interior levels** (Toxic Sewers, Prison): complementary palettes (opposing hues) to create confinement and tension
- **Outdoor levels** (Promenade, Ramparts): analogous palettes (adjacent hues) for landscape depth and openness
- Both use the **3-layer readability stack**: bg fused < interactable distinct < threat screaming

### Celeste: Per-Chapter Emotional Palettes

Celeste assigns each chapter a distinct color identity that mirrors Madeline's emotional state:

- **Forsaken City (Ch.1)**: Cool blues and purples, desaturated. Isolation, uncertainty. Madeline's red hair = the only warm accent, always visible = you are the only warmth in this cold world
- **Old Site (Ch.2)**: Warmer tones, browns and ambers. Nostalgia, familiarity mixed with decay
- **Celestial Resort (Ch.3)**: Pinks and magentas against dark backgrounds. Anxiety, artificial hospitality
- **Golden Ridge (Ch.4)**: Warm golds and oranges at base, shifting to cold whites at altitude. Physical and emotional ascent
- **Mirror Temple (Ch.5)**: Deep purples and cyans. Introspection, confrontation with self
- **Core (Ch.8)**: Extreme reds and oranges. Emotional climax, determination, fire

The technique: background tiles all share a "dark infill" color per chapter (the inner solid). Trees and decoration use more vibrant but still desaturated colors (atmospheric interference). Solid gameplay tiles are the most saturated/contrasted — readability never sacrificed for mood.

### Hollow Knight: Area Color Identity

Each area in Hollow Knight uses a dominant hue family that you recognize within seconds:

**Greenpath** — Extracted palette (from Hornet fight):
`#b2d2ad` `#628e88` `#4f787a` `#336d65` `#34785d` `#144d40` (greens)
`#454869` `#aca9c8` `#797188` (purple-grays for depth)
`#6af9ff` (cyan accent — Hornet, Soul)
`#fbffff` (highlight)
`#212660` `#192656` `#172c47` `#051022` `#000000` (deep backgrounds)

**City of Tears** — Blues and blue-grays dominating. Western sections colder, eastern sections introduce red/purple (infection creeping in). Rain particles add constant value noise.

**Deepnest** — Near-black with dim bioluminescent accents. The palette is almost monochromatic darkness. Enemies are brighter than the environment = isolated, lonely feeling.

**Crystal Peak** — Cold whites and pinks against deep purples. Crystalline, alien, beautiful but hostile.

The universal technique: enemies and hazards are **always brighter and more saturated** than their environment. This is the "voice hierarchy" again — the environment whispers, the danger shouts.

---

## 2. Mood Palettes (Hex Values)

### Cozy / Safe

| Role | Hex | Color() | Usage |
|------|-----|---------|-------|
| Base warm | `#2B1B17` | `Color(0.17, 0.11, 0.09)` | Dark wood, deep shadow |
| Mid warm | `#8B5E3C` | `Color(0.55, 0.37, 0.24)` | Wood, earth, furniture |
| Light warm | `#F5DEB3` | `Color(0.96, 0.87, 0.70)` | Candlelight, parchment |
| Accent | `#FF9B50` | `Color(1.0, 0.61, 0.31)` | Fire glow, lantern, interactive |
| Support cool | `#4A6741` | `Color(0.29, 0.40, 0.25)` | Plants, nature, calm |

Feeling: hearth warmth, cabin, "you're home". Low contrast between base and mid. Accent = fire.

### Menacing / Hostile

| Role | Hex | Color() | Usage |
|------|-----|---------|-------|
| Base dark | `#0A0A12` | `Color(0.04, 0.04, 0.07)` | Void, deep shadow |
| Mid cold | `#1A1A3E` | `Color(0.10, 0.10, 0.24)` | Stone, architecture |
| Blood | `#8B0000` | `Color(0.55, 0.0, 0.0)` | Danger, blood, wounds |
| Accent hot | `#FF2200` | `Color(1.0, 0.13, 0.0)` | Eyes, fire, threat |
| Bruise | `#3D1A4A` | `Color(0.24, 0.10, 0.29)` | Corruption, magic |

Feeling: dread, "something is wrong". High contrast between dark base and accent. Red = immediate threat.

### Melancholic / Sad

| Role | Hex | Color() | Usage |
|------|-----|---------|-------|
| Base | `#1C2333` | `Color(0.11, 0.14, 0.20)` | Heavy sky, weight |
| Mid blue | `#4A6A8A` | `Color(0.29, 0.42, 0.54)` | Rain, stone, distance |
| Pale | `#A8B8C8` | `Color(0.66, 0.72, 0.78)` | Mist, ghosts, memory |
| Desaturated warm | `#8A7B6B` | `Color(0.54, 0.48, 0.42)` | Old wood, decay |
| Accent muted | `#6B8A9C` | `Color(0.42, 0.54, 0.61)` | Water, tears |

Feeling: rain on a window, loss. Everything is desaturated. Even the "warm" color is cold. No true bright accent — nothing demands attention.

### Ethereal / Mystical

| Role | Hex | Color() | Usage |
|------|-----|---------|-------|
| Base deep | `#0D0B1A` | `Color(0.05, 0.04, 0.10)` | Cosmic void |
| Mid purple | `#2D1B4E` | `Color(0.18, 0.11, 0.31)` | Magic, unknown |
| Glow cyan | `#00E5FF` | `Color(0.0, 0.90, 1.0)` | Soul, energy, portals |
| Glow pink | `#FF69B4` | `Color(1.0, 0.41, 0.71)` | Life, warmth in void |
| Star white | `#E8E0F0` | `Color(0.91, 0.88, 0.94)` | Particles, highlights |

Feeling: floating, wonder, "bigger than me". Deep purple base with high-saturation neon accents. The contrast between void-dark and neon-bright creates the sense of impossible light sources.

### Chaotic / Frantic

| Role | Hex | Color() | Usage |
|------|-----|---------|-------|
| Base | `#1A0A0A` | `Color(0.10, 0.04, 0.04)` | Burnt ground |
| Hot orange | `#FF6600` | `Color(1.0, 0.40, 0.0)` | Explosions, urgency |
| Electric yellow | `#FFE600` | `Color(1.0, 0.90, 0.0)` | Sparks, speed |
| Toxic green | `#39FF14` | `Color(0.22, 1.0, 0.08)` | Acid, poison, chaos |
| Warning red | `#FF0033` | `Color(1.0, 0.0, 0.20)` | Damage, critical |

Feeling: sensory overload, panic. Multiple high-saturation accents competing for attention. This palette breaks the "one accent" rule deliberately — chaos means no visual hierarchy.

---

## 3. Color Temperature for Emotion

### Temperature Hex Reference

**Warm spectrum** (safety, comfort, nostalgia, friendship):

| Temperature | Hex | Color() | Emotion |
|-------------|-----|---------|---------|
| Hot | `#FF4500` | `Color(1.0, 0.27, 0.0)` | Passion, danger-warmth |
| Warm | `#FF8C00` | `Color(1.0, 0.55, 0.0)` | Energy, sunset |
| Amber | `#FFB347` | `Color(1.0, 0.70, 0.28)` | Lantern, safe fire |
| Golden | `#FFD700` | `Color(1.0, 0.84, 0.0)` | Treasure, divine |
| Soft warm | `#FFECD2` | `Color(1.0, 0.93, 0.82)` | Candlelight, home |

**Cool spectrum** (mystery, sadness, danger, isolation):

| Temperature | Hex | Color() | Emotion |
|-------------|-----|---------|---------|
| Deep cold | `#0D1B2A` | `Color(0.05, 0.11, 0.16)` | Abyss, fear |
| Night blue | `#1B2838` | `Color(0.11, 0.16, 0.22)` | Night, unknown |
| Steel | `#415A77` | `Color(0.25, 0.35, 0.47)` | Industrial, harsh |
| Ice | `#778DA9` | `Color(0.47, 0.55, 0.66)` | Cold, distant |
| Moonlight | `#E0E1DD` | `Color(0.88, 0.88, 0.87)` | Ghost, ethereal |

**Neutral / Desaturated** (tension, limbo, exhaustion):

| Temperature | Hex | Color() | Emotion |
|-------------|-----|---------|---------|
| Ash | `#2C2C2C` | `Color(0.17, 0.17, 0.17)` | Death, void |
| Concrete | `#4A4A4A` | `Color(0.29, 0.29, 0.29)` | Urban, institutional |
| Fog | `#7A7A7A` | `Color(0.48, 0.48, 0.48)` | Limbo, confusion |
| Dust | `#A09080` | `Color(0.63, 0.56, 0.50)` | Decay, abandonment |
| Bone | `#D4C9B8` | `Color(0.83, 0.79, 0.72)` | Exhaustion, bleached |

### Temperature Shift During Gameplay

The principle: **warm = safe, cold = danger, desaturated = dying**. Transition smoothly between these as game state changes.

Implementation pattern in GDScript:

```gdscript
## Tween the CanvasModulate color based on danger level (0.0 = safe, 1.0 = critical)
func update_mood(danger: float) -> void:
    var safe_color := Color(1.0, 0.95, 0.85)      # warm off-white
    var danger_color := Color(0.55, 0.60, 0.75)    # cold blue-gray
    var critical_color := Color(0.40, 0.35, 0.35)  # desaturated dark

    var target: Color
    if danger < 0.6:
        target = safe_color.lerp(danger_color, danger / 0.6)
    else:
        target = danger_color.lerp(critical_color, (danger - 0.6) / 0.4)

    var tween := create_tween()
    tween.tween_property($CanvasModulate, "color", target, 2.0).set_ease(Tween.EASE_IN_OUT)
```

**Timing**: 2-3 seconds for gradual mood shifts. 0.3-0.5s for sudden danger spikes. Never instant — the player should feel the world changing, not see a jump cut.

---

## 4. Color as Game State Signal

### Health State Color Grading

As player health drops, the world visually degrades:

| Health % | Saturation | Vignette Intensity | Vignette Color | Red Tint |
|----------|------------|-------------------|----------------|----------|
| 100-70% | 1.0 (full) | 0.0 (none) | N/A | 0.0 |
| 70-40% | 0.85 | 0.15 | `Color(0.0, 0.0, 0.0)` | 0.0 |
| 40-20% | 0.60 | 0.30 | `Color(0.3, 0.0, 0.0)` | 0.05 |
| 20-10% | 0.35 | 0.50 | `Color(0.5, 0.0, 0.0)` | 0.12 |
| < 10% | 0.20 | 0.65 | `Color(0.7, 0.0, 0.0)` | 0.20 |

The desaturation curve is **not linear** — it accelerates below 40%. The player barely notices the first 30% loss, then the world visibly drains. This matches the "panic point" where players start making mistakes.

Red tint: apply as a screen-wide overlay at low opacity. Never go above 0.25 — too much red = the player thinks their screen is broken, not that their character is hurt.

### Day/Night Cycle (CanvasModulate Values)

Specific `Color()` values for a full day/night cycle:

| Phase | Color() | Hex | Feeling |
|-------|---------|-----|---------|
| Dawn (6h) | `Color(0.90, 0.75, 0.65)` | `#E6BFA6` | Warm, waking, gentle |
| Morning (9h) | `Color(1.0, 0.98, 0.92)` | `#FFFAEB` | Bright, energetic, clear |
| Midday (12h) | `Color(1.0, 1.0, 1.0)` | `#FFFFFF` | Full visibility, neutral |
| Afternoon (15h) | `Color(1.0, 0.95, 0.85)` | `#FFF2D9` | Golden hour starting |
| Sunset (18h) | `Color(0.99, 0.85, 0.75)` | `#FCD9BF` | Warm, melancholic, ending |
| Dusk (20h) | `Color(0.60, 0.50, 0.65)` | `#9980A6` | Purple, transitional, uneasy |
| Night (22h) | `Color(0.25, 0.28, 0.45)` | `#404773` | Blue, mysterious, vulnerable |
| Deep night (2h) | `Color(0.15, 0.17, 0.30)` | `#262B4D` | Near-black blue, isolation |
| Pre-dawn (5h) | `Color(0.40, 0.35, 0.50)` | `#665980` | Cold purple, anticipation |

Transition with `lerp()` between phases. Duration per transition: 3-5 seconds for fast game cycles, 30-60 seconds for realistic feel.

### Corruption / Curse Visual Progression

How to visually show world state degradation:

| Corruption % | Technique | Values |
|--------------|-----------|--------|
| 0-20% | Subtle hue shift toward purple | Hue offset +0.02 per 10% |
| 20-40% | Desaturation begins + green channel reduction | Sat 0.85, green mult 0.90 |
| 40-60% | Vignette appears + noise grain | Vignette 0.20, grain 0.03 |
| 60-80% | Strong purple tint + pulsing brightness | Purple overlay 0.15 alpha, brightness oscillates +/- 0.05 at 0.5Hz |
| 80-100% | Near-monochrome purple + chromatic aberration | Sat 0.25, purple tint 0.30 alpha, chroma offset 2-4px |

The key: corruption is **purple and green** across games (Hollow Knight infection = orange, but the universal default = purple/green). Purple = royal, unnatural, wrong. Green = toxic, sick, organic decay.

### Safe Zone vs Danger Zone

Subtle temperature shift — the player shouldn't consciously notice, just feel:

| Zone | CanvasModulate | Saturation | Notes |
|------|---------------|------------|-------|
| Safe (hub, shop) | `Color(1.0, 0.97, 0.92)` | 1.0 | Slightly warm, full color |
| Neutral (exploration) | `Color(1.0, 1.0, 1.0)` | 1.0 | True white, no bias |
| Tense (pre-boss area) | `Color(0.92, 0.90, 0.95)` | 0.90 | Slightly cool, slight desat |
| Danger (boss arena) | `Color(0.85, 0.82, 0.90)` | 0.80 | Noticeably cool, desaturated |
| Critical (boss phase 2) | `Color(0.75, 0.70, 0.80)` | 0.65 | Cold, drained |

The shift from safe to danger is a temperature drop of ~0.15 on the red channel and ~0.10 saturation. Subtle enough that the player won't say "the screen changed color" — they'll say "something feels wrong."

---

## 5. Shader Techniques for Mood Shifts

### Full Color Grading Shader (Godot 4)

Apply to a fullscreen `ColorRect` for scene-wide color grading:

```gdshader
shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;

// Color grading controls
uniform float brightness : hint_range(-0.5, 0.5) = 0.0;
uniform float contrast : hint_range(0.5, 2.0) = 1.0;
uniform float saturation : hint_range(0.0, 2.0) = 1.0;

// Temperature: negative = cool (blue shift), positive = warm (orange shift)
uniform float temperature : hint_range(-1.0, 1.0) = 0.0;

// Tint overlay
uniform vec4 tint_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform float tint_strength : hint_range(0.0, 1.0) = 0.0;

void fragment() {
    vec4 color = texture(screen_texture, SCREEN_UV);

    // Brightness
    color.rgb += brightness;

    // Contrast (pivot at 0.5 gray)
    color.rgb = (color.rgb - 0.5) * contrast + 0.5;

    // Saturation (luminance-preserving)
    float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114));
    color.rgb = mix(vec3(luma), color.rgb, saturation);

    // Temperature shift
    color.r += temperature * 0.1;
    color.b -= temperature * 0.1;

    // Tint overlay
    color.rgb = mix(color.rgb, tint_color.rgb, tint_strength);

    COLOR = clamp(color, 0.0, 1.0);
}
```

**Typical values by mood**:

| Mood | Brightness | Contrast | Saturation | Temperature | Tint |
|------|-----------|----------|------------|-------------|------|
| Normal | 0.0 | 1.0 | 1.0 | 0.0 | none |
| Cozy indoor | 0.02 | 0.95 | 0.9 | 0.3 | none |
| Night outdoor | -0.08 | 1.1 | 0.7 | -0.3 | none |
| Low health | -0.05 | 1.2 | 0.4 | -0.1 | red 0.08 |
| Memory/flashback | 0.05 | 0.85 | 0.3 | 0.2 | sepia 0.15 |
| Corruption | -0.03 | 1.15 | 0.5 | -0.2 | purple 0.12 |
| Boss rage phase | 0.0 | 1.3 | 1.2 | 0.4 | red-orange 0.05 |

### Vignette Shader (Godot 4)

```gdshader
shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;
uniform float vignette_intensity : hint_range(0.0, 1.0) = 0.4;
uniform float vignette_softness : hint_range(0.0, 1.0) = 0.5;
uniform vec4 vignette_color : source_color = vec4(0.0, 0.0, 0.0, 1.0);

void fragment() {
    vec4 color = texture(screen_texture, SCREEN_UV);

    // Radial distance from center (0.0 at center, ~0.7 at corners)
    vec2 uv = SCREEN_UV;
    uv *= 1.0 - uv;
    float vig = uv.x * uv.y * 15.0;
    vig = pow(vig, vignette_softness);

    // Apply vignette
    float vignette = clamp(1.0 - vig * (1.0 - vignette_intensity), 0.0, 1.0);
    color.rgb = mix(color.rgb, vignette_color.rgb, vignette);

    COLOR = color;
}
```

**Vignette intensity by context**:

| Context | Intensity | Softness | Color |
|---------|-----------|----------|-------|
| Normal gameplay | 0.0 | -- | -- |
| Slight tension | 0.15 | 0.5 | Black |
| Entering boss room | 0.25 | 0.4 | Dark purple `#1A0020` |
| Low health | 0.35 | 0.3 | Dark red `#200000` |
| Critical health | 0.55 | 0.2 | Red `#400000` |
| Near death | 0.65 | 0.15 | Bright red `#600000` |
| Death/game over | 0.85 | 0.1 | Black |

### Desaturation Shader

Luminance-weighted desaturation that respects human color perception:

```gdshader
shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;
uniform float saturation : hint_range(0.0, 1.0) = 1.0;

void fragment() {
    vec4 color = texture(screen_texture, SCREEN_UV);
    // Rec. 709 luminance weights
    float luma = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
    color.rgb = mix(vec3(luma), color.rgb, saturation);
    COLOR = color;
}
```

**Desaturation amounts by purpose**:
- 0.85 — barely noticeable, "something's off" (tense exploration)
- 0.70 — clearly desaturated, somber mood (rain scene, sad cutscene)
- 0.50 — dramatic, the world is draining (corruption spreading)
- 0.30 — near-grayscale, crisis state (near death, flashback)
- 0.10 — almost fully gray, time-stop or death screen
- 0.0 — full grayscale (photo mode, pause screen, death confirm)

### Film Grain for Memory/Flashback

```gdshader
shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;
uniform float grain_amount : hint_range(0.0, 0.5) = 0.05;
uniform float grain_size : hint_range(0.5, 5.0) = 1.5;

void fragment() {
    vec4 color = texture(screen_texture, SCREEN_UV);

    // Animated noise
    vec2 noise_uv = SCREEN_UV * grain_size;
    float noise = fract(sin(dot(noise_uv, vec2(12.9898, 78.233)) + TIME * 5.0) * 43758.5453);
    noise = (noise - 0.5) * 2.0;

    color.rgb += noise * grain_amount;
    COLOR = clamp(color, 0.0, 1.0);
}
```

**Grain amounts by effect**:

| Effect | Amount | Size | Combine with |
|--------|--------|------|-------------|
| Subtle atmosphere (rain) | 0.02 | 1.0 | None |
| Old film / memory | 0.08 | 1.5 | Desaturation 0.35, warm temp 0.2 |
| Nightmare / horror | 0.12 | 2.0 | Desaturation 0.50, cold temp -0.3 |
| VHS / glitch | 0.20 | 3.0 | Chromatic aberration, scanlines |
| Death / fade out | 0.15 | 1.5 | Full desaturation, high vignette |

### Corruption Shader (Sprite-Level)

Apply to individual sprites to show corruption spreading on objects/characters:

```gdshader
shader_type canvas_item;

uniform sampler2D noise_texture;
uniform float corruption_amount : hint_range(0.0, 1.0) = 0.0;
uniform vec4 corruption_color : source_color = vec4(0.3, 0.0, 0.5, 1.0);

void fragment() {
    vec4 tex = texture(TEXTURE, UV);

    // Animate noise sampling
    vec2 noise_uv = UV + vec2(sin(TIME * 0.3) * 0.1, cos(TIME * 0.2) * 0.1);
    float noise = texture(noise_texture, noise_uv).r;

    // Corruption mask: noise threshold based on amount
    float mask = step(noise, corruption_amount);

    // Desaturate corrupted pixels
    float luma = dot(tex.rgb, vec3(0.299, 0.587, 0.114));
    vec3 desaturated = mix(tex.rgb, vec3(luma), 0.7);

    // Tint corrupted pixels
    vec3 corrupted = mix(desaturated, corruption_color.rgb, 0.5);

    tex.rgb = mix(tex.rgb, corrupted, mask);
    COLOR = tex;
}
```

### LUT-Based Color Grading

For complex mood shifts, a LUT (Look-Up Table) texture is more expressive than shader math. The workflow:

1. Take an in-game screenshot
2. Open it in Krita/Photoshop with a neutral LUT overlay (256x16 strip)
3. Apply color grading to the whole image (including the LUT strip)
4. Crop the modified LUT strip
5. Use it as a texture in a LUT shader

This allows per-biome/per-mood grading that a single shader with uniforms can't achieve. Swap LUT textures to change the entire mood instantly.

In Godot 4, use the [godot-color-lut-shader](https://github.com/thiagoamendola/godot-color-lut-shader) addon or [Godot-4-Color-Correction-and-Screen-Effects](https://github.com/ArseniyMirniy/Godot-4-Color-Correction-and-Screen-Effects) for production-ready implementations.

---

## 6. UI Color Language

### Rarity Tiers

Convention established by Diablo (1996), standardized by World of Warcraft, now universal:

| Tier | Color | Hex | Color() | Psychological basis |
|------|-------|-----|---------|-------------------|
| Common | White/Gray | `#B0B0B0` | `Color(0.69, 0.69, 0.69)` | Mundane, unremarkable |
| Uncommon | Green | `#1EFF00` | `Color(0.12, 1.0, 0.0)` | Growth, "better than nothing" |
| Rare | Blue | `#0070FF` | `Color(0.0, 0.44, 1.0)` | Royal blue = value, trust |
| Epic | Purple | `#A335EE` | `Color(0.64, 0.21, 0.93)` | Royalty, magic, supernatural |
| Legendary | Orange/Gold | `#FF8000` | `Color(1.0, 0.50, 0.0)` | Fire, sun, divine power |
| Mythic/Artifact | Red/Crimson | `#E6CC80` | `Color(0.90, 0.80, 0.50)` | Blood, sacrifice, ultimate |

**Why this order works**: it follows the light spectrum from low-energy (gray) to high-energy (gold). Each step is a perceptual jump in saturation and "specialness." Purple specifically triggers "unnatural" — it's the least common color in nature.

**Design trap**: don't use rarity colors for other purposes. If green means "uncommon loot," don't use the same green for "heal." The player's brain has already mapped green = loot tier.

### Damage Type Colors

| Type | Primary Hex | Secondary Hex | Color() Primary |
|------|------------|---------------|----------------|
| Physical | `#C0C0C0` | `#808080` | `Color(0.75, 0.75, 0.75)` |
| Fire | `#FF4500` | `#FF8C00` | `Color(1.0, 0.27, 0.0)` |
| Ice/Frost | `#00BFFF` | `#87CEEB` | `Color(0.0, 0.75, 1.0)` |
| Lightning | `#FFD700` | `#FFFF00` | `Color(1.0, 0.84, 0.0)` |
| Poison | `#32CD32` | `#7FFF00` | `Color(0.20, 0.80, 0.20)` |
| Holy/Light | `#FFFACD` | `#FFD700` | `Color(1.0, 0.98, 0.80)` |
| Dark/Necrotic | `#4B0082` | `#800080` | `Color(0.29, 0.0, 0.51)` |
| Arcane | `#DA70D6` | `#FF00FF` | `Color(0.85, 0.44, 0.84)` |
| Nature/Earth | `#8B4513` | `#228B22` | `Color(0.55, 0.27, 0.07)` |
| Wind | `#B0E0E6` | `#AFEEEE` | `Color(0.69, 0.88, 0.90)` |

**Primary** = damage numbers, particles, UI icon background. **Secondary** = trails, ambient glow, highlights.

### Feedback Colors (Beyond Red/Green)

The red-damage/green-heal binary works but has subtler alternatives:

| Feedback | Simple | Refined | Why refined works |
|----------|--------|---------|-------------------|
| Heal | Green `#00FF00` | Soft gold `#FFD700` | Gold = divine/positive without colorblind conflict |
| Damage taken | Red `#FF0000` | White flash + red fade | Flash = impact, fade = the actual hurt |
| Shield/block | Blue `#0088FF` | Cyan `#00E5FF` | Cyan = tech/energy/barrier, distinct from ice |
| Buff | Green `#00FF88` | Warm glow `#FFAA00` | Warm = empowerment, avoids heal confusion |
| Debuff | Red `#FF4444` | Purple `#9933FF` | Purple = unnatural/wrong, distinct from damage |
| XP/progress | Yellow `#FFFF00` | Soft white `#E8E0F0` | White = pure gain, doesn't compete with gold/loot |
| Critical hit | Bright orange | White `#FFFFFF` + scale 1.5x | Size > color for crits. Color gets lost in chaos |

---

## 7. Combining Techniques (Stack Examples)

### Stack: "Entering a Boss Arena"

Trigger sequence over 2-3 seconds as player crosses threshold:

1. CanvasModulate: `Color(1.0, 1.0, 1.0)` --> `Color(0.85, 0.82, 0.90)` (cool shift)
2. Color grading: saturation 1.0 --> 0.80, contrast 1.0 --> 1.15
3. Vignette: 0.0 --> 0.25, color = dark purple `#1A0020`
4. Music: crossfade to boss theme
5. PointLight2D: reduce ambient lights, focus light on arena center

### Stack: "Corruption Spreading"

Progressive over game time:

1. Global: color grading temperature shifts from 0.0 to -0.3
2. Global: grain 0.0 --> 0.04, purple tint strength 0.0 --> 0.10
3. Per-sprite: corruption shader amount 0.0 --> 1.0 (spreads over time on world objects)
4. CanvasModulate: green channel drops 1.0 --> 0.85
5. Vignette: 0.0 --> 0.20 with purple color
6. Ambient sounds: add low drone, increase reverb

### Stack: "Memory/Flashback Sequence"

Transition over 1 second:

1. Color grading: saturation 1.0 --> 0.30, temperature 0.0 --> 0.25 (warm sepia)
2. Film grain: amount 0.0 --> 0.08, size 1.5
3. Vignette: 0.0 --> 0.35, color = dark sepia `#1A1000`
4. Brightness: 0.0 --> 0.05 (slightly overexposed)
5. Contrast: 1.0 --> 0.85 (washed out)
6. Optional: slight letterbox (black bars top/bottom)

### Stack: "Player Near Death"

Intensifies as health drops below 20%:

1. Desaturation: ramps from 0.60 to 0.20
2. Vignette: ramps from 0.35 to 0.65, color shifts from dark red to bright red
3. Contrast: spikes to 1.3 (harsh shadows)
4. Heartbeat pulse: brightness oscillates +/- 0.03 at ~1Hz, accelerating
5. Chromatic aberration: subtle (1-2px offset) starts at 15% health
6. Audio: low-pass filter increases, heartbeat SFX

---

## Gotchas

1. **CanvasModulate is multiplicative, not additive** — `Color(0.5, 0.5, 1.0)` doesn't add blue, it halves red and green. Dark colors become near-black fast. Never go below `Color(0.02, 0.02, 0.02)` or the halo edge becomes visible
2. **Stacking multiple ColorRect shaders = multiple full-screen passes** — each is a draw call sampling the entire screen. 2-3 is fine, 5+ will kill mobile. Combine effects into a single shader when possible
3. **Screen texture sampling in Godot 4 syntax** — always use `uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;` not the old `SCREEN_TEXTURE` global
4. **Color values in shaders are linear, not sRGB** — `Color(0.5, 0.0, 0.0)` in Godot's inspector (sRGB) looks different from `vec4(0.5, 0.0, 0.0)` in a shader (linear). Use `source_color` hint on uniform colors for automatic conversion
5. **CanvasModulate + PointLight2D interaction** — lights are applied AFTER CanvasModulate. A dark CanvasModulate makes lights look brighter by contrast. Design your light energy values with your target CanvasModulate color in mind
6. **Desaturation weights matter** — `vec3(0.33, 0.33, 0.33)` (naive average) makes green too dark and blue too bright. Always use perceptual weights: `vec3(0.299, 0.587, 0.114)` (Rec. 601) or `vec3(0.2126, 0.7152, 0.0722)` (Rec. 709)
7. **Film grain + pixel art = visual noise** — at low resolutions, grain competes with actual pixel detail. Use grain_amount < 0.03 for pixel art, or apply grain in screen space (after upscaling) rather than in game space
8. **Multiple CanvasModulate nodes** — only the last one processed wins. Don't place multiple in the same scene expecting them to blend. Use a single one and tween its color
9. **Temperature shift via r/b channel nudge is crude** — adding to red and subtracting from blue works for simple cases, but breaks on already-saturated colors. For production, convert to a color space that separates chrominance (like YCbCr) or use a LUT
10. **Vignette on non-16:9 aspect ratios** — the standard `uv *= 1.0 - uv` produces an oval, not a circle. On ultrawide monitors, the vignette is flatter on top/bottom. This is usually fine (eyes track horizontal more than vertical) but be aware
