Inverting the capture↔part question (invert_capture over one container, vcount_index over all 166) identifies every large draw in the 2026-07-31 capture: the player's own DeltaSaber (10891 verts), its weapon packs, the backdrop and particles. Of f101/e105/e106 only 1-3 of 15-37 resources have a drawn vcount, each a 44-225-vertex far-LOD/effect piece whose count collides with dozens of unrelated resources. So the zero-correlation was not an LOD-list gap, not over-strict position validation and not a different draw path: the ships were too far away to be drawn. approach_capture.py flies at a locked capital ship and presses F10 per range band, stamping each capture with its distance. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
99 lines
4.2 KiB
Rust
99 lines
4.2 KiB
Rust
//! Global "which resource has N vertices?" index over every `.xpr` container in
|
|
//! an extracted `resource3d` directory, answered for the vcounts a capture log
|
|
//! actually drew.
|
|
//!
|
|
//! Companion to `invert_capture`: that one asks the question inside a single
|
|
//! stage container, this one asks it across ALL containers — so a draw whose
|
|
//! geometry lives in `Common.xpr`, a `rou_*` weapon pack or a `BG_*` backdrop is
|
|
//! still identified instead of coming back "no resource".
|
|
//!
|
|
//! Usage:
|
|
//! cargo run --release --example vcount_index -- <resource3d_dir> <capture.log> [top_n]
|
|
//! cargo run --release --example vcount_index -- <resource3d_dir> --vcounts 10891,6000
|
|
|
|
use sylpheed_formats::mesh::{xbg7_resource_names, Xbg7Model};
|
|
use sylpheed_formats::ship_capture::{parse_capture, parse_drawlog};
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
fn main() {
|
|
let args: Vec<String> = std::env::args().collect();
|
|
if args.len() < 3 {
|
|
eprintln!("usage: vcount_index <resource3d_dir> <capture.log|--vcounts a,b,c> [top_n]");
|
|
std::process::exit(2);
|
|
}
|
|
let dir = &args[1];
|
|
|
|
// Which vertex counts are we asking about, and how often was each drawn?
|
|
let mut draw_count: HashMap<u32, usize> = HashMap::new();
|
|
let mut bufs: HashMap<u32, HashSet<u32>> = HashMap::new();
|
|
if args[2] == "--vcounts" {
|
|
for v in args[3].split(',').filter_map(|s| s.trim().parse::<u32>().ok()) {
|
|
draw_count.insert(v, 0);
|
|
}
|
|
} else {
|
|
let text = std::fs::read_to_string(&args[2]).expect("read log");
|
|
let mut draws = parse_capture(&text);
|
|
if draws.is_empty() {
|
|
draws = parse_drawlog(&text);
|
|
}
|
|
eprintln!("parsed {} draws", draws.len());
|
|
for d in &draws {
|
|
*draw_count.entry(d.vcount).or_default() += 1;
|
|
bufs.entry(d.vcount).or_default().insert(d.vbase);
|
|
}
|
|
}
|
|
let top_n: usize = args.get(3).and_then(|s| s.parse().ok()).unwrap_or(usize::MAX);
|
|
|
|
// Decode every container once; keep only the vcount → names mapping.
|
|
let mut by_vcount: HashMap<u32, Vec<String>> = HashMap::new();
|
|
let mut files: Vec<std::path::PathBuf> = std::fs::read_dir(dir)
|
|
.expect("read dir")
|
|
.filter_map(|e| e.ok().map(|e| e.path()))
|
|
.filter(|p| p.extension().is_some_and(|e| e == "xpr"))
|
|
.collect();
|
|
files.sort();
|
|
let mut total_res = 0usize;
|
|
for f in &files {
|
|
let Ok(bytes) = std::fs::read(f) else { continue };
|
|
let names = xbg7_resource_names(&bytes);
|
|
if names.is_empty() {
|
|
continue;
|
|
}
|
|
let want: HashSet<String> = names.iter().cloned().collect();
|
|
let models = Xbg7Model::models_named(&bytes, &want, &|| false);
|
|
let container = f.file_stem().unwrap().to_string_lossy().to_string();
|
|
for m in &models {
|
|
total_res += 1;
|
|
let whole: usize = m.meshes.iter().map(|s| s.positions.len()).sum();
|
|
by_vcount.entry(whole as u32).or_default().push(format!("{container}:{}", m.name));
|
|
if m.meshes.len() > 1 {
|
|
for (i, s) in m.meshes.iter().enumerate() {
|
|
by_vcount
|
|
.entry(s.positions.len() as u32)
|
|
.or_default()
|
|
.push(format!("{container}:{}#{i}", m.name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
eprintln!("indexed {} resources from {} containers", total_res, files.len());
|
|
|
|
let mut vcounts: Vec<u32> = draw_count.keys().copied().collect();
|
|
vcounts.sort_unstable_by(|a, b| b.cmp(a));
|
|
println!("\nvcount draws bufs resources anywhere in resource3d/");
|
|
for v in vcounts.into_iter().take(top_n) {
|
|
let n = draw_count[&v];
|
|
let b = bufs.get(&v).map(|s| s.len()).unwrap_or(0);
|
|
let hit = by_vcount.get(&v).cloned().unwrap_or_default();
|
|
let label = if hit.is_empty() {
|
|
"— NONE".to_string()
|
|
} else {
|
|
let mut h = hit.clone();
|
|
h.sort();
|
|
let shown = h.len().min(8);
|
|
format!("{}{}", h[..shown].join(", "), if h.len() > shown { format!(", … ({} total)", h.len()) } else { String::new() })
|
|
};
|
|
println!("{v:6} {n:5} {b:4} {label}");
|
|
}
|
|
}
|