Environment Lighting
Contract
The renderer consumes evaluated scene lights, not a title's authored source.
ENVIRONLIGHT in common/shared.h is the semantic sample: direction (toward the light),
directional color/intensity, ambient color/intensity, type (RMODELLIGHTTYPE), and valid.
Type 0 is omni, so presence is valid, not type == 0.
viewDef_t carries two samples:
terrainLight— world/groundentityLight— models; when invalid, WC3 reusesterrainLight
MODELLIGHTING in renderer/r_shader.h remains the 8-slot shader pack. Convert with
R_EnvironLightFromModel / R_LightingFromEnviron. Do not put MODELLIGHTING on the wire.
Data Flow
game-owned config
WC3: SetDayNightModels → CS_TERRAIN_LIGHT_MODEL / CS_ENTITY_LIGHT_MODEL
WoW: Light*.dbc (missing in 1.5) / synthesized sun
SC2: map sc2MapLighting_t
|
v
playerState.stats[UI_PLAYERSTAT_ENV_PHASE] (normalized clock; 0 if unused)
|
v
client copies optional model handles + phase onto viewDef (no game #ifdef)
|
v
R_SetupEnvironmentLighting() (per-game hook in games/*/renderer/r_game.c)
|
v
viewDef.terrainLight / entityLight
|
v
R_SetDefaultLighting / R_SetModelLighting
client/cl_view.c must not include a game header or compile-guard the clock slot.
PR #296 originally resolved WC3_UI_PLAYERSTAT_TIME_PHASE behind #if !defined(WOW) && !defined(SC2);
that is the same shared-dispatcher mistake as camera #ifdef WC3 (see
server-selected-effects.md).
Configuration vs current conditions
| Owner | Channel | |
|---|---|---|
| Authored source (DNC MDX, Light.dbc, map rig) | games/<game>/ |
configstrings, DBC, map data |
| Normalized day phase | server game module | UI_PLAYERSTAT_ENV_PHASE (stats[16]) |
| Environment presentation variant | server game module | UI_PLAYERSTAT_ENV_VARIANT (stats[23]) |
| Evaluated sample | game renderer hook | viewDef ENVIRONLIGHT |
Do not unify the authored sources into one common config struct. They are incommensurable. The shared object is the evaluated sample.
UI_PLAYERSTAT_ENV_PHASE occupies stats[16], packed with cinematic portrait color in the
existing stats[16] NFT_LONG. It must not reuse slot 17 (UI_PLAYERSTAT_CINEMATIC_PORTRAIT_COLOR).
UI_PLAYERSTAT_ENV_VARIANT occupies stats[23], transported by the already-declared stats[22]
NFT_LONG. It is presentation metadata, not a second lighting clock; WC3 uses it to choose the alternate
time-indicator sequence while the same normalized phase continues to drive DNC evaluation.
PlayerState
Evaluated ENVIRONLIGHT is not on playerState_t yet. The 32-bit player-state field mask has
spare bits (MSG_FIELD_COUNT(playerStateFields) in common/msg.c); a new netField still needs an
explicit table entry. Lighting is world-global, and WC3/WoW evaluation sources live in the renderer
(MDX tracks, camera-dependent DBC). The portrait-present flag, team, color, and race pack as one NFT_LONG;
the full portrait model index is sent separately in UI_PLAYERSTAT_CINEMATIC_PORTRAIT_MODEL.
The interpolated quantity today is the phase stat at snapshot rate (10 Hz). The game renderer
samples authored tracks at that phase every render frame. Camera-style lightstate[0/1] lerp of
evaluated RGB waits on a 64-bit mask (or a packed NFT_ENVLIGHT) and must snap on explicit
SetTimeOfDay rather than smearing the jump.
Game hooks
R_SetupEnvironmentLighting is declared in renderer/r_game.h and called from R_RenderFrame
after the view copy. Each game fills tr.viewDef.terrainLight / entityLight:
| Game | Source |
|---|---|
| WC3 | sequence 0, first light of the DNC models, at environmentPhase |
| WoW | Wow_SunDirection / WOW_LIGHT_* (later Light.dbc at camera) |
| SC2 | map key directional (SC2_LIGHT_KEY) |
valid == 0 keeps each path's historical fallback.
Known Pitfalls
- Do not put MDX model handles on
viewDefas the lighting contract. They are WC3 sampling inputs. Draw paths readENVIRONLIGHT. - Do not evaluate WoW lights on the server: classic
Light.dbcis camera-position dependent. - Midnight wrap: do not lerp phase 0.99→0.01 without wrap handling; that flashes midday.
- HUD clock sprites bind to
UI_PLAYERSTAT_ENV_PHASE. That slot is a clock, not a light. - A HUD clock may also bind its explicit
#Nsequence throughUIFLAG_SPRITE_STAT_SEQUENCEandUI_PLAYERSTAT_ENV_VARIANT; do not derive lighting phase from the variant.
Verification
renderer_shader.environ_light_converts_to_model_lighting locks the sample↔shader conversion
and the distinct stats[16] / stats[17] slots. Runtime: terrain and units follow dusk/night/dawn
continuously; SuspendTimeOfDay(true) freezes both clock and lighting; maps without DNC models
keep the legacy fixed sun.