ass_core/analysis/events/scoring.rs
1//! Complexity scoring algorithms for ASS events
2//!
3//! Provides efficient calculation of animation complexity and rendering performance
4//! impact scores for dialogue events. Scores are computed based on override tag
5//! analysis, text content, and performance heuristics.
6//!
7//! # Scoring System
8//!
9//! - Animation Score: 0-10 scale based on tag complexity
10//! - Complexity Score: 0-100 scale combining multiple factors
11//! - Performance Impact: Categorical assessment for rendering optimization
12//!
13//! # Performance
14//!
15//! - Target: <0.1ms per scoring operation
16//! - Memory: Zero allocations, operates on borrowed data
17//! - Scalability: Linear complexity O(n) where n = tag count
18
19use crate::analysis::events::tags::OverrideTag;
20
21/// Performance impact category for rendering complexity
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
23pub enum PerformanceImpact {
24 /// Minimal impact - simple static text
25 Minimal,
26 /// Low impact - basic formatting
27 Low,
28 /// Medium impact - animations or complex styling
29 Medium,
30 /// High impact - many animations or large text
31 High,
32 /// Critical impact - may cause performance issues
33 Critical,
34}
35
36/// Calculate animation complexity score from override tags
37///
38/// Analyzes ASS override tags to determine animation complexity on a 0-10 scale.
39/// Higher scores indicate more computationally expensive rendering operations.
40///
41/// # Scoring Rules
42///
43/// - Basic formatting (b, i, u, s, colors): 1 point each
44/// - Positioning (pos, an, org): 2 points each
45/// - Transforms (frx, fry, frz, fscx, fscy, etc.): 3 points each
46/// - Movement (move): 4 points
47/// - Transitions (t): 5 points
48/// - Drawing (p): 8 points
49///
50/// # Arguments
51///
52/// * `tags` - Slice of parsed override tags
53///
54/// # Returns
55///
56/// Animation complexity score capped at 10
57///
58/// # Example
59///
60/// ```rust
61/// # use ass_core::analysis::events::scoring::calculate_animation_score;
62/// # use ass_core::analysis::events::tags::OverrideTag;
63/// let tags = vec![]; // Empty for this example
64/// let score = calculate_animation_score(&tags);
65/// assert_eq!(score, 0);
66/// ```
67#[must_use]
68pub fn calculate_animation_score(tags: &[OverrideTag<'_>]) -> u8 {
69 tags.iter()
70 .map(|tag| match tag.name() {
71 "b" | "i" | "u" | "s" | "c" | "1c" | "2c" | "3c" | "4c" | "alpha" | "1a" | "2a"
72 | "3a" | "4a" => 1,
73 "frx" | "fry" | "frz" | "fscx" | "fscy" | "fsp" | "fad" | "fade" | "clip" | "iclip" => {
74 3
75 }
76 "move" => 4,
77 "t" | "pbo" => 5,
78 "p" => 8,
79 _ => 2,
80 })
81 .sum::<u8>()
82 .min(10)
83}
84
85/// Calculate overall complexity score combining multiple factors
86///
87/// Computes a comprehensive complexity score (0-100) by combining animation
88/// complexity, text length, and override tag count. Used for performance
89/// optimization and rendering strategy selection.
90///
91/// # Scoring Components
92///
93/// - Animation score: Weighted 5x (0-50 points)
94/// - Character count: Variable based on text length (0-50 points)
95/// - Override count: Variable based on tag density (0-35 points)
96///
97/// # Arguments
98///
99/// * `animation_score` - Pre-calculated animation complexity (0-10)
100/// * `char_count` - Number of characters in event text
101/// * `override_count` - Number of override tags found
102///
103/// # Returns
104///
105/// Overall complexity score capped at 100
106///
107/// # Example
108///
109/// ```rust
110/// # use ass_core::analysis::events::scoring::calculate_complexity_score;
111/// let score = calculate_complexity_score(3, 100, 5);
112/// assert!(score > 0 && score <= 100);
113/// ```
114#[must_use]
115pub fn calculate_complexity_score(
116 animation_score: u8,
117 char_count: usize,
118 override_count: usize,
119) -> u8 {
120 let mut score = u32::from(animation_score) * 5;
121
122 score += match char_count {
123 0..=50 => 0,
124 51..=200 => 5,
125 201..=500 => 15,
126 501..=1000 => 30,
127 _ => 50,
128 };
129
130 score += match override_count {
131 0 => 0,
132 1..=5 => 5,
133 6..=15 => 15,
134 16..=30 => 25,
135 _ => 35,
136 };
137
138 (score.min(255) as u8).min(100)
139}
140
141/// Determine performance impact category from complexity score
142///
143/// Maps numerical complexity scores to categorical performance impact levels
144/// for easier rendering optimization decisions.
145///
146/// # Arguments
147///
148/// * `complexity_score` - Overall complexity score (0-100)
149///
150/// # Returns
151///
152/// Performance impact category for rendering optimization
153///
154/// # Example
155///
156/// ```rust
157/// # use ass_core::analysis::events::scoring::{get_performance_impact, PerformanceImpact};
158/// let impact = get_performance_impact(75);
159/// assert_eq!(impact, PerformanceImpact::High);
160/// ```
161#[must_use]
162pub const fn get_performance_impact(complexity_score: u8) -> PerformanceImpact {
163 match complexity_score {
164 0..=20 => PerformanceImpact::Minimal,
165 21..=40 => PerformanceImpact::Low,
166 41..=60 => PerformanceImpact::Medium,
167 61..=80 => PerformanceImpact::High,
168 _ => PerformanceImpact::Critical,
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 #[cfg(not(feature = "std"))]
176 use alloc::vec;
177
178 #[test]
179 fn test_animation_score_empty() {
180 let tags = vec![];
181 assert_eq!(calculate_animation_score(&tags), 0);
182 }
183
184 #[test]
185 fn test_animation_score_basic_formatting() {
186 // Mock tags would go here in a real implementation
187 // Testing with empty for now since OverrideTag construction requires parser
188 let tags = vec![];
189 assert_eq!(calculate_animation_score(&tags), 0);
190 }
191
192 #[test]
193 fn test_complexity_score_minimal() {
194 let score = calculate_complexity_score(0, 10, 0);
195 assert_eq!(score, 0);
196 }
197
198 #[test]
199 fn test_complexity_score_high() {
200 let score = calculate_complexity_score(10, 1000, 50);
201 assert_eq!(score, 100);
202 }
203
204 #[test]
205 fn test_performance_impact_mapping() {
206 assert_eq!(get_performance_impact(0), PerformanceImpact::Minimal);
207 assert_eq!(get_performance_impact(30), PerformanceImpact::Low);
208 assert_eq!(get_performance_impact(50), PerformanceImpact::Medium);
209 assert_eq!(get_performance_impact(70), PerformanceImpact::High);
210 assert_eq!(get_performance_impact(90), PerformanceImpact::Critical);
211 }
212
213 #[test]
214 fn test_complexity_score_medium_char_count() {
215 // Test the 501-1000 character range (line 127)
216 let score = calculate_complexity_score(0, 750, 0);
217 assert_eq!(score, 30); // Should match the 501..=1000 => 30 case
218 }
219}