diff options
| author | Valentin Popov <valentin@popov.link> | 2026-07-03 16:03:40 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-07-03 16:03:40 +0300 |
| commit | 66804e0bd2821e12ba0ed41de1133cd2b48febfa (patch) | |
| tree | 2ac761d1e75ad7fde37dbd3a46f3b0150be28367 | |
| parent | ee9b318172bf50fadd90e0cfc364330fb85f778a (diff) | |
| download | fparkan-66804e0bd2821e12ba0ed41de1133cd2b48febfa.tar.xz fparkan-66804e0bd2821e12ba0ed41de1133cd2b48febfa.zip | |
feat(stage2): materialize visual dependency graph
| -rw-r--r-- | apps/fparkan-cli/src/main.rs | 4 | ||||
| -rw-r--r-- | crates/fparkan-assets/src/lib.rs | 560 | ||||
| -rw-r--r-- | crates/fparkan-prototype/src/lib.rs | 31 | ||||
| -rw-r--r-- | crates/fparkan-runtime/src/lib.rs | 4 |
4 files changed, 532 insertions, 67 deletions
diff --git a/apps/fparkan-cli/src/main.rs b/apps/fparkan-cli/src/main.rs index 9f1d23e..b1d3f36 100644 --- a/apps/fparkan-cli/src/main.rs +++ b/apps/fparkan-cli/src/main.rs @@ -165,9 +165,9 @@ fn inspect_prototype(args: &[String]) -> Result<(), String> { let vfs = Arc::new(DirectoryVfs::new(root)); let repository = CachedResourceRepository::new(vfs.clone()); let roots = [resource_name(key.as_bytes())]; - let (graph, resolved, mut report) = + let (mut graph, resolved, mut report) = build_prototype_graph_report(&repository, vfs.as_ref(), &roots); - extend_graph_report_with_visual_dependencies(&repository, &mut report, &graph, &resolved); + extend_graph_report_with_visual_dependencies(&repository, &mut report, &mut graph, &resolved); println!("{}", prototype_inspect_json(&key, &graph, &report)); Ok(()) } diff --git a/crates/fparkan-assets/src/lib.rs b/crates/fparkan-assets/src/lib.rs index b128822..ddf5e65 100644 --- a/crates/fparkan-assets/src/lib.rs +++ b/crates/fparkan-assets/src/lib.rs @@ -20,7 +20,7 @@ )] //! Asset manager ports and transactional preparation models. -use fparkan_material::{decode_wear, resolve_material, MaterialError, WEAR_KIND}; +use fparkan_material::{decode_wear, resolve_material, MaterialError, MAT0_KIND, WEAR_KIND}; use fparkan_mission_format::{decode_tma, decode_tma_land_path}; pub use fparkan_mission_format::{LpString, MissionDocument, MissionError, TmaProfile}; use fparkan_msh::{decode_msh, validate_msh, MshError}; @@ -601,22 +601,71 @@ fn prepared_visual_signature(proto: &EffectivePrototype) -> PreparedVisualSignat pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( repository: &R, report: &mut PrototypeGraphReport, - graph: &PrototypeGraph, + graph: &mut PrototypeGraph, prototypes: &[EffectivePrototype], ) { - let texture_archive = parse_path(TEXTURES_ARCHIVE).ok(); - let lightmap_archive = parse_path(LIGHTMAP_ARCHIVE).ok(); + let material_archive = parse_path("material.lib") + .expect("static material archive path must satisfy host-compatible normalization"); + let mut next_node = graph + .nodes + .iter() + .map(|node| node.id.0) + .max() + .map_or(0, |value| value.saturating_add(1)); + let mut next_edge = graph + .edges + .iter() + .map(|edge| edge.id.0) + .max() + .map_or(0, |value| value.saturating_add(1)); for (prototype_index, prototype) in prototypes.iter().enumerate() { let PrototypeGeometry::Mesh(mesh) = &prototype.geometry else { continue; }; - report.mesh_dependency_count += prototype.dependencies.len(); report.wear_request_count += 1; + let Some(prototype_node_id) = prototype_node_id(graph, prototype_index) else { + continue; + }; + let Some(mesh_node_id) = prototype_mesh_node_id(graph, prototype_node_id) else { + continue; + }; + let mesh_parent_edge = mesh_edge_id(graph, prototype_node_id); + let root_index = root_index_for_prototype(graph, prototype_index); match resolve_wear_table(repository, mesh) { Ok(table) => { report.wear_resolved_count += 1; + let wear_key = match wear_resource_key(mesh) { + Ok(key) => key, + Err(message) => { + push_visual_failure( + report, + graph, + prototype_index, + mesh.name.0.clone(), + PrototypeGraphEdge::MeshToWear, + PrototypeGraphRequiredness::Required, + &message.to_string(), + ); + continue; + } + }; + let wear_node_id = push_graph_resource_node( + graph, + PrototypeGraphNodeKind::WearResource, + wear_key.clone(), + &mut next_node, + ); + let wear_edge_id = push_graph_edge( + graph, + mesh_node_id, + wear_node_id, + fparkan_prototype::PrototypeGraphEdgeKind::MeshToWear, + PrototypeGraphRequiredness::Required, + Some(provenance_for_resource(root_index, mesh_parent_edge, &wear_key)), + &mut next_edge, + ); report.material_slot_count += table.entries.len(); for (material_index, _entry) in table.entries.iter().enumerate() { let Ok(material_index) = u16::try_from(material_index) else { @@ -634,14 +683,59 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( match resolve_material(repository, &table, material_index) { Ok(material) => { report.material_resolved_count += 1; + let material_key = ResourceKey { + archive: material_archive.clone(), + name: material.name.clone(), + type_id: Some(MAT0_KIND), + }; + let material_node_id = push_graph_resource_node( + graph, + PrototypeGraphNodeKind::MaterialResource, + material_key.clone(), + &mut next_node, + ); + let material_edge_id = push_graph_edge( + graph, + wear_node_id, + material_node_id, + fparkan_prototype::PrototypeGraphEdgeKind::WearToMaterial, + PrototypeGraphRequiredness::Required, + Some(provenance_for_resource( + root_index, + Some(wear_edge_id), + &material_key, + )), + &mut next_edge, + ); for texture in material.document.texture_requests() { report.texture_request_count += 1; - match resolve_texm_from_candidates( - repository, - &texture, - [texture_archive.as_ref(), lightmap_archive.as_ref()], - ) { - Ok(()) => report.texture_resolved_count += 1, + match resolve_texture(repository, &texture) { + Ok(()) => { + report.texture_resolved_count += 1; + if let Ok(texture_key) = + texm_resource_key(TEXTURES_ARCHIVE, &texture) + { + let texture_node_id = push_graph_resource_node( + graph, + PrototypeGraphNodeKind::TextureResource, + texture_key.clone(), + &mut next_node, + ); + push_graph_edge( + graph, + material_node_id, + texture_node_id, + fparkan_prototype::PrototypeGraphEdgeKind::MaterialToTexture, + PrototypeGraphRequiredness::Required, + Some(provenance_for_resource( + root_index, + Some(material_edge_id), + &texture_key, + )), + &mut next_edge, + ); + } + } Err(message) => { let message = message.to_string(); push_visual_failure( @@ -670,12 +764,33 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( } for lightmap in &table.lightmaps { report.lightmap_request_count += 1; - match resolve_texm_from_candidates( - repository, - &lightmap.lightmap, - [lightmap_archive.as_ref(), texture_archive.as_ref()], - ) { - Ok(()) => report.lightmap_resolved_count += 1, + match resolve_lightmap(repository, &lightmap.lightmap) { + Ok(()) => { + report.lightmap_resolved_count += 1; + if let Ok(lightmap_key) = + texm_resource_key(LIGHTMAP_ARCHIVE, &lightmap.lightmap) + { + let lightmap_node_id = push_graph_resource_node( + graph, + PrototypeGraphNodeKind::LightmapResource, + lightmap_key.clone(), + &mut next_node, + ); + push_graph_edge( + graph, + wear_node_id, + lightmap_node_id, + fparkan_prototype::PrototypeGraphEdgeKind::WearToLightmap, + PrototypeGraphRequiredness::Required, + Some(provenance_for_resource( + root_index, + Some(wear_edge_id), + &lightmap_key, + )), + &mut next_edge, + ); + } + } Err(message) => { let message = message.to_string(); push_visual_failure( @@ -704,6 +819,89 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( } } +fn push_graph_resource_node( + graph: &mut PrototypeGraph, + kind: PrototypeGraphNodeKind, + resource: ResourceKey, + next_node: &mut u32, +) -> fparkan_prototype::PrototypeGraphNodeId { + let id = fparkan_prototype::PrototypeGraphNodeId(*next_node); + *next_node = (*next_node).saturating_add(1); + graph + .nodes + .push(fparkan_prototype::PrototypeGraphNode::resource(kind, resource, id)); + id +} + +fn push_graph_edge( + graph: &mut PrototypeGraph, + from: fparkan_prototype::PrototypeGraphNodeId, + to: fparkan_prototype::PrototypeGraphNodeId, + kind: fparkan_prototype::PrototypeGraphEdgeKind, + requiredness: PrototypeGraphRequiredness, + provenance: Option<PrototypeGraphProvenance>, + next_edge: &mut u32, +) -> fparkan_prototype::PrototypeGraphEdgeId { + let id = fparkan_prototype::PrototypeGraphEdgeId(*next_edge); + *next_edge = (*next_edge).saturating_add(1); + graph.edges.push(fparkan_prototype::PrototypeGraphEdgeInstance { + id, + from, + to, + kind, + requiredness, + provenance, + }); + id +} + +fn prototype_mesh_node_id( + graph: &PrototypeGraph, + prototype_node: fparkan_prototype::PrototypeGraphNodeId, +) -> Option<fparkan_prototype::PrototypeGraphNodeId> { + graph + .edges + .iter() + .find(|edge| { + edge.from == prototype_node + && matches!( + edge.kind, + fparkan_prototype::PrototypeGraphEdgeKind::PrototypeToMesh + ) + }) + .map(|edge| edge.to) +} + +fn provenance_for_resource( + root_index: usize, + parent_edge: Option<fparkan_prototype::PrototypeGraphEdgeId>, + resource: &ResourceKey, +) -> PrototypeGraphProvenance { + PrototypeGraphProvenance { + root_index, + parent_edge, + archive: Some(resource.archive.display_lossy().to_string()), + resource: Some(resource.name.0.clone()), + span: None, + } +} + +fn wear_resource_key(mesh: &ResourceKey) -> Result<ResourceKey, AssetError> { + Ok(ResourceKey { + archive: mesh.archive.clone(), + name: sibling_name(mesh, "wea")?, + type_id: Some(WEAR_KIND), + }) +} + +fn texm_resource_key(archive: &str, name: &ResourceName) -> Result<ResourceKey, AssetError> { + Ok(ResourceKey { + archive: parse_path(archive)?, + name: name.clone(), + type_id: None, + }) +} + /// Validates a prototype visual without resolving cross-resource dependencies. /// /// This is useful for tests and API callers that only need a stable visual id. @@ -913,52 +1111,6 @@ fn resolve_wear_table<R: ResourceRepository>( decode_wear(&bytes).map_err(AssetError::Material) } -fn resolve_texm_from_candidates<'a, R: ResourceRepository>( - repository: &R, - texture: &ResourceName, - candidates: impl IntoIterator<Item = Option<&'a NormalizedPath>>, -) -> Result<(), AssetError> { - let mut missing_archive = false; - for path in candidates.into_iter().flatten() { - let key = ResourceKey { - archive: path.to_owned(), - name: texture.clone(), - type_id: None, - }; - let archive = match repository.open_archive(path) { - Ok(archive) => archive, - Err(ResourceError::MissingArchive { .. }) => { - missing_archive = true; - continue; - } - Err(err) => return Err(map_resource_error("texm", &key, err)), - }; - let Some(handle) = repository - .find(archive, texture) - .map_err(|err| map_resource_error("texm", &key, err))? - else { - continue; - }; - let bytes = repository - .read(handle) - .map_err(|err| map_resource_error("texm", &key, err))? - .into_owned(); - decode_texm(Arc::from(bytes)).map_err(AssetError::Texture)?; - return Ok(()); - } - if missing_archive { - Err(AssetError::MissingDependency(format!( - "texm archive missing for {}", - String::from_utf8_lossy(&texture.0) - ))) - } else { - Err(AssetError::MissingDependency(format!( - "missing texm {}", - String::from_utf8_lossy(&texture.0) - ))) - } -} - fn push_visual_failure( report: &mut PrototypeGraphReport, graph: &PrototypeGraph, @@ -1299,6 +1451,185 @@ mod tests { } #[test] + fn graph_materializes_visual_dependency_nodes_and_edges() { + let mesh_key = ResourceKey { + archive: parse_path("static.rlb").expect("archive"), + name: resource_name(b"tree.msh"), + type_id: Some(0x4853_454D), + }; + let mat0 = mat0_with_texture(b"TEX_A"); + let texm = texm_payload(); + let lightmap_texm = texm_payload(); + let repo = repository_with_archives_meta(&[ + ( + "static.rlb", + &[TestNresEntry { + name: b"tree.wea", + payload: b"1\n0 MAT_A\n\nLIGHTMAPS\n1\n0 LM_A\n", + type_id: WEAR_KIND, + attr2: 0, + }], + ), + ( + "material.lib", + &[TestNresEntry { + name: b"MAT_A", + payload: &mat0, + type_id: MAT0_KIND, + attr2: 0, + }], + ), + ( + TEXTURES_ARCHIVE, + &[TestNresEntry { + name: b"TEX_A", + payload: &texm, + type_id: 0, + attr2: 0, + }], + ), + ( + LIGHTMAP_ARCHIVE, + &[TestNresEntry { + name: b"LM_A", + payload: &lightmap_texm, + type_id: 0, + attr2: 0, + }], + ), + ]); + let prototype = EffectivePrototype { + key: fparkan_prototype::PrototypeKey(resource_name(b"tree")), + geometry: PrototypeGeometry::Mesh(mesh_key.clone()), + source: fparkan_prototype::PrototypeSource::DirectArchive, + dependencies: vec![mesh_key.clone()], + }; + let mut graph = prototype_graph_for_mesh(&prototype); + let mut report = PrototypeGraphReport { + root_count: 1, + direct_reference_count: 1, + resolved_count: 1, + mesh_dependency_count: 1, + ..PrototypeGraphReport::default() + }; + + extend_graph_report_with_visual_dependencies( + &repo, + &mut report, + &mut graph, + std::slice::from_ref(&prototype), + ); + + assert!(graph + .nodes + .iter() + .any(|node| node.kind == PrototypeGraphNodeKind::WearResource)); + assert!(graph + .nodes + .iter() + .any(|node| node.kind == PrototypeGraphNodeKind::MaterialResource)); + assert!(graph + .nodes + .iter() + .any(|node| node.kind == PrototypeGraphNodeKind::TextureResource)); + assert!(graph + .nodes + .iter() + .any(|node| node.kind == PrototypeGraphNodeKind::LightmapResource)); + assert!(graph + .edges + .iter() + .any(|edge| edge.kind == fparkan_prototype::PrototypeGraphEdgeKind::MeshToWear)); + assert!(graph + .edges + .iter() + .any(|edge| edge.kind == fparkan_prototype::PrototypeGraphEdgeKind::WearToMaterial)); + assert!(graph + .edges + .iter() + .any(|edge| edge.kind == fparkan_prototype::PrototypeGraphEdgeKind::MaterialToTexture)); + assert!(graph + .edges + .iter() + .any(|edge| edge.kind == fparkan_prototype::PrototypeGraphEdgeKind::WearToLightmap)); + assert_eq!(report.wear_request_count, 1); + assert_eq!(report.wear_resolved_count, 1); + assert_eq!(report.material_slot_count, 1); + assert_eq!(report.material_resolved_count, 1); + assert_eq!(report.texture_request_count, 1); + assert_eq!(report.texture_resolved_count, 1); + assert_eq!(report.lightmap_request_count, 1); + assert_eq!(report.lightmap_resolved_count, 1); + assert!(report.failures.is_empty()); + } + + #[test] + fn graph_report_uses_strict_texture_archive_policy() { + let mesh_key = ResourceKey { + archive: parse_path("static.rlb").expect("archive"), + name: resource_name(b"tree.msh"), + type_id: Some(0x4853_454D), + }; + let mat0 = mat0_with_texture(b"TEX_A"); + let texm = texm_payload(); + let repo = repository_with_archives_meta(&[ + ( + "static.rlb", + &[TestNresEntry { + name: b"tree.wea", + payload: b"1\n0 MAT_A\n", + type_id: WEAR_KIND, + attr2: 0, + }], + ), + ( + "material.lib", + &[TestNresEntry { + name: b"MAT_A", + payload: &mat0, + type_id: MAT0_KIND, + attr2: 0, + }], + ), + ( + LIGHTMAP_ARCHIVE, + &[TestNresEntry { + name: b"TEX_A", + payload: &texm, + type_id: 0, + attr2: 0, + }], + ), + ]); + let prototype = EffectivePrototype { + key: fparkan_prototype::PrototypeKey(resource_name(b"tree")), + geometry: PrototypeGeometry::Mesh(mesh_key.clone()), + source: fparkan_prototype::PrototypeSource::DirectArchive, + dependencies: vec![mesh_key.clone()], + }; + let mut graph = prototype_graph_for_mesh(&prototype); + let mut report = PrototypeGraphReport { + root_count: 1, + direct_reference_count: 1, + resolved_count: 1, + mesh_dependency_count: 1, + ..PrototypeGraphReport::default() + }; + + extend_graph_report_with_visual_dependencies( + &repo, + &mut report, + &mut graph, + std::slice::from_ref(&prototype), + ); + + assert_eq!(report.texture_request_count, 1); + assert_eq!(report.texture_resolved_count, 0); + assert_eq!(report.failures.len(), 1); + assert_eq!(report.failures[0].edge, PrototypeGraphEdge::MaterialToTexture); + } + + #[test] #[ignore = "requires licensed corpus"] fn prepares_real_unit_asset_plan() { let root = fixture_root("IS"); @@ -1375,6 +1706,25 @@ mod tests { CachedResourceRepository::new(Arc::new(vfs)) } + #[derive(Clone, Copy)] + struct TestNresEntry<'a> { + name: &'a [u8], + payload: &'a [u8], + type_id: u32, + attr2: u32, + } + + fn repository_with_archives_meta( + archives: &[(&str, &[TestNresEntry<'_>])], + ) -> CachedResourceRepository { + let mut vfs = MemoryVfs::default(); + for (archive, entries) in archives { + let path = parse_path(archive).expect("archive path"); + vfs.insert(path, Arc::from(build_nres_with_meta(entries).into_boxed_slice())); + } + CachedResourceRepository::new(Arc::new(vfs)) + } + fn texm_payload() -> Vec<u8> { let mut out = Vec::new(); out.extend_from_slice(&0x6d78_6554_u32.to_le_bytes()); @@ -1389,6 +1739,58 @@ mod tests { out } + fn mat0_with_texture(texture: &[u8]) -> Vec<u8> { + let mut bytes = vec![0; 4 + 34]; + bytes[0..2].copy_from_slice(&1_u16.to_le_bytes()); + let len = texture.len().min(16); + bytes[22..22 + len].copy_from_slice(&texture[..len]); + bytes + } + + fn prototype_graph_for_mesh(prototype: &EffectivePrototype) -> PrototypeGraph { + let root_node = fparkan_prototype::PrototypeGraphNode::root( + fparkan_prototype::PrototypeKey(prototype.key.0.clone()), + false, + fparkan_prototype::PrototypeGraphNodeId(0), + ); + let prototype_node = fparkan_prototype::PrototypeGraphNode::prototype( + prototype.key.clone(), + fparkan_prototype::PrototypeGraphNodeId(1), + ); + let mesh_key = match &prototype.geometry { + PrototypeGeometry::Mesh(mesh) => mesh.clone(), + PrototypeGeometry::NonGeometric => panic!("mesh prototype expected"), + }; + let mesh_node = fparkan_prototype::PrototypeGraphNode::mesh( + mesh_key, + fparkan_prototype::PrototypeGraphNodeId(2), + ); + PrototypeGraph { + roots: vec![prototype.key.clone()], + prototype_requests: vec![prototype.key.clone()], + root_prototype_request_spans: std::iter::once(0..1).collect(), + nodes: vec![root_node, prototype_node, mesh_node], + edges: vec![ + fparkan_prototype::PrototypeGraphEdgeInstance { + id: fparkan_prototype::PrototypeGraphEdgeId(0), + from: fparkan_prototype::PrototypeGraphNodeId(0), + to: fparkan_prototype::PrototypeGraphNodeId(1), + kind: fparkan_prototype::PrototypeGraphEdgeKind::MissionToRoot, + requiredness: PrototypeGraphRequiredness::Required, + provenance: None, + }, + fparkan_prototype::PrototypeGraphEdgeInstance { + id: fparkan_prototype::PrototypeGraphEdgeId(1), + from: fparkan_prototype::PrototypeGraphNodeId(1), + to: fparkan_prototype::PrototypeGraphNodeId(2), + kind: fparkan_prototype::PrototypeGraphEdgeKind::PrototypeToMesh, + requiredness: PrototypeGraphRequiredness::Required, + provenance: None, + }, + ], + } + } + fn build_nres(entries: &[(&[u8], &[u8])]) -> Vec<u8> { let mut out = vec![0; 16]; let mut offsets = Vec::with_capacity(entries.len()); @@ -1421,6 +1823,38 @@ mod tests { out } + fn build_nres_with_meta(entries: &[TestNresEntry<'_>]) -> Vec<u8> { + let mut out = vec![0; 16]; + let mut offsets = Vec::with_capacity(entries.len()); + for entry in entries { + offsets.push(u32::try_from(out.len()).expect("offset")); + out.extend_from_slice(entry.payload); + let padding = (8 - (out.len() % 8)) % 8; + out.resize(out.len() + padding, 0); + } + let mut order: Vec<usize> = (0..entries.len()).collect(); + order.sort_by(|left, right| entries[*left].name.cmp(entries[*right].name)); + for (idx, entry) in entries.iter().enumerate() { + push_u32(&mut out, entry.type_id); + push_u32(&mut out, 0); + push_u32(&mut out, entry.attr2); + push_u32(&mut out, u32::try_from(entry.payload.len()).expect("payload")); + push_u32(&mut out, 0); + let mut name_raw = [0; 36]; + let len = name_raw.len().saturating_sub(1).min(entry.name.len()); + name_raw[..len].copy_from_slice(&entry.name[..len]); + out.extend_from_slice(&name_raw); + push_u32(&mut out, offsets[idx]); + push_u32(&mut out, u32::try_from(order[idx]).expect("sort index")); + } + out[0..4].copy_from_slice(b"NRes"); + out[4..8].copy_from_slice(&0x100_u32.to_le_bytes()); + out[8..12].copy_from_slice(&u32::try_from(entries.len()).expect("count").to_le_bytes()); + let total_size = u32::try_from(out.len()).expect("total size"); + out[12..16].copy_from_slice(&total_size.to_le_bytes()); + out + } + fn push_u32(out: &mut Vec<u8>, value: u32) { out.extend_from_slice(&value.to_le_bytes()); } diff --git a/crates/fparkan-prototype/src/lib.rs b/crates/fparkan-prototype/src/lib.rs index e85322b..7ee07ad 100644 --- a/crates/fparkan-prototype/src/lib.rs +++ b/crates/fparkan-prototype/src/lib.rs @@ -179,6 +179,16 @@ pub enum PrototypeGraphNodeKind { Prototype, /// Mesh dependency. MeshResource, + /// WEAR dependency. + WearResource, + /// MAT0 dependency. + MaterialResource, + /// TEXM texture dependency. + TextureResource, + /// TEXM lightmap dependency. + LightmapResource, + /// Effect dependency placeholder for later stages. + EffectResource, /// Non-geometric prototype. NonGeometric, } @@ -197,6 +207,17 @@ pub struct PrototypeGraphNode { } impl PrototypeGraphNode { + /// Creates a typed resource node. + #[must_use] + pub fn resource(kind: PrototypeGraphNodeKind, resource: ResourceKey, id: PrototypeGraphNodeId) -> Self { + Self { + id, + kind, + key: None, + resource: Some(resource), + } + } + /// Creates a mesh resource node. #[must_use] pub const fn mesh(resource: ResourceKey, id: PrototypeGraphNodeId) -> Self { @@ -244,6 +265,16 @@ pub enum PrototypeGraphEdgeKind { UnitDatToComponent, /// Prototype to mesh dependency. PrototypeToMesh, + /// Mesh resource to WEAR table. + MeshToWear, + /// WEAR table to MAT0 material. + WearToMaterial, + /// MAT0 material to TEXM texture. + MaterialToTexture, + /// WEAR table to TEXM lightmap. + WearToLightmap, + /// MAT0 material to effect dependency. + MaterialToEffect, } /// Prototype graph edge record. diff --git a/crates/fparkan-runtime/src/lib.rs b/crates/fparkan-runtime/src/lib.rs index d70b327..ca97409 100644 --- a/crates/fparkan-runtime/src/lib.rs +++ b/crates/fparkan-runtime/src/lib.rs @@ -516,12 +516,12 @@ fn load_mission_with_options( .iter() .map(|object| resource_name(&object.resource_name.raw)) .collect(); - let (prototype_graph, resolved_prototypes, mut prototype_report) = + let (mut prototype_graph, resolved_prototypes, mut prototype_report) = build_prototype_graph_report(&repository, vfs.as_ref(), &graph_roots); extend_graph_report_with_visual_dependencies( &repository, &mut prototype_report, - &prototype_graph, + &mut prototype_graph, &resolved_prototypes, ); if !prototype_report.is_success() { |
