cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix(streaming): require frame progress for resolution recovery

Dami committed 033572a
Keep damage-driven no-frame intervals from counting as clean recovery evidence while preserving pressure downshifts and genuine low-motion restoration. Add a deterministic regression for the static-screen failure path.
commit
033572a9840ff4298e9ed37b5c487c9370c19e14
parent
99bfba92d6731e8ff7075d3229bfd6b3652bf43f
author
Dami <dami.etoile@gmail.com>
1 file changed, +78 -1
Mcrates/sigil-host/src/server.rs79++++++++++++++++++++++++++++++-
M crates/sigil-host/src/server.rs +78 -1
@@ -271,7 +271,14 @@ impl MotionResolutionPolicy {
271271 });272272 self.update_motion(motion_active, observed_at);273273 }274- self.update_feedback(evaluation.pressure_severity, evaluation.fresh);274+ let resolution_severity = if evaluation.pressure_severity == FeedbackSeverity::Clean275+ && !evaluation.clean_recovery_evidence276+ {277+ FeedbackSeverity::InsufficientEvidence278+ } else {279+ evaluation.pressure_severity280+ };281+ self.update_feedback(resolution_severity, evaluation.fresh);275282 self.evaluate_target(observed_at)276283 }277284
@@ -523,6 +530,7 @@ struct ShadowBitrateController {
523530 struct AdaptiveBitrateEvaluation {524531 decision: AdaptiveBitrateDecisionV1,525532 pressure_severity: FeedbackSeverity,533+ clean_recovery_evidence: bool,526534 fresh: bool,527535 }528536
@@ -649,6 +657,7 @@ impl ShadowBitrateController {
649657 Ok(AdaptiveBitrateEvaluation {650658 decision,651659 pressure_severity,660+ clean_recovery_evidence: severity == FeedbackSeverity::Clean,652661 fresh: !stale,653662 })654663 }
@@ -5366,6 +5375,74 @@ mod tests {
53665375 assert!(!held.changed);53675376 }53685377 5378+ #[test]5379+ fn damage_driven_no_progress_cannot_restore_native_resolution() {5380+ let now = Instant::now();5381+ let telemetry = MediaV3TelemetrySnapshot::default();5382+ let mut bitrate = ShadowBitrateController::new(12_000, telemetry);5383+ let mut resolution = MotionResolutionPolicy::new(native_dimensions()).unwrap();5384+5385+ let mut seed = clean_feedback(1);5386+ seed.last_sequence = Some(100);5387+ let seed_evaluation = bitrate.evaluate(&seed, telemetry, now).unwrap();5388+ resolution.observe_window(&seed, &seed_evaluation, now);5389+5390+ let mut pressure = clean_feedback(2);5391+ pressure.last_sequence = Some(110);5392+ pressure.flags = MediaFeedbackFlags::RESYNC_ACTIVE;5393+ let pressure_evaluation = bitrate5394+ .evaluate(&pressure, telemetry, now + Duration::from_secs(1))5395+ .unwrap();5396+ let downshift = resolution.observe_window(5397+ &pressure,5398+ &pressure_evaluation,5399+ now + Duration::from_secs(1),5400+ );5401+ assert_eq!(downshift.tier, ResolutionTier::Reduced);5402+ assert!(downshift.changed);5403+5404+ for second in 2..=4 {5405+ let mut no_progress = clean_feedback(second + 1);5406+ no_progress.last_sequence = Some(110);5407+ let evaluation = bitrate5408+ .evaluate(&no_progress, telemetry, now + Duration::from_secs(second))5409+ .unwrap();5410+ assert_eq!(evaluation.pressure_severity, FeedbackSeverity::Clean);5411+ assert!(!evaluation.clean_recovery_evidence);5412+ let decision = resolution.observe_window(5413+ &no_progress,5414+ &evaluation,5415+ now + Duration::from_secs(second),5416+ );5417+ assert_eq!(decision.tier, ResolutionTier::Reduced);5418+ assert_eq!(decision.trigger, ResolutionTrigger::Pressure);5419+ assert!(!decision.changed);5420+ assert_eq!(resolution.clean_windows, 0);5421+ }5422+5423+ for (report_id, second, sequence) in [(6, 5, 111), (7, 6, 112), (8, 7, 113)] {5424+ let mut low_motion = clean_feedback(report_id);5425+ low_motion.last_sequence = Some(sequence);5426+ let evaluation = bitrate5427+ .evaluate(&low_motion, telemetry, now + Duration::from_secs(second))5428+ .unwrap();5429+ assert!(evaluation.clean_recovery_evidence);5430+ let decision = resolution.observe_window(5431+ &low_motion,5432+ &evaluation,5433+ now + Duration::from_secs(second),5434+ );5435+ if report_id < 8 {5436+ assert_eq!(decision.tier, ResolutionTier::Reduced);5437+ assert!(!decision.changed);5438+ } else {5439+ assert_eq!(decision.tier, ResolutionTier::Native);5440+ assert_eq!(decision.trigger, ResolutionTrigger::Recovery);5441+ assert!(decision.changed);5442+ }5443+ }5444+ }5445+53695446 #[test]53705447 fn shadow_controller_immediately_decreases_for_severe_feedback_and_stays_bounded() {53715448 let mut controller =