- 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>
95 lines
4.2 KiB
Rust
95 lines
4.2 KiB
Rust
//! **The last chunk clamps to EOF.** A WMO's chunk stream is not guaranteed to tile its file
|
|
//! exactly, and the reference tolerates the slop: its walk "reads chunks while the 8-byte header is
|
|
//! in-bounds and clamps the last chunk to EOF (never requires exact tiling)" — wow-re `models.md`,
|
|
//! "WMO chunk-structure contract" (VERIFIED, with a 6332-file corpus oracle), whose own worked
|
|
//! example is the single file in the game that needs the rule: `Undercity_144.wmo`'s MOGP declares
|
|
//! one byte more than the file holds.
|
|
//!
|
|
//! We used to `break` there and hand the caller nothing. That cost g144 its whole MOGP — flags,
|
|
//! portal-ref span, area id, fog, doodad and light refs — and since the portal flood reaches a
|
|
//! neighbour only through a group's ref span, the group became a **dead end**: g144 is the short
|
|
//! corridor joining g95 and g152, so the rooms past it culled from either side and you saw sky
|
|
//! through the doorway (B26, decision 0972).
|
|
//!
|
|
//! Two oracles, because the bug had two halves. The corpus sweep is the one that generalises: it is
|
|
//! the same shape as the reference's own "0 rejected" oracle, and it fails for a *new* file as well
|
|
//! as for this one. The byte pins on g144 catch a clamp that silently returns the wrong slice.
|
|
//! Skips when the client isn't present.
|
|
|
|
use benilla_formats::{parse_wmo_root, wmo_group_header, Chain};
|
|
|
|
/// The file the rule exists for. Its MOGP header must come back whole, with the exact values the
|
|
/// raw bytes carry at MOGP+0x08 (flags) and MOGP+0x24/0x26 (the portal-ref span) — the two fields
|
|
/// whose loss dead-ended the flood.
|
|
#[test]
|
|
fn undercity_144_mogp_survives_its_one_byte_overrun() {
|
|
let data = benilla_formats::wow_data_or_skip!();
|
|
let reader = Chain::open(&data).expect("open vanilla patch chain");
|
|
let bytes = reader
|
|
.read("World\\wmo\\Lorderon\\Undercity\\Undercity_144.wmo")
|
|
.expect("read Undercity_144");
|
|
|
|
// The overrun is a property of the shipped file — if this ever stops holding, the test below is
|
|
// no longer exercising the clamp and the rest of this file is decoration.
|
|
let declared =
|
|
u32::from_le_bytes([bytes[0x10], bytes[0x11], bytes[0x12], bytes[0x13]]) as usize;
|
|
assert_eq!(
|
|
0x0c + 8 + declared,
|
|
bytes.len() + 1,
|
|
"Undercity_144's MOGP is supposed to declare exactly one byte past EOF"
|
|
);
|
|
|
|
let h = wmo_group_header(&bytes).expect("MOGP must survive the clamp");
|
|
assert_eq!(h.flags, 0xa805, "MOGP+0x08 flags");
|
|
assert_eq!(h.portal_ref_start, 397, "MOGP+0x24 portal-ref start");
|
|
assert_eq!(h.portal_ref_count, 2, "MOGP+0x26 portal-ref count");
|
|
}
|
|
|
|
/// The general oracle, mirroring the reference's own: **every** WMO in the corpus yields the chunk
|
|
/// its loader needs — a root parses, a group gives up its MOGP header. This is what makes the rule a
|
|
/// contract rather than a one-file patch; `wmo_chunk_census --all` is the same sweep with a readout.
|
|
#[test]
|
|
fn every_wmo_in_the_corpus_yields_its_loader_chunk() {
|
|
let data = benilla_formats::wow_data_or_skip!();
|
|
let mut reader = Chain::open(&data).expect("open vanilla patch chain");
|
|
let mut paths: Vec<String> = reader
|
|
.list()
|
|
.expect("list the patch chain")
|
|
.into_iter()
|
|
.map(|e| e.name)
|
|
.filter(|n| n.to_ascii_lowercase().ends_with(".wmo"))
|
|
.collect();
|
|
paths.sort();
|
|
paths.dedup();
|
|
assert!(
|
|
paths.len() > 5000,
|
|
"expected the full WMO corpus, found {} files",
|
|
paths.len()
|
|
);
|
|
|
|
let mut checked = 0u32;
|
|
let mut bad: Vec<String> = Vec::new();
|
|
for p in &paths {
|
|
let Ok(b) = reader.read_file(p) else { continue };
|
|
if b.is_empty() {
|
|
continue; // 0-byte stubs ship in the corpus; the reference accepts them too
|
|
}
|
|
checked += 1;
|
|
// A group file is MVER + MOGP, so its 4CC sits at 0x0c; anything else is a root.
|
|
let ok = if b.len() > 16 && &b[12..16] == b"PGOM" {
|
|
wmo_group_header(&b).is_some()
|
|
} else {
|
|
parse_wmo_root(&b).is_ok()
|
|
};
|
|
if !ok {
|
|
bad.push(p.clone());
|
|
}
|
|
}
|
|
assert!(
|
|
bad.is_empty(),
|
|
"{} of {checked} WMO files lost the chunk their loader needs: {:?}",
|
|
bad.len(),
|
|
&bad[..bad.len().min(10)]
|
|
);
|
|
}
|