MONKEY (leftovers): torchTerrainShadows evidence - census light height, clearance, near count

lamp_terrain_occlusion_scan takes an optional light height and ray clearance, counts blocked
samples within 15 yd, prints the ground height, and ranks by the near count. Used to pick the
leftovers B1 scenes (Elwynn campfires, lampposts, bridge lamp): the gather and the terrain
receiver work, the terrain-blocked ground is grazing or at the rim, so the lit image is unchanged.
LIGHTING.md says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pkuzic
2026-09-26 17:09:13 +01:00
co-authored by Claude Opus 5.5
parent af38c6eec8
commit bd756f2a72
2 changed files with 16 additions and 7 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ behind each constant.
| Synthesised lights | torches, braziers, lanterns, campfires and lampposts emit a light in the colour of their flame, with flicker | `fireLightGain`, `fireFlicker` |
| Torch shadows | cube-map shadows from point lights onto buildings, models and terrain, with a contact-hardening penumbra and a shadow floor | `interiorShadows`, `exteriorShadows`, `torchShadowStrength`, `interiorShadowSoft` |
| Daylight and doorways | calibrated fixtures at doors, windows and open boundaries carry daylight into a room, and doorways between rooms bleed light | `interiorDaylight`, env `WOW_DAYLIGHT`, `WOW_BLEED` |
| Terrain in torch shadows | the ground casts into an outdoor fire's cube map, so a hill or bank blocks the fire (settled exterior slots only; `torch_terrain.rs` gathers the resident MCNK chunks inside the cube's 48 yd) | `torchTerrainShadows` (default 0, High 1), env `WOW_TORCH_TERRAIN=0\|1` |
| Terrain in torch shadows | the ground casts into an outdoor fire's cube map, so a hill or bank blocks the fire (settled exterior slots only; `torch_terrain.rs` gathers the resident MCNK chunks inside the cube's 48 yd; the gather and the ground receiver both work, but stock Elwynn lamps/campfires show no visible change: the terrain-blocked ground is grazing (N·L ≈ 0, inside the 0.15 yd normal offset) or at the pool rim, below one 8-bit step — measured by the `lamp_terrain_occlusion_scan` census) | `torchTerrainShadows` (default 0, High 1), env `WOW_TORCH_TERRAIN=0\|1` |
| City day floor | `interiorDaylight`'s room floor also reaches Stormwind/Ironforge rooms that the portal graph connects to the sky (an exterior-facing portal, or one hop from one; `district_sky_rooms` in `daylight.rs`); before this no city room could take it | `interiorDaylight` (default 0, suggested High 0.10) |
| Spell and firework lights | fire, holy and fel effects light their surroundings for their lifetime; frost, nature, arcane and shadow do not | `spellLightGain`, env `WOW_SPELL_LIGHT=0` |
| Moon shadows | at night the same shadow rig re-aims at the moon and casts a faint shadow; dims only the night sky term, never point lights | `moonShadowStrength` |
@@ -306,10 +306,18 @@ fn lamp_terrain_occlusion_scan() {
.find_map(|c| benilla_formats::terrain_height_at(c, [x, y, 1.0e4]))
};
let mut rows = Vec::new();
// MONKEY (leftovers): optional 6th field = light height above the doodad origin (default the
// lamppost's 3.5 yd; a campfire's flame sits near 1 yd).
let lift = p.get(5).and_then(|v| v.parse::<f32>().ok()).unwrap_or(3.5);
// 7th field = how far the ground must rise above the ray to count (default 5 cm; the receiver's
// normal offset is 0.15 yd, so a grazing 5 cm crest is below what a cube map can resolve).
let clear = p.get(6).and_then(|v| v.parse::<f32>().ok()).unwrap_or(0.05);
for l in lamps {
let head = Vec3::new(l[0], l[1], l[2] + 3.5);
let head = Vec3::new(l[0], l[1], l[2] + lift);
let (mut total, mut blocked) = (0u32, 0u32);
let mut best = (0.0f32, 0.0f32);
// MONKEY (leftovers): blocked samples inside 15 yd, where a fire's pool is still visible.
let mut near = 0u32;
for ring in 1..=10 {
let d = ring as f32 * 2.5;
for k in 0..36 {
@@ -321,22 +329,23 @@ fn lamp_terrain_occlusion_scan() {
let steps = (d / 0.5) as i32;
let hit = (1..steps).any(|s| {
let q = head.lerp(target, s as f32 / steps as f32);
h(q.x, q.y).is_some_and(|g| g > q.z + 0.05)
h(q.x, q.y).is_some_and(|g| g > q.z + clear)
});
if hit {
blocked += 1;
best = (a.to_degrees(), d);
near += (d <= 15.0) as u32;
}
}
}
if total > 0 {
rows.push((blocked as f32 / total as f32, l, best));
rows.push((blocked as f32 / total as f32, l, best, near, h(l[0], l[1]).unwrap_or(f32::NAN)));
}
}
rows.sort_by(|a, b| b.0.total_cmp(&a.0));
for (f, l, (a, d)) in rows.iter().take(12) {
rows.sort_by(|a, b| b.3.cmp(&a.3).then(b.0.total_cmp(&a.0)));
for (f, l, (a, d), near, g) in rows.iter().take(12) {
println!(
"lamp ({:8.1},{:7.1},{:6.1}) terrain-blocked {:5.1}% e.g. bearing {a:5.0} deg at {d:4.1} yd",
"lamp ({:8.1},{:7.1},{:6.1}) ground {g:6.2} terrain-blocked {:5.1}% ({near} within 15 yd) e.g. bearing {a:5.0} deg at {d:4.1} yd",
l[0], l[1], l[2], f * 100.0
);
}