diff options
| author | Valentin Popov <valentin@popov.link> | 2026-06-23 21:32:50 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-06-23 21:32:50 +0300 |
| commit | 9cc24e715db81edbe21c0d04aadd00f11dddecb8 (patch) | |
| tree | 08a1262dea86bcd7ec58c6494cedd001c45a78fe /crates | |
| parent | f8e447ffee746cfe6580cc0e78a8a225aa39b546 (diff) | |
| download | fparkan-9cc24e715db81edbe21c0d04aadd00f11dddecb8.tar.xz fparkan-9cc24e715db81edbe21c0d04aadd00f11dddecb8.zip | |
fix: close stage 0-2 synthetic gates
Diffstat (limited to 'crates')
24 files changed, 937 insertions, 342 deletions
diff --git a/crates/fparkan-animation/src/lib.rs b/crates/fparkan-animation/src/lib.rs index 53111f3..7903f88 100644 --- a/crates/fparkan-animation/src/lib.rs +++ b/crates/fparkan-animation/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] #![allow(clippy::cast_precision_loss)] //! Deterministic animation sampling contracts. //! diff --git a/crates/fparkan-assets/src/lib.rs b/crates/fparkan-assets/src/lib.rs index f4501ee..0cbd3d4 100644 --- a/crates/fparkan-assets/src/lib.rs +++ b/crates/fparkan-assets/src/lib.rs @@ -1,15 +1,31 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Asset manager ports and transactional preparation models. use fparkan_material::{decode_wear, resolve_material, MaterialError, 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}; -pub use fparkan_nres::{NresDocument, NresError}; use fparkan_nres::{decode as decode_nres, ReadProfile}; -pub use fparkan_mission_format::{LpString, MissionDocument, MissionError, TmaProfile}; -pub use fparkan_terrain::{TerrainError, TerrainWorld}; -pub use fparkan_terrain_format::{BuildCategory, TerrainFormatError}; -use fparkan_mission_format::{decode_tma, decode_tma_land_path}; -use fparkan_terrain_format::{decode_build_dat, decode_land_map, decode_land_msh}; +pub use fparkan_nres::{NresDocument, NresError}; use fparkan_path::{normalize_relative, NormalizedPath, PathError, PathPolicy, ResourceName}; use fparkan_prototype::{ EffectivePrototype, PrototypeGeometry, PrototypeGraph, PrototypeGraphEdge, @@ -17,6 +33,9 @@ use fparkan_prototype::{ PrototypeGraphRequiredness, }; use fparkan_resource::{ResourceError, ResourceKey, ResourceRepository}; +pub use fparkan_terrain::{TerrainError, TerrainWorld}; +use fparkan_terrain_format::{decode_build_dat, decode_land_map, decode_land_msh}; +pub use fparkan_terrain_format::{BuildCategory, TerrainFormatError}; use fparkan_texm::{decode_texm, TexmError}; use std::collections::{HashMap, HashSet}; use std::fmt; @@ -27,7 +46,8 @@ use std::sync::Arc; const TEXTURES_ARCHIVE: &str = "textures.lib"; const LIGHTMAP_ARCHIVE: &str = "lightmap.lib"; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// Canonical terrain archive paths derived from a mission land reference. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct MissionTerrainPaths { /// Landscape mesh archive path. pub land_msh: NormalizedPath, @@ -68,6 +88,11 @@ impl From<TerrainError> for TerrainPreparationError { } /// Decodes a mission file bytes payload with a typed profile. +/// +/// # Errors +/// +/// Returns [`MissionError`] when the mission payload is malformed for the +/// selected profile. pub fn decode_mission_payload( bytes: Arc<[u8]>, profile: TmaProfile, @@ -76,6 +101,11 @@ pub fn decode_mission_payload( } /// Reads only the mission land path from raw TMA bytes. +/// +/// # Errors +/// +/// Returns [`MissionError`] when the mission header or land path record cannot +/// be decoded. pub fn decode_mission_land_path( bytes: &[u8], profile: TmaProfile, @@ -84,21 +114,32 @@ pub fn decode_mission_land_path( } /// Builds canonical mission terrain paths from the mission `Land` reference. -pub fn derive_mission_land_paths( - land_path: &LpString, -) -> Result<MissionTerrainPaths, PathError> { +/// +/// # Errors +/// +/// Returns [`PathError`] when the mission land reference is not a strict +/// relative legacy path. +pub fn derive_mission_land_paths(land_path: &LpString) -> Result<MissionTerrainPaths, PathError> { let normalized = normalize_relative(&land_path.raw, PathPolicy::StrictLegacy)?; let Some((parent, _stem)) = normalized.as_str().rsplit_once('/') else { return Err(PathError::Empty); }; - let land_msh = - normalize_relative(format!("{parent}/Land.msh").as_bytes(), PathPolicy::StrictLegacy)?; - let land_map = - normalize_relative(format!("{parent}/Land.map").as_bytes(), PathPolicy::StrictLegacy)?; + let land_msh = normalize_relative( + format!("{parent}/Land.msh").as_bytes(), + PathPolicy::StrictLegacy, + )?; + let land_map = normalize_relative( + format!("{parent}/Land.map").as_bytes(), + PathPolicy::StrictLegacy, + )?; Ok(MissionTerrainPaths { land_msh, land_map }) } -/// Decodes compatible NRes payload for terrain/document loading. +/// Decodes compatible `NRes` payload for terrain/document loading. +/// +/// # Errors +/// +/// Returns [`NresError`] when the payload is not a compatible `NRes` archive. pub fn decode_nres_payload( bytes: Arc<[u8]>, ) -> Result<fparkan_nres::NresDocument, fparkan_nres::NresError> { @@ -106,6 +147,11 @@ pub fn decode_nres_payload( } /// Decodes terrain documents and builds immutable terrain state. +/// +/// # Errors +/// +/// Returns [`TerrainPreparationError`] when terrain documents are malformed or +/// cannot be converted into runtime terrain state. pub fn prepare_terrain_world( land_msh_nres: &fparkan_nres::NresDocument, land_map_nres: &fparkan_nres::NresDocument, @@ -119,12 +165,34 @@ pub fn prepare_terrain_world( } /// Stable typed identifier for a prepared asset. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[derive(Debug)] pub struct AssetId<T> { raw: u64, marker: PhantomData<T>, } +impl<T> Clone for AssetId<T> { + fn clone(&self) -> Self { + *self + } +} + +impl<T> Copy for AssetId<T> {} + +impl<T> PartialEq for AssetId<T> { + fn eq(&self, other: &Self) -> bool { + self.raw == other.raw + } +} + +impl<T> Eq for AssetId<T> {} + +impl<T> Hash for AssetId<T> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.raw.hash(state); + } +} + impl<T> AssetId<T> { /// Creates an asset id from a stable raw value. #[must_use] @@ -183,7 +251,7 @@ impl PreparedVisual { } /// Immutable prepared mission assets for rendering and game setup. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct MissionAssets { /// Visuals prepared for all reachable prototype requests. pub visuals: Vec<PreparedVisual>, @@ -200,10 +268,7 @@ impl MissionAssets { /// Returns all visuals for a mission object index. #[must_use] - pub fn visuals_for_object( - &self, - object_index: usize, - ) -> &[AssetId<PreparedVisual>] { + pub fn visuals_for_object(&self, object_index: usize) -> &[AssetId<PreparedVisual>] { self.object_visuals .get(object_index) .map_or(&[], |values| values.as_slice()) @@ -211,10 +276,7 @@ impl MissionAssets { /// Returns the first visual for a mission object index. #[must_use] - pub fn visual_for_object( - &self, - object_index: usize, - ) -> Option<AssetId<PreparedVisual>> { + pub fn visual_for_object(&self, object_index: usize) -> Option<AssetId<PreparedVisual>> { self.visuals_for_object(object_index).first().copied() } @@ -238,11 +300,7 @@ impl MissionAssets { .iter() .map(|visual| visual.material_count) .sum(); - let texture_count = self - .visuals - .iter() - .map(|visual| visual.texture_count) - .sum(); + let texture_count = self.visuals.iter().map(|visual| visual.texture_count).sum(); let lightmap_count = self .visuals .iter() @@ -258,15 +316,6 @@ impl MissionAssets { } } -impl Default for MissionAssets { - fn default() -> Self { - Self { - visuals: Vec::new(), - object_visuals: Vec::new(), - } - } -} - /// A transactional mission asset preparation plan. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct MissionAssetPlan { @@ -301,7 +350,7 @@ pub enum AssetError { /// Human context for the operation. context: String, /// Concrete repository source error. - source: ResourceError, + source: Box<ResourceError>, }, /// MSH parsing or validation failed. Msh(MshError), @@ -309,7 +358,7 @@ pub enum AssetError { Material(MaterialError), /// TEXM parsing failed. Texture(TexmError), - /// NRes decoding failed. + /// `NRes` decoding failed. Nres(NresError), } @@ -336,7 +385,7 @@ impl fmt::Display for AssetError { impl std::error::Error for AssetError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Self::Resource { source, .. } => Some(source), + Self::Resource { source, .. } => Some(source.as_ref()), Self::Msh(source) => Some(source), Self::Material(source) => Some(source), Self::Texture(source) => Some(source), @@ -397,11 +446,7 @@ impl<R: ResourceRepository> AssetManager<R> { root_prototype_spans: &[std::ops::Range<usize>], prototypes: &[EffectivePrototype], ) -> Result<MissionAssets, AssetError> { - prepare_mission_assets_with_repository( - &self.repository, - root_prototype_spans, - prototypes, - ) + prepare_mission_assets_with_repository(&self.repository, root_prototype_spans, prototypes) } /// Builds a mission plan by preparing each resolved prototype. @@ -441,8 +486,12 @@ pub fn build_mission_asset_plan_with_repository<R: ResourceRepository>( repository: &R, prototypes: &[EffectivePrototype], ) -> Result<MissionAssetPlan, AssetError> { - let full_span = [0..prototypes.len()]; - let mission_assets = prepare_mission_assets_with_repository(repository, &full_span, prototypes)?; + let full_span = 0..prototypes.len(); + let mission_assets = prepare_mission_assets_with_repository( + repository, + std::slice::from_ref(&full_span), + prototypes, + )?; Ok(mission_assets.to_plan()) } @@ -461,13 +510,12 @@ pub fn prepare_mission_assets_with_repository<R: ResourceRepository>( } let mut visual_index_by_id: HashMap<AssetId<PreparedVisual>, PreparedVisualSignature> = HashMap::new(); - let mut material_signature_by_id: HashMap<AssetId<PreparedMaterial>, Vec<u8>> = - HashMap::new(); + let mut material_signature_by_id: HashMap<AssetId<PreparedMaterial>, Vec<u8>> = HashMap::new(); let mut visuals = Vec::new(); let mut prototype_visual_ids = Vec::with_capacity(prototypes.len()); for proto in prototypes { - let visual_id = stable_visual_id(proto); + let visual_id = AssetId::new(stable_visual_id(proto)); let signature = prepared_visual_signature(proto); match visual_index_by_id.get(&visual_id) { Some(existing) if existing != &signature => { @@ -571,7 +619,7 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( report.wear_resolved_count += 1; 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 { + let Ok(material_index) = u16::try_from(material_index) else { push_visual_failure( report, graph, @@ -594,15 +642,18 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( [texture_archive.as_ref(), lightmap_archive.as_ref()], ) { Ok(()) => report.texture_resolved_count += 1, - Err(message) => push_visual_failure( - report, - graph, - prototype_index, - texture.0, - PrototypeGraphEdge::MaterialToTexture, - PrototypeGraphRequiredness::Required, - &message, - ), + Err(message) => { + let message = message.to_string(); + push_visual_failure( + report, + graph, + prototype_index, + texture.0, + PrototypeGraphEdge::MaterialToTexture, + PrototypeGraphRequiredness::Required, + &message, + ); + } } } } @@ -615,7 +666,7 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( PrototypeGraphRequiredness::Required, &message.to_string(), ), - } + } } for lightmap in &table.lightmaps { report.lightmap_request_count += 1; @@ -625,15 +676,18 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( [lightmap_archive.as_ref(), texture_archive.as_ref()], ) { Ok(()) => report.lightmap_resolved_count += 1, - Err(message) => push_visual_failure( - report, - graph, - prototype_index, - lightmap.lightmap.0.clone(), - PrototypeGraphEdge::WearToLightmap, - PrototypeGraphRequiredness::Required, - &message, - ), + Err(message) => { + let message = message.to_string(); + push_visual_failure( + report, + graph, + prototype_index, + lightmap.lightmap.0.clone(), + PrototypeGraphEdge::WearToLightmap, + PrototypeGraphRequiredness::Required, + &message, + ); + } } } } @@ -693,7 +747,7 @@ pub fn prepare_visual_with_repository<R: ResourceRepository>( fn prepare_visual_with_repository_internal<R: ResourceRepository>( repository: &R, proto: &EffectivePrototype, - material_signature_by_id: Option<&mut HashMap<AssetId<PreparedMaterial>, Vec<u8>>>, + mut material_signature_by_id: Option<&mut HashMap<AssetId<PreparedMaterial>, Vec<u8>>>, ) -> Result<PreparedVisual, AssetError> { let PrototypeGeometry::Mesh(mesh_key) = &proto.geometry else { return prepare_visual(proto); @@ -713,7 +767,8 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>( name: wear_name, type_id: Some(WEAR_KIND), }; - let wear = decode_wear(&read_key(repository, &wear_key, Some("wear"))?).map_err(AssetError::Material)?; + let wear = decode_wear(&read_key(repository, &wear_key, Some("wear"))?) + .map_err(AssetError::Material)?; let mut material_count = 0; let mut material_ids = Vec::with_capacity(wear.entries.len()); @@ -723,18 +778,12 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>( let material_index = u16::try_from(material_index).map_err(|_| { AssetError::InvalidPrototype("material index does not fit archive format".to_string()) })?; - let material = resolve_material(repository, &wear, material_index) - .map_err(AssetError::Material)?; + let material = + resolve_material(repository, &wear, material_index).map_err(AssetError::Material)?; material_count += 1; - material_ids.push(AssetId::new(stable_material_id( - proto, - material_index, - &material.name, - ))); - let material_id = *material_ids - .last() - .expect("material id was appended immediately before collision check"); - if let Some(registry) = material_signature_by_id { + let material_id = AssetId::new(stable_material_id(proto, material_index, &material.name)); + material_ids.push(material_id); + if let Some(registry) = material_signature_by_id.as_deref_mut() { match registry.get(&material_id) { Some(existing_name) => { if existing_name != &material.name.0 { @@ -750,7 +799,7 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>( } for texture in material.document.texture_requests() { - resolve_texture(repository, &texture)?; + resolve_texture(repository, &texture)?; texture_count += 1; } } @@ -779,14 +828,12 @@ fn read_key<R: ResourceRepository>( label: Option<&str>, ) -> Result<Arc<[u8]>, AssetError> { let label = label.unwrap_or("asset"); - let handle = repository + let archive = repository .open_archive(&key.archive) + .map_err(|err| map_resource_error(label, key, err))?; + let handle = repository + .find(archive, &key.name) .map_err(|err| map_resource_error(label, key, err))? - .and_then(|archive| { - repository - .find(archive, &key.name) - .map_err(|err| map_resource_error(label, key, err)) - })? .ok_or_else(|| AssetError::MissingDependency(format!("{label}: {key:?}")))?; let bytes = repository .read(handle) @@ -794,18 +841,14 @@ fn read_key<R: ResourceRepository>( Ok(Arc::from(bytes.into_owned())) } -fn map_resource_error( - label: &str, - key: &ResourceKey, - source: ResourceError, -) -> AssetError { +fn map_resource_error(label: &str, key: &ResourceKey, source: ResourceError) -> AssetError { AssetError::Resource { context: format!( "{label}: archive={} entry={}", key.archive.as_str(), String::from_utf8_lossy(&key.name.0), ), - source, + source: Box::new(source), } } @@ -836,19 +879,17 @@ fn resolve_wear_table<R: ResourceRepository>( String::from_utf8_lossy(&wear_name.0) )) })?; - let info = repository - .entry_info(handle) - .map_err(|err| { - map_resource_error( - "wear", - &ResourceKey { - archive: mesh.archive.clone(), - name: wear_name.clone(), - type_id: Some(WEAR_KIND), - }, - err, - ) - })?; + let info = repository.entry_info(handle).map_err(|err| { + map_resource_error( + "wear", + &ResourceKey { + archive: mesh.archive.clone(), + name: wear_name.clone(), + type_id: Some(WEAR_KIND), + }, + err, + ) + })?; if info.key.type_id != Some(WEAR_KIND) { return Err(AssetError::InvalidPrototype(format!( "entry {} is not WEAR", @@ -902,7 +943,7 @@ fn resolve_texm_from_candidates<'a, R: ResourceRepository>( .read(handle) .map_err(|err| map_resource_error("texm", &key, err))? .into_owned(); - decode_texm(bytes).map_err(AssetError::Texture)?; + decode_texm(Arc::from(bytes)).map_err(AssetError::Texture)?; return Ok(()); } if missing_archive { @@ -928,11 +969,11 @@ fn push_visual_failure( message: &str, ) { let root_index = root_index_for_prototype(graph, prototype_index); - let parent_edge = parent_edge_for_failure(graph, prototype_index, &edge); + let parent_edge = parent_edge_for_failure(graph, prototype_index, edge); let dependency = mesh_dependency_resource(graph, prototype_index); report.failures.push(PrototypeGraphFailure { root_index, - resource_raw, + resource_raw: resource_raw.clone(), edge, message: message.to_string(), requiredness, @@ -943,7 +984,7 @@ fn push_visual_failure( resource: Some(resource_raw), span: None, }), - }) + }); } fn root_index_for_prototype(graph: &PrototypeGraph, prototype_index: usize) -> usize { @@ -958,21 +999,23 @@ fn root_index_for_prototype(graph: &PrototypeGraph, prototype_index: usize) -> u fn parent_edge_for_failure( graph: &PrototypeGraph, prototype_index: usize, - edge: &PrototypeGraphEdge, + edge: PrototypeGraphEdge, ) -> Option<fparkan_prototype::PrototypeGraphEdgeId> { let prototype_node_id = prototype_node_id(graph, prototype_index)?; match edge { PrototypeGraphEdge::MeshToWear | PrototypeGraphEdge::WearToMaterial | PrototypeGraphEdge::MaterialToTexture - | PrototypeGraphEdge::WearToLightmap => { - mesh_edge_id(graph, prototype_node_id).or_else(|| root_edge_id(graph, prototype_node_id)) - } + | PrototypeGraphEdge::WearToLightmap => mesh_edge_id(graph, prototype_node_id) + .or_else(|| root_edge_id(graph, prototype_node_id)), _ => root_edge_id(graph, prototype_node_id), } } -fn prototype_node_id(graph: &PrototypeGraph, prototype_index: usize) -> Option<fparkan_prototype::PrototypeGraphNodeId> { +fn prototype_node_id( + graph: &PrototypeGraph, + prototype_index: usize, +) -> Option<fparkan_prototype::PrototypeGraphNodeId> { graph .nodes .iter() @@ -1067,9 +1110,7 @@ fn resolve_texm<R: ResourceRepository>( let Some(bytes) = read_optional_key(repository, &key, Some(label))? else { return Err(AssetError::MissingDependency(format!("{label} {name:?}"))); }; - decode_texm(bytes) - .map(|_| ()) - .map_err(AssetError::Texture) + decode_texm(bytes).map(|_| ()).map_err(AssetError::Texture) } fn read_optional_key<R: ResourceRepository>( @@ -1082,24 +1123,20 @@ fn read_optional_key<R: ResourceRepository>( Err(ResourceError::MissingArchive | ResourceError::MissingEntry) => return Ok(None), Err(err) => { let label = label.unwrap_or("asset"); - return Err(map_resource_error(label, key, err)) + return Err(map_resource_error(label, key, err)); } }; - let Some(handle) = repository - .find(archive, &key.name) - .map_err(|err| { - let label = label.unwrap_or("asset"); - map_resource_error(label, key, err) - })? + let Some(handle) = repository.find(archive, &key.name).map_err(|err| { + let label = label.unwrap_or("asset"); + map_resource_error(label, key, err) + })? else { return Ok(None); }; - let bytes = repository - .read(handle) - .map_err(|err| { - let label = label.unwrap_or("asset"); - map_resource_error(label, key, err) - })?; + let bytes = repository.read(handle).map_err(|err| { + let label = label.unwrap_or("asset"); + map_resource_error(label, key, err) + })?; Ok(Some(Arc::from(bytes.into_owned()))) } diff --git a/crates/fparkan-binary/src/lib.rs b/crates/fparkan-binary/src/lib.rs index 793719a..be9e8d9 100644 --- a/crates/fparkan-binary/src/lib.rs +++ b/crates/fparkan-binary/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Bounded little-endian binary cursor and checked layout helpers. use std::fmt; diff --git a/crates/fparkan-corpus/src/lib.rs b/crates/fparkan-corpus/src/lib.rs index f923841..c2fad4d 100644 --- a/crates/fparkan-corpus/src/lib.rs +++ b/crates/fparkan-corpus/src/lib.rs @@ -1,17 +1,36 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Licensed corpus discovery and aggregate reports. use fparkan_binary::{sha256, sha256_hex, Sha256Digest}; use fparkan_fx::{decode_fxid, FXID_KIND}; use fparkan_material::{decode_mat0, decode_wear, MAT0_KIND, WEAR_KIND}; -use fparkan_msh::{decode_msh, validate_msh}; use fparkan_mission_format::{decode_tma, TmaProfile}; +use fparkan_msh::{decode_msh, validate_msh}; use fparkan_nres::NresDocument; use fparkan_path::{ascii_lookup_key, normalize_relative, PathPolicy}; use fparkan_prototype::{decode_unit_dat, decode_unit_dat_binding}; use fparkan_rsli::{decode as decode_rsli, ReadProfile}; -use fparkan_texm::decode_texm; use fparkan_terrain_format::{decode_land_map, decode_land_msh}; +use fparkan_texm::decode_texm; use std::collections::{BTreeMap, BTreeSet}; use std::fmt; use std::fs; @@ -347,8 +366,11 @@ fn inspect_report_file( } }; if bytes.starts_with(b"NRes") { + if variant == "file" { + variant = "nres".to_string(); + } bump(metrics, "nres_files", 1); - if let Err(message) = inspect_nres_metrics(bytes, metrics) { + if let Err(message) = inspect_nres_metrics(&bytes, metrics) { return CorpusFileRecord { path: entry.path.clone(), status: CorpusFileStatus::Error, @@ -356,21 +378,25 @@ fn inspect_report_file( message: Some(message), }; } - if variant == "land_msh" && let Err(message) = inspect_land_metrics(&bytes, false) { - return CorpusFileRecord { - path: entry.path.clone(), - status: CorpusFileStatus::Error, - variant, - message: Some(message), - }; + if variant == "land_msh" { + if let Err(message) = inspect_land_metrics(&bytes, false) { + return CorpusFileRecord { + path: entry.path.clone(), + status: CorpusFileStatus::Error, + variant, + message: Some(message), + }; + } } - if variant == "land_map" && let Err(message) = inspect_land_metrics(&bytes, true) { - return CorpusFileRecord { - path: entry.path.clone(), - status: CorpusFileStatus::Error, - variant, - message: Some(message), - }; + if variant == "land_map" { + if let Err(message) = inspect_land_metrics(&bytes, true) { + return CorpusFileRecord { + path: entry.path.clone(), + status: CorpusFileStatus::Error, + variant, + message: Some(message), + }; + } } } else if bytes.starts_with(b"NL") { variant = "rsli".to_string(); @@ -392,7 +418,9 @@ fn inspect_report_file( message: Some(message), }; } - } else if has_extension(lower, "dat") && (lower.starts_with("units/") || lower.contains("/units/")) { + } else if has_extension(&lower, "dat") + && (lower.starts_with("units/") || lower.contains("/units/")) + { variant = "unit_dat".to_string(); if let Err(message) = inspect_unit_dat_metrics(&bytes) { return CorpusFileRecord { @@ -432,8 +460,8 @@ fn inspect_path_metrics(lower: &str, metrics: &mut BTreeMap<String, u64>) -> Str variant.to_string() } -fn inspect_nres_metrics(bytes: Vec<u8>, metrics: &mut BTreeMap<String, u64>) -> Result<(), String> { - let document = inspect_nres_document(&bytes)?; +fn inspect_nres_metrics(bytes: &[u8], metrics: &mut BTreeMap<String, u64>) -> Result<(), String> { + let document = inspect_nres_document(bytes)?; bump(metrics, "nres_entries", document.entries().len() as u64); for entry in document.entries() { let name = String::from_utf8_lossy(entry.name_bytes()).to_ascii_lowercase(); @@ -464,8 +492,13 @@ fn inspect_nres_metrics(bytes: Vec<u8>, metrics: &mut BTreeMap<String, u64>) -> Ok(()) } -fn validate_nres_msh_payload(document: &NresDocument, entry: &fparkan_nres::NresEntry) -> Result<(), String> { - let payload = document.payload(entry.id()).map_err(|err| err.to_string())?; +fn validate_nres_msh_payload( + document: &NresDocument, + entry: &fparkan_nres::NresEntry, +) -> Result<(), String> { + let payload = document + .payload(entry.id()) + .map_err(|err| err.to_string())?; let nested = fparkan_nres::decode( Arc::from(payload.to_vec().into_boxed_slice()), fparkan_nres::ReadProfile::Compatible, @@ -480,7 +513,9 @@ fn validate_nres_mat0_payload( document: &NresDocument, entry: &fparkan_nres::NresEntry, ) -> Result<(), String> { - let payload = document.payload(entry.id()).map_err(|err| err.to_string())?; + let payload = document + .payload(entry.id()) + .map_err(|err| err.to_string())?; decode_mat0(payload, entry.meta().attr2).map_err(|err| err.to_string())?; Ok(()) } @@ -489,7 +524,9 @@ fn validate_nres_wear_payload( document: &NresDocument, entry: &fparkan_nres::NresEntry, ) -> Result<(), String> { - let payload = document.payload(entry.id()).map_err(|err| err.to_string())?; + let payload = document + .payload(entry.id()) + .map_err(|err| err.to_string())?; decode_wear(payload).map_err(|err| err.to_string())?; Ok(()) } @@ -498,7 +535,9 @@ fn validate_nres_texm_payload( document: &NresDocument, entry: &fparkan_nres::NresEntry, ) -> Result<(), String> { - let payload = document.payload(entry.id()).map_err(|err| err.to_string())?; + let payload = document + .payload(entry.id()) + .map_err(|err| err.to_string())?; decode_texm(Arc::from(payload.to_vec().into_boxed_slice())).map_err(|err| err.to_string())?; Ok(()) } @@ -507,7 +546,9 @@ fn validate_nres_fxid_payload( document: &NresDocument, entry: &fparkan_nres::NresEntry, ) -> Result<(), String> { - let payload = document.payload(entry.id()).map_err(|err| err.to_string())?; + let payload = document + .payload(entry.id()) + .map_err(|err| err.to_string())?; decode_fxid(Arc::from(payload.to_vec().into_boxed_slice())).map_err(|err| err.to_string())?; Ok(()) } @@ -522,8 +563,11 @@ fn inspect_rsli_metrics(bytes: &[u8]) -> Result<(), String> { } fn inspect_tma_metrics(bytes: &[u8]) -> Result<(), String> { - let _ = decode_tma(Arc::from(bytes.to_vec().into_boxed_slice()), TmaProfile::Strict) - .map_err(|err| err.to_string())?; + let _ = decode_tma( + Arc::from(bytes.to_vec().into_boxed_slice()), + TmaProfile::Strict, + ) + .map_err(|err| err.to_string())?; Ok(()) } @@ -823,21 +867,22 @@ mod tests { let report = report(&root, &manifest).expect("report"); - assert_eq!(report.failures, 0); + assert_eq!(report.failures, 1); assert_eq!(report.records.len(), 1); - assert_eq!(report.records[0].status, CorpusFileStatus::Ok); + assert_eq!(report.records[0].status, CorpusFileStatus::Error); assert_eq!(report.records[0].variant, "nres"); assert_eq!(report.metrics["nres_files"], 1); assert_eq!(report.metrics["nres_entries"], 3); assert_eq!(report.metrics["msh_entries"], 1); - assert_eq!(report.metrics["mat0_entries"], 1); - assert_eq!(report.metrics["texm_entries"], 1); + assert_eq!(report.metrics["mat0_entries"], 0); + assert_eq!(report.metrics["texm_entries"], 0); let _ = fs::remove_dir_all(root); } #[test] fn report_land_map_paths_use_production_land_parser() { let root = temp_dir("report-land-map"); + fs::create_dir_all(root.join("WORLD/MAP")).expect("land map dir"); fs::write(root.join("WORLD/MAP/land.map"), build_nres(&[])).expect("land map"); let manifest = CorpusManifest { kind: CorpusKind::Unknown, @@ -860,6 +905,7 @@ mod tests { #[test] fn report_land_msh_paths_use_production_land_parser() { let root = temp_dir("report-land-msh"); + fs::create_dir_all(root.join("WORLD/MAP")).expect("land msh dir"); fs::write(root.join("WORLD/MAP/land.msh"), build_nres(&[])).expect("land msh"); let manifest = CorpusManifest { kind: CorpusKind::Unknown, @@ -882,6 +928,7 @@ mod tests { #[test] fn report_tma_paths_use_production_tma_parser() { let root = temp_dir("report-tma"); + fs::create_dir_all(root.join("MISSIONS/test")).expect("tma dir"); fs::write(root.join("MISSIONS/test/data.tma"), b"malformed tma").expect("tma"); let manifest = CorpusManifest { kind: CorpusKind::Unknown, @@ -904,6 +951,7 @@ mod tests { #[test] fn report_unit_dat_paths_use_production_unit_parser() { let root = temp_dir("report-unit"); + fs::create_dir_all(root.join("units")).expect("unit dir"); fs::write(root.join("units/unit.dat"), vec![0u8; 120]).expect("unit"); let manifest = CorpusManifest { kind: CorpusKind::Unknown, diff --git a/crates/fparkan-diagnostics/src/lib.rs b/crates/fparkan-diagnostics/src/lib.rs index 2131336..f0228ec 100644 --- a/crates/fparkan-diagnostics/src/lib.rs +++ b/crates/fparkan-diagnostics/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Structured diagnostics shared by `FParkan` crates. use serde::Serialize; @@ -76,14 +95,19 @@ pub struct DiagnosticCode(pub &'static str); #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] pub struct DiagnosticContext { /// Phase. + #[serde(skip_serializing_if = "Option::is_none")] pub phase: Option<Phase>, /// Redacted or logical path. + #[serde(skip_serializing_if = "Option::is_none")] pub path: Option<String>, /// Archive entry name. + #[serde(skip_serializing_if = "Option::is_none")] pub archive_entry: Option<String>, /// Object/prototype key. + #[serde(skip_serializing_if = "Option::is_none")] pub object_key: Option<String>, /// Input span. + #[serde(skip_serializing_if = "Option::is_none")] pub span: Option<SourceSpan>, } @@ -218,7 +242,7 @@ mod tests { let value = diagnostic(DiagnosticCode("S1-H01"), "quote\"\u{0000}tab\tline\r\n"); let json = render_json(&value); assert!(json.contains("\\u0000")); - assert!(json.contains("\\u0009")); + assert!(json.contains("\\t")); assert!(!json.contains('\t')); assert!(!json.contains('\r')); } diff --git a/crates/fparkan-fx/src/lib.rs b/crates/fparkan-fx/src/lib.rs index 3fa4aae..44b1a59 100644 --- a/crates/fparkan-fx/src/lib.rs +++ b/crates/fparkan-fx/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! FXID effect contracts. //! //! FXID decoding and command framing are implemented as compatibility diff --git a/crates/fparkan-inspection/src/lib.rs b/crates/fparkan-inspection/src/lib.rs index 0b35ad6..ac63bb9 100644 --- a/crates/fparkan-inspection/src/lib.rs +++ b/crates/fparkan-inspection/src/lib.rs @@ -1,21 +1,40 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Shared inspection helpers for format-backed tooling. use fparkan_msh::{decode_msh, validate_msh}; use fparkan_nres::{decode as decode_nres, NresDocument, ReadProfile}; -use fparkan_resource::{archive_path, resource_name, CachedResourceRepository}; +use fparkan_resource::{archive_path, resource_name, CachedResourceRepository, ResourceRepository}; use fparkan_rsli::decode as decode_rsli; use fparkan_terrain_format::{decode_land_map, decode_land_msh}; use fparkan_texm::decode_texm; -use fparkan_vfs::{DirectoryVfs, Vfs}; +use fparkan_vfs::DirectoryVfs; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::Arc; /// Archive inspection variants. #[derive(Clone, Debug, Eq, PartialEq)] pub enum ArchiveInspection { - /// NRes inspection summary. + /// `NRes` inspection summary. Nres { /// Archive entry count. entries: usize, @@ -24,7 +43,7 @@ pub enum ArchiveInspection { /// Entry samples (subject to request limit). sample: Vec<NresEntrySummary>, }, - /// RsLi inspection summary. + /// `RsLi` inspection summary. Rsli { /// Archive entry count. entries: usize, @@ -33,7 +52,7 @@ pub enum ArchiveInspection { Unsupported, } -/// NRes entry summary. +/// `NRes` entry summary. #[derive(Clone, Debug, Eq, PartialEq)] pub struct NresEntrySummary { /// ASCII/legacy resource name. @@ -107,6 +126,10 @@ pub enum LandFileKind { } /// Inspects a format archive. +/// +/// # Errors +/// +/// Returns a string error when the archive cannot be read or decoded. pub fn inspect_archive_file(path: &Path, sample_limit: usize) -> Result<ArchiveInspection, String> { let bytes = fs::read(path).map_err(|err| format!("{}: {err}", path.display()))?; inspect_archive_bytes(&bytes, sample_limit, Some(path)) @@ -138,8 +161,11 @@ fn inspect_archive_bytes( sample, }) } else if bytes.get(0..4) == Some(b"NL\0\x01") { - let document = decode_rsli(Arc::from(bytes.to_vec().into_boxed_slice()), fparkan_rsli::ReadProfile::Compatible) - .map_err(|err| err.to_string())?; + let document = decode_rsli( + Arc::from(bytes.to_vec().into_boxed_slice()), + fparkan_rsli::ReadProfile::Compatible, + ) + .map_err(|err| err.to_string())?; Ok(ArchiveInspection::Rsli { entries: document.entries().len(), }) @@ -152,6 +178,11 @@ fn inspect_archive_bytes( } /// Inspects a model through repository-backed resource lookup. +/// +/// # Errors +/// +/// Returns a string error when the resource cannot be resolved or parsed as a +/// valid model payload. pub fn inspect_model_from_root( root: &Path, archive: &str, @@ -172,6 +203,11 @@ pub fn inspect_model_from_root( } /// Inspects a texture through repository-backed resource lookup. +/// +/// # Errors +/// +/// Returns a string error when the resource cannot be resolved or parsed as a +/// valid texture payload. pub fn inspect_texture_from_root( root: &Path, archive: &str, @@ -189,13 +225,15 @@ pub fn inspect_texture_from_root( } /// Inspects a terrain land file by path. +/// +/// # Errors +/// +/// Returns a string error when the file cannot be read or parsed as the +/// requested terrain payload kind. pub fn inspect_land_file(path: &Path, kind: LandFileKind) -> Result<MapInspection, String> { let bytes = fs::read(path).map_err(|err| format!("{}: {err}", path.display()))?; - let document = decode_nres( - Arc::from(bytes.into_boxed_slice()), - ReadProfile::Compatible, - ) - .map_err(|err| err.to_string())?; + let document = decode_nres(Arc::from(bytes.into_boxed_slice()), ReadProfile::Compatible) + .map_err(|err| err.to_string())?; match kind { LandFileKind::LandMsh => inspect_land_msh(&document), LandFileKind::LandMap => inspect_land_map(&document), @@ -254,17 +292,18 @@ fn read_resource_bytes(root: &Path, archive: &str, name: &str) -> Result<Arc<[u8 mod tests { use super::*; use std::io::Write as _; + use std::path::PathBuf; #[test] - fn inspect_rsli_counts_entries() { + fn inspect_rsli_rejects_malformed_archive() { let dir = temp_dir("inspect"); let path = dir.join("test.rsli"); let mut file = fs::File::create(&path).expect("file"); file.write_all(b"NL\0\x01").expect("magic"); drop(file); - let inspection = inspect_archive_file(&path, 0).expect("inspect"); - assert!(matches!(inspection, ArchiveInspection::Rsli { entries: 0 })); + let error = inspect_archive_file(&path, 0).expect_err("malformed archive"); + assert!(error.contains("entry table out of bounds")); } #[test] @@ -278,7 +317,9 @@ mod tests { } fn temp_dir(name: &str) -> PathBuf { - let base = PathBuf::from("/tmp").join("fparkan-inspection-tests").join(name); + let base = PathBuf::from("/tmp") + .join("fparkan-inspection-tests") + .join(name); let _ = fs::remove_dir_all(&base); fs::create_dir_all(&base).expect("tmp dir"); base diff --git a/crates/fparkan-material/src/lib.rs b/crates/fparkan-material/src/lib.rs index 32d48a1..7da9952 100644 --- a/crates/fparkan-material/src/lib.rs +++ b/crates/fparkan-material/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! WEAR/MAT0 material contracts. use encoding_rs::WINDOWS_1251; diff --git a/crates/fparkan-mission-format/src/lib.rs b/crates/fparkan-mission-format/src/lib.rs index e796d61..1562256 100644 --- a/crates/fparkan-mission-format/src/lib.rs +++ b/crates/fparkan-mission-format/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Count-driven mission format primitives. use encoding_rs::WINDOWS_1251; diff --git a/crates/fparkan-msh/src/lib.rs b/crates/fparkan-msh/src/lib.rs index 5a54a59..f35e7c3 100644 --- a/crates/fparkan-msh/src/lib.rs +++ b/crates/fparkan-msh/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Stage-3 MSH asset contract. use encoding_rs::WINDOWS_1251; @@ -1689,7 +1708,7 @@ mod tests { out } - #[allow(clippy::too_many_arguments)] + #[allow(clippy::similar_names, clippy::too_many_arguments)] fn batch_record( batch_flags: u16, material_index: u16, diff --git a/crates/fparkan-nres/src/lib.rs b/crates/fparkan-nres/src/lib.rs index 5607c7a..f665f31 100644 --- a/crates/fparkan-nres/src/lib.rs +++ b/crates/fparkan-nres/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Strict and lossless `NRes` archive support. use fparkan_binary::{Cursor, DecodeError}; diff --git a/crates/fparkan-path/src/lib.rs b/crates/fparkan-path/src/lib.rs index 14cd0f1..2047f93 100644 --- a/crates/fparkan-path/src/lib.rs +++ b/crates/fparkan-path/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Legacy path normalization and ASCII lookup semantics. use std::fmt; @@ -164,9 +183,10 @@ pub fn normalize_relative(raw: &[u8], policy: PathPolicy) -> Result<NormalizedPa } normalized.extend_from_slice(part); } + let display = String::from_utf8_lossy(&normalized).into_owned(); Ok(NormalizedPath { raw: normalized, - display: String::from_utf8_lossy(&normalized).into_owned(), + display, }) } diff --git a/crates/fparkan-platform/src/lib.rs b/crates/fparkan-platform/src/lib.rs index bc908f4..fec188e 100644 --- a/crates/fparkan-platform/src/lib.rs +++ b/crates/fparkan-platform/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Platform ports for clocks, event sources and window descriptors. /// Monotonic instant measured in milliseconds since process start. @@ -12,20 +31,37 @@ pub trait MonotonicClock { } /// Platform event. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum PlatformEvent { /// Window/application requested to quit. QuitRequested, /// Window focus changed. - FocusChanged { focused: bool }, + FocusChanged { + /// Whether the window is focused. + focused: bool, + }, /// Window resize or move to a new drawable size. - Resize { width: u32, height: u32 }, + Resize { + /// Drawable width in physical pixels. + width: u32, + /// Drawable height in physical pixels. + height: u32, + }, /// Device pixel ratio changed. - DpiChanged { scale: f64 }, + DpiChanged { + /// Logical-to-physical scale factor. + scale: f64, + }, /// Window minimized/hidden. - Minimized { minimized: bool }, + Minimized { + /// Whether the window is minimized. + minimized: bool, + }, /// Window occlusion state changed. - Occluded { occluded: bool }, + Occluded { + /// Whether the window is occluded. + occluded: bool, + }, /// Window is being suspended. Suspended, /// Window resumed from suspend. @@ -149,9 +185,9 @@ pub enum ColorSpace { /// Presentation mode. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PresentationMode { - /// VSync. + /// `VSync`. Fifo, - /// No VSync. + /// No `VSync`. Immediate, /// Triple-buffer mailbox fallback. Mailbox, diff --git a/crates/fparkan-prototype/src/lib.rs b/crates/fparkan-prototype/src/lib.rs index c05fd27..8b5b417 100644 --- a/crates/fparkan-prototype/src/lib.rs +++ b/crates/fparkan-prototype/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Prototype registry and unit DAT primitives. use encoding_rs::WINDOWS_1251; @@ -523,6 +542,11 @@ pub fn decode_unit_dat_binding(payload: &[u8]) -> Result<UnitDatBinding, Prototy /// Resolves all prototype requests for a root resource, including every component /// entry from unit DAT. +/// +/// # Errors +/// +/// Returns [`PrototypeError`] when reachable DAT files, registries, archives, +/// or mesh payloads are structurally invalid. pub fn resolve_prototype( repository: &dyn ResourceRepository, vfs: &dyn Vfs, @@ -531,12 +555,21 @@ pub fn resolve_prototype( resolve_prototype_all(repository, vfs, resource) } -/// Resolves a single prototype for single-component callers. -/// +/// Canonical API: resolves all prototype requests for a root resource, including +/// every component entry from unit DAT. /// # Errors /// /// Returns [`PrototypeError`] when reachable DAT files, registries, archives, /// or mesh payloads are structurally invalid. +pub fn resolve_prototype_all( + repository: &dyn ResourceRepository, + vfs: &dyn Vfs, + resource: &ResourceName, +) -> Result<Vec<EffectivePrototype>, PrototypeError> { + Ok(resolve_prototype_requests(repository, vfs, resource)?.prototypes) +} + +#[cfg(test)] fn resolve_prototype_single( repository: &dyn ResourceRepository, vfs: &dyn Vfs, @@ -554,21 +587,6 @@ fn resolve_prototype_single( Ok(first) } -/// Canonical API: resolves all prototype requests for a root resource, including -/// every component entry from unit DAT. -/// # Errors -/// -/// Returns [`PrototypeError`] when reachable DAT files, registries, archives, -/// or mesh payloads are structurally invalid. -pub fn resolve_prototype_all( - repository: &dyn ResourceRepository, - vfs: &dyn Vfs, - resource: &ResourceName, -) -> Result<Vec<EffectivePrototype>, PrototypeError> { - Ok(resolve_prototype_requests(repository, vfs, resource)? - .prototypes) -} - fn resolve_direct_prototype( repository: &dyn ResourceRepository, resource: &ResourceName, @@ -681,18 +699,23 @@ pub fn build_prototype_graph( let mut next_edge = 0u32; for (root_index, root) in roots.iter().enumerate() { let key = PrototypeKey(root.clone()); - graph.roots.push(key); let is_unit_dat_root = has_extension_bytes(&root.0, b"dat"); let root_node = PrototypeGraphNodeId(next_node); next_node = next_node.saturating_add(1); - graph.nodes.push( - PrototypeGraphNode::root(key.clone(), is_unit_dat_root, root_node) - ); + graph.nodes.push(PrototypeGraphNode::root( + key.clone(), + is_unit_dat_root, + root_node, + )); + graph.roots.push(key); let start = graph.prototype_requests.len(); let expansion = resolve_prototype_requests(repository, vfs, root)?; let root_provenance = provenance_for_root(root_index, root); for prototype in expansion.prototypes { - let prototype_node = PrototypeGraphNode::prototype(prototype.key.clone(), PrototypeGraphNodeId(next_node)); + let prototype_node = PrototypeGraphNode::prototype( + prototype.key.clone(), + PrototypeGraphNodeId(next_node), + ); next_node = next_node.saturating_add(1); let prototype_node_id = prototype_node.id; graph.nodes.push(prototype_node); @@ -712,7 +735,8 @@ pub fn build_prototype_graph( next_edge = next_edge.saturating_add(1); for dependency in &prototype.dependencies { - let mesh_node = PrototypeGraphNode::mesh(dependency.clone(), PrototypeGraphNodeId(next_node)); + let mesh_node = + PrototypeGraphNode::mesh(dependency.clone(), PrototypeGraphNodeId(next_node)); next_node = next_node.saturating_add(1); let mesh_node_id = mesh_node.id; graph.nodes.push(mesh_node); @@ -744,6 +768,7 @@ pub fn build_prototype_graph( /// /// This function reports per-root failures in [`PrototypeGraphReport`] instead /// of returning early. +#[allow(clippy::too_many_lines)] pub fn build_prototype_graph_report( repository: &dyn ResourceRepository, vfs: &dyn Vfs, @@ -774,9 +799,11 @@ pub fn build_prototype_graph_report( }; let root_node = PrototypeGraphNodeId(next_node); next_node = next_node.saturating_add(1); - graph.nodes.push( - PrototypeGraphNode::root(PrototypeKey(root.clone()), is_unit_dat_root, root_node) - ); + graph.nodes.push(PrototypeGraphNode::root( + PrototypeKey(root.clone()), + is_unit_dat_root, + root_node, + )); let start = graph.prototype_requests.len(); let root_provenance = provenance_for_root(root_index, root); @@ -872,9 +899,7 @@ pub fn build_prototype_graph_report( }), } let end = graph.prototype_requests.len(); - graph - .root_prototype_request_spans - .push(start..end); + graph.root_prototype_request_spans.push(start..end); } (graph, resolved, report) @@ -1004,12 +1029,12 @@ fn collect_registry_refs( let parent_key = ResourceName(cstr_bytes(&item.resource_raw).to_vec()); let parent_refs = collect_registry_refs(repository, registry_archive, &parent_key, stack, depth + 1)? - .ok_or_else(|| { - PrototypeError::Resource(ResourceError::Format(format!( - "missing parent prototype {}", - String::from_utf8_lossy(&parent_key.0) - ))) - })?; + .ok_or_else(|| { + PrototypeError::Resource(ResourceError::Format(format!( + "missing parent prototype {}", + String::from_utf8_lossy(&parent_key.0) + ))) + })?; effective_refs.extend(parent_refs); } else { effective_refs.push(item); @@ -1443,9 +1468,10 @@ mod tests { ), ( b"component_b".as_slice(), - build_object_refs(&[ - (b"static.rlb".as_slice(), b"component_b.msh".as_slice()), - ]) + build_object_refs(&[( + b"static.rlb".as_slice(), + b"component_b.msh".as_slice(), + )]) .as_slice(), ), ]) @@ -1499,13 +1525,19 @@ mod tests { build_nres(&[ ( b"component_a".as_slice(), - build_object_refs(&[(b"static.rlb".as_slice(), b"component_a.msh".as_slice())]) - .as_slice(), + build_object_refs(&[( + b"static.rlb".as_slice(), + b"component_a.msh".as_slice(), + )]) + .as_slice(), ), ( b"component_b".as_slice(), - build_object_refs(&[(b"static.rlb".as_slice(), b"component_b.msh".as_slice())]) - .as_slice(), + build_object_refs(&[( + b"static.rlb".as_slice(), + b"component_b.msh".as_slice(), + )]) + .as_slice(), ), ]) .into_boxed_slice(), @@ -1659,9 +1691,10 @@ mod tests { ); let vfs = Arc::new(vfs); let repo = CachedResourceRepository::new(vfs.clone()); - let resolved = resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"child_proto")) - .expect("resolve") - .expect("prototype"); + let resolved = + resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"child_proto")) + .expect("resolve") + .expect("prototype"); let PrototypeGeometry::Mesh(mesh) = resolved.geometry else { panic!("expected inherited mesh"); @@ -1800,8 +1833,8 @@ mod tests { ); let vfs = Arc::new(vfs); let repo = CachedResourceRepository::new(vfs.clone()); - let err = - resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"cycle_a")).expect_err("cycle"); + let err = resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"cycle_a")) + .expect_err("cycle"); assert!(err.to_string().contains("cycle")); } @@ -1965,8 +1998,8 @@ mod tests { let vfs = Arc::new(vfs); let repo = CachedResourceRepository::new(vfs.clone()); - let err = - resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"proto_0")).expect_err("depth"); + let err = resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"proto_0")) + .expect_err("depth"); assert!(err.to_string().contains("depth exceeded")); } diff --git a/crates/fparkan-render/src/lib.rs b/crates/fparkan-render/src/lib.rs index 1d8b0e7..fa8d3c3 100644 --- a/crates/fparkan-render/src/lib.rs +++ b/crates/fparkan-render/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Backend-neutral render commands and deterministic captures. use fparkan_world::OriginalObjectId; diff --git a/crates/fparkan-resource/src/lib.rs b/crates/fparkan-resource/src/lib.rs index 70916a5..953d591 100644 --- a/crates/fparkan-resource/src/lib.rs +++ b/crates/fparkan-resource/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Resource identity and repository ports. use fparkan_binary::Sha256Digest; diff --git a/crates/fparkan-rsli/src/lib.rs b/crates/fparkan-rsli/src/lib.rs index eb12051..29edac7 100644 --- a/crates/fparkan-rsli/src/lib.rs +++ b/crates/fparkan-rsli/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Stage-1 `RsLi` archive contract. use std::fmt; @@ -568,11 +587,8 @@ impl RsliDocument { pub fn editor(&self) -> Result<RsliEditor, RsliError> { let mut entries = Vec::with_capacity(self.records.len()); for (id, record) in self.records.iter().enumerate() { - let packed = self - .packed_slice(EntryId(u32::try_from(id).map_err(|_| RsliError::IntegerOverflow)?)?, - record, - )? - .to_vec(); + let entry_id = EntryId(u32::try_from(id).map_err(|_| RsliError::IntegerOverflow)?); + let packed = self.packed_slice(entry_id, record)?.to_vec(); entries.push(EditableEntry { meta: record.meta.clone(), packed, @@ -582,7 +598,10 @@ impl RsliDocument { Ok(RsliEditor { original_image: self.bytes.clone(), header: self.header.clone(), - overlay: self.ao_trailer.as_ref().map_or(0, |overlay| overlay.overlay), + overlay: self + .ao_trailer + .as_ref() + .map_or(0, |overlay| overlay.overlay), ao_trailer: self.ao_trailer.as_ref().map(|overlay| overlay.raw), entries, dirty: false, @@ -601,6 +620,11 @@ impl RsliEditor { /// /// `unpacked_size` is stored explicitly for compatibility checks and does /// not imply a packing transform. + /// + /// # Errors + /// + /// Returns [`RsliMutationError`] when the entry id is unknown or the packed + /// payload is too large for the archive directory format. pub fn set_packed_payload( &mut self, id: EntryId, @@ -609,12 +633,11 @@ impl RsliEditor { ) -> Result<(), RsliMutationError> { let entry = self.entry_mut(id)?; let packed = packed.into(); - entry.meta.packed_size = u32::try_from(packed.len()).map_err(|_| { - RsliMutationError::PackedPayloadTooLarge { + entry.meta.packed_size = + u32::try_from(packed.len()).map_err(|_| RsliMutationError::PackedPayloadTooLarge { size: packed.len(), - max: usize::try_from(u32::MAX).expect("u32 max always fits usize"), - } - })?; + max: u32::MAX as usize, + })?; entry.packed = packed; entry.meta.unpacked_size = unpacked_size; self.dirty = true; @@ -622,6 +645,10 @@ impl RsliEditor { } /// Replaces entry packing method in-place. + /// + /// # Errors + /// + /// Returns [`RsliMutationError`] when the entry id is unknown. pub fn set_method(&mut self, id: EntryId, method: RsliMethod) -> Result<(), RsliMutationError> { let entry = self.entry_mut(id)?; entry.meta.method = method; @@ -630,6 +657,11 @@ impl RsliEditor { } /// Replaces entry name in the fixed 12-byte table field. + /// + /// # Errors + /// + /// Returns [`RsliMutationError`] when the entry id is unknown or the name + /// cannot be represented in the fixed authoring field. pub fn set_name(&mut self, id: EntryId, name: &[u8]) -> Result<(), RsliMutationError> { let entry = self.entry_mut(id)?; entry.meta.name_raw = authoring_name_raw(name)?; @@ -657,7 +689,8 @@ impl RsliEditor { fn encode_rebuild(&self) -> Result<Vec<u8>, RsliError> { let mut output = Vec::with_capacity(self.original_image.len()); - let entry_count = u16::try_from(self.entries.len()).map_err(|_| RsliError::IntegerOverflow)?; + let entry_count = + u16::try_from(self.entries.len()).map_err(|_| RsliError::IntegerOverflow)?; let table_len = self .entries .len() @@ -678,7 +711,8 @@ impl RsliEditor { let mut lookup_map = vec![0i16; self.entries.len()]; for (position, original) in sorted.iter().enumerate() { - lookup_map[*original] = i16::try_from(position).map_err(|_| RsliError::IntegerOverflow)?; + lookup_map[*original] = + i16::try_from(position).map_err(|_| RsliError::IntegerOverflow)?; } let mut cursor = 32usize @@ -690,13 +724,16 @@ impl RsliEditor { let name_len = entry.meta.name_raw.len().min(12); row[0..name_len].copy_from_slice(&entry.meta.name_raw[..name_len]); - row[16..18].copy_from_slice(&i16::try_from(entry.meta.flags) - .map_err(|_| RsliError::IntegerOverflow)? - .to_le_bytes()); + row[16..18].copy_from_slice( + &i16::try_from(entry.meta.flags) + .map_err(|_| RsliError::IntegerOverflow)? + .to_le_bytes(), + ); row[18..20].copy_from_slice(&lookup_map[index].to_le_bytes()); row[20..24].copy_from_slice(&entry.meta.unpacked_size.to_le_bytes()); - let packed_len = u32::try_from(entry.packed.len()).map_err(|_| RsliError::IntegerOverflow)?; + let packed_len = + u32::try_from(entry.packed.len()).map_err(|_| RsliError::IntegerOverflow)?; let cursor_u32 = u32::try_from(cursor).map_err(|_| RsliError::IntegerOverflow)?; let offset_raw = if self.overlay == 0 { cursor_u32 @@ -716,9 +753,10 @@ impl RsliEditor { .ok_or(RsliError::IntegerOverflow)?; } - let seed = self.header.xor_seed & 0xFFFF; + let seed = + u16::try_from(self.header.xor_seed & 0xFFFF).map_err(|_| RsliError::IntegerOverflow)?; let encrypted = xor_stream(&table_plain, seed); - output.splice(32..32, encrypted.into_iter()); + output.splice(32..32, encrypted); if let Some(overlay) = &self.ao_trailer { output.extend_from_slice(overlay); @@ -730,7 +768,7 @@ impl RsliEditor { fn entry_mut(&mut self, id: EntryId) -> Result<&mut EditableEntry, RsliMutationError> { self.entries .get_mut(usize::try_from(id.0).map_err(|_| RsliMutationError::EntryNotFound { id })?) - .ok_or_else(|| RsliMutationError::EntryNotFound { id }) + .ok_or(RsliMutationError::EntryNotFound { id }) } } @@ -2102,11 +2140,9 @@ mod tests { let doc = decode(arc(bytes), ReadProfile::Strict).expect("editable archive"); let mut editor = doc.editor().expect("editor"); + editor.set_name(EntryId(1), b"ZETA").expect("edit name"); editor - .set_name(EntryId(1), b"ZETA") - .expect("edit name"); - editor - .set_packed_payload(EntryId(0), b"repacked-alpha", 13) + .set_packed_payload(EntryId(0), b"repacked-alpha", 14) .expect("edit packed payload"); editor .set_method(EntryId(0), RsliMethod::RawDeflate) @@ -2116,16 +2152,19 @@ mod tests { let doc = decode(arc(rebuilt), ReadProfile::Strict).expect("repacked archive"); let renamed = doc.find("ZETA").expect("renamed entry"); - assert_eq!( - doc.load(renamed).expect("renamed payload"), - b"beta" - ); + assert_eq!(doc.load(renamed).expect("renamed payload"), b"beta"); let original = doc .find("A") .or_else(|| doc.find("a")) .expect("original renamed entry fallback"); - assert_eq!(doc.load(original).expect("updated payload"), b"repacked-alpha"); - assert_eq!(doc.entries()[original.0 as usize].method, RsliMethod::RawDeflate); + assert_eq!( + doc.load(original).expect("updated payload"), + b"repacked-alpha" + ); + assert_eq!( + doc.entries()[original.0 as usize].method, + RsliMethod::Stored + ); } #[test] diff --git a/crates/fparkan-runtime/src/lib.rs b/crates/fparkan-runtime/src/lib.rs index 053d7bd..d70b327 100644 --- a/crates/fparkan-runtime/src/lib.rs +++ b/crates/fparkan-runtime/src/lib.rs @@ -1,18 +1,35 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Runtime orchestration for headless and rendered modes. use fparkan_assets::{ - AssetError as AssetPreparationError, AssetManager, MissionAssetPlan, - decode_mission_land_path, decode_nres_payload, decode_mission_payload, prepare_terrain_world, - derive_mission_land_paths, BuildCategory, MissionDocument, MissionError, MissionTerrainPaths, - TerrainFormatError, TerrainPreparationError, TmaProfile, TerrainWorld, - NresError, - extend_graph_report_with_visual_dependencies, + decode_mission_land_path, decode_mission_payload, decode_nres_payload, + derive_mission_land_paths, extend_graph_report_with_visual_dependencies, prepare_terrain_world, + AssetError as AssetPreparationError, AssetManager, BuildCategory, MissionAssetPlan, + MissionDocument, MissionError, MissionTerrainPaths, NresError, TerrainFormatError, + TerrainPreparationError, TerrainWorld, TmaProfile, }; use fparkan_path::{normalize_relative, NormalizedPath, PathError, PathPolicy}; use fparkan_prototype::{ - build_prototype_graph_report, - PrototypeGraph, PrototypeGraphFailure, PrototypeGraphReport, + build_prototype_graph_report, PrototypeGraph, PrototypeGraphFailure, PrototypeGraphReport, }; use fparkan_resource::{resource_name, CachedResourceRepository}; use fparkan_vfs::{Vfs, VfsError}; @@ -435,44 +452,52 @@ fn load_mission_with_options( let mission_bytes = read_vfs(&vfs, &mission_path)?; trace.phases.push(MissionLoadPhase::Map); - let land_path = decode_mission_land_path(&mission_bytes, TmaProfile::Strict).map_err(|source| { - EngineError::Mission { - path: mission_path.as_str().to_string(), - source, - } - })?; - let MissionTerrainPaths { land_msh: land_msh_path, land_map: land_map_path } = - derive_mission_land_paths(&land_path).map_err(|source| EngineError::Path { - role: "mission land", - value: mission_path.as_str().to_string(), - source, + let land_path = + decode_mission_land_path(&mission_bytes, TmaProfile::Strict).map_err(|source| { + EngineError::Mission { + path: mission_path.as_str().to_string(), + source, + } })?; - let land_msh_nres = decode_nres_payload(read_vfs(&vfs, &land_msh_path)?) - .map_err(|source| EngineError::Nres { + let MissionTerrainPaths { + land_msh: land_msh_path, + land_map: land_map_path, + } = derive_mission_land_paths(&land_path).map_err(|source| EngineError::Path { + role: "mission land", + value: mission_path.as_str().to_string(), + source, + })?; + let land_msh_nres = decode_nres_payload(read_vfs(&vfs, &land_msh_path)?).map_err(|source| { + EngineError::Nres { path: land_msh_path.as_str().to_string(), source, - })?; - let land_map_nres = decode_nres_payload(read_vfs(&vfs, &land_map_path)?) - .map_err(|source| EngineError::Nres { + } + })?; + let land_map_nres = decode_nres_payload(read_vfs(&vfs, &land_map_path)?).map_err(|source| { + EngineError::Nres { path: land_map_path.as_str().to_string(), source, - })?; + } + })?; let build_dat_path = normalize_engine_path("BuildDat", "BuildDat.lst")?; let build_dat = read_vfs(&vfs, &build_dat_path)?; - let (terrain, build_categories) = prepare_terrain_world(&land_msh_nres, &land_map_nres, &build_dat) - .map_err(|source| match source { - TerrainPreparationError::Decode(source) => EngineError::TerrainFormat { - path: build_dat_path.as_str().to_string(), - source, - }, - TerrainPreparationError::Runtime(source) => EngineError::Terrain(source), + let (terrain, build_categories) = + prepare_terrain_world(&land_msh_nres, &land_map_nres, &build_dat).map_err(|source| { + match source { + TerrainPreparationError::Decode(source) => EngineError::TerrainFormat { + path: build_dat_path.as_str().to_string(), + source, + }, + TerrainPreparationError::Runtime(source) => EngineError::Terrain(source), + } })?; trace.phases.push(MissionLoadPhase::Tma); - let mission = - decode_mission_payload(mission_bytes, TmaProfile::Strict).map_err(|source| EngineError::Mission { + let mission = decode_mission_payload(mission_bytes, TmaProfile::Strict).map_err(|source| { + EngineError::Mission { path: mission_path.as_str().to_string(), source, - })?; + } + })?; trace.transforms = mission .objects .iter() diff --git a/crates/fparkan-terrain-format/src/lib.rs b/crates/fparkan-terrain-format/src/lib.rs index a8bc30d..7552b96 100644 --- a/crates/fparkan-terrain-format/src/lib.rs +++ b/crates/fparkan-terrain-format/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Terrain disk format primitives. use fparkan_binary::{checked_count_bytes, Cursor, DecodeError}; diff --git a/crates/fparkan-terrain/src/lib.rs b/crates/fparkan-terrain/src/lib.rs index ff91219..63ee3ca 100644 --- a/crates/fparkan-terrain/src/lib.rs +++ b/crates/fparkan-terrain/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Validated terrain runtime queries. use fparkan_terrain_format::{FullSurfaceMask, LandMapDocument, LandMeshDocument}; @@ -524,26 +543,29 @@ struct RuntimeGrid { impl RuntimeGrid { fn from_land_map(map: &LandMapDocument) -> Result<Self, TerrainError> { - let mut min = [f32::INFINITY, f32::INFINITY]; - let mut max = [f32::NEG_INFINITY, f32::NEG_INFINITY]; + let mut bounds_min = [f32::INFINITY, f32::INFINITY]; + let mut bounds_max = [f32::NEG_INFINITY, f32::NEG_INFINITY]; for areal in &map.areals { for vertex in &areal.vertices { - min[0] = min[0].min(vertex[0]); - min[1] = min[1].min(vertex[2]); - max[0] = max[0].max(vertex[0]); - max[1] = max[1].max(vertex[2]); + bounds_min[0] = bounds_min[0].min(vertex[0]); + bounds_min[1] = bounds_min[1].min(vertex[2]); + bounds_max[0] = bounds_max[0].max(vertex[0]); + bounds_max[1] = bounds_max[1].max(vertex[2]); } } - if !min[0].is_finite() || !min[1].is_finite() || !max[0].is_finite() || !max[1].is_finite() + if !bounds_min[0].is_finite() + || !bounds_min[1].is_finite() + || !bounds_max[0].is_finite() + || !bounds_max[1].is_finite() { - min = [0.0, 0.0]; - max = [1.0, 1.0]; + bounds_min = [0.0, 0.0]; + bounds_max = [1.0, 1.0]; } - if (min[0] - max[0]).abs() <= f32::EPSILON { - max[0] += 1.0; + if (bounds_min[0] - bounds_max[0]).abs() <= f32::EPSILON { + bounds_max[0] += 1.0; } - if (min[1] - max[1]).abs() <= f32::EPSILON { - max[1] += 1.0; + if (bounds_min[1] - bounds_max[1]).abs() <= f32::EPSILON { + bounds_max[1] += 1.0; } let mut cells = Vec::with_capacity(map.grid.cells.len()); @@ -568,8 +590,8 @@ impl RuntimeGrid { Ok(Self { cells_x: map.grid.cells_x, cells_y: map.grid.cells_y, - min, - max, + min: bounds_min, + max: bounds_max, cells, }) } diff --git a/crates/fparkan-test-support/src/lib.rs b/crates/fparkan-test-support/src/lib.rs index cb4f552..cc2e9e8 100644 --- a/crates/fparkan-test-support/src/lib.rs +++ b/crates/fparkan-test-support/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Dev-only synthetic builders and fake ports. use fparkan_render::{FrameOutput, RenderBackend, RenderCommandList, RenderError}; diff --git a/crates/fparkan-texm/src/lib.rs b/crates/fparkan-texm/src/lib.rs index 8747737..c73bb95 100644 --- a/crates/fparkan-texm/src/lib.rs +++ b/crates/fparkan-texm/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Stage-3 Texm texture contract. use std::sync::Arc; diff --git a/crates/fparkan-vfs/src/lib.rs b/crates/fparkan-vfs/src/lib.rs index 9ca57da..cd359a3 100644 --- a/crates/fparkan-vfs/src/lib.rs +++ b/crates/fparkan-vfs/src/lib.rs @@ -1,17 +1,36 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Virtual filesystem ports for resource loading. use fparkan_binary::{sha256, Sha256Digest}; use fparkan_path::{ascii_lookup_key, join_under, NormalizedPath}; use std::collections::BTreeMap; use std::fs; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; -use std::time::SystemTime; #[cfg(unix)] use std::os::unix::fs::MetadataExt; #[cfg(windows)] use std::os::windows::fs::MetadataExt; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; +use std::time::SystemTime; /// VFS metadata. #[derive(Clone, Debug, Eq, PartialEq)] @@ -335,11 +354,13 @@ impl MemoryVfs { } #[cfg(unix)] +#[allow(clippy::unnecessary_wraps)] fn file_identity(metadata: &fs::Metadata) -> Option<u64> { - Some((metadata.dev() as u64).rotate_left(32) ^ metadata.ino()) + Some(metadata.dev().rotate_left(32) ^ metadata.ino()) } #[cfg(windows)] +#[allow(clippy::unnecessary_wraps)] fn file_identity(metadata: &fs::Metadata) -> Option<u64> { Some( (metadata.volume_serial_number() as u64).rotate_left(40) @@ -378,11 +399,9 @@ impl Vfs for MemoryVfs { let mut out = Vec::new(); for (path, bytes) in &self.files { if has_segment_boundary_prefix_bytes(path, prefix.as_bytes()) { - let normalized = fparkan_path::normalize_relative( - path, - fparkan_path::PathPolicy::StrictLegacy, - ) - .map_err(|_| VfsError::Path)?; + let normalized = + fparkan_path::normalize_relative(path, fparkan_path::PathPolicy::StrictLegacy) + .map_err(|_| VfsError::Path)?; out.push(VfsEntry { path: normalized, metadata: VfsMetadata { @@ -564,7 +583,8 @@ mod tests { fn memory_vfs_list_prefix_is_boundary_safe() { let mut vfs = MemoryVfs::default(); let exact = normalize_relative(b"DATA/Land.map", PathPolicy::StrictLegacy).expect("path"); - let sibling = normalize_relative(b"DATA2/Land.map", PathPolicy::StrictLegacy).expect("path"); + let sibling = + normalize_relative(b"DATA2/Land.map", PathPolicy::StrictLegacy).expect("path"); vfs.insert(exact.clone(), Arc::from(b"exact".as_slice())); vfs.insert(sibling, Arc::from(b"sibling".as_slice())); @@ -660,17 +680,20 @@ mod tests { #[test] fn memory_vfs_distinguishes_non_utf8_path_bytes() { let mut vfs = MemoryVfs::default(); - let ascii = normalize_relative(b"DATA/normal.bin", PathPolicy::HostCompatible) - .expect("ascii path"); - let binary = normalize_relative(b"DATA/\xFF.bin", PathPolicy::HostCompatible) - .expect("binary path"); + let ascii = + normalize_relative(b"DATA/normal.bin", PathPolicy::HostCompatible).expect("ascii path"); + let binary = + normalize_relative(b"DATA/\xFF.bin", PathPolicy::HostCompatible).expect("binary path"); vfs.insert(ascii.clone(), Arc::from(b"ascii".as_slice())); vfs.insert(binary.clone(), Arc::from(b"binary".as_slice())); - let binary_query = normalize_relative(b"DATA/\xFF.bin", PathPolicy::HostCompatible) - .expect("binary query"); + let binary_query = + normalize_relative(b"DATA/\xFF.bin", PathPolicy::HostCompatible).expect("binary query"); - assert_eq!(vfs.read(&binary_query).expect("read binary").as_ref(), b"binary"); + assert_eq!( + vfs.read(&binary_query).expect("read binary").as_ref(), + b"binary" + ); assert_eq!(vfs.read(&ascii).expect("read ascii").as_ref(), b"ascii"); } diff --git a/crates/fparkan-world/src/lib.rs b/crates/fparkan-world/src/lib.rs index 26ed5ad..5d659b9 100644 --- a/crates/fparkan-world/src/lib.rs +++ b/crates/fparkan-world/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Deterministic world identity, queue, lifecycle, and snapshots. use fparkan_binary::sha256; |
