1#![forbid(unsafe_code)]
2
3use std::fmt::Write as _;
24
25use ftui_render::diff_strategy::{DiffStrategy, StrategyEvidence};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub enum DiffRegime {
30 StableFrame,
32 BurstyChange,
34 ResizeRegime,
36 DegradedTerminal,
38}
39
40impl DiffRegime {
41 pub const fn as_str(self) -> &'static str {
43 match self {
44 Self::StableFrame => "stable_frame",
45 Self::BurstyChange => "bursty_change",
46 Self::ResizeRegime => "resize",
47 Self::DegradedTerminal => "degraded",
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
54pub struct Observation {
55 pub metric_name: String,
57 pub value: f64,
59 pub prior_contribution: f64,
61}
62
63impl Observation {
64 pub fn new(metric_name: impl Into<String>, value: f64, prior_contribution: f64) -> Self {
66 Self {
67 metric_name: metric_name.into(),
68 value,
69 prior_contribution,
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct DiffStrategyRecord {
77 pub frame_id: u64,
79 pub regime: DiffRegime,
81 pub posterior: Vec<(DiffStrategy, f64)>,
83 pub chosen_strategy: DiffStrategy,
85 pub confidence: f64,
87 pub evidence: StrategyEvidence,
89 pub fallback_triggered: bool,
91 pub observations: Vec<Observation>,
93}
94
95impl DiffStrategyRecord {
96 pub fn to_jsonl(&self) -> String {
98 let mut out = String::with_capacity(512);
99 out.push_str("{\"type\":\"diff_decision\"");
100 let _ = write!(out, ",\"frame\":{}", self.frame_id);
101 let _ = write!(out, ",\"regime\":\"{}\"", self.regime.as_str());
102 let _ = write!(out, ",\"strategy\":\"{:?}\"", self.chosen_strategy);
103 let _ = write!(out, ",\"confidence\":{:.6}", self.confidence);
104 let _ = write!(out, ",\"fallback\":{}", self.fallback_triggered);
105 let _ = write!(
106 out,
107 ",\"posterior_mean\":{:.6},\"posterior_var\":{:.6}",
108 self.evidence.posterior_mean, self.evidence.posterior_variance
109 );
110 let _ = write!(
111 out,
112 ",\"cost_full\":{:.4},\"cost_dirty\":{:.4},\"cost_redraw\":{:.4}",
113 self.evidence.cost_full, self.evidence.cost_dirty, self.evidence.cost_redraw
114 );
115 let _ = write!(
116 out,
117 ",\"alpha\":{:.4},\"beta\":{:.4}",
118 self.evidence.alpha, self.evidence.beta
119 );
120
121 out.push_str(",\"obs\":[");
123 for (i, obs) in self.observations.iter().enumerate() {
124 if i > 0 {
125 out.push(',');
126 }
127 let _ = write!(
128 out,
129 "{{\"m\":\"{}\",\"v\":{:.6},\"c\":{:.6}}}",
130 obs.metric_name.replace('"', "\\\""),
131 obs.value,
132 obs.prior_contribution
133 );
134 }
135 out.push_str("]}");
136 out
137 }
138}
139
140#[derive(Debug, Clone)]
142pub struct RegimeTransition {
143 pub frame_id: u64,
145 pub from_regime: DiffRegime,
147 pub to_regime: DiffRegime,
149 pub trigger: String,
151 pub confidence: f64,
153}
154
155impl RegimeTransition {
156 pub fn to_jsonl(&self) -> String {
158 format!(
159 "{{\"type\":\"regime_transition\",\"frame\":{},\"from\":\"{}\",\"to\":\"{}\",\"trigger\":\"{}\",\"confidence\":{:.6}}}",
160 self.frame_id,
161 self.from_regime.as_str(),
162 self.to_regime.as_str(),
163 self.trigger.replace('"', "\\\""),
164 self.confidence,
165 )
166 }
167}
168
169pub struct DiffEvidenceLedger {
174 decisions: Vec<Option<DiffStrategyRecord>>,
175 transitions: Vec<Option<RegimeTransition>>,
176 decision_head: usize,
177 transition_head: usize,
178 decision_count: usize,
179 transition_count: usize,
180 decision_capacity: usize,
181 transition_capacity: usize,
182 current_regime: DiffRegime,
183}
184
185impl std::fmt::Debug for DiffEvidenceLedger {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 f.debug_struct("DiffEvidenceLedger")
188 .field("decisions", &self.decision_count)
189 .field("transitions", &self.transition_count)
190 .field("capacity", &self.decision_capacity)
191 .field("regime", &self.current_regime)
192 .finish()
193 }
194}
195
196impl DiffEvidenceLedger {
197 pub fn new(decision_capacity: usize) -> Self {
202 let decision_capacity = decision_capacity.max(1);
203 let transition_capacity = (decision_capacity / 10).max(16);
204 Self {
205 decisions: (0..decision_capacity).map(|_| None).collect(),
206 transitions: (0..transition_capacity).map(|_| None).collect(),
207 decision_head: 0,
208 transition_head: 0,
209 decision_count: 0,
210 transition_count: 0,
211 decision_capacity,
212 transition_capacity,
213 current_regime: DiffRegime::StableFrame,
214 }
215 }
216
217 pub fn record(&mut self, record: DiffStrategyRecord) {
219 if record.regime != self.current_regime {
221 let transition = RegimeTransition {
222 frame_id: record.frame_id,
223 from_regime: self.current_regime,
224 to_regime: record.regime,
225 trigger: format!(
226 "confidence={:.3} strategy={:?}",
227 record.confidence, record.chosen_strategy
228 ),
229 confidence: record.confidence,
230 };
231 self.record_transition(transition);
232 self.current_regime = record.regime;
233 }
234
235 self.decisions[self.decision_head] = Some(record);
236 self.decision_head = (self.decision_head + 1) % self.decision_capacity;
237 if self.decision_count < self.decision_capacity {
238 self.decision_count += 1;
239 }
240 }
241
242 pub fn record_transition(&mut self, transition: RegimeTransition) {
244 self.transitions[self.transition_head] = Some(transition);
245 self.transition_head = (self.transition_head + 1) % self.transition_capacity;
246 if self.transition_count < self.transition_capacity {
247 self.transition_count += 1;
248 }
249 }
250
251 pub fn len(&self) -> usize {
253 self.decision_count
254 }
255
256 pub fn is_empty(&self) -> bool {
258 self.decision_count == 0
259 }
260
261 pub fn transition_count(&self) -> usize {
263 self.transition_count
264 }
265
266 pub fn current_regime(&self) -> DiffRegime {
268 self.current_regime
269 }
270
271 pub fn decisions(&self) -> impl Iterator<Item = &DiffStrategyRecord> {
273 let cap = self.decision_capacity;
274 let count = self.decision_count;
275 let head = self.decision_head;
276 let start = if count < cap { 0 } else { head };
277
278 (0..count).filter_map(move |i| {
279 let idx = (start + i) % cap;
280 self.decisions[idx].as_ref()
281 })
282 }
283
284 pub fn transitions(&self) -> impl Iterator<Item = &RegimeTransition> {
286 let cap = self.transition_capacity;
287 let count = self.transition_count;
288 let head = self.transition_head;
289 let start = if count < cap { 0 } else { head };
290
291 (0..count).filter_map(move |i| {
292 let idx = (start + i) % cap;
293 self.transitions[idx].as_ref()
294 })
295 }
296
297 pub fn last_decision(&self) -> Option<&DiffStrategyRecord> {
299 if self.decision_count == 0 {
300 return None;
301 }
302 let idx = if self.decision_head == 0 {
303 self.decision_capacity - 1
304 } else {
305 self.decision_head - 1
306 };
307 self.decisions[idx].as_ref()
308 }
309
310 pub fn export_jsonl(&self) -> String {
312 let mut out = String::new();
313 for d in self.decisions() {
314 out.push_str(&d.to_jsonl());
315 out.push('\n');
316 }
317 for t in self.transitions() {
318 out.push_str(&t.to_jsonl());
319 out.push('\n');
320 }
321 out
322 }
323
324 pub fn flush_to_sink(&self, sink: &crate::evidence_sink::EvidenceSink) -> std::io::Result<()> {
326 for d in self.decisions() {
327 sink.write_jsonl(&d.to_jsonl())?;
328 }
329 for t in self.transitions() {
330 sink.write_jsonl(&t.to_jsonl())?;
331 }
332 Ok(())
333 }
334
335 pub fn clear(&mut self) {
337 for slot in &mut self.decisions {
338 *slot = None;
339 }
340 for slot in &mut self.transitions {
341 *slot = None;
342 }
343 self.decision_head = 0;
344 self.transition_head = 0;
345 self.decision_count = 0;
346 self.transition_count = 0;
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353 use ftui_render::diff_strategy::StrategyEvidence;
354
355 fn make_evidence() -> StrategyEvidence {
356 StrategyEvidence {
357 strategy: DiffStrategy::DirtyRows,
358 cost_full: 1.0,
359 cost_dirty: 0.5,
360 cost_redraw: 2.0,
361 posterior_mean: 0.05,
362 posterior_variance: 0.001,
363 alpha: 2.0,
364 beta: 38.0,
365 dirty_rows: 3,
366 total_rows: 24,
367 total_cells: 1920,
368 guard_reason: "none",
369 hysteresis_applied: false,
370 hysteresis_ratio: 0.05,
371 }
372 }
373
374 fn make_record(frame_id: u64, regime: DiffRegime) -> DiffStrategyRecord {
375 DiffStrategyRecord {
376 frame_id,
377 regime,
378 posterior: vec![
379 (DiffStrategy::Full, 0.3),
380 (DiffStrategy::DirtyRows, 0.6),
381 (DiffStrategy::FullRedraw, 0.1),
382 ],
383 chosen_strategy: DiffStrategy::DirtyRows,
384 confidence: 0.6,
385 evidence: make_evidence(),
386 fallback_triggered: false,
387 observations: vec![
388 Observation::new("change_fraction", 0.05, 0.3),
389 Observation::new("dirty_rows", 3.0, 0.2),
390 ],
391 }
392 }
393
394 #[test]
395 fn empty_ledger() {
396 let ledger = DiffEvidenceLedger::new(100);
397 assert!(ledger.is_empty());
398 assert_eq!(ledger.len(), 0);
399 assert_eq!(ledger.transition_count(), 0);
400 assert!(ledger.last_decision().is_none());
401 assert_eq!(ledger.current_regime(), DiffRegime::StableFrame);
402 }
403
404 #[test]
405 fn record_single_decision() {
406 let mut ledger = DiffEvidenceLedger::new(100);
407 ledger.record(make_record(1, DiffRegime::StableFrame));
408 assert_eq!(ledger.len(), 1);
409 assert_eq!(ledger.last_decision().unwrap().frame_id, 1);
410 }
411
412 #[test]
413 fn ring_buffer_wraps() {
414 let mut ledger = DiffEvidenceLedger::new(5);
415 for i in 0..10 {
416 ledger.record(make_record(i, DiffRegime::StableFrame));
417 }
418 assert_eq!(ledger.len(), 5);
420 let frames: Vec<u64> = ledger.decisions().map(|d| d.frame_id).collect();
422 assert_eq!(frames, vec![5, 6, 7, 8, 9]);
423 }
424
425 #[test]
426 fn regime_transition_auto_detected() {
427 let mut ledger = DiffEvidenceLedger::new(100);
428 ledger.record(make_record(1, DiffRegime::StableFrame));
429 ledger.record(make_record(2, DiffRegime::BurstyChange));
430 assert_eq!(ledger.transition_count(), 1);
431 assert_eq!(ledger.current_regime(), DiffRegime::BurstyChange);
432 let t = ledger.transitions().next().unwrap();
433 assert_eq!(t.from_regime, DiffRegime::StableFrame);
434 assert_eq!(t.to_regime, DiffRegime::BurstyChange);
435 assert_eq!(t.frame_id, 2);
436 }
437
438 #[test]
439 fn no_transition_on_same_regime() {
440 let mut ledger = DiffEvidenceLedger::new(100);
441 ledger.record(make_record(1, DiffRegime::StableFrame));
442 ledger.record(make_record(2, DiffRegime::StableFrame));
443 assert_eq!(ledger.transition_count(), 0);
444 }
445
446 #[test]
447 fn multiple_transitions() {
448 let mut ledger = DiffEvidenceLedger::new(100);
449 ledger.record(make_record(1, DiffRegime::StableFrame));
450 ledger.record(make_record(2, DiffRegime::BurstyChange));
451 ledger.record(make_record(3, DiffRegime::ResizeRegime));
452 ledger.record(make_record(4, DiffRegime::StableFrame));
453 assert_eq!(ledger.transition_count(), 3);
454 }
455
456 #[test]
457 fn jsonl_round_trip_decision() {
458 let record = make_record(42, DiffRegime::StableFrame);
459 let jsonl = record.to_jsonl();
460 assert!(jsonl.contains("\"type\":\"diff_decision\""));
461 assert!(jsonl.contains("\"frame\":42"));
462 assert!(jsonl.contains("\"regime\":\"stable_frame\""));
463 assert!(jsonl.contains("\"strategy\":\"DirtyRows\""));
464 assert!(jsonl.contains("\"obs\":["));
465 assert!(jsonl.contains("\"m\":\"change_fraction\""));
466 }
467
468 #[test]
469 fn jsonl_round_trip_transition() {
470 let transition = RegimeTransition {
471 frame_id: 10,
472 from_regime: DiffRegime::StableFrame,
473 to_regime: DiffRegime::BurstyChange,
474 trigger: "burst detected".to_string(),
475 confidence: 0.85,
476 };
477 let jsonl = transition.to_jsonl();
478 assert!(jsonl.contains("\"type\":\"regime_transition\""));
479 assert!(jsonl.contains("\"frame\":10"));
480 assert!(jsonl.contains("\"from\":\"stable_frame\""));
481 assert!(jsonl.contains("\"to\":\"bursty_change\""));
482 }
483
484 #[test]
485 fn export_jsonl_output() {
486 let mut ledger = DiffEvidenceLedger::new(100);
487 ledger.record(make_record(1, DiffRegime::StableFrame));
488 ledger.record(make_record(2, DiffRegime::BurstyChange));
489 let output = ledger.export_jsonl();
490 let lines: Vec<&str> = output.lines().collect();
491 assert_eq!(lines.len(), 3);
493 assert!(lines[0].contains("\"frame\":1"));
494 assert!(lines[1].contains("\"frame\":2"));
495 assert!(lines[2].contains("regime_transition"));
496 }
497
498 #[test]
499 fn clear_resets_everything() {
500 let mut ledger = DiffEvidenceLedger::new(100);
501 ledger.record(make_record(1, DiffRegime::StableFrame));
502 ledger.record(make_record(2, DiffRegime::BurstyChange));
503 ledger.clear();
504 assert!(ledger.is_empty());
505 assert_eq!(ledger.transition_count(), 0);
506 assert!(ledger.last_decision().is_none());
507 }
508
509 #[test]
510 fn last_decision_returns_most_recent() {
511 let mut ledger = DiffEvidenceLedger::new(100);
512 ledger.record(make_record(1, DiffRegime::StableFrame));
513 ledger.record(make_record(2, DiffRegime::StableFrame));
514 ledger.record(make_record(3, DiffRegime::StableFrame));
515 assert_eq!(ledger.last_decision().unwrap().frame_id, 3);
516 }
517
518 #[test]
519 fn last_decision_after_wrap() {
520 let mut ledger = DiffEvidenceLedger::new(3);
521 for i in 0..10 {
522 ledger.record(make_record(i, DiffRegime::StableFrame));
523 }
524 assert_eq!(ledger.last_decision().unwrap().frame_id, 9);
525 }
526
527 #[test]
528 fn observation_fields() {
529 let obs = Observation::new("test_metric", 42.0, 1.5);
530 assert_eq!(obs.metric_name, "test_metric");
531 assert!((obs.value - 42.0).abs() < f64::EPSILON);
532 assert!((obs.prior_contribution - 1.5).abs() < f64::EPSILON);
533 }
534
535 #[test]
536 fn regime_as_str() {
537 assert_eq!(DiffRegime::StableFrame.as_str(), "stable_frame");
538 assert_eq!(DiffRegime::BurstyChange.as_str(), "bursty_change");
539 assert_eq!(DiffRegime::ResizeRegime.as_str(), "resize");
540 assert_eq!(DiffRegime::DegradedTerminal.as_str(), "degraded");
541 }
542
543 #[test]
544 fn transition_ring_buffer_wraps() {
545 let mut ledger = DiffEvidenceLedger::new(10); let regimes = [
548 DiffRegime::StableFrame,
549 DiffRegime::BurstyChange,
550 DiffRegime::ResizeRegime,
551 DiffRegime::DegradedTerminal,
552 ];
553 for i in 0..100 {
554 ledger.record(make_record(i, regimes[i as usize % regimes.len()]));
555 }
556 assert!(ledger.transition_count() <= 16);
558 }
559
560 #[test]
561 fn decisions_order_before_wrap() {
562 let mut ledger = DiffEvidenceLedger::new(10);
563 for i in 0..5 {
564 ledger.record(make_record(i, DiffRegime::StableFrame));
565 }
566 let frames: Vec<u64> = ledger.decisions().map(|d| d.frame_id).collect();
567 assert_eq!(frames, vec![0, 1, 2, 3, 4]);
568 }
569
570 #[test]
571 fn flush_to_sink_writes_all() {
572 let mut ledger = DiffEvidenceLedger::new(100);
573 ledger.record(make_record(1, DiffRegime::StableFrame));
574 ledger.record(make_record(2, DiffRegime::BurstyChange));
575
576 let config = crate::evidence_sink::EvidenceSinkConfig::enabled_stdout();
577 if let Ok(Some(sink)) = crate::evidence_sink::EvidenceSink::from_config(&config) {
578 let result = ledger.flush_to_sink(&sink);
580 assert!(result.is_ok());
581 }
582 }
583
584 #[test]
585 fn simulate_1000_frames() {
586 let mut ledger = DiffEvidenceLedger::new(10_000);
587 let regimes = [
588 DiffRegime::StableFrame,
589 DiffRegime::BurstyChange,
590 DiffRegime::ResizeRegime,
591 DiffRegime::StableFrame,
592 DiffRegime::DegradedTerminal,
593 DiffRegime::StableFrame,
594 ];
595
596 for i in 0..1000 {
597 let regime = regimes[(i / 100) % regimes.len()];
599 ledger.record(make_record(i as u64, regime));
600 }
601
602 assert_eq!(ledger.len(), 1000);
603 assert!(ledger.transition_count() > 0);
605
606 let mut prev_frame = 0u64;
608 for d in ledger.decisions() {
609 assert!(d.frame_id >= prev_frame);
610 prev_frame = d.frame_id;
611 }
612
613 let jsonl = ledger.export_jsonl();
615 let lines: Vec<&str> = jsonl.lines().collect();
616 assert_eq!(lines.len(), ledger.len() + ledger.transition_count());
617 }
618
619 #[test]
620 fn debug_format() {
621 let ledger = DiffEvidenceLedger::new(100);
622 let debug = format!("{ledger:?}");
623 assert!(debug.contains("DiffEvidenceLedger"));
624 assert!(debug.contains("decisions: 0"));
625 }
626
627 #[test]
628 fn minimum_capacity() {
629 let mut ledger = DiffEvidenceLedger::new(0); ledger.record(make_record(1, DiffRegime::StableFrame));
631 assert_eq!(ledger.len(), 1);
632 ledger.record(make_record(2, DiffRegime::StableFrame));
633 assert_eq!(ledger.len(), 1); assert_eq!(ledger.last_decision().unwrap().frame_id, 2);
635 }
636
637 #[test]
640 fn contract_stable_to_bursty_transition() {
641 let mut ledger = DiffEvidenceLedger::new(100);
643
644 for i in 0..10 {
646 ledger.record(make_record(i, DiffRegime::StableFrame));
647 }
648 assert_eq!(ledger.current_regime(), DiffRegime::StableFrame);
649 assert_eq!(ledger.transition_count(), 0);
650
651 ledger.record(make_record(10, DiffRegime::BurstyChange));
653 assert_eq!(ledger.current_regime(), DiffRegime::BurstyChange);
654 assert_eq!(ledger.transition_count(), 1);
655
656 let t = ledger.transitions().next().unwrap();
657 assert_eq!(t.from_regime, DiffRegime::StableFrame);
658 assert_eq!(t.to_regime, DiffRegime::BurstyChange);
659 assert_eq!(t.frame_id, 10);
660 }
661
662 #[test]
663 fn contract_bursty_recovery_to_stable() {
664 let mut ledger = DiffEvidenceLedger::new(100);
666
667 ledger.record(make_record(0, DiffRegime::StableFrame));
669 ledger.record(make_record(1, DiffRegime::BurstyChange));
670 assert_eq!(ledger.transition_count(), 1); for i in 2..5 {
674 ledger.record(make_record(i, DiffRegime::StableFrame));
675 }
676 assert_eq!(ledger.current_regime(), DiffRegime::StableFrame);
677 assert_eq!(ledger.transition_count(), 2); }
679
680 #[test]
681 fn contract_resize_returns_to_previous() {
682 let mut ledger = DiffEvidenceLedger::new(100);
684
685 ledger.record(make_record(0, DiffRegime::StableFrame));
687
688 ledger.record(make_record(1, DiffRegime::ResizeRegime));
690 assert_eq!(ledger.current_regime(), DiffRegime::ResizeRegime);
691
692 ledger.record(make_record(2, DiffRegime::StableFrame));
694 assert_eq!(ledger.current_regime(), DiffRegime::StableFrame);
695
696 assert_eq!(ledger.transition_count(), 2);
698 }
699
700 #[test]
701 fn contract_degraded_entry_and_recovery() {
702 let mut ledger = DiffEvidenceLedger::new(100);
704
705 ledger.record(make_record(0, DiffRegime::StableFrame));
706 ledger.record(make_record(1, DiffRegime::DegradedTerminal));
707 assert_eq!(ledger.current_regime(), DiffRegime::DegradedTerminal);
708
709 for i in 2..10 {
711 ledger.record(make_record(i, DiffRegime::DegradedTerminal));
712 }
713 assert_eq!(ledger.current_regime(), DiffRegime::DegradedTerminal);
714 assert_eq!(ledger.transition_count(), 1); ledger.record(make_record(10, DiffRegime::StableFrame));
718 assert_eq!(ledger.current_regime(), DiffRegime::StableFrame);
719 assert_eq!(ledger.transition_count(), 2);
720 }
721
722 #[test]
723 fn contract_no_flapping() {
724 let mut ledger = DiffEvidenceLedger::new(100);
727
728 let sequence = [
729 DiffRegime::StableFrame,
730 DiffRegime::BurstyChange,
731 DiffRegime::StableFrame,
732 DiffRegime::BurstyChange,
733 DiffRegime::StableFrame,
734 ];
735
736 for (i, ®ime) in sequence.iter().enumerate() {
737 ledger.record(make_record(i as u64, regime));
738 }
739
740 assert_eq!(ledger.transition_count(), 4);
742
743 let transitions: Vec<(DiffRegime, DiffRegime)> = ledger
745 .transitions()
746 .map(|t| (t.from_regime, t.to_regime))
747 .collect();
748 assert_eq!(
749 transitions,
750 vec![
751 (DiffRegime::StableFrame, DiffRegime::BurstyChange),
752 (DiffRegime::BurstyChange, DiffRegime::StableFrame),
753 (DiffRegime::StableFrame, DiffRegime::BurstyChange),
754 (DiffRegime::BurstyChange, DiffRegime::StableFrame),
755 ]
756 );
757 }
758
759 #[test]
760 fn contract_full_lifecycle() {
761 let mut ledger = DiffEvidenceLedger::new(100);
763
764 let lifecycle = [
765 (0, DiffRegime::StableFrame),
766 (1, DiffRegime::StableFrame),
767 (2, DiffRegime::BurstyChange),
768 (3, DiffRegime::BurstyChange),
769 (4, DiffRegime::ResizeRegime),
770 (5, DiffRegime::StableFrame),
771 (6, DiffRegime::StableFrame),
772 (7, DiffRegime::DegradedTerminal),
773 (8, DiffRegime::DegradedTerminal),
774 (9, DiffRegime::StableFrame),
775 ];
776
777 for &(frame, regime) in &lifecycle {
778 ledger.record(make_record(frame, regime));
779 }
780
781 assert_eq!(ledger.len(), 10);
782 assert_eq!(ledger.transition_count(), 5);
785 assert_eq!(ledger.current_regime(), DiffRegime::StableFrame);
786
787 for t in ledger.transitions() {
789 assert!(t.frame_id <= 9);
790 assert_ne!(t.from_regime, t.to_regime);
791 }
792 }
793}