- a player build ships beside the dev one — dev chords and probe surfaces go dark, and a gate enforces what a player can still reach, by medium not by module - the binary carries its own shaders and its shipped UI, finds the WoW folder by itself, and keeps its local state in one benilla-config folder - the glue screens get a capture shutter, and the engine stops depending on the protocol crate Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
124 lines
5.1 KiB
Rust
124 lines
5.1 KiB
Rust
//! **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 benilla_formats::{Chain, LightCatalog, Submersion};
|
||
|
||
/// `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 = benilla_formats::wow_data_or_skip!();
|
||
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 = benilla_formats::wow_data_or_skip!();
|
||
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"
|
||
);
|
||
}
|