Skip to main content

validate_wires/
validate_wires.rs

1//! TEMP diagnostic: validate every wire endpoint in a .brz resolves to a
2//! brick that actually carries the referenced component type, mimicking the
3//! game loader's wire-port resolution. Usage:
4//!   cargo run --example validate_wires -- path/to/world.brz [grid] [lo..hi]
5use brdb::{Brdb, Brz, IntoReader, WireChunkSoA};
6use std::collections::HashMap;
7use std::path::PathBuf;
8
9fn main() -> Result<(), Box<dyn std::error::Error>> {
10    let path = PathBuf::from(std::env::args().nth(1).expect("usage: validate_wires <brz|brdb>"));
11    if path.extension().is_some_and(|e| e == "brdb") {
12        run(Brdb::open(&path)?.into_reader())
13    } else {
14        run(Brz::open(&path)?.into_reader())
15    }
16}
17
18fn run<T: brdb::BrFsReader>(
19    db: brdb::BrReader<T>,
20) -> Result<(), Box<dyn std::error::Error>> {
21    let data = db.global_data()?;
22
23    // Collect all grid ids: main grid (1) + every brick-grid entity.
24    let mut grid_ids = vec![1usize];
25    for index in db.entity_chunk_index()? {
26        for e in db.entity_chunk(index)? {
27            if e.is_brick_grid() || e.is_microchip_grid() {
28                if let Some(id) = e.id {
29                    grid_ids.push(id);
30                }
31            }
32        }
33    }
34
35    // Pass 1: per (grid, chunk) build brick component lists + brick counts.
36    // brick_components[(gid, chunk)][brick_index] = Vec<component_type_index>
37    let mut brick_counts: HashMap<(usize, String), usize> = HashMap::new();
38    let mut brick_components: HashMap<(usize, String), HashMap<u32, Vec<u16>>> = HashMap::new();
39    let mut brick_types: HashMap<(usize, String), Vec<u32>> = HashMap::new();
40    for &gid in &grid_ids {
41        let chunks = match db.brick_chunk_index(gid) {
42            Ok(c) => c,
43            Err(_) => continue,
44        };
45        for chunk in &chunks {
46            let key = (gid, format!("{:?}", chunk.index));
47            let soa = db.brick_chunk_soa(gid, chunk.index)?;
48            brick_counts.insert(key.clone(), soa.brick_type_indices.len());
49            brick_types.insert(key.clone(), soa.brick_type_indices.clone());
50            let mut per_brick: HashMap<u32, Vec<u16>> = HashMap::new();
51            if chunk.num_components > 0 {
52                let (csoa, _components) = db.component_chunk_soa(gid, chunk.index)?;
53                let type_indices: Vec<u16> = csoa
54                    .component_type_counters
55                    .iter()
56                    .flat_map(|v| {
57                        let index = v.type_index as u16;
58                        (0..v.num_instances).map(move |_| index)
59                    })
60                    .collect();
61                for (i, bi) in csoa.component_brick_indices.iter().enumerate() {
62                    let brick_index = *bi;
63                    per_brick.entry(brick_index).or_default().push(type_indices[i]);
64                }
65            }
66            brick_components.insert(key, per_brick);
67        }
68    }
69
70    let cname = |t: u16| -> String {
71        data.component_type_names
72            .get_index(t as usize)
73            .map(|s| s.to_string())
74            .unwrap_or_else(|| format!("<type {t}>"))
75    };
76    let pname = |p: u16| -> String {
77        data.component_wire_port_names
78            .get_index(p as usize)
79            .map(|s| s.to_string())
80            .unwrap_or_else(|| format!("<port {p}>"))
81    };
82
83    // Pass 2: validate wires.
84    let mut total = 0u64;
85    let mut bad = 0u64;
86    for &gid in &grid_ids {
87        let chunks = match db.brick_chunk_index(gid) {
88            Ok(c) => c,
89            Err(_) => continue,
90        };
91        for chunk in &chunks {
92            if chunk.num_wires == 0 {
93                continue;
94            }
95            let key = (gid, format!("{:?}", chunk.index));
96            let soa = db.wire_chunk_soa(gid, chunk.index)?.to_value();
97            let soa: WireChunkSoA = (&soa).try_into()?;
98
99            let mut check = |ctx: &str,
100                             ggid: usize,
101                             ckey: &str,
102                             brick_index: u32,
103                             ct: u16,
104                             port: u16| {
105                total += 1;
106                let k = (ggid, ckey.to_string());
107                let n = brick_counts.get(&k).copied().unwrap_or(0);
108                if brick_index as usize >= n {
109                    bad += 1;
110                    println!(
111                        "BAD {ctx}: grid {ggid} chunk {ckey} brick {brick_index} OUT OF RANGE (chunk has {n} bricks); wanted {} {}",
112                        cname(ct),
113                        pname(port)
114                    );
115                    return;
116                }
117                let comps = brick_components
118                    .get(&k)
119                    .and_then(|m| m.get(&brick_index))
120                    .cloned()
121                    .unwrap_or_default();
122                if !comps.contains(&ct) {
123                    bad += 1;
124                    println!(
125                        "BAD {ctx}: grid {ggid} chunk {ckey} brick {brick_index} has components [{}] but wire wants {} {}",
126                        comps.iter().map(|c| cname(*c)).collect::<Vec<_>>().join(", "),
127                        cname(ct),
128                        pname(port)
129                    );
130                }
131            };
132
133            for p in &soa.local_wire_sources {
134                check("local-src", gid, &key.1, p.brick_index_in_chunk, p.component_type_index, p.port_index);
135            }
136            for p in &soa.local_wire_targets {
137                check("local-tgt", gid, &key.1, p.brick_index_in_chunk, p.component_type_index, p.port_index);
138            }
139            for p in &soa.remote_wire_sources {
140                let ckey = format!("{:?}", p.chunk_index);
141                check(
142                    "remote-src",
143                    p.grid_persistent_index as usize,
144                    &ckey,
145                    p.brick_index_in_chunk,
146                    p.component_type_index,
147                    p.port_index,
148                );
149            }
150            // Remote wires: the source names another grid; the target is
151            // local to THIS chunk.
152            for p in &soa.remote_wire_targets {
153                check("remote-tgt", gid, &key.1, p.brick_index_in_chunk, p.component_type_index, p.port_index);
154            }
155        }
156    }
157
158    println!("validated {total} wire endpoints, {bad} bad");
159
160    // Optional dump: grid + index range, e.g. `-- file 2 580 590`
161    let args: Vec<String> = std::env::args().collect();
162    if args.len() >= 5 {
163        let gid: usize = args[2].parse()?;
164        let lo: u32 = args[3].parse()?;
165        let hi: u32 = args[4].parse()?;
166        for ((g, ckey), types) in &brick_types {
167            if *g != gid {
168                continue;
169            }
170            for i in lo..=hi.min(types.len().saturating_sub(1) as u32) {
171                let comps = brick_components
172                    .get(&(gid, ckey.clone()))
173                    .and_then(|m| m.get(&i))
174                    .cloned()
175                    .unwrap_or_default();
176                let asset = data
177                    .basic_brick_asset_names
178                    .get_index(types[i as usize] as usize)
179                    .map(|s| s.to_string())
180                    .unwrap_or_else(|| format!("<pb {}>", types[i as usize]));
181                println!(
182                    "grid {gid} chunk {ckey} brick {i}: {asset} [{}]",
183                    comps.iter().map(|c| cname(*c)).collect::<Vec<_>>().join(", ")
184                );
185            }
186        }
187    }
188    Ok(())
189}