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
|
/// 2. every tfrag patch's bytes are rewritten, and patches that cannot morph are
|
||||||
/// rewritten as much as patches that can;
|
/// rewritten as much as patches that can;
|
||||||
/// 3. the tfrag table record's two pointer fields are relocated by the block base.
|
/// 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>> {
|
fn ram_image() -> Option<Vec<u8>> {
|
||||||
let p = std::env::var_os("GC_SSTATE")?;
|
let p = std::env::var_os("GC_SSTATE")?;
|
||||||
gc_trace::Savestate::open(std::path::Path::new(&p))
|
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
|
//! the remainder would repeat the pattern this project keeps running into: a
|
||||||
//! field order that produces plausible output and is wrong.
|
//! 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.
|
//! A coincidence is not a plausible explanation for that.
|
||||||
//! * the LOD distances are ordered `0 < near < mid < far`: 315 of 320. The
|
//! * the LOD distances are ordered `0 < near < mid < far` on most classes, but
|
||||||
//! five exceptions are not yet explained and are worth chasing.
|
//! **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::buf::{Buf, Error, ErrorKind, Result};
|
||||||
use crate::level_core::ClassEntry;
|
use crate::level_core::ClassEntry;
|
||||||
|
|||||||
Reference in New Issue
Block a user