From e9c1a83c51070c4f8da76540c17c2393922b6fcc Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sat, 18 Jul 2026 17:39:52 +0400 Subject: fix(terrain): preserve opaque shade lookup keys --- apps/fparkan-cli/src/main.rs | 95 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 22 deletions(-) (limited to 'apps/fparkan-cli/src') diff --git a/apps/fparkan-cli/src/main.rs b/apps/fparkan-cli/src/main.rs index e469509..d951c5e 100644 --- a/apps/fparkan-cli/src/main.rs +++ b/apps/fparkan-cli/src/main.rs @@ -27,7 +27,7 @@ use fparkan_assets::{ use fparkan_corpus::{discover, render_report_json, report, DiscoverOptions}; use fparkan_inspection::{ inspect_archive_file, inspect_land_msh_bounds_file, inspect_model_from_root, - load_land_msh_from_path, ArchiveInspection, ModelInspection, + inspect_wear_from_root, load_land_msh_from_path, ArchiveInspection, ModelInspection, }; use fparkan_path::{normalize_relative, PathPolicy}; use fparkan_prototype::build_prototype_graph_report; @@ -46,6 +46,7 @@ const MISSION_GRAPH_SCHEMA: &str = "fparkan-mission-graph-v1"; const MISSION_INSPECT_SCHEMA: &str = "fparkan-mission-inspect-v1"; const TERRAIN_INSPECT_SCHEMA: &str = "fparkan-terrain-inspect-v1"; const MODEL_INSPECT_SCHEMA: &str = "fparkan-model-inspect-v1"; +const WEAR_INSPECT_SCHEMA: &str = "fparkan-wear-inspect-v1"; #[derive(Serialize)] struct ArchiveInspectOutput<'a> { @@ -128,11 +129,11 @@ struct TerrainInspectOutput { faces: usize, slots: usize, material_tags: Vec, - shade_material_pairs: usize, + shade_pairs: usize, #[serde(skip_serializing_if = "Option::is_none")] - shade_material_lookup_min: Option, + shade_lookup_key_min: Option, #[serde(skip_serializing_if = "Option::is_none")] - shade_material_lookup_max: Option, + shade_lookup_key_max: Option, shade_batch_boundaries: usize, } @@ -142,6 +143,19 @@ struct TerrainMaterialTagCount { faces: usize, } +#[derive(Serialize)] +struct WearInspectOutput { + schema_version: &'static str, + archive: String, + resource: String, + materials: usize, + lightmaps: usize, + #[serde(skip_serializing_if = "Option::is_none")] + first_material: Option, + #[serde(skip_serializing_if = "Option::is_none")] + last_material: Option, +} + #[derive(Serialize)] struct ModelInspectOutput<'a> { schema_version: &'static str, @@ -238,6 +252,10 @@ fn run(args: &[String]) -> Result<(), String> { let rest = strip_format_json(rest)?; inspect_terrain(&rest) } + [domain, command, rest @ ..] if domain == "wear" && command == "inspect" => { + let rest = strip_format_json(rest)?; + inspect_wear(&rest) + } [domain, command, rest @ ..] if domain == "model" && command == "inspect" => { let rest = strip_format_json(rest)?; inspect_model(&rest) @@ -479,19 +497,13 @@ fn inspect_terrain(args: &[String]) -> Result<(), String> { .map(|(tag, faces)| TerrainMaterialTagCount { tag, faces }) .collect::>(); material_tags.sort_unstable_by_key(|entry| entry.tag); - let shade_material_pairs = (0..terrain.slots.slots_raw.len()) + let shade_pairs = (0..terrain.slots.slots_raw.len()) .filter_map(|slot_index| terrain.slot_material_pairs(slot_index)) .flatten() .collect::>(); - let shade_material_lookup_min = shade_material_pairs - .iter() - .map(|pair| pair.shade_selection().material_index) - .min(); - let shade_material_lookup_max = shade_material_pairs - .iter() - .map(|pair| pair.shade_selection().material_index) - .max(); - let shade_batch_boundaries = shade_material_pairs + let shade_lookup_key_min = shade_pairs.iter().map(|pair| pair.shade_lookup_key()).min(); + let shade_lookup_key_max = shade_pairs.iter().map(|pair| pair.shade_lookup_key()).max(); + let shade_batch_boundaries = shade_pairs .iter() .filter(|pair| pair.flags & 0x0010 != 0) .count(); @@ -506,15 +518,35 @@ fn inspect_terrain(args: &[String]) -> Result<(), String> { faces: terrain.faces.len(), slots: terrain.slots.slots_raw.len(), material_tags, - shade_material_pairs: shade_material_pairs.len(), - shade_material_lookup_min, - shade_material_lookup_max, + shade_pairs: shade_pairs.len(), + shade_lookup_key_min, + shade_lookup_key_max, shade_batch_boundaries, })? ); Ok(()) } +fn inspect_wear(args: &[String]) -> Result<(), String> { + let root = parse_root_alias(args)?; + let archive = parse_required(args, &["--archive"], "--archive")?; + let resource = parse_required(args, &["--resource"], "--resource")?; + let inspection = inspect_wear_from_root(&root, &archive, &resource)?; + println!( + "{}", + serialize_json(&WearInspectOutput { + schema_version: WEAR_INSPECT_SCHEMA, + archive, + resource, + materials: inspection.materials, + lightmaps: inspection.lightmaps, + first_material: inspection.first_material, + last_material: inspection.last_material, + })? + ); + Ok(()) +} + fn inspect_model(args: &[String]) -> Result<(), String> { let root = parse_root_alias(args)?; let archive = parse_required(args, &["--archive"], "--archive")?; @@ -632,7 +664,7 @@ fn prototype_graph_requiredness_label( } fn usage() -> String { - "usage: fparkan corpus discover|validate --root [--format json] | archive inspect [--format json] | terrain inspect [--format json] | model inspect --root --archive --resource [--format json] | prototype inspect --root --key [--format json] | mission graph|inspect --root --mission [--format json]".to_string() + "usage: fparkan corpus discover|validate --root [--format json] | archive inspect [--format json] | terrain inspect [--format json] | wear inspect --root --archive --resource [--format json] | model inspect --root --archive --resource [--format json] | prototype inspect --root --key [--format json] | mission graph|inspect --root --mission [--format json]".to_string() } #[cfg(test)] @@ -759,16 +791,35 @@ mod tests { TerrainMaterialTagCount { tag: 0, faces: 1 }, TerrainMaterialTagCount { tag: 3, faces: 1 }, ], - shade_material_pairs: 2, - shade_material_lookup_min: Some(1), - shade_material_lookup_max: Some(2), + shade_pairs: 2, + shade_lookup_key_min: Some(1), + shade_lookup_key_max: Some(2), shade_batch_boundaries: 1, }) .expect("serialize terrain inspection"); assert_eq!( json, - "{\"schema_version\":\"fparkan-terrain-inspect-v1\",\"path\":\"DATA/MAPS/AutoMAP/Land.msh\",\"positions\":3,\"min\":[-1.0,-2.0,-3.0],\"max\":[4.0,5.0,6.0],\"faces\":2,\"slots\":4,\"material_tags\":[{\"tag\":0,\"faces\":1},{\"tag\":3,\"faces\":1}],\"shade_material_pairs\":2,\"shade_material_lookup_min\":1,\"shade_material_lookup_max\":2,\"shade_batch_boundaries\":1}" + "{\"schema_version\":\"fparkan-terrain-inspect-v1\",\"path\":\"DATA/MAPS/AutoMAP/Land.msh\",\"positions\":3,\"min\":[-1.0,-2.0,-3.0],\"max\":[4.0,5.0,6.0],\"faces\":2,\"slots\":4,\"material_tags\":[{\"tag\":0,\"faces\":1},{\"tag\":3,\"faces\":1}],\"shade_pairs\":2,\"shade_lookup_key_min\":1,\"shade_lookup_key_max\":2,\"shade_batch_boundaries\":1}" + ); + } + + #[test] + fn wear_inspect_output_retains_material_bounds() { + let json = serialize_json(&WearInspectOutput { + schema_version: WEAR_INSPECT_SCHEMA, + archive: "system.rlb".to_string(), + resource: "SHADE.WEA".to_string(), + materials: 1, + lightmaps: 0, + first_material: Some("LIGHT1".to_string()), + last_material: Some("LIGHT1".to_string()), + }) + .expect("serialize wear inspection"); + + assert_eq!( + json, + "{\"schema_version\":\"fparkan-wear-inspect-v1\",\"archive\":\"system.rlb\",\"resource\":\"SHADE.WEA\",\"materials\":1,\"lightmaps\":0,\"first_material\":\"LIGHT1\",\"last_material\":\"LIGHT1\"}" ); } } -- cgit v1.2.3