lighting: remove the palette terrain model - physics is the only lighting, themes tint it through THEMES[theme].lighting; debug panel shows the active palette and its tint/gain; credits no longer claim VIIRS

This commit is contained in:
Richard Zink committed 2026-10-11 15:39:42 +02:00
1 parent f26ac6c0ce
commit 4369afd3ef
3 files changed
+405 -478

No files matched your search

+318 -289
View File
@@ -1,347 +1,376 @@
/*
* Wallpaper background config.
* animated_wallpaper - configuration
*
* Properties:
* theme - Active palette theme. One of the keys in window.THEMES
* (default 'ash'). The matching CSS file is loaded at
* startup.
* language - UI / i18n language code (default 'en').
* timeFormat - Format string for the clock using the token set below
* (Moment.js-like names, own mini-formatter).
* debug - If 1, the right-side debug panel is shown.
* units - Object with 'temperature' ('C' or 'F').
* location - Geographic coordinates for sun/moon calculations. Used
* by SunCalc (https://github.com/mourner/suncalc). Object
* with 'lat' (latitude, -90 to +90) and 'lon' (longitude,
* -180 to +180). Positive lat = north, positive lon = east.
* Optionally add 'label' (string) to override the
* reverse-geocoded city name.
* Note: the height grid is axis-aligned (N up); terrain
* matches the square ground mosaic and straight flights
* (prefetch direction via #fetch=N). Heights: Copernicus
* DEM via terrarium tiles (z9, ~200 m).
* terrain - 3D ground from real elevation data. Object:
* enabled - true (default) / false = flat plane.
* exaggeration - vertical scale factor on the DEM
* (default 8; 1 would be physically
* true and nearly invisible at this
* horizontal scale).
* weatherSim - If true, ignore open-meteo and cycle through
* ['sunny','cloudy','stormy','night'] automatically every
* weatherSimInterval ms. Useful for A/B testing every
* theme without waiting on the API.
* weatherSimInterval - Sim cycle period in ms (default 30000).
* orbit - Figure-eight flight (Bernoulli lemniscate) around
* CONFIG.location instead of a straight drift. The
* eight's self-crossing sits exactly on the configured
* coordinate. Each time the aircraft crosses that point
* a new random orientation is picked and blended in
* over one lobe (spline through the crossing). Object:
* radiusKm - lobe tip reach from the centre in km
* (~13 km per tile at z11/48deg N).
* Clamped (~190 km) so tips + fog horizon
* fit inside the 43x43 tile mosaic.
* clockwise - travel direction along the eight.
* reorient - random re-aim at every crossing
* (default true; false keeps the bearing).
* Set to null (or remove) for the straight-line flight.
* fog - Ground fog / distance haze. Object with 'distanceKm'
* (visibility range at which terrain fully blends into
* a milky haze; default 120). false/0 disables fog.
* clouds - Vertical extent of the cloud deck in world units
* (1 unit = ~12.9 km at zoom 11; camera band 15-35 km,
* i.e. y ~ 1.16-2.70). Default puts the deck top (~11.6
* km) below the lowest flight altitude.
* tiles - All downloads (street tiles, elevation grid, last
* weather payload, geo label) live permanently in
* IndexedDB and are never re-fetched once cached; a
* warm cache = fully offline rendering. Set
* expireDays (number > 0) to re-fetch street tiles
* periodically (e.g. 90).
* Everything the scene reads at runtime lives here. Values are plain
* JavaScript so the file can also be edited while the page is open
* (window.CONFIG is read every frame for most knobs).
*
* Time format tokens (subset):
* YYYY 4-digit year (2026)
* MMMM full month name (October)
* MM month with leading zero (10)
* DD day with leading zero (07)
* HH 24-hour hour with zero (18)
* mm minute with leading zero (14)
* ss second with leading zero (25)
* a am/pm lowercase (pm)
* A AM/PM uppercase (PM)
* dddd full weekday name (lowercase!)
* Any other character is emitted verbatim.
* TOP LEVEL
* theme active palette, a key of window.THEMES (default 'ash')
* language UI language code for i18n.js ('en', 'de', ...)
* timeFormat clock format string, token list at the bottom
* debug 1 = show the debug panel (bottom left: weather, shader
* counters, theme palette). 0 (default) = hidden
* credits true = attribution line at the bottom of the info box
* units { temperature: 'C' | 'F' }
* location { lat, lon, label? } - sun/moon geometry, traffic side,
* tile/DEM/Overpass origin, figure-eight centre
* renderScale internal 3D resolution (0.2..1) with blocky upscale
* maxFps scene frame cap (0 = free running)
* weatherSim ignore Open-Meteo and cycle the weather themes
* tiles basemap/elevation cache behaviour + debug overlays
* ground basemap style
* orbit flight path (figure eight or straight line)
* fog distance haze
* clouds cloud deck height + per-weather coverage
* flights civil aviation layer (aircraft + contrails)
* terrain 3D ground, car lights, settlement lights
* carLights tuning of the vehicle light layer
* lights twilight switching of artificial lights
* atmosphere physical light model + shadow/haze knobs
* stars star catalogue tint and brightness
* day simulated day cycle or the real sun
*
* WORLD SCALE
* 1 world unit = 1 zoom-11 tile ~ 12.9 km at 48.6 deg N.
* World axes: +X = west, +Y = up, +Z = north (verified against the
* basemap and the star frame). Camera flies a 15-35 km band (y ~ 1.16-2.70).
*
* TIME FORMAT TOKENS (own mini formatter, Moment.js-like names)
* YYYY 4-digit year MMMM full month name MM month 01-12
* DD day 01-31 HH hour 00-23 mm minute
* ss second a am/pm A AM/PM
* dddd full weekday name DDD short weekday name
* any other character is copied verbatim
*/
window.CONFIG = {
theme: 'ash',
language: 'en',
// ------------------------------------------------------------------
// Identity and UI
// ------------------------------------------------------------------
theme: 'ash', // 'ash' | 'nord' | 'gruvbox' (see window.THEMES)
language: 'en', // 'en', 'de' - keys of the i18n.js dictionaries
timeFormat: 'DD. MMMM, YYYY ‒ HH:mm:SS',
debug: 0,
units: {
temperature: 'C'
},
// Data attribution at the bottom of the info box (automatic text
// based on the enabled layers). false = hidden.
credits: true,
// Internal 3D resolution scale (0.3..1, default 1). Lower values
// render the clouds/map shader at reduced resolution and upscale
// via CSS - use it if fps is low (GPU-bound) despite a quiet CPU.
// 1 = native. 0.5 = a quarter of the pixels (half width, half height),
// upscaled 2x as hard blocks; the DOM UI on top stays crisp.
debug: 0, // 1 = debug panel with the theme palette
credits: true, // false = hide the attribution line
units: { temperature: 'C' }, // 'F' for Fahrenheit
// ------------------------------------------------------------------
// Rendering budget
// ------------------------------------------------------------------
// Internal 3D resolution. The value is snapped to an exact integer
// divisor of the CSS size (0.5 -> 2x blocks, 0.33 -> 3x, 0.25 -> 4x)
// and upscaled with image-rendering: pixelated, so it reads as pixel
// art instead of a blurry rescale. The DOM UI stays crisp at any value.
// 1 = native resolution (sharpest, most expensive).
renderScale: 0.5,
// WebGL scene frame cap (default 30; 0 = free-running/vsync). All
// scene motion is time-based, so the cap only changes smoothness.
// Scene frame cap. All motion is time-based, so this only changes
// smoothness, never speed. 0 = free running (vsync).
maxFps: 30,
// ------------------------------------------------------------------
// Location
// ------------------------------------------------------------------
// Drives sun/moon positions, the traffic side lookup, the tile/DEM
// origin, the Overpass grid and the figure-eight centre.
location: {
// Latitude and longitude for sun/moon calculations
lat: 48.5667,
lon: 13.4319
// label: 'Passau' // overrides the reverse-geocoded name
},
// weatherSim: true = Szenario-Wetter statt Open-Meteo (A/B-Durchlauf
// aller Wetterstufen alle weatherSimInterval ms). false = echtes
// Wetter von Open-Meteo.
// ------------------------------------------------------------------
// Weather
// ------------------------------------------------------------------
// false = live Open-Meteo conditions (also used for the theme choice).
// true = cycle sunny -> cloudy -> stormy -> night, useful to compare
// all weather looks without waiting on the API.
weatherSim: false,
weatherSimInterval: 30000,
// All downloads (basemap tiles, elevation grid, weather JSON, geo
// label) stay in IndexedDB permanently and are never re-fetched once
// cached. expireDays (days, > 0) re-enables periodic basemap refresh.
weatherSimInterval: 30000, // ms per weather step when weatherSim is true
// ------------------------------------------------------------------
// Tiles and caches
// ------------------------------------------------------------------
// Basemap tiles, the elevation grid, Overpass cells, the star catalogue
// and the last weather payload live permanently in IndexedDB
// ('background-tiles' / store 'tiles') and are never re-fetched once
// cached: a warm cache renders fully offline.
tiles: {
expireDays: null,
// true = draw tile borders + N/S + x/y coords into the ground
// texture (debug orientation). Off = clean map.
debug: true
expireDays: null, // e.g. 90 = re-fetch basemap tiles every 90 days
debug: false // true = tile borders + N/S + x/y labels baked
// into the ground texture (orientation debug)
},
// Ground imagery: 'satellite' = ESRI World_Imagery (aerial, no
// labels), 'streets' = ESRI World_Street_Map (with place names).
// Both cached permanently (separate cache prefix per style).
ground: {
style: 'streets'
// 'satellite' = ESRI World_Imagery (aerial, no labels)
// 'streets' = ESRI World_Street_Map (with place names)
// Both are cached separately (cache id prefix 'sat/' vs plain).
style: 'satellite'
},
// Figure-eight course around location (lat/lon); the crossing point
// sits exactly on the coordinate. null/removed = straight flight.
// ------------------------------------------------------------------
// Flight path
// ------------------------------------------------------------------
// Figure eight (Bernoulli lemniscate) whose crossing sits exactly on
// CONFIG.location. At every crossing a new bearing is picked and blended
// over one lobe. Set orbit: null for a straight drift.
orbit: {
radiusKm: 60, // lobe tip: max distance from the centre
clockwise: true,
reorient: true, // random heading at every crossing pass
// Slow vertical bobbing inside the 15-35 km flight band:
// centre 25 km (~1.93 world units), amp = half the band
// (10 km ~ 0.77); hard clamp at the band limits.
// periodS = seconds per cycle, tilt = camera nose reaction to
// climb/descent rate. enabled: false = constant 25 km.
radiusKm: 60, // lobe tip reach (clamped ~190 km so the
// tips plus the fog dome fit the mosaic)
clockwise: true, // travel direction along the eight
reorient: true, // false keeps the bearing across crossings
// Vertical bobbing inside the 15-35 km band: centre 25 km, amp is
// half the band (10 km ~ 0.77 world units), tilt is how much the
// camera nose follows the climb/descent rate.
altBob: { enabled: true, amp: 0.77, periodS: 90, tilt: 0.3 }
// altBob: { enabled: false } // constant 25 km, no bobbing
},
// Ground fog: distance haze. distanceKm = visibility until fully
// saturated haze (false/0 = off).
// ------------------------------------------------------------------
// Distance haze
// ------------------------------------------------------------------
fog: {
distanceKm: 120
distanceKm: 120 // fully saturated haze at this range; 0/false = off
},
// ---- Civil aviation (CONFIG.flights) ---------------------------------
// Low-poly jets (CC0 model, vendor/plane3d.js) with randomized target
// airports: the destination is rolled at every take-off (hubs weighted
// via wHub/wField/wStrip). Paths are gentle Bezier curves resampled to
// arc length (constant ground speed) flown as a full profile: take-off
// and climb, cruise, descent and landing, a short ground hold, then a
// fresh destination. By day sun-lit silhouettes; by night white
// strobing position lights with a red beacon. Contrails form above
// contrailStartKm on contrailChance of the aircraft and stay visible
// for contrailLifeS. All distances in km (world scale: 1 unit = one
// z11 tile ~ 12.9 km at 48.6 deg N).
// ------------------------------------------------------------------
// Cloud deck
// ------------------------------------------------------------------
// World units (1 unit ~ 12.9 km). Keep 'top' below the lowest flight
// altitude so aircraft fly above the deck.
clouds: {
top: 0.9, // ~11.6 km
bottom: -0.3, // ~-3.9 km (deck base below the terrain)
// Coverage per weather level, 0 = clear, 1 = overcast. Only the
// levels listed here override the built-in defaults
// (sunny 0.20, cloudy 0.70, stormy 0.80, night 0.45).
density: { stormy: 0.80 }
// density: { sunny: 0.05, cloudy: 0.9, stormy: 1.0, night: 0.2 }
},
// ------------------------------------------------------------------
// Civil aviation
// ------------------------------------------------------------------
// Low-poly jets (CC0 model in vendor/plane3d.js) with a destination
// rolled at every take-off (hubs weighted by wHub/wField/wStrip).
// Paths are Bezier curves resampled to arc length, flown as a full
// profile: climb, cruise, descent, landing, ground hold, new leg.
flights: {
enabled: true,
altitudeKm: 14, // cruise altitude
climbKm: 100, // climb/descent run to/from cruise altitude
count: 21, // max simultaneous aircraft
speedKmh: 850, // mean ground speed
speedVar: 0.18, // +/- deviation (0..0.5)
minLegKm: 60, // shortest leg (no taxi hops)
holdMinS: 50, // ground hold at destination (min..max)
altitudeKm: 14, // cruise altitude
climbKm: 100, // climb/descent run to and from cruise
count: 21, // simultaneous aircraft (max 240)
speedKmh: 850, // mean ground speed
speedVar: 0.18, // +/- speed deviation (0..0.5)
minLegKm: 60, // shortest leg (no taxi hops)
holdMinS: 50, // ground hold at the destination
holdMaxS: 150,
wHub: 4, // destination weights: hub / airfield / strip
wHub: 4, // destination weights
wField: 2,
wStrip: 1,
contrailChance: 0.85, // share of aircraft with a persistent trail
contrailStartKm: 6, // trail formation altitude
contrailLifeS: 300, // trail visibility duration
contrailWidthM: 620, // full trail width
// trails fade out completely by this distance (start is 35% of it)
contrailFadeKm: 100,
// Night strobes. Size shrinks with distance but never below
// lightPx, and never grows past lightMaxPx for a jet right above.
// The white strobe is a double flash: two equal flashes
// blinkGapS apart, then blinkPeriodS - blinkGapS - blinkFlashS
// of darkness (1.4 - 0.16 - 0.08 = 1.16 s pause).
// Contrails: contrailChance of the aircraft leaves a trail once
// above contrailStartKm, visible for contrailLifeS seconds.
contrailChance: 0.85,
contrailStartKm: 6,
contrailLifeS: 300,
contrailWidthM: 620, // full trail width
contrailFadeKm: 100, // fully faded at this distance (ramp starts at 35 %)
// Night strobes. Point size shrinks with distance but never below
// lightPx and never above lightMaxPx for a jet directly overhead.
// The white strobe is a double flash: two flashes blinkFlashS long,
// blinkGapS apart, then the rest of blinkPeriodS is dark.
lightPx: 7,
lightMaxPx: 16,
lightBoost: 9,
blinkPeriodS: 1.4,
blinkGapS: 0.16,
blinkFlashS: 0.08
// flights: false // remove the aviation layer completely
},
// 3D ground from real elevation data (Copernicus DEM, terrarium
// tiles). enabled: false = flat plane. exaggeration: vertical
// elevation scale (1 = real, nearly invisible; default 8).
// wireframe: true = overlay mesh edges as orange lines (debug).
// carLights: moving lights on the real road network (OSM/Overpass
// curves, additive over the terrain at night). JSON once, cached.
// cityLights: settlement lights - OSM place nodes (city/town/
// village/hamlet) as slightly yellow glow dots from dusk on;
// bigger settlements (place class + population tag) get more and
// brighter dots. One Overpass query, cached permanently.
// Data (c) OpenStreetMap contributors, ODbL.
// ------------------------------------------------------------------
// 3D ground
// ------------------------------------------------------------------
// Copernicus DEM via terrarium tiles (z10), mesh built from the same
// height field the terrain shadows are cast against.
terrain: {
// soft rim at the mosaic border, in tiles (1 tile ~ 13 km)
edgeFadeTiles: 4,
enabled: true,
exaggeration: 8,
wireframe: false,
// How far the day map is dimmed at full night: 0 = pitch black,
// 1 = unchanged. Only affects night; the day map is untouched.
// (The NASA VIIRS city-lights layer was removed; city lights come
// from OSM place nodes via cityLights.)
nightDim: 0.22,
carLights: true,
cityLights: true
enabled: true, // false = flat plane (no DEM, no terrain shadows)
exaggeration: 8, // vertical scale (1 = real, nearly invisible here)
edgeFadeTiles: 4, // soft rim at the mosaic border, in tiles (~13 km)
carLights: true, // moving vehicle lights on the real road network
cityLights: true, // settlement lights from OSM place nodes
wireframe: false // true = draw the mesh edges (orientation debug)
},
// Car lights on the real road network (on/off stays in terrain.carLights).
// Two layers now: a thin road line (bandPx / bandBrightness) and one
// round point sprite per vehicle (minPx..maxPx), same idea as the city
// dots' gl_PointSize floor. laneM is the real distance between the two
// traffic directions, so they split close to the camera and merge into
// one line far away instead of drifting apart with the camera distance.
// dashM: mean vehicle spacing (secondary roads use dashM * 2.8).
// speed: multiplier on the real driving speed (motorway ~120 km/h,
// secondary ~70 km/h at dashM spacing).
// ------------------------------------------------------------------
// Vehicle lights
// ------------------------------------------------------------------
// Roads come from Overpass (motorway/trunk for the A-net, primary/
// secondary for the side layer), cached per grid cell.
carLights: {
roadWidthM: 24, // real carriageway width of the road line
bandPx: 1.2, // pixel floor for the road line
bandBrightness: 0.10, // road line glow (points are the main layer)
minPx: 3.6, // guaranteed vehicle light diameter
maxPx: 10, // ceiling for a light right above the camera
boost: 5, // extra light size close to the camera
laneM: 8, // real distance between the two directions
lanePx: 2.6, // screen floor for that distance, so the two
// directions never collapse into one stream
// Traffic lives on a disc around the configured location: roads
// outside rangeKm carry no vehicles. fadeKm / fadeSpanKm are the
// distance fade belonging to that disc (start, and how far past it
// until nothing is left) - keep them at or beyond rangeKm so the
// region never ends in a hard cut.
roadWidthM: 24, // real carriageway width of the road line
bandPx: 1.2, // pixel floor of the road line
bandBrightness: 0.10, // road line glow (the points are the main layer)
minPx: 3.6, // guaranteed vehicle light diameter
maxPx: 10, // ceiling for a light right above the camera
boost: 5, // extra light size close to the camera
laneM: 8, // real distance between the two directions
lanePx: 2.6, // screen floor for that distance, so the two
// streams never collapse into one line
// Traffic lives on a disc around CONFIG.location. Roads outside
// rangeKm carry no vehicles; fadeKm/fadeSpanKm are the matching
// distance fade (start, and how far past it nothing is left) - keep
// them at or beyond rangeKm so the disc never ends in a hard cut.
rangeKm: 200,
fadeKm: 150,
fadeSpanKm: 60,
// 'auto' = side of the road taken from location (lat/lon): left in
// the UK, Japan, Australia, India, Southern Africa, ... and right
// everywhere else. Set 'left' or 'right' to force it.
// 'auto' derives the driving side from the location (left in the UK,
// Japan, Australia, India, southern Africa, ...), 'left'/'right' forces it.
trafficSide: 'auto',
dashM: 620, // mean vehicle spacing on motorways
// Traffic spread around the motorway network (gaussian): peak on the
// motorway itself, thinning with distance to the nearest one.
// spreadKm = sigma, spreadFloor = share that far side roads keep.
spreadKm: 45,
spreadFloor: 0.30,
speed: 1.0, // flow speed multiplier
density: 1.0, // vehicle density multiplier
brightness: 1.3 // vehicle light intensity
dashM: 620, // mean vehicle spacing on motorways
// (side roads use dashM * 2.8)
spreadKm: 45, // gaussian sigma around the motorway network
spreadFloor: 0.30, // share that far side roads keep
speed: 1.0, // flow speed multiplier
density: 1.0, // vehicle density multiplier
brightness: 1.3 // vehicle light intensity
},
// Headlights and house lights do not ignite at once: every single
// light gets its own sun-altitude threshold between offDeg (nothing
// lit) and onDeg (everything lit), drawn per vehicle / per city block.
// offDeg +5 deg is roughly 30 min before sunset here, onDeg -6 deg is
// civil twilight. In the morning the same thresholds switch the lights
// off again as the sun climbs.
// ------------------------------------------------------------------
// Artificial light switching
// ------------------------------------------------------------------
// Every single light (vehicle, city dot, ground block) gets its own
// switch altitude inside this window, so a city does not ignite as one
// piece. offDeg +5 deg is about 30 min before sunset here, onDeg -6 deg
// is civil twilight. In the morning the same thresholds switch off again.
lights: {
offDeg: 5,
onDeg: -6,
softDeg: 1.2
softDeg: 1.2 // width of the individual transition
},
// Physical lighting: direct beam from a Kasten-Young air mass with
// Rayleigh + ozone + aerosol extinction, diffuse skylight and moonlight
// from the same model. terrainLighting: 'palette' switches back to the
// old hand-painted hillshade. turbidity scales the aerosol load,
// moonGain the moonlight strength, nightFloor the darkest skylight
// (airglow + starlight), albedoTint is the theme's share on surfaces.
// ------------------------------------------------------------------
// Physical light model
// ------------------------------------------------------------------
// Direct beam through a Kasten-Young air mass with Rayleigh + ozone +
// aerosol extinction; diffuse skylight, moonlight, starlight floor and
// aerial perspective all come from the same model, and sky, clouds,
// terrain, contrails and aircraft share one light state per frame.
atmosphere: {
terrainLighting: 'physical',
turbidity: 1.0,
// moonlight strength; scales the direct term AND the diffuse sky
// lift, both with the illuminated fraction (new moon -> none)
moonGain: 0.60,
// stylised starlight + airglow floor on a dark night (real
// integrated starlight is ~1e-6 of daylight and would be invisible)
starGain: 0.18,
// extra share on the ground. The product is the starlit floor and
// must stay below the skylight at sunset, otherwise the night would
// be brighter than the moment of sunset. Moonlight (moonGain) is
// what lifts a night above that floor.
starGainGround: 0.4,
nightFloor: 0.10,
// cloud albedo in physical mode: theme hue kept, brightness comes
// from the light model (the night palette alone would double-darken)
cloudBrightness: 1.0,
// moving cloud patches darken the ground beneath them (the terrain
// samples the same cloud field as the sky pass)
turbidity: 1.0, // aerosol load of the extinction
moonGain: 0.60, // moonlight, scaled by the illuminated fraction
starGain: 0.18, // stylised starlight + airglow (real integrated
// starlight would be invisible)
starGainGround: 0.4, // extra share on the ground; the product must
// stay below the skylight at sunset
nightFloor: 0.10, // darkest skylight before starlight takes over
albedoGain: 1.0, // theme share on surface colours
skyGain: 0.45, // diffuse skylight share on surfaces
airGain: 0.55, // Rayleigh airlight strength (blue-biased)
hazeResidual: 0.30, // surface colour surviving at the full fog distance
cloudBrightness: 1.0, // cloud albedo (theme hue, brightness from the model)
cloudHaze: 0.22, // share of the ground haze applied to the deck
// Cloud shadows: the terrain samples the same cloud field as the sky pass.
cloudShadows: true,
cloudShadowDensity: 1.8, // optical depth through the deck
cloudShadowFloor: 0.06, // darkest shade under a thick cloud
cloudShadowAmbient: 0.65, // share of the skylight a cloud also blocks
// ridges occlude the sun for the ground behind them (march over the
// same height field the mesh was built from)
cloudShadowDensity: 1.8, // optical depth through the deck
cloudShadowFloor: 0.06, // darkest shade under a thick cloud
cloudShadowAmbient: 0.65, // share of the skylight a deck also blocks
// Terrain self-shadowing over the DEM height field.
terrainShadows: true,
// brightness a fully shadowed slope keeps (0 = pitch black, 1 = off)
terrainShadowFloor: 0.42,
// how much of the ground haze applies to the cloud deck (clouds sit
// above most of the air that causes it)
cloudHaze: 0.22,
albedoGain: 1.0,
// share of the daylight column that the distance fog / horizon takes,
// and the diffuse skylight share on surfaces
skyGain: 0.45,
// Aerial perspective in physical mode: the terrain is attenuated by
// exp(-tau) and the scattered light of the same air column is added.
// airGain scales the Rayleigh airlight (blue-biased), hazeResidual
// is how much of the surface survives at the full fog distance
// (0.3 = distant ground keeps 30 % of its own colour).
airGain: 0.55,
hazeResidual: 0.30
terrainShadowFloor: 0.42, // brightness a fully shadowed slope keeps (0 = black)
// Optional variants (leave commented out to use the defaults):
// turbidity: 3.0 // hazy, warm, strongly reddened low sun
// moonGain: 1.2 // bright full-moon nights
// starGain: 0.30 // stronger starcast on clouds and contrails
// hazeResidual: 0.10 // flatter, milkier distance haze
// cloudShadows: false // no moving cloud shade on the ground
// terrainShadows: false // no ridge shadows
},
// Star colour: every catalogue star gets its real blackbody tint, mixed
// towards white by this factor. 0 = pure white points, 1 = full colour.
// ------------------------------------------------------------------
// Star catalogue
// ------------------------------------------------------------------
stars: {
tint: 0.10, // 90 % white + 10 % of the star's real colour
brightness: 1.6 // overall glow of the catalogue points
tint: 0.10, // 0 = pure white points, 1 = full blackbody colour
brightness: 1.6 // overall glow of the catalogue points
},
// Day simulation: true = a full 24 h cycle runs in cycleSeconds
// (sun rises east, culminates south, sets west).
// false = real sun/moon positions via SunCalc.
// fixedTime: 'HH:MM' (or a number 0-24) freezes the sim clock at
// that time of day (moving objects, clouds, cars and the weather
// sim keep running unaffected); null/omitted = the clock advances.
// ------------------------------------------------------------------
// Day cycle
// ------------------------------------------------------------------
// simulation: true runs a full 24 h cycle in cycleSeconds (sun rises
// east, culminates south, sets west) and the clock plus the sun/moon
// tables follow the simulated date. false uses the real SunCalc positions.
day: {
// simulation: true runs a full 24 h cycle in cycleSeconds.
// 60 s = one simulated day per minute of wall time (one simulated
// hour every 2.5 s). The analog clock and the sun/moon tables follow
// the simulated date.
simulation: true,
cycleSeconds: 60,
fixedTime: null
},
// Cloud deck in world units (1 unit ~ 12.9 km at zoom 11; flight
// band 15-35 km = y ~ 1.16-2.70). Keep the top below the lowest
// flight altitude.
clouds: {
top: 0.9,
bottom: -0.3,
// per weather level (0 = clear, 1 = overcast); overrides the built-in
// sunny 0.20 / cloudy 0.70 / stormy 0.80 / night 0.45
density: { stormy: 0.80 }
cycleSeconds: 60, // 60 s = one simulated day per minute
fixedTime: null // 'HH:MM' or 0-24 freezes the sim clock
// (clouds, traffic and flights keep moving)
// day: { simulation: false } // real sun and moon
},
// ------------------------------------------------------------------
// Debug helpers (off by default, documented so they are findable)
// ------------------------------------------------------------------
// debug: 1 // show the debug panel (weather, shader
// counters, theme palette, theme lighting)
// tiles: { debug: true } // bake tile borders and x/y labels into the map
// terrain: { wireframe: true } // draw the mesh edges
// ground: { style: 'streets' } // labelled street map instead of satellite
};
// List of available themes. The key is the theme id (used in config.theme),
// the value is the CSS file name. Add a new theme by adding a new entry here
// and dropping the corresponding CSS file in the same directory.
/*
* Themes.
*
* Each entry is an object:
* css stylesheet loaded for this theme (also the palette source:
* the scene reads the CSS custom properties of the active theme)
* lighting optional tint of the physical light model. The physics stays
* the base; the theme only pulls the colours toward its own hue
* by 'tint' (0 = pure physics, 1 = theme colour) and scales the
* exposure with gain (day) and nightGain (night).
* sun/moon/air/sky accept '#rgb' or '#rrggbb'.
* Omit 'lighting' entirely for a purely physical theme.
*
* Add a theme by adding an entry here and dropping the CSS file next to
* config.js - build.py inlines every stylesheet referenced here.
*/
window.THEMES = {
'ash': 'ash.css',
'gruvbox': 'gruvbox.css',
'nord': 'nord.css'
};
'ash': {
css: 'ash.css',
lighting: {
tint: 0.28,
sun: '#ffd9a6', // warm amber beam
sky: '#5b6f96', // cool slate skylight
air: '#7d93bd', // haze and horizon colour
moon: '#c9d6ea',
gain: 1.00,
nightGain: 1.00
}
},
'nord': {
css: 'nord.css',
lighting: {
tint: 0.45,
sun: '#f2e3c0',
sky: '#5e81ac',
air: '#81a1c1',
moon: '#d8e3ee',
gain: 0.95,
nightGain: 1.12 // nord nights read a little brighter
}
},
'gruvbox': {
css: 'gruvbox.css',
lighting: {
tint: 0.50,
sun: '#fabd7f',
sky: '#83a598',
air: '#b8a27f',
moon: '#e6d5ae',
gain: 1.05,
nightGain: 1.05
}
}
// A theme without a lighting block keeps the pure physical colours:
// 'mono': { css: 'mono.css' }
};