Files
benilla-octo/crates/benilla-formats/tests/m2_lights.rs
T
SamandClaude Fable 5 6b980b9efb the player build: one binary that carries its content
- 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>
2026-08-11 18:18:02 +07:00

92 lines
3.8 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.
//! M2 light-block parse — byte-level check against a real Elwynn campfire, the dynamic point-light
//! "hot-spot" caster (decision 0016). Pins the vanilla `0xd4` record stride and the diffuse-colour track
//! offset. Skips when the client isn't present.
use benilla_formats::{parse_m2_lights, Chain};
#[test]
fn elwynn_campfire_has_one_warm_point_light() {
let data = benilla_formats::wow_data_or_skip!();
let reader = Chain::open(&data).expect("open vanilla patch chain");
let bytes = reader
.read("World\\Azeroth\\Elwynn\\PassiveDoodads\\Campfire\\ElwynnCampfire.m2")
.expect("read ElwynnCampfire.m2");
let lights = parse_m2_lights(&bytes);
assert_eq!(lights.len(), 1, "campfire has exactly one M2 light");
let l = lights[0];
assert!(
l.is_point(),
"campfire light is type 1 (point), got type {}",
l.light_type
);
// The authored warm-orange diffuse pins the 0xd4 record stride AND the 0x48 diffuse-colour track
// offset (independently cross-checked ≈ (0.71, 0.20, 0.00) — a garbled stride would not land here).
let d = l.diffuse_color;
assert!(
(d[0] - 0.71).abs() < 0.03 && (d[1] - 0.20).abs() < 0.03 && d[2] < 0.05,
"campfire diffuse ≈ (0.71, 0.20, 0.00), got {d:?}"
);
}
/// The **held torch** — the light an NPC carries into the world (`Club_1H_Torch_A_01.m2`, the model
/// `ItemDisplayInfo` 12236 / item 1906 "Monster - Torch" resolves to). Pins the one asset the entity
/// carried-light path exists for: a `type==1` point light on bone 9, up the shaft, warm, `×3`
/// intensity, and — the gate — a visibility track whose first key is nonzero, so it CASTS.
#[test]
fn held_torch_casts_one_warm_point_light_up_the_shaft() {
let data = benilla_formats::wow_data_or_skip!();
let reader = Chain::open(&data).expect("open vanilla patch chain");
let bytes = reader
.read("Item\\ObjectComponents\\Weapon\\Club_1H_Torch_A_01.m2")
.expect("read Club_1H_Torch_A_01.m2");
let lights = parse_m2_lights(&bytes);
assert_eq!(lights.len(), 1, "the torch has exactly one M2 light");
let l = lights[0];
assert!(l.casts(), "point + visibility-on: the torch really glows");
assert_eq!(l.bone, 9, "the light hangs on the torch's own bone");
assert!(
(l.position[0] - 0.5765).abs() < 0.01 && l.position[1].abs() < 0.01,
"0.58 yd up the shaft (the head), got {:?}",
l.position
);
let d = l.diffuse_color.map(|c| c * l.diffuse_intensity);
assert!(
(d[0] - 1.40).abs() < 0.01 && (d[1] - 0.871).abs() < 0.01 && (d[2] - 0.40).abs() < 0.01,
"diffuse × intensity ≈ (1.40, 0.87, 0.40) — flame orange, over-driven, got {d:?}"
);
}
/// The **dark** shape (wow-re `m2-dynamic-lights.md` §9.4): a `type==1` light whose visibility track
/// ships a static `0` key never casts. 11 of the corpus's 85 point lights are this — mostly spell
/// impact VFX — so the gate is not academic; before it, every one of them lit the world.
#[test]
fn a_static_zero_visibility_key_reads_as_dark() {
let data = benilla_formats::wow_data_or_skip!();
let reader = Chain::open(&data).expect("open vanilla patch chain");
let bytes = reader
.read("Spells\\FlameStrike_ImpactDD_Med_Base.m2")
.expect("read FlameStrike_ImpactDD_Med_Base.m2");
let dark: Vec<_> = parse_m2_lights(&bytes)
.into_iter()
.filter(|l| l.is_point())
.collect();
assert!(
!dark.is_empty(),
"the impact model does author point lights"
);
assert!(
dark.iter().all(|l| l.visibility_off && !l.casts()),
"every one is held dark by its visibility track"
);
}
#[test]
fn too_short_yields_no_lights() {
// No MD20 lights array header → empty, no panic.
assert!(parse_m2_lights(&[0u8; 16]).is_empty());
}