aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-06-23 21:32:50 +0300
committerValentin Popov <valentin@popov.link>2026-06-23 21:32:50 +0300
commit9cc24e715db81edbe21c0d04aadd00f11dddecb8 (patch)
tree08a1262dea86bcd7ec58c6494cedd001c45a78fe /crates
parentf8e447ffee746cfe6580cc0e78a8a225aa39b546 (diff)
downloadfparkan-9cc24e715db81edbe21c0d04aadd00f11dddecb8.tar.xz
fparkan-9cc24e715db81edbe21c0d04aadd00f11dddecb8.zip
fix: close stage 0-2 synthetic gates
Diffstat (limited to 'crates')
-rw-r--r--crates/fparkan-animation/src/lib.rs19
-rw-r--r--crates/fparkan-assets/src/lib.rs309
-rw-r--r--crates/fparkan-binary/src/lib.rs19
-rw-r--r--crates/fparkan-corpus/src/lib.rs112
-rw-r--r--crates/fparkan-diagnostics/src/lib.rs26
-rw-r--r--crates/fparkan-fx/src/lib.rs19
-rw-r--r--crates/fparkan-inspection/src/lib.rs75
-rw-r--r--crates/fparkan-material/src/lib.rs19
-rw-r--r--crates/fparkan-mission-format/src/lib.rs19
-rw-r--r--crates/fparkan-msh/src/lib.rs21
-rw-r--r--crates/fparkan-nres/src/lib.rs19
-rw-r--r--crates/fparkan-path/src/lib.rs22
-rw-r--r--crates/fparkan-platform/src/lib.rs52
-rw-r--r--crates/fparkan-prototype/src/lib.rs131
-rw-r--r--crates/fparkan-render/src/lib.rs19
-rw-r--r--crates/fparkan-resource/src/lib.rs19
-rw-r--r--crates/fparkan-rsli/src/lib.rs99
-rw-r--r--crates/fparkan-runtime/src/lib.rs95
-rw-r--r--crates/fparkan-terrain-format/src/lib.rs19
-rw-r--r--crates/fparkan-terrain/src/lib.rs52
-rw-r--r--crates/fparkan-test-support/src/lib.rs19
-rw-r--r--crates/fparkan-texm/src/lib.rs19
-rw-r--r--crates/fparkan-vfs/src/lib.rs57
-rw-r--r--crates/fparkan-world/src/lib.rs19
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;