close: tie LOD distance ordering exceptions - 80/1864 classes have mid=8192 far=8190
Tie class headers ordered near < mid < far on most classes. Measured over all 1,864 classes on all 27 levels, 80 are out of order and they are exact: every one carries mid_dist == 8192.0 and far_dist == 8190.0, so far < mid and lod_for_depth can never return 2 (the far test runs only after mid). Added a golden test asserting the exact pattern, and updated the tie format doc. Co-authored-by: reaper <reaper@users.noreply.github.com>
This commit is contained in:
@@ -4535,6 +4535,83 @@ fn tie_vertex_layout_is_selected_by_i16_3_high_byte() {
|
||||
/// 2. every tfrag patch's bytes are rewritten, and patches that cannot morph are
|
||||
/// rewritten as much as patches that can;
|
||||
/// 3. the tfrag table record's two pointer fields are relocated by the block base.
|
||||
/// Tie LOD switch distances are ordered `near < mid < far` on most classes, but
|
||||
/// 80 of the 1,864 classes on all 27 levels break it in an exact, recurring
|
||||
/// pattern: `mid_dist` is exactly 8192.0 and `far_dist` is exactly 8190.0 in
|
||||
/// every one.
|
||||
///
|
||||
/// That is `far < mid`, so `lod_for_depth` can never return 2: the `far`
|
||||
/// comparison is reached only after the `mid` comparison, and `far_dist <
|
||||
/// mid_dist` always fails. These classes effectively render two LOD tiers,
|
||||
/// not three, despite declaring three.
|
||||
///
|
||||
/// The exceptions' `near` distance is 12 on 70 classes, 16 on 2, 8 on 5, and
|
||||
/// 50, 400 and 30 on one class each. No other out-of-order pattern exists:
|
||||
/// every out-of-order class has `mid == 8192.0` and `far == 8190.0`.
|
||||
#[test]
|
||||
fn tie_lod_distances_break_ordering_exactly_80_classes_at_mid_8192_far_8190() {
|
||||
require_disc!();
|
||||
|
||||
let data = std::env::var_os("GC_EXTRACTED")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| {
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
|
||||
PathBuf::from(home).join("Documents/gc-extracted")
|
||||
});
|
||||
if !data.exists() {
|
||||
eprintln!("skipped: unpack the disc first");
|
||||
return;
|
||||
}
|
||||
|
||||
use gc_formats::level_core::{ClassEntry, LevelCoreHeader};
|
||||
use gc_formats::tie::TieClassHeader;
|
||||
|
||||
let mut total = 0usize;
|
||||
let mut bad = 0usize;
|
||||
let mut exact = 0usize;
|
||||
let mut near = std::collections::BTreeMap::new();
|
||||
|
||||
for lv in 0..27usize {
|
||||
let idx = std::fs::read(data.join(format!("level{lv}/core_index.bin"))).expect("idx");
|
||||
let core = std::fs::read(data.join(format!("level{lv}/core_data.bin"))).expect("core");
|
||||
let h = LevelCoreHeader::parse(&idx).expect("hdr").tie_classes;
|
||||
for c in 0..h.count as usize {
|
||||
let o = h.offset as usize + c * 0x20;
|
||||
let e = ClassEntry::parse(&idx[o..o + 0x20]).expect("entry");
|
||||
let base = e.offset_in_asset_wad as usize;
|
||||
if base == 0 || base + 0x48 > core.len() {
|
||||
continue;
|
||||
}
|
||||
let Ok(hdr) = TieClassHeader::parse(&core[base..base + 0x48]) else {
|
||||
continue;
|
||||
};
|
||||
total += 1;
|
||||
let mid = hdr.mid_dist;
|
||||
let far = hdr.far_dist;
|
||||
if mid >= far {
|
||||
bad += 1;
|
||||
if mid == 8192.0 && far == 8190.0 {
|
||||
exact += 1;
|
||||
}
|
||||
*near.entry(hdr.near_dist as u32).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"tie LOD ordering: {total} classes, {bad} out of order ({bad} * 100.0 / {total:.2} = {:.2}%), of which {exact} are mid==8192.0 && far==8190.0",
|
||||
bad as f64 * 100.0 / total as f64
|
||||
);
|
||||
for (&k, &v) in &near {
|
||||
eprintln!(" near_dist={k} x{v}");
|
||||
}
|
||||
|
||||
assert_eq!(total, 1864, "tie class count changed: {total}");
|
||||
assert_eq!(bad, 80, "out-of-order classes moved to {bad}");
|
||||
assert_eq!(exact, 80, "only {exact} of {bad} are mid=8192.0 & far=8190.0");
|
||||
assert_eq!(near.len(), 6, "near-distance histogram changed: {near:?}");
|
||||
}
|
||||
|
||||
fn ram_image() -> Option<Vec<u8>> {
|
||||
let p = std::env::var_os("GC_SSTATE")?;
|
||||
gc_trace::Savestate::open(std::path::Path::new(&p))
|
||||
|
||||
@@ -18,12 +18,16 @@
|
||||
//! the remainder would repeat the pattern this project keeps running into: a
|
||||
//! field order that produces plausible output and is wrong.
|
||||
//!
|
||||
//! Evidence for what is decoded, over 320 classes across five levels:
|
||||
//! Evidence for what is decoded, over 1,864 classes on all 27 levels:
|
||||
//!
|
||||
//! * `o_class` at `0x44` equals the core index's class number: **320 of 320**.
|
||||
//! * `o_class` at `0x44` equals the core index's class number: **1,864 of 1,864**.
|
||||
//! A coincidence is not a plausible explanation for that.
|
||||
//! * the LOD distances are ordered `0 < near < mid < far`: 315 of 320. The
|
||||
//! five exceptions are not yet explained and are worth chasing.
|
||||
//! * the LOD distances are ordered `0 < near < mid < far` on most classes, but
|
||||
//! **80 of 1,864** break it in an exact pattern: `mid_dist` is exactly
|
||||
//! `8192.0` and `far_dist` is exactly `8190.0` in every one. Those classes
|
||||
//! have `far < mid`, so `lod_for_depth` can never return 2 (the `far` test is
|
||||
//! reached only after the `mid` test). Effectively two LOD tiers, not three.
|
||||
//! See `docs/plan/level-assets.md` for the test.
|
||||
|
||||
use crate::buf::{Buf, Error, ErrorKind, Result};
|
||||
use crate::level_core::ClassEntry;
|
||||
|
||||
Reference in New Issue
Block a user