diff --git a/crates/benilla-formats/examples/dbc_diff.rs b/crates/benilla-formats/examples/dbc_diff.rs index c19be638..eb7f8243 100644 --- a/crates/benilla-formats/examples/dbc_diff.rs +++ b/crates/benilla-formats/examples/dbc_diff.rs @@ -134,28 +134,6 @@ fn main() -> Result<()> { // `copies` is ascending priority, so the client reads the last one. let win = open(copies[copies.len() - 1].0)?; - // `--row=N` just prints that row of the winning copy, text slots as text: for naming the thing - // behind an id a decoded catalog only gave a number for. - if let Some(want) = row_arg { - match win.rows.get(&want) { - Some(r) => { - let cells: Vec = r - .iter() - .enumerate() - .map(|(f, &v)| { - if win.is_text(v) { - format!("[{f}] {:?}", win.text(v)) - } else { - format!("[{f}] {v}") - } - }) - .collect(); - println!("\nrow {want}: {}", cells.join(" ")); - } - None => println!("\nno row {want}"), - } - return Ok(()); - } let base = match base_arg { Some(ref want) => copies .iter() @@ -174,6 +152,33 @@ fn main() -> Result<()> { file_name(base.0) ); let old = open(base.0)?; + + // `--row=N` prints that row of the winning copy and of the baseline, text slots as text: for + // naming the thing behind an id a decoded catalog only gave a number for, and for reading a row + // the realm *deleted*, which is only legible in the copy underneath. + if let Some(want) = row_arg { + let dump = |t: &Table, label: &str| match t.rows.get(&want) { + Some(r) => { + let cells: Vec = r + .iter() + .enumerate() + .map(|(f, &v)| { + if t.is_text(v) { + format!("[{f}] {:?}", t.text(v)) + } else { + format!("[{f}] {v}") + } + }) + .collect(); + println!("{label} row {want}: {}", cells.join(" ")); + } + None => println!("{label} has no row {want}"), + }; + println!(); + dump(&win, "wins "); + dump(&old, "under"); + return Ok(()); + } println!( "\nwins: {} rows x {} fields under: {} rows x {} fields", win.rows.len(), diff --git a/crates/benilla-formats/src/world_map_area.rs b/crates/benilla-formats/src/world_map_area.rs index a9e5ccc9..516ba8c5 100644 --- a/crates/benilla-formats/src/world_map_area.rs +++ b/crates/benilla-formats/src/world_map_area.rs @@ -44,12 +44,16 @@ impl WorldMapAreaCatalog { self.by_id.get(&id) } - /// The continent-wide row (`area_id == 0`) for `map_id`, one per continent in 5875. + /// The continent-wide row (`area_id == 0`) for `map_id`: the **first** in file order, as the + /// reference's own walk takes it. + /// + /// 5875 had one such row per map, so the order never mattered. The realm ships two for map 0 — + /// 14 "Azeroth" and its own 694 "World" — and this used to `find` over the `by_id` `HashMap`, + /// which made the answer a coin flip between them from run to run. File order settles it on + /// Azeroth, the row 1.12 shipped. pub fn continent(&self, map_id: u32) -> Option<(u32, &WorldMapArea)> { - self.by_id - .iter() + self.iter() .find(|(_, a)| a.map_id == map_id && a.area_id == 0) - .map(|(&id, a)| (id, a)) } /// Every row as `(id, row)`, in file order. @@ -131,13 +135,70 @@ pub fn load_world_map_area_catalog(chain: &mut Chain) -> Result = cat + .iter() + .filter(|(_, a)| a.area_id == 0) + .map(|(id, a)| (id, a.map_id, a.name.as_str())) + .collect(); + continents.sort_unstable(); + assert_eq!( + continents, + vec![(13, 1, "Kalimdor"), (14, 0, "Azeroth"), (694, 0, "World")] + ); + + // Nothing nameless survived, and no surviving row carries a degenerate rect — the two + // properties the drop exists to keep. + assert!( + cat.iter().all(|(_, a)| !a.name.is_empty()), + "a nameless row reached the catalog" + ); + // "World" is named and has art — `Interface\WorldMap\World\World1.blp` ships in + // patch-M — but all four of its rect columns are zero, and it is the only named row like + // that. `map_proj::zone_uv`'s `axis` already refuses a zero span and answers 0.0 rather + // than dividing, so selecting that continent puts a blip in the corner; it does not NaN. + let degenerate: Vec<(u32, &str)> = cat + .iter() + .filter(|(_, a)| a.loc_left == a.loc_right || a.loc_top == a.loc_bottom) + .map(|(id, a)| (id, a.name.as_str())) + .collect(); + assert_eq!( + degenerate, + vec![(694, "World")], + "only the realm's World sheet ships a zero rect" + ); + let world = cat.get(694).expect("row 694"); + assert_eq!( + ( + world.loc_left, + world.loc_right, + world.loc_top, + world.loc_bottom + ), + (0.0, 0.0, 0.0, 0.0) + ); let azeroth = cat.get(14).expect("Azeroth continent row (id 14)"); assert_eq!((azeroth.map_id, azeroth.area_id), (0, 0)); @@ -147,8 +208,12 @@ mod tests { assert_eq!((kalimdor.map_id, kalimdor.area_id), (1, 0)); assert_eq!(kalimdor.name, "Kalimdor"); + // Two rows answer map 0 now, and file order must keep picking 1.12's. let (azeroth_id, _) = cat.continent(0).expect("Azeroth via continent(0)"); - assert_eq!(azeroth_id, 14); + assert_eq!( + azeroth_id, 14, + "not the realm's World row, which is also map 0" + ); let (kalimdor_id, _) = cat.continent(1).expect("Kalimdor via continent(1)"); assert_eq!(kalimdor_id, 13); diff --git a/crates/benilla-formats/src/world_map_continent.rs b/crates/benilla-formats/src/world_map_continent.rs index 51fea1ea..bfdacbe8 100644 --- a/crates/benilla-formats/src/world_map_continent.rs +++ b/crates/benilla-formats/src/world_map_continent.rs @@ -138,7 +138,20 @@ mod tests { let data = crate::wow_data_or_skip!(); let mut chain = crate::open_chain(&data).expect("open chain"); let cat = load_world_map_continent_catalog(&mut chain).expect("load WorldMapContinent"); - assert_eq!(cat.len(), 2); + // Three rows where 5875 had two. The realm's is row 3, and its `MapID` is 42 — which the + // same patch *deleted* from `Map.dbc`, along with the rest of 1.12's dev test maps (13 + // "Testing", 25 "Scott Test", 29 "CashTest", 37 "Azshara Crater", 42 "Collin's Test", 44 + // " Monastery"). So the row names a map that does not exist. + // + // It is inert rather than broken: the continent list the world map builds comes from + // `WorldMapArea`'s `AreaID == 0` rows (`ui_world_map.rs:146`), which name maps 0 and 1 + // only, and this table is reached by `wmc.get(map_id)` for those — never walked. Nothing + // asks for continent 42. + assert_eq!(cat.len(), 3, "5875 shipped two continents"); + assert!( + cat.get(42).is_some(), + "the realm's third row is keyed on the map it deleted" + ); let azeroth = cat.get(0).expect("Azeroth (mapId 0)"); assert_eq!( diff --git a/crates/benilla-formats/src/world_map_overlay.rs b/crates/benilla-formats/src/world_map_overlay.rs index b0f45a9a..405725e9 100644 --- a/crates/benilla-formats/src/world_map_overlay.rs +++ b/crates/benilla-formats/src/world_map_overlay.rs @@ -127,7 +127,10 @@ mod tests { let data = crate::wow_data_or_skip!(); let mut chain = crate::open_chain(&data).expect("open chain"); let cat = load_world_map_overlay_catalog(&mut chain).expect("load WorldMapOverlay"); - assert_eq!(cat.len(), 526, "all 526 rows load"); + // 707 rows where 5875 had 526, and `dbc_diff` says the realm did not extend this table but + // rebuilt it: 360 ids added, 179 removed and 347 of the survivors given a different texture + // and rect. So nothing here is 1.12's numbering — only the joins are worth asserting. + assert_eq!(cat.len(), 707, "all 707 rows load; 5875 had 526"); let teldrassil = cat.for_area(41); assert!( diff --git a/crates/benilla-formats/src/zone_map.rs b/crates/benilla-formats/src/zone_map.rs index 80199c8e..63883ff5 100644 --- a/crates/benilla-formats/src/zone_map.rs +++ b/crates/benilla-formats/src/zone_map.rs @@ -37,6 +37,17 @@ mod tests { use super::*; /// Cell 12735 is Goldshire: row from wx = -9450, column from wy = 60. + /// + /// The realm repainted both bitmaps (its own copies in patch-3 and patch-4) at a coarser + /// granularity: every id it writes is a **zone**, where 1.12 wrote sub-areas and left the + /// client to roll them up. Azeroth carries 1874 labelled cells over 34 distinct ids, all + /// zone-level, where `patch.MPQ` had 2630 over 334 of which 310 were sub-areas — Northshire + /// Valley, Crystal Lake, Fargodeep Mine. Comparing the two after the one-hop parent rollup + /// `ui_world_map.rs:193` applies: 1280 cells agree, 534 name a *different* zone, 816 lost a + /// zone and 60 gained one, so this is a redrawn map and not a mechanical flattening. + /// + /// Nothing of ours needed changing — the rollup is simply a no-op on an already-flat grid — + /// and the cost is that the ~800 cells the realm left blank name no zone on hover. #[test] fn real_azeroth_zone_map_goldshire_cell() { let data = crate::wow_data_or_skip!(); @@ -45,9 +56,32 @@ mod tests { assert_eq!(grid.len(), 16384); assert_eq!(grid[12735], 12, "the Goldshire cell is Elwynn Forest"); let nonzero = grid.iter().filter(|&&v| v != 0).count(); + assert_eq!(nonzero, 1874, "labelled cells; 1.12's file had 2630"); + + // The structural claim behind that number, and the one a decode slip would break: every + // labelled cell names a real `AreaTable` row that is itself a zone. 1.12's file fails this. + let areas = crate::load_area_table_catalog(&mut chain).expect("load AreaTable"); + let not_a_zone: Vec = { + let mut v: Vec = grid + .iter() + .copied() + .filter(|&id| id != 0) + .filter(|&id| areas.get(id).is_none_or(|r| r.zone_id != 0)) + .collect(); + v.sort_unstable(); + v.dedup(); + v + }; assert!( - (2000..6000).contains(&nonzero), - "land cells in a plausible band, got {nonzero}" + not_a_zone.is_empty(), + "every labelled cell is a top-level zone: {not_a_zone:?}" + ); + let distinct: std::collections::BTreeSet = + grid.iter().copied().filter(|&v| v != 0).collect(); + assert_eq!( + distinct.len(), + 34, + "distinct zones painted; 1.12 had 334 ids" ); } }