refactor: harden stage 2 graph and asset limits
This commit is contained in:
@@ -412,11 +412,34 @@ pub struct MissionAssetPlan {
|
|||||||
pub lightmap_count: usize,
|
pub lightmap_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Coarse CPU-side asset budgets.
|
/// Bounded CPU-side asset preparation limits enforced before renderer upload.
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct AssetBudgets {
|
pub struct AssetPreparationLimits {
|
||||||
/// Bytes parsed from source resource payloads.
|
/// Maximum number of unique prepared models.
|
||||||
pub parsed_bytes: u64,
|
pub max_models: Option<usize>,
|
||||||
|
/// Maximum number of unique prepared WEAR tables.
|
||||||
|
pub max_wears: Option<usize>,
|
||||||
|
/// Maximum number of unique prepared materials.
|
||||||
|
pub max_materials: Option<usize>,
|
||||||
|
/// Maximum number of unique prepared textures, including lightmaps.
|
||||||
|
pub max_textures: Option<usize>,
|
||||||
|
/// Maximum sum of unique texture base-level pixels.
|
||||||
|
pub max_texture_pixels: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Summary emitted by bounded asset preparation.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct AssetPreparationReport {
|
||||||
|
/// Number of unique prepared models.
|
||||||
|
pub model_count: usize,
|
||||||
|
/// Number of unique prepared WEAR tables.
|
||||||
|
pub wear_count: usize,
|
||||||
|
/// Number of unique prepared materials.
|
||||||
|
pub material_count: usize,
|
||||||
|
/// Number of unique prepared textures, including lightmaps.
|
||||||
|
pub texture_count: usize,
|
||||||
|
/// Sum of unique texture base-level pixels.
|
||||||
|
pub texture_pixels: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors raised while preparing CPU-side assets.
|
/// Errors raised while preparing CPU-side assets.
|
||||||
@@ -426,6 +449,8 @@ pub enum AssetError {
|
|||||||
MissingDependency(String),
|
MissingDependency(String),
|
||||||
/// A prototype did not describe a usable visual.
|
/// A prototype did not describe a usable visual.
|
||||||
InvalidPrototype(String),
|
InvalidPrototype(String),
|
||||||
|
/// Asset preparation exceeded explicit limits.
|
||||||
|
BudgetExceeded(String),
|
||||||
/// A repository operation failed.
|
/// A repository operation failed.
|
||||||
Resource {
|
Resource {
|
||||||
/// Human context for the operation.
|
/// Human context for the operation.
|
||||||
@@ -448,6 +473,7 @@ impl fmt::Display for AssetError {
|
|||||||
match self {
|
match self {
|
||||||
Self::MissingDependency(value) => write!(f, "missing dependency: {value}"),
|
Self::MissingDependency(value) => write!(f, "missing dependency: {value}"),
|
||||||
Self::InvalidPrototype(value) => write!(f, "invalid prototype: {value}"),
|
Self::InvalidPrototype(value) => write!(f, "invalid prototype: {value}"),
|
||||||
|
Self::BudgetExceeded(value) => write!(f, "asset budget exceeded: {value}"),
|
||||||
Self::Resource { context, source } => {
|
Self::Resource { context, source } => {
|
||||||
if context.is_empty() {
|
if context.is_empty() {
|
||||||
write!(f, "resource error: {source}")
|
write!(f, "resource error: {source}")
|
||||||
@@ -471,7 +497,9 @@ impl std::error::Error for AssetError {
|
|||||||
Self::Material(source) => Some(source),
|
Self::Material(source) => Some(source),
|
||||||
Self::Texture(source) => Some(source),
|
Self::Texture(source) => Some(source),
|
||||||
Self::Nres(source) => Some(source),
|
Self::Nres(source) => Some(source),
|
||||||
Self::MissingDependency(_) | Self::InvalidPrototype(_) => None,
|
Self::MissingDependency(_) | Self::InvalidPrototype(_) | Self::BudgetExceeded(_) => {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,6 +558,26 @@ impl<R: ResourceRepository> AssetManager<R> {
|
|||||||
prepare_mission_assets_with_repository(&self.repository, root_prototype_spans, prototypes)
|
prepare_mission_assets_with_repository(&self.repository, root_prototype_spans, prototypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds mission assets together with a bounded preparation report.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`AssetError`] if any visual dependency is missing or malformed,
|
||||||
|
/// or when explicit limits are exceeded.
|
||||||
|
pub fn prepare_mission_assets_profiled(
|
||||||
|
&self,
|
||||||
|
root_prototype_spans: &[std::ops::Range<usize>],
|
||||||
|
prototypes: &[EffectivePrototype],
|
||||||
|
limits: AssetPreparationLimits,
|
||||||
|
) -> Result<(MissionAssets, AssetPreparationReport), AssetError> {
|
||||||
|
prepare_mission_assets_profiled_with_repository(
|
||||||
|
&self.repository,
|
||||||
|
root_prototype_spans,
|
||||||
|
prototypes,
|
||||||
|
limits,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds a mission plan by preparing each resolved prototype.
|
/// Builds a mission plan by preparing each resolved prototype.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
@@ -586,11 +634,35 @@ pub fn prepare_mission_assets_with_repository<R: ResourceRepository>(
|
|||||||
root_prototype_spans: &[std::ops::Range<usize>],
|
root_prototype_spans: &[std::ops::Range<usize>],
|
||||||
prototypes: &[EffectivePrototype],
|
prototypes: &[EffectivePrototype],
|
||||||
) -> Result<MissionAssets, AssetError> {
|
) -> Result<MissionAssets, AssetError> {
|
||||||
|
Ok(
|
||||||
|
prepare_mission_assets_profiled_with_repository(
|
||||||
|
repository,
|
||||||
|
root_prototype_spans,
|
||||||
|
prototypes,
|
||||||
|
AssetPreparationLimits::default(),
|
||||||
|
)?
|
||||||
|
.0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds mission assets while enforcing explicit preparation limits.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`AssetError`] if any visual dependency is missing or malformed, or
|
||||||
|
/// when explicit limits are exceeded.
|
||||||
|
pub fn prepare_mission_assets_profiled_with_repository<R: ResourceRepository>(
|
||||||
|
repository: &R,
|
||||||
|
root_prototype_spans: &[std::ops::Range<usize>],
|
||||||
|
prototypes: &[EffectivePrototype],
|
||||||
|
limits: AssetPreparationLimits,
|
||||||
|
) -> Result<(MissionAssets, AssetPreparationReport), AssetError> {
|
||||||
prepare_mission_assets_with_repository_internal(
|
prepare_mission_assets_with_repository_internal(
|
||||||
repository,
|
repository,
|
||||||
root_prototype_spans,
|
root_prototype_spans,
|
||||||
prototypes,
|
prototypes,
|
||||||
AssetIdentityPolicy::default(),
|
AssetIdentityPolicy::default(),
|
||||||
|
limits,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,9 +671,10 @@ fn prepare_mission_assets_with_repository_internal<R: ResourceRepository>(
|
|||||||
root_prototype_spans: &[std::ops::Range<usize>],
|
root_prototype_spans: &[std::ops::Range<usize>],
|
||||||
prototypes: &[EffectivePrototype],
|
prototypes: &[EffectivePrototype],
|
||||||
identity_policy: AssetIdentityPolicy,
|
identity_policy: AssetIdentityPolicy,
|
||||||
) -> Result<MissionAssets, AssetError> {
|
limits: AssetPreparationLimits,
|
||||||
|
) -> Result<(MissionAssets, AssetPreparationReport), AssetError> {
|
||||||
if prototypes.is_empty() {
|
if prototypes.is_empty() {
|
||||||
return Ok(MissionAssets::default());
|
return Ok((MissionAssets::default(), AssetPreparationReport::default()));
|
||||||
}
|
}
|
||||||
let mut visual_index_by_id: HashMap<AssetId<PreparedVisual>, PreparedVisualSignature> =
|
let mut visual_index_by_id: HashMap<AssetId<PreparedVisual>, PreparedVisualSignature> =
|
||||||
HashMap::new();
|
HashMap::new();
|
||||||
@@ -704,14 +777,27 @@ fn prepare_mission_assets_with_repository_internal<R: ResourceRepository>(
|
|||||||
object_visuals.push(ids);
|
object_visuals.push(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(MissionAssets {
|
let assets = MissionAssets {
|
||||||
models,
|
models,
|
||||||
wears,
|
wears,
|
||||||
materials,
|
materials,
|
||||||
textures,
|
textures,
|
||||||
visuals,
|
visuals,
|
||||||
object_visuals,
|
object_visuals,
|
||||||
})
|
};
|
||||||
|
let report = AssetPreparationReport {
|
||||||
|
model_count: assets.models.len(),
|
||||||
|
wear_count: assets.wears.len(),
|
||||||
|
material_count: assets.materials.len(),
|
||||||
|
texture_count: assets.textures.len(),
|
||||||
|
texture_pixels: assets
|
||||||
|
.textures
|
||||||
|
.iter()
|
||||||
|
.map(|texture| u64::from(texture.texm.width()) * u64::from(texture.texm.height()))
|
||||||
|
.sum(),
|
||||||
|
};
|
||||||
|
enforce_asset_preparation_limits(&report, limits)?;
|
||||||
|
Ok((assets, report))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
@@ -876,6 +962,53 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enforce_asset_preparation_limits(
|
||||||
|
report: &AssetPreparationReport,
|
||||||
|
limits: AssetPreparationLimits,
|
||||||
|
) -> Result<(), AssetError> {
|
||||||
|
if let Some(limit) = limits.max_models {
|
||||||
|
if report.model_count > limit {
|
||||||
|
return Err(AssetError::BudgetExceeded(format!(
|
||||||
|
"models={} exceeds limit={limit}",
|
||||||
|
report.model_count
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(limit) = limits.max_wears {
|
||||||
|
if report.wear_count > limit {
|
||||||
|
return Err(AssetError::BudgetExceeded(format!(
|
||||||
|
"wears={} exceeds limit={limit}",
|
||||||
|
report.wear_count
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(limit) = limits.max_materials {
|
||||||
|
if report.material_count > limit {
|
||||||
|
return Err(AssetError::BudgetExceeded(format!(
|
||||||
|
"materials={} exceeds limit={limit}",
|
||||||
|
report.material_count
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(limit) = limits.max_textures {
|
||||||
|
if report.texture_count > limit {
|
||||||
|
return Err(AssetError::BudgetExceeded(format!(
|
||||||
|
"textures={} exceeds limit={limit}",
|
||||||
|
report.texture_count
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(limit) = limits.max_texture_pixels {
|
||||||
|
if report.texture_pixels > limit {
|
||||||
|
return Err(AssetError::BudgetExceeded(format!(
|
||||||
|
"texture_pixels={} exceeds limit={limit}",
|
||||||
|
report.texture_pixels
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Extends a prototype dependency report with visual dependency failures.
|
/// Extends a prototype dependency report with visual dependency failures.
|
||||||
///
|
///
|
||||||
/// This function validates WEAR/material/TEXM/LIGHTMAP resolution for each resolved
|
/// This function validates WEAR/material/TEXM/LIGHTMAP resolution for each resolved
|
||||||
@@ -886,6 +1019,9 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>(
|
|||||||
graph: &mut PrototypeGraph,
|
graph: &mut PrototypeGraph,
|
||||||
prototypes: &[EffectivePrototype],
|
prototypes: &[EffectivePrototype],
|
||||||
) {
|
) {
|
||||||
|
if graph.visual_dependencies_expanded {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let material_archive = parse_path("material.lib")
|
let material_archive = parse_path("material.lib")
|
||||||
.expect("static material archive path must satisfy host-compatible normalization");
|
.expect("static material archive path must satisfy host-compatible normalization");
|
||||||
let mut next_node = graph
|
let mut next_node = graph
|
||||||
@@ -1103,6 +1239,7 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>(
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
graph.visual_dependencies_expanded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_graph_resource_node(
|
fn push_graph_resource_node(
|
||||||
@@ -2106,6 +2243,90 @@ mod tests {
|
|||||||
.all(|provenance| provenance.root_index == 0));
|
.all(|provenance| provenance.root_index == 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graph_visual_dependency_expansion_is_idempotent() {
|
||||||
|
let mesh_key = ResourceKey {
|
||||||
|
archive: parse_path("static.rlb").expect("archive"),
|
||||||
|
name: resource_name(b"tree.msh"),
|
||||||
|
type_id: Some(0x4853_454D),
|
||||||
|
};
|
||||||
|
let mat0 = mat0_with_texture(b"TEX_A");
|
||||||
|
let texm = texm_payload();
|
||||||
|
let lightmap_texm = texm_payload();
|
||||||
|
let repo = repository_with_archives_meta(&[
|
||||||
|
(
|
||||||
|
"static.rlb",
|
||||||
|
&[TestNresEntry {
|
||||||
|
name: b"tree.wea",
|
||||||
|
payload: b"1\n0 MAT_A\n\nLIGHTMAPS\n1\n0 LM_A\n",
|
||||||
|
type_id: WEAR_KIND,
|
||||||
|
attr2: 0,
|
||||||
|
}],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"material.lib",
|
||||||
|
&[TestNresEntry {
|
||||||
|
name: b"MAT_A",
|
||||||
|
payload: &mat0,
|
||||||
|
type_id: MAT0_KIND,
|
||||||
|
attr2: 0,
|
||||||
|
}],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
TEXTURES_ARCHIVE,
|
||||||
|
&[TestNresEntry {
|
||||||
|
name: b"TEX_A",
|
||||||
|
payload: &texm,
|
||||||
|
type_id: 0,
|
||||||
|
attr2: 0,
|
||||||
|
}],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
LIGHTMAP_ARCHIVE,
|
||||||
|
&[TestNresEntry {
|
||||||
|
name: b"LM_A",
|
||||||
|
payload: &lightmap_texm,
|
||||||
|
type_id: 0,
|
||||||
|
attr2: 0,
|
||||||
|
}],
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
let prototype = EffectivePrototype {
|
||||||
|
key: fparkan_prototype::PrototypeKey(resource_name(b"tree")),
|
||||||
|
geometry: PrototypeGeometry::Mesh(mesh_key),
|
||||||
|
source: fparkan_prototype::PrototypeSource::DirectArchive,
|
||||||
|
dependencies: Vec::new(),
|
||||||
|
};
|
||||||
|
let mut graph = prototype_graph_for_mesh(&prototype);
|
||||||
|
let mut report = PrototypeGraphReport {
|
||||||
|
root_count: 1,
|
||||||
|
direct_reference_count: 1,
|
||||||
|
resolved_count: 1,
|
||||||
|
mesh_dependency_count: 1,
|
||||||
|
..PrototypeGraphReport::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
extend_graph_report_with_visual_dependencies(
|
||||||
|
&repo,
|
||||||
|
&mut report,
|
||||||
|
&mut graph,
|
||||||
|
std::slice::from_ref(&prototype),
|
||||||
|
);
|
||||||
|
let first = (graph.nodes.clone(), graph.edges.clone(), report.clone());
|
||||||
|
|
||||||
|
extend_graph_report_with_visual_dependencies(
|
||||||
|
&repo,
|
||||||
|
&mut report,
|
||||||
|
&mut graph,
|
||||||
|
std::slice::from_ref(&prototype),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(graph.visual_dependencies_expanded);
|
||||||
|
assert_eq!(graph.nodes, first.0);
|
||||||
|
assert_eq!(graph.edges, first.1);
|
||||||
|
assert_eq!(report, first.2);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn prepare_single_visual_mission_assets_materialize_model_wear_material_and_texture_payloads() {
|
fn prepare_single_visual_mission_assets_materialize_model_wear_material_and_texture_payloads() {
|
||||||
let mesh_key = ResourceKey {
|
let mesh_key = ResourceKey {
|
||||||
@@ -2236,6 +2457,44 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn profiled_asset_preparation_reports_unique_asset_counts() {
|
||||||
|
let (repo, prototypes) = collision_fixture();
|
||||||
|
|
||||||
|
let (assets, report) = prepare_mission_assets_profiled_with_repository(
|
||||||
|
&repo,
|
||||||
|
&[0..1, 1..2],
|
||||||
|
&prototypes,
|
||||||
|
AssetPreparationLimits::default(),
|
||||||
|
)
|
||||||
|
.expect("profiled assets");
|
||||||
|
|
||||||
|
assert_eq!(report.model_count, assets.models.len());
|
||||||
|
assert_eq!(report.wear_count, assets.wears.len());
|
||||||
|
assert_eq!(report.material_count, assets.materials.len());
|
||||||
|
assert_eq!(report.texture_count, assets.textures.len());
|
||||||
|
assert!(report.texture_pixels > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn asset_preparation_limits_reject_texture_pixel_budget() {
|
||||||
|
let (repo, prototypes) = collision_fixture();
|
||||||
|
|
||||||
|
let err = prepare_mission_assets_profiled_with_repository(
|
||||||
|
&repo,
|
||||||
|
&[0..1, 1..2],
|
||||||
|
&prototypes,
|
||||||
|
AssetPreparationLimits {
|
||||||
|
max_texture_pixels: Some(1),
|
||||||
|
..AssetPreparationLimits::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect_err("budget should fail");
|
||||||
|
|
||||||
|
assert!(matches!(err, AssetError::BudgetExceeded(_)));
|
||||||
|
assert!(err.to_string().contains("texture_pixels"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn graph_report_uses_strict_texture_archive_policy() {
|
fn graph_report_uses_strict_texture_archive_policy() {
|
||||||
let mesh_key = ResourceKey {
|
let mesh_key = ResourceKey {
|
||||||
@@ -2362,6 +2621,7 @@ mod tests {
|
|||||||
&[0..1, 1..2],
|
&[0..1, 1..2],
|
||||||
&prototypes,
|
&prototypes,
|
||||||
policy,
|
policy,
|
||||||
|
AssetPreparationLimits::default(),
|
||||||
)
|
)
|
||||||
.expect_err("collision should fail");
|
.expect_err("collision should fail");
|
||||||
|
|
||||||
@@ -2652,6 +2912,7 @@ mod tests {
|
|||||||
roots: vec![prototype.key.clone()],
|
roots: vec![prototype.key.clone()],
|
||||||
prototype_requests: vec![prototype.key.clone()],
|
prototype_requests: vec![prototype.key.clone()],
|
||||||
root_prototype_request_spans: std::iter::once(0..1).collect(),
|
root_prototype_request_spans: std::iter::once(0..1).collect(),
|
||||||
|
visual_dependencies_expanded: false,
|
||||||
nodes: vec![root_node, prototype_node, mesh_node],
|
nodes: vec![root_node, prototype_node, mesh_node],
|
||||||
edges: vec![
|
edges: vec![
|
||||||
fparkan_prototype::PrototypeGraphEdgeInstance {
|
fparkan_prototype::PrototypeGraphEdgeInstance {
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ pub struct PrototypeGraph {
|
|||||||
pub prototype_requests: Vec<PrototypeKey>,
|
pub prototype_requests: Vec<PrototypeKey>,
|
||||||
/// Mission object-local spans of effective prototype requests.
|
/// Mission object-local spans of effective prototype requests.
|
||||||
pub root_prototype_request_spans: Vec<std::ops::Range<usize>>,
|
pub root_prototype_request_spans: Vec<std::ops::Range<usize>>,
|
||||||
|
/// Whether visual dependency expansion has already been applied.
|
||||||
|
pub visual_dependencies_expanded: bool,
|
||||||
/// Materialized prototype dependency graph nodes.
|
/// Materialized prototype dependency graph nodes.
|
||||||
pub nodes: Vec<PrototypeGraphNode>,
|
pub nodes: Vec<PrototypeGraphNode>,
|
||||||
/// Materialized prototype dependency graph edges.
|
/// Materialized prototype dependency graph edges.
|
||||||
@@ -187,8 +189,6 @@ pub enum PrototypeGraphNodeKind {
|
|||||||
TextureResource,
|
TextureResource,
|
||||||
/// TEXM lightmap dependency.
|
/// TEXM lightmap dependency.
|
||||||
LightmapResource,
|
LightmapResource,
|
||||||
/// Effect dependency placeholder for later stages.
|
|
||||||
EffectResource,
|
|
||||||
/// Non-geometric prototype.
|
/// Non-geometric prototype.
|
||||||
NonGeometric,
|
NonGeometric,
|
||||||
}
|
}
|
||||||
@@ -273,8 +273,6 @@ pub enum PrototypeGraphEdgeKind {
|
|||||||
MaterialToTexture,
|
MaterialToTexture,
|
||||||
/// WEAR table to TEXM lightmap.
|
/// WEAR table to TEXM lightmap.
|
||||||
WearToLightmap,
|
WearToLightmap,
|
||||||
/// MAT0 material to effect dependency.
|
|
||||||
MaterialToEffect,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prototype graph edge record.
|
/// Prototype graph edge record.
|
||||||
|
|||||||
@@ -209,12 +209,14 @@ S2-GRAPH-005 covered cargo test -p fparkan-cli --offline prototype_graph_json_ha
|
|||||||
S2-GRAPH-006 covered cargo test -p fparkan-prototype --offline graph_report_records_resolved_roots_and_failures
|
S2-GRAPH-006 covered cargo test -p fparkan-prototype --offline graph_report_records_resolved_roots_and_failures
|
||||||
S2-GRAPH-EDGE-001 covered cargo test -p fparkan-assets --offline graph_materializes_visual_dependency_nodes_and_edges
|
S2-GRAPH-EDGE-001 covered cargo test -p fparkan-assets --offline graph_materializes_visual_dependency_nodes_and_edges
|
||||||
S2-GRAPH-PROV-001 covered cargo test -p fparkan-assets --offline graph_visual_dependency_edges_preserve_root_and_parent_provenance
|
S2-GRAPH-PROV-001 covered cargo test -p fparkan-assets --offline graph_visual_dependency_edges_preserve_root_and_parent_provenance
|
||||||
|
S2-GRAPH-IDEMP-001 covered cargo test -p fparkan-assets --offline graph_visual_dependency_expansion_is_idempotent
|
||||||
S2-UNIT-EMPTY-001 covered cargo test -p fparkan-prototype --offline empty_unit_dat_resolves_as_zero_component_root
|
S2-UNIT-EMPTY-001 covered cargo test -p fparkan-prototype --offline empty_unit_dat_resolves_as_zero_component_root
|
||||||
S2-ASSET-MODEL-001 covered cargo test -p fparkan-assets --offline prepare_single_visual_mission_assets_materialize_model_wear_material_and_texture_payloads
|
S2-ASSET-MODEL-001 covered cargo test -p fparkan-assets --offline prepare_single_visual_mission_assets_materialize_model_wear_material_and_texture_payloads
|
||||||
S2-ASSET-WEAR-001 covered cargo test -p fparkan-assets --offline prepare_single_visual_mission_assets_materialize_model_wear_material_and_texture_payloads
|
S2-ASSET-WEAR-001 covered cargo test -p fparkan-assets --offline prepare_single_visual_mission_assets_materialize_model_wear_material_and_texture_payloads
|
||||||
S2-ASSET-MATERIAL-001 covered cargo test -p fparkan-assets --offline prepare_single_visual_mission_assets_materialize_model_wear_material_and_texture_payloads
|
S2-ASSET-MATERIAL-001 covered cargo test -p fparkan-assets --offline prepare_single_visual_mission_assets_materialize_model_wear_material_and_texture_payloads
|
||||||
S2-ASSET-TEXM-001 covered cargo test -p fparkan-assets --offline prepare_single_visual_mission_assets_materialize_model_wear_material_and_texture_payloads
|
S2-ASSET-TEXM-001 covered cargo test -p fparkan-assets --offline prepare_single_visual_mission_assets_materialize_model_wear_material_and_texture_payloads
|
||||||
S2-ASSET-ID-001 covered cargo test -p fparkan-assets --offline forced_model_id_collision_is_rejected forced_wear_id_collision_is_rejected forced_material_id_collision_is_rejected forced_texture_id_collision_is_rejected
|
S2-ASSET-ID-001 covered cargo test -p fparkan-assets --offline forced_model_id_collision_is_rejected forced_wear_id_collision_is_rejected forced_material_id_collision_is_rejected forced_texture_id_collision_is_rejected
|
||||||
|
S2-ASSET-LIMIT-001 covered cargo test -p fparkan-assets --offline profiled_asset_preparation_reports_unique_asset_counts asset_preparation_limits_reject_texture_pixel_budget
|
||||||
S2-RUNTIME-DRAFT-001 covered local testdata/IS and testdata/IS2; cargo test -p fparkan-runtime --offline -- --ignored selected_is_and_is2_missions_preserve_runtime_object_drafts
|
S2-RUNTIME-DRAFT-001 covered local testdata/IS and testdata/IS2; cargo test -p fparkan-runtime --offline -- --ignored selected_is_and_is2_missions_preserve_runtime_object_drafts
|
||||||
S2-CLI-JSON-001 covered cargo test -p fparkan-cli --offline prototype_graph_json_has_canonical_field_order mission_graph_json_has_canonical_field_order
|
S2-CLI-JSON-001 covered cargo test -p fparkan-cli --offline prototype_graph_json_has_canonical_field_order mission_graph_json_has_canonical_field_order
|
||||||
S2-LICENSED-MISSION-P1-001 covered local testdata/IS; cargo test -p fparkan-runtime --offline -- --ignored selected_is_and_is2_missions_preserve_runtime_object_drafts
|
S2-LICENSED-MISSION-P1-001 covered local testdata/IS; cargo test -p fparkan-runtime --offline -- --ignored selected_is_and_is2_missions_preserve_runtime_object_drafts
|
||||||
|
|||||||
|
@@ -194,12 +194,14 @@
|
|||||||
`S2-GRAPH-006`
|
`S2-GRAPH-006`
|
||||||
`S2-GRAPH-EDGE-001`
|
`S2-GRAPH-EDGE-001`
|
||||||
`S2-GRAPH-PROV-001`
|
`S2-GRAPH-PROV-001`
|
||||||
|
`S2-GRAPH-IDEMP-001`
|
||||||
`S2-UNIT-EMPTY-001`
|
`S2-UNIT-EMPTY-001`
|
||||||
`S2-ASSET-MODEL-001`
|
`S2-ASSET-MODEL-001`
|
||||||
`S2-ASSET-WEAR-001`
|
`S2-ASSET-WEAR-001`
|
||||||
`S2-ASSET-MATERIAL-001`
|
`S2-ASSET-MATERIAL-001`
|
||||||
`S2-ASSET-TEXM-001`
|
`S2-ASSET-TEXM-001`
|
||||||
`S2-ASSET-ID-001`
|
`S2-ASSET-ID-001`
|
||||||
|
`S2-ASSET-LIMIT-001`
|
||||||
`S2-RUNTIME-DRAFT-001`
|
`S2-RUNTIME-DRAFT-001`
|
||||||
`S2-CLI-JSON-001`
|
`S2-CLI-JSON-001`
|
||||||
`S2-LICENSED-MISSION-P1-001`
|
`S2-LICENSED-MISSION-P1-001`
|
||||||
|
|||||||
Reference in New Issue
Block a user