Art Direction & Lighting

Readable, toylike-but-grounded look; one dynamic sun/sky, no baked lighting.

Content PipelineArt & Lighting

Direction

Aim for a painterly look: warm, illustrative, hand-painted feel; clear shape and readable silhouettes over photoreal detail — toylike but grounded, warm daylight default, consistent with the idea map’s own tagging habit.

Lighting

Single dynamic sun/sky lighting the whole city, tied to the day/night cycle (Time & Weather doc). The day/night terminator is softened via smoothstep on the Lambertian dot product, and the heightmap uses linear filtering + mipmaps so central-difference normal sampling stays smooth across texel boundaries. Soft real-time shadows; no per-building baked lightmaps.

Atmosphere (Fog & Horizon)

The terrain has reached its intended km scale (256×256 cells at 20m/cell = 5.12km²), so the earlier "51m prototype map" condition for the horizon plane — a large flat camera-relative quad sharing the terrain shader, extended past the procedural terrain so fog reaches the visual horizon before its edge shows — no longer holds; it can now shrink or be removed.

The fog factor is 1 - exp(-viewDistance * density) per cm, where viewDistance is the Euclidean length of the camera-relative vertex position (so it survives the floating-origin shift; see the floating-origin section in the rendering notes for the why). The fog color tracks the sky's horizon palette via the same dayBrightness(dayPhase) curve the sky uses, blending a per-weather day/night pair. FOG_BY_WEATHER in tweaks.ts is the single source of truth — see the Time & Weather doc for the day/night blend and the per-weather profiles (clear, fog, rain, snow).

Physically Grounded Density

density is not eyeballed — it's the extinction coefficient β in Koschmieder's Law, the standard atmospheric-optics formula relating extinction to meteorological visibility distance V (the range at which an object's contrast against the sky falls to the 2% threshold where it's considered to have vanished): β = -ln(0.02) / V ≈ 3.912 / V. That's exactly the shader's fog-factor exponent, so density = 3.912 / V reproduces a given real-world visibility exactly. Per-weather V is drawn from WMO and aviation-meteorology visibility categories, not toybox intuition:

Weather Visibility (V) β = 3.912/V Basis
Clear 30 km ≈1.3×10⁻⁶/cm WMO's "clear" visibility threshold (≥30km)
Fog 500 m ≈7.8×10⁻⁵/cm Moderate fog
Rain 2 km ≈2.0×10⁻⁵/cm Moderate rain (heavy rain drops below 1km)
Snow 800 m ≈4.9×10⁻⁵/cm Snowfall, commonly cited as cutting visibility below 1km

Visibility V is where an object is effectively gone (2% contrast), not where haze first becomes noticeable — a clear day's 30km V still gives a soft atmospheric-perspective fade at the terrain's 5.12km far edge (~49% fog factor there), while nearby grass (under ~1km) reads with only light haze (~12%). This replaces an earlier tuning (clear ~50% fog at 35m) picked for the terrain's old ~51m-per-side scale, which read as a permanent haze once the terrain reached its real km scale — see tweaks.ts's FOG_BY_WEATHER for the exact values this doc mirrors.

Palette

Buildings are colored by material family, not literally by zone type — avoids the "red = residential" cliché while keeping the world legible.

The MaterialFamily enum defines the valid material families for generated buildings:

enum MaterialFamily {
  CorrugatedMetal   = "corrugated-metal",
  ConcreteBlock     = "concrete-block",
  Paint             = "paint",
  VinylSiding       = "vinyl-siding",
  Stucco            = "stucco",
  Timber            = "timber",
  WoodClapboard     = "wood-clapboard",
  Brick             = "brick",
  Stone             = "stone",
  CurtainWallGlass  = "curtain-wall-glass",
}

Land value tiers

Materials are grouped by land value tiereconomy-zoning is the single source of truth for the landValue formula and the tier boundaries; this doc only assigns materials to the resulting tier index (0–3), not to any numeric landValue range. The procedural-buildings plan's GenerationRule.materialsByLandValue selects a material from the array for the parcel's current tier; the same parcel visibly upgrades its facade as its land value rises. Two or three materials per tier give variety without confusing the cheap-vs-expensive signal.

Tier Materials
0 (cheap) Corrugated metal, Concrete block, Paint
1 (low-mid) Vinyl siding, Stucco, Timber
2 (mid-high) Wood clapboard, Brick
3 (high) Stone, Curtain wall glass

Visual signals

Each material has 2–3 visual signals the procedural-materials shader pass must hit. The signals are what makes "cheap reads cheap at a glance" work — they're not just color, they're shape, mass, and texture.

Color palette, noise pattern, and window rhythm for each material are owned by the Procedural Materials doc; the signals above are the silhouette-level commitment this doc makes.

Biomes (future work)

The list above is a single global set, but the same land value tier can read very differently in different regional contexts. A timber-rich biome should weight Timber upward; a desert biome should weight ConcreteBlock and Stucco. A future biomes pass would replace the global tier-to-material array with a biome-keyed variant. Out of scope for v1; deferred to a follow-up design pass.

Materials Tie-In

The base-color + noise-layer approach from the Procedural Materials doc is the candidate default for building facades and ground cover as well.

Photo Mode Tie-In

Photo Mode's time-of-day presets (Time & Weather doc) are the art-directed answer to the fixed screenshot lighting question below — Noon, Morning, and Golden Hour are just named `dayPhase` values driving this doc's one dynamic sun/sky, not a separate lighting path.