Files
benilla-octo/crates/benilla-formats/tests/light_no_row_fallback.rs
T
SamandClaude Fable 5 77dc8f8ead pets get their action bar, macros arrive, and hunters draw the bow
- a hunter's or warlock's pet has its action bar now: attack, follow, stay, the pet's own spells with cooldowns and autocast sparkles
- the macro system lands: the macro window, the icon chooser, /cast and /macro, and macros dragged onto the action bar
- hunter shots look right at last: the bow draws and releases on the shot, and the arrow lays a spark trail its whole flight
- the options window re-dresses in 1.12's own art with the classic dialog look, and gets a working search box and clickable steppers

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 19:03:13 +07:00

138 lines
5.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! **A map with no `Light.dbc` row of its own falls back to record ID 1, not to an invention.**
//!
//! `Light.dbc` carries rows for 26 map ids; **Deeprun Tram (369) has none** — not even the
//! falloff-0 `(0,0,0)` global that maps 0/1 carry. The client handles that in
//! `dn_light_array_build 0x6d6170`: the per-map filter (`0x6d61a9 cmp [row+4], mapId`) matches
//! nothing, and the tail (`0x6d62b2`–`0x6d62c9`) writes `idMap[1]` — the record whose **ID column**
//! is 1, not row-index 1 — into slot 0. `dn_light_select 0x6d2d00` then no-ops (count ≤ 1 ⇒ empty
//! blend heap) and the colour table commits that record whole. (wow-re
//! `system/lighting/scratch/no-light-row-fallback.md`.)
//!
//! Row 1 is the Azeroth global → LightParams **12**, whose bands are an ordinary six-key day curve.
//! Before this was wired, benilla substituted a hardcoded bright noon (fog `[140,183,234]` at
//! 1000 yd) and the Tram's undersea stretch rendered as a daylight scene against the reference's
//! deep blue. The numbers below are read straight off the shipped DBC — LightParams 12 → int bands
//! 199..216 (sub-0 diffuse, sub-1 ambient, sub-7 fog) and float bands 67..72 (sub-0 fog end in
//! inches, sub-1 start fraction) — so they pin the *identity of the record*, which is the part that
//! was wrong.
use std::path::PathBuf;
use benilla_formats::{Chain, LightCatalog, Submersion};
fn vanilla_data_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../WoW/Data")
}
/// `Map.dbc` id of Deeprun Tram — the WMO-only map with no `Light.dbc` row.
const MAP_DEEPRUN_TRAM: u32 = 369;
/// Half-minutes; 1440 = noon.
const NOON: u32 = 1440;
#[test]
fn deeprun_tram_takes_light_record_1_not_an_invented_default() {
let data = vanilla_data_dir();
if !data.is_dir() {
eprintln!("skipping: vanilla client not present at {}", data.display());
return;
}
let mut chain = Chain::open(&data).expect("open vanilla patch chain");
let cat = LightCatalog::load(&mut chain).expect("load Light/LightParams/*Band");
// The Tram's own position is irrelevant — nothing covers it, which is the whole point.
let tram = cat.sample_blended(
MAP_DEEPRUN_TRAM,
[25.0, -1256.0, -117.0],
NOON,
false,
Submersion::Dry,
false,
);
// LightParams 12 at noon, off the shipped bands.
let rgb = |c: [f32; 3]| c.map(|v| (v * 255.0).round() as i32);
assert_eq!(
rgb(tram.fog_color),
[77, 120, 143],
"fog must be LightParams 12's own band, not the old [140,183,234] invention"
);
assert_eq!(rgb(tram.ambient), [104, 130, 154], "ambient = band sub-1");
assert_eq!(rgb(tram.sun_diffuse), [255, 136, 0], "diffuse = band sub-0");
assert!(
(tram.fog_end - 500.0).abs() < 1.0,
"fog end {} should be 18000 inches / 36 = 500 yd (was 1000)",
tram.fog_end
);
assert!(
(tram.fog_start_frac - 0.25).abs() < 1e-3,
"start fraction {} should be 0.25 (was 0.40)",
tram.fog_start_frac
);
// The fallback is a RECORD LOOKUP, not a per-map constant: every map with no row of its own
// lands on the same record, regardless of id or sample position. (Deliberately not compared
// against map 0 sampled at its origin — local Azeroth spheres cover that point and blend over
// the global, so it resolves [62,96,102], not the bare record.)
let other_rowless = cat.sample_blended(
4242,
[9000.0, -1000.0, 60.0],
NOON,
false,
Submersion::Dry,
false,
);
assert_eq!(
rgb(other_rowless.fog_color),
rgb(tram.fog_color),
"any map with no Light.dbc row takes the same record 1"
);
// And it is a real day curve, not a frozen constant: midnight differs from noon.
let midnight = cat.sample_blended(
MAP_DEEPRUN_TRAM,
[25.0, -1256.0, -117.0],
0,
false,
Submersion::Dry,
false,
);
assert_ne!(
rgb(midnight.fog_color),
rgb(tram.fog_color),
"the fallback runs the ordinary day/night pipeline, so the hours must differ"
);
}
/// The scoping guard for the fix: the seven shipped maps that carry positioned rows but **no**
/// `(0,0,0)` global are a *different* case, and one the byte finding does not settle (the reference
/// seeds the array count at 1 and fills slots 1.. with the positioned rows, leaving slot 0
/// unwritten). They must not be swept into the record-1 fallback on the strength of a guess.
#[test]
fn maps_with_positioned_rows_but_no_global_are_left_alone() {
let data = vanilla_data_dir();
if !data.is_dir() {
eprintln!("skipping: vanilla client not present at {}", data.display());
return;
}
let mut chain = Chain::open(&data).expect("open vanilla patch chain");
let cat = LightCatalog::load(&mut chain).expect("load Light/LightParams/*Band");
// Map 169 (Emerald Dream) carries 4 rows, none of them a global. Sampled far from all of them,
// it must NOT come back as record 1's atmosphere.
let far = cat.sample_blended(169, [0.0, 0.0, 0.0], NOON, false, Submersion::Dry, false);
let tram = cat.sample_blended(
MAP_DEEPRUN_TRAM,
[25.0, -1256.0, -117.0],
NOON,
false,
Submersion::Dry,
false,
);
let rgb = |c: [f32; 3]| c.map(|v| (v * 255.0).round() as i32);
assert_ne!(
rgb(far.fog_color),
rgb(tram.fog_color),
"a map WITH rows must not inherit the zero-match fallback"
);
}