Authors the full style bible: camera table separating D-148 gameplay from
D-019 offline-renderer use; the delegated toon-vs-PBR ruling (environment
props share the character toon treatment, with a minimal-PBR carve-out for
glass/polished metal so sightlines read truthfully under occlusion-based
perception); a color/material register for every D-235 ObjectTag (hue,
grain, D-217-keyed weathering); and explicit supersession notes both ways
with visual-grammar-v01.md and the mood-board workshop transcript.
Includes branch-side pql changelog rows (T-1050/T-1052 -> review); the
worktree pre-commit export step failed benignly (write-through had
already refreshed the files) so they are staged explicitly here.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thebatchreportforcheapveto.VERDICT:READY.', 'in_progress', 'medium', 'araminta', 'visual', 'D-235', '2026-06-1210:40:59', '2026-07-2518:11:46.095', NULL, '4df3e92b2bc003a7b73f860dd47704f2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERTINTOtickets(record_id,type,parent_record_id,title,description,status,priority,assigned_to,team,decision_ref,created_at,updated_at,deleted_at,hash,canonical_version)VALUES('06FBPTXPV39JX54HYP83RZJFDM','task','06FB0TNSRZXCHGS16BFHSSGSV4','Write the Phase-4 visual style bible into docs/assets/visual/palette.md','(description follows in first append)
See `docs/workshops/art-direction-mood-board/` for the established visual identity.
---
## Key Decisions
## 1. Camera
- **Camera angle:** 15-20deg from vertical ("the angle"), rendered via orthographic Camera3D at -72.5deg from horizontal (D-019)
- **Entity colors:** Relationship-based per D-033 (green = known/friendly, amber = neutral, red = hostile, etc.)
- **Environmental neutrality:** Spaces don't visually shift with narrative state (D-045)
- **Functional warmth:** Industrial infrastructure that people made livable — not military, not luxury
Two camera records exist and apply to **different surfaces** — this was
previously conflated in this document; corrected here.
## Style Guide
| Camera | Angle | Where it applies | Status |
|--------|-------|-------------------|--------|
| **D-148** | 30° low-angle `Camera3D` tilt (−30° from horizontal in the confirmed preset convention — see D-148's 2026-07-06 editorial note), 45° static map rotation | **Gameplay** — the default in-world camera, Phase 5+ | Active, confirmed default |
| **D-019** | −72.5° from horizontal (midpoint of the old "15–20° from vertical" sprite-tilt range) | **Offline renderer only** — the retired 2D-sprite production pipeline (`renderer/`, now repurposed per D-244 as the flat-2D-artwork generator; see `renderer/README.md`) | Superseded for gameplay by D-148; survives only as an offline-render setting |
To be populated from art direction workshop synthesis and first visual sprint.
**Why both survive:** D-148 replaced D-019 as the *gameplay* camera when the
project pivoted to live 3D rendering (D-149) and then to fully 3D objects
(D-244) — there are no more pre-rendered sprites to view "at the angle" during
play. D-019's −72.5° Camera3D setting doesn't disappear, though: it is still
the correct setting for the **offline** render rig that produces flat 2D
artwork (paintings, flags, billboards, signage — the D-244(b) content class),
because that pipeline still renders a 3D scene down to a flat texture and
needs a camera angle to do it. Two different cameras, two different jobs:
one drives what the player sees, the other bakes a flat image asset.
Do not describe D-019's angle as "the gameplay camera" anywhere — that
sentence is what this document is correcting.
---
## 2. Rendering Treatment — Toon vs. PBR (delegated ruling)
**Ruling (Araminta, delegated by team lead this batch — surfaced to Jeroen for
veto in the batch report):**
**Environment props and furniture share the character toon treatment.** One
shader family, one look, across the whole in-world scene — with a single,
narrow, explicitly-named exception for glass and polished/mirror-finish metal,
which get a distinct minimal-PBR material rather than being forced through
the flat toon response.
### 2.1 What "sharing the toon treatment" means concretely
- Environment `.glb` models use the same shader family as characters:
`toon.gdshader` (flat lit/shadow-band ALBEDO, no PBR lighting response) or
`toon_masked.gdshader` (recolor-mask variant, for props that need
runtime tint — e.g. a favorite-color object, a faction-liveried crate)
where a tintable variant is needed. `outline.gdshader` (inverted-hull) applies
to entities and to any prop the visual hierarchy calls out for emphasis (see
§4 tie-in below) — not universally to every static prop, matching the
| `rendered_wall` | Whatever the template's color_range picks — this tag is a **finish**, not a hue driver; render surface is smooth and matte regardless of picked hue | Smooth, minimal grain, occasional hairline render-crack pattern at Worn+ | Intact: clean flat. Worn: surface staining/streaking under sills and joints. Cracked: render cracking, patches of exposed substrate. Broken: large render loss, substrate fully exposed. |
| `stone_wall` | Warm grey-tan (`#a89e8c`–`#8c8172`) — **explicitly not cool grey**; this is the D-235 fallback-graph's flagship "gets it wrong if literal" case | Visible coursing, natural stone color variance tile-to-tile, tool-marked or rough-cut surface depending on template | Intact: clean-cut edges. Worn: lichen/mineral staining in shadowed recesses. Cracked: mortar loss between courses, chipped arrises. Broken: displaced/missing stone units. |
| `timber_wall` | Warm mid-brown (`#7a5c3e`–`#9c7a52`), never grey-driftwood unless template explicitly calls for weathered/coastal register | Visible grain direction, board/plank joint lines | Intact: sealed/painted finish. Worn: graying at exposed edges, finish wear. Cracked: splitting along grain, missing sealant. Broken: rot, structural failure at joints. |
| `stucco_wall` | Warm off-white to sand (`#d8cfc0`–`#c4b89e`) — whitewashed per the tag description, not pure white | Smooth, slight hand-applied texture variance | Intact: clean whitewash. Worn: staining streaks below any protrusion. Cracked: hairline cracking, patch-color mismatch. Broken: large loss exposing substrate. |
| `glass_curtain_wall` | Neutral-to-cool tint on the glass itself (`#c8d4dc` @ low opacity) — see §2.1 PBR exception, this tag needs the specular/transparency carve-out to read correctly | Minimal — reflection/transparency IS the surface character | Intact: clean, full reflectivity. Worn: dust film, reduced reflectivity. Cracked: visible cracked panes, some opacity/frosting. Broken: missing panes, boarded sections. |
| `composite_panel` | Whatever the template picks — this is a **prefab finish** tag like `rendered_wall`, hue is template-driven, not material-driven | Flat, uniform, visible panel-seam grid (regular rhythm, unlike masonry coursing) | Intact: crisp seams, clean finish. Worn: seam staining, minor panel discoloration. Cracked: panel warping, seam gaps. Broken: missing/detached panels. |
| `rammed_earth_wall` | Warm ochre-to-umber banding (`#8a6a48`–`#6e5236`) | Strong horizontal layer banding (the construction method's signature texture) | Intact: crisp banding. Worn: surface erosion softening band edges. Cracked: vertical cracking across bands. Broken: section collapse/erosion loss. |
### 3.2 Roof forms
| Tag | Hue range | Grain / texture character | Weathering baseline |
| `flat_roof` | Neutral dark grey (`#4a4844`) membrane | Flat, minimal detail, visible seam lines on membrane roofing | Intact→Broken: ponding stains, membrane cracking, vegetation growth at Broken. |
| `pitched_roof` | Warm dark grey-brown (`#4e463e`) generic shingle/slate register unless a specific tag (below) narrows it | Directional shingle/slate coursing | Moss/lichen accumulation in shadowed pitches as condition drops; missing units at Cracked+. |
| `corrugated_roof` | Weathers to **rust-orange** (`#a65c34` at Worn+), starts galvanized silver-grey (`#a8aaa8`) when Intact — **explicitly does not stay silver**, this is D-235's own worked example | Strong linear corrugation shadow pattern | Intact: clean galvanized sheen. Worn: rust bleeding from fastener points. Cracked: broad rust staining, sheet lifting at edges. Broken: rust-through holes, missing sheets. |
| `terraced_roof` | Match the deck surface (paved/planted per template), not a distinct roof hue | Flat deck texture, visible rail/parapet line | Surface wear on deck material, planter/rail deterioration at lower bands. |
| `vaulted_roof` | Match wall material register (masonry vaults read as an extension of the wall) | Strong directional shadow from the vault curvature | Standard masonry weathering (see stone/brick wall rows). |
| `green_roof` | Vegetated green, seasonally variable — the one roof tag where hue is intentionally NOT template-locked | Organic, irregular, canopy-like from above | Condition reads as vegetation health/coverage, not surface damage — sparse/patchy at low prosperity, not "broken" in the structural sense. |
### 3.3 Facade rhythms
Facade tags are about **pattern**, not hue — they inherit the wall tag's
color register. Grain/weathering guidance:
| Tag | Grain / texture character | Weathering note |
| `regular_facade` | Even punched-window grid, no added ornament | Weathers uniformly with the wall tag beneath it. |
| `ornamental_facade` | Raised/carved detail catches shadow distinctly from the flat wall plane | Ornament erodes/chips *before* the flat wall does — detail loss is the earliest visible sign of Worn. |
| `arcade_facade` | Strong repeating vertical shadow rhythm from the colonnade | Ground-level wear concentrates at column bases (foot traffic), not evenly. |
| `shuttered_facade` | Shutters as a distinct, often higher-saturation accent color against the wall's muted register — this is the one facade tag allowed a saturation bump (bounded by §5's object-tier ceiling below) | Shutter paint fades/peels faster than the wall behind it — a secondary, faster weathering clock on the same building. |
| `screen_facade` | Fine repeating perforation/louvre pattern, strong dappled shadow | Screen material (usually metal or timber lattice) weathers per its own material, independent of the wall plane behind. |
| `colonnade` | Freestanding column rhythm, deep shadow gaps between wall plane and columns | Column bases weather fastest (ground contact, foot traffic) — same principle as arcade_facade. |
| `lattice_screen` | Fine timber/metal lattice, mashrabiya-register — highest facade information-density texture in the set | Individual lattice elements can go missing at Cracked+ without the whole screen reading as "broken." |
### 3.4 Street surfaces
| Tag | Hue range | Grain / texture character | Weathering baseline |
| `paved` | Neutral grey (`#66645e`) | Fine asphalt/poured texture, minimal joint pattern | Cracking, pothole pattern, patch-color mismatch (repairs never quite match original pour age). |
| `cobble` | Warm grey-tan, matches `stone_wall`'s register | Strong individual-unit texture, visible mortar/sand joints | Displaced/missing setts at low condition, not surface cracking (it's a unit system, not a monolithic pour). |
| `packed_earth` | Warm ochre-brown (`#7a6248`) | Irregular, rutted, no joint pattern | Erosion channels, puddling, vegetation encroachment at edges as condition drops (or rises — an unmaintained packed-earth street can look "worse" toward Intact-adjacent if it's simply less used, this tag is condition-light). |
| `canal_way` | Water surface — hue driven by reflection/sediment, not a fixed material hue | N/A — water plane, not a solid surface | Water clarity/debris accumulation stands in for the condition ladder here. |
| `elevated_walkway` | Match structural material used (steel_frame/timber/composite per template) | Grated or planked, visible support structure below | Structural weathering per the material tag, plus grate/plank gap wear from foot traffic. |
| `heavy_haul` | Neutral dark grey (`#4e4c48`), reinforced-surface register | Deep rut/track wear pattern, wider joint spacing than `paved` | Track-line wear concentrates on wheel paths, not the full surface — an asymmetric wear pattern unlike `paved`'s even cracking. |
| `boardwalk` | Warm grey-brown weathered timber (`#8a7a64`), coastal/wetland register — assume some grey weathering even at Intact, unlike `timber_wall` | Plank coursing, visible gap joints | Individual plank replacement creates natural color patchwork even at high condition — this is the one street tag where slight inconsistency reads as *authentic*, not neglected. |
| **D-033 entity color = relationship to player** | visual-grammar-v01.md §3 | Unchanged — still the live decision, still the single most important color system in the game. Applies to entities regardless of render fidelity (rectangle, sprite, or 3D mesh). |
| **"Functional warmth"** — industrial infrastructure that people made livable, not military/not luxury | workshop-outcomes.md visual identity statements + Gore's naming | Standing mood target for the whole D-235 register above: hue ranges lean warm (terracotta, ochre, warm grey) rather than cool/clinical by default; the *cool* end of the palette is reserved for deliberately institutional template registers, not the default. |
| **Environmental neutrality — strict zero shift (D-045)** | workshop-outcomes.md §1.10 | Unchanged, still active. The D-235 material register above is diegetic/static — condition bands (D-217) shift with simulated `prosperity_score`, never with narrative/investigation state. This document adds no new exception to D-045. |
| **Entity always wins visual ties** (visual hierarchy: entity > object > structure) | visual-grammar-v01.md §1.4 / workshop-outcomes.md §1.4 | Survives as a rendering-order and saturation-hierarchy principle. In 3D terms: entity materials/outlines render at the highest effective saturation and are never occluded in a way that reads ambiguous against a similarly-toned prop. The saturation ceiling table (below) replaces the old sprite-outline-weight table. |
| **Object favorite-color saturation ceiling** | visual-grammar-v01.md §3.6 | Restated: entity colors (D-033) remain the highest-saturation elements in any scene; object/prop colors (including the `shuttered_facade` accent exception in §3.3) stay moderate; structure/material register (§3) stays lowest. Same three-tier ceiling, now anchored to D-235 tags instead of "Era 1/2/3" object palettes. |
| **Readability over beauty** | workshop-outcomes.md Principle 1 | Standing design principle, unchanged, applies to 3D rendering exactly as it applied to sprites. |
| **Lighting-driven atmosphere, not baked mood** | workshop-outcomes.md Principle 2 / visual-grammar-v01.md's "sprites are shape templates" | Re-expressed in §2 above: toon shading gives shape + local shadow-band response; scene lighting (not baked texture darkening) still carries mood. The specific mechanism (Light2D CanvasModulate) is retired with the sprite pipeline, but the principle — materials stay neutral, the lighting rig does the emotional work — carries forward to whatever 3D lighting setup Phase 5 builds. |
---
## 5. Superseded Documents
Two earlier documents overlap this one. Both are **explicitly marked
superseded** here (and should carry a pointer back to this section) so no two
contradictory style documents stay live at the same time:
- **`docs/design/visual-grammar-v01.md`** — already carries its own
superseded banner (2026-06-12, pointing at D-235/D-228 +
character-visuals-spec.md). That banner is correct and unchanged by this
ticket. What's **specifically superseded**: the entire sprite-era
production model (§2 entity sprite dimensions, §4 z-level stack as sprite
not its sprite-specific implementation detail) remains valid design
intent, inherited into current decisions rather than into this document
directly.
**Going forward:** this document (`palette.md`) is the single live style
bible for Phase 4+ rendered look. New style guidance should land here, not in
a new parallel document, unless it's genuinely a different concern (e.g. the
model-naming/manifest conventions living in `docs/assets/visual/conventions.md`,
authored separately under T-1050).
---
## Appendix — Decision Cross-References
| Decision | Relevance |
|----------|-----------|
| D-019 | Original top-down camera + offline-renderer −72.5° angle. Superseded for gameplay by D-148; survives for the offline 2D-artwork render rig. |
| D-033 | Entity color = relationship to player. Unchanged, still the primary information-bearing color system. |
| D-045 | Environmental neutrality — strict zero shift. Governs how the D-235 material register (§3) may and may not respond to game state. |
| D-217 | Tile condition thresholds (prosperity_score bands) — the weathering-baseline mechanism referenced throughout §3. |
| D-232 | Trait-template catalog — era reframed as maintenance/wear + past-vogue holdover, not a material-tech ladder. Backing rationale for retiring the Era 1/2/3 ladder in §5. |
| D-235 | Building exterior visual grammar + ratified ObjectTag vocabulary (`object_tag_vocabulary.toml`). §3 is this document's rendered-look layer on top of D-235's logical tokens. |
| D-244 | 3D objects in-world; 2D limited to textures + flat artwork. Establishes the toon-shader-family context for §2 and retires the sprite catalog framing this document used to have. |
> **SUPERSEDED (2026-06-12, cascade-refocus sweep):** superseded by D-235/D-228 (generated-world visual grammar) + docs/design/character-visuals-spec.md (character rules). Kept as historical record; do not build against it.
>
> **Style-bible pointer (2026-07-25, T-1052):** the live Phase-4 style bible is
> `docs/assets/visual/palette.md`. Its §4/§5 spell out exactly what survives from
> this document (D-033 colors, "functional warmth," the visual-hierarchy/saturation
> principles) versus what's superseded (the sprite-era production model: entity
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.