1use std::collections::BTreeMap;
4
5use eredu_core::{
6 RealtimeForcedSource, RealtimeFrameScheduleState, RealtimeFrameSlot, RealtimeFrameTransition,
7 RealtimeScheduleError, RealtimeSpeechConfig, RealtimeTargetSource, RealtimeTemporalSource,
8};
9
10use crate::{
11 MaterializedRealtimeInput, PredictionDirective, RealtimePayloadHistory,
12 RealtimePayloadHistoryError,
13};
14
15pub trait RealtimeFrameTensorMechanisms {
17 type Tensor: Clone;
19 type Error;
21
22 fn column(&mut self, matrix: &Self::Tensor, column: usize)
24 -> Result<Self::Tensor, Self::Error>;
25
26 fn filled_column(&mut self, token: i32, batch: usize) -> Result<Self::Tensor, Self::Error>;
28
29 fn stack_columns(
31 &mut self,
32 columns: &[Self::Tensor],
33 batch: usize,
34 ) -> Result<Self::Tensor, Self::Error>;
35}
36
37pub struct PreparedRealtimeFrame<T> {
39 schedule: RealtimeSpeechConfig,
40 transition: RealtimeFrameTransition,
41 temporal: Vec<T>,
42 directives: Vec<PredictionDirective<T>>,
43 batch: usize,
44 retain_diagnostics: bool,
45}
46
47impl<T> PreparedRealtimeFrame<T> {
48 pub const fn transition(&self) -> &RealtimeFrameTransition {
50 &self.transition
51 }
52
53 pub fn temporal(&self) -> &[T] {
55 &self.temporal
56 }
57
58 pub fn directives(&self) -> &[PredictionDirective<T>] {
60 &self.directives
61 }
62
63 pub const fn batch(&self) -> usize {
65 self.batch
66 }
67
68 pub const fn retains_diagnostics(&self) -> bool {
70 self.retain_diagnostics
71 }
72
73 pub fn into_parts(
75 self,
76 ) -> (
77 RealtimeSpeechConfig,
78 RealtimeFrameTransition,
79 Vec<T>,
80 Vec<PredictionDirective<T>>,
81 usize,
82 bool,
83 ) {
84 (
85 self.schedule,
86 self.transition,
87 self.temporal,
88 self.directives,
89 self.batch,
90 self.retain_diagnostics,
91 )
92 }
93}
94
95pub struct CompletedRealtimeFrame<T, D> {
97 text: T,
98 decision_audio: T,
99 sampled_audio: T,
100 aligned_audio: Option<T>,
101 diagnostics: Vec<D>,
102}
103
104impl<T, D> CompletedRealtimeFrame<T, D> {
105 pub const fn text(&self) -> &T {
107 &self.text
108 }
109
110 pub const fn decision_audio(&self) -> &T {
112 &self.decision_audio
113 }
114
115 pub const fn sampled_audio(&self) -> &T {
117 &self.sampled_audio
118 }
119
120 pub const fn aligned_audio(&self) -> Option<&T> {
122 self.aligned_audio.as_ref()
123 }
124
125 pub fn diagnostics(&self) -> &[D] {
127 &self.diagnostics
128 }
129
130 pub fn into_parts(self) -> (T, T, T, Option<T>, Vec<D>) {
132 (
133 self.text,
134 self.decision_audio,
135 self.sampled_audio,
136 self.aligned_audio,
137 self.diagnostics,
138 )
139 }
140}
141
142pub fn complete_realtime_frame<M, D>(
144 schedule: &RealtimeSpeechConfig,
145 history: &mut RealtimePayloadHistory<M::Tensor>,
146 prepared: PreparedRealtimeFrame<M::Tensor>,
147 decisions: Vec<M::Tensor>,
148 diagnostics: Vec<D>,
149 mechanisms: &mut M,
150) -> Result<CompletedRealtimeFrame<M::Tensor, D>, RealtimeFrameInterpretationError<M::Error>>
151where
152 M: RealtimeFrameTensorMechanisms,
153{
154 history
155 .validate_schedule(schedule)
156 .map_err(RealtimeFrameInterpretationError::History)?;
157 let (prepared_schedule, transition, _, directives, batch, retain_diagnostics) =
158 prepared.into_parts();
159 if &prepared_schedule != schedule {
160 return Err(RealtimeFrameInterpretationError::PreparedScheduleMismatch);
161 }
162 if !transition.model_call_required() {
163 if !decisions.is_empty() {
164 return Err(RealtimeFrameInterpretationError::DecisionCount {
165 expected: 0,
166 actual: decisions.len(),
167 });
168 }
169 if !diagnostics.is_empty() {
170 return Err(RealtimeFrameInterpretationError::DiagnosticCount {
171 expected: 0,
172 actual: diagnostics.len(),
173 });
174 }
175 let text = mechanisms
176 .filled_column(schedule.text_padding_token(), batch)
177 .map_err(RealtimeFrameInterpretationError::Mechanism)?;
178 let padding = mechanisms
179 .filled_column(schedule.audio_padding_token(), batch)
180 .map_err(RealtimeFrameInterpretationError::Mechanism)?;
181 let sampled_columns = vec![padding; schedule.generated_audio_codebooks()];
182 let sampled_audio = mechanisms
183 .stack_columns(&sampled_columns, batch)
184 .map_err(RealtimeFrameInterpretationError::Mechanism)?;
185 let decision_audio = mechanisms
186 .stack_columns(&[], batch)
187 .map_err(RealtimeFrameInterpretationError::Mechanism)?;
188 return Ok(CompletedRealtimeFrame {
189 text,
190 decision_audio,
191 sampled_audio,
192 aligned_audio: None,
193 diagnostics,
194 });
195 }
196 if decisions.len() != directives.len() {
197 return Err(RealtimeFrameInterpretationError::DecisionCount {
198 expected: directives.len(),
199 actual: decisions.len(),
200 });
201 }
202 let expected_diagnostics = usize::from(retain_diagnostics) * decisions.len();
203 if diagnostics.len() != expected_diagnostics {
204 return Err(RealtimeFrameInterpretationError::DiagnosticCount {
205 expected: expected_diagnostics,
206 actual: diagnostics.len(),
207 });
208 }
209 let text = decisions
210 .first()
211 .cloned()
212 .ok_or(RealtimeFrameInterpretationError::MissingTextDecision)?;
213 let generated = schedule.generated_audio_codebooks();
214 let generated_end = 1usize
215 .checked_add(generated)
216 .ok_or(RealtimeFrameInterpretationError::DecisionCountOverflow { generated })?;
217 if generated_end > decisions.len() {
218 return Err(RealtimeFrameInterpretationError::DecisionCount {
219 expected: generated_end,
220 actual: decisions.len(),
221 });
222 }
223
224 let mut history_branch = history.clone();
225 let resolved_targets = transition
226 .targets()
227 .iter()
228 .zip(&decisions)
229 .filter_map(|(target, payload)| {
230 target
231 .coordinate()
232 .map(|coordinate| (coordinate, payload.clone()))
233 })
234 .collect::<Vec<_>>();
235 history_branch
236 .overwrite_many(schedule, resolved_targets)
237 .map_err(RealtimeFrameInterpretationError::History)?;
238
239 let decision_audio = mechanisms
240 .stack_columns(&decisions[1..], batch)
241 .map_err(RealtimeFrameInterpretationError::Mechanism)?;
242 let sampled_audio = mechanisms
243 .stack_columns(&decisions[1..generated_end], batch)
244 .map_err(RealtimeFrameInterpretationError::Mechanism)?;
245 let aligned_audio = transition
246 .output()
247 .map(|coordinates| {
248 let columns = history_branch
249 .resolve_required(schedule, coordinates.iter().copied())
250 .map_err(RealtimeFrameInterpretationError::History)?
251 .into_iter()
252 .cloned()
253 .collect::<Vec<_>>();
254 mechanisms
255 .stack_columns(&columns, batch)
256 .map_err(RealtimeFrameInterpretationError::Mechanism)
257 })
258 .transpose()?;
259 history_branch
260 .prune_for_next_frontier(schedule, transition.next_frontier())
261 .map_err(RealtimeFrameInterpretationError::History)?;
262 *history = history_branch;
263 Ok(CompletedRealtimeFrame {
264 text,
265 decision_audio,
266 sampled_audio,
267 aligned_audio,
268 diagnostics,
269 })
270}
271
272pub fn prepare_realtime_frame<M>(
277 schedule: &RealtimeSpeechConfig,
278 schedule_state: &mut RealtimeFrameScheduleState,
279 history: &mut RealtimePayloadHistory<M::Tensor>,
280 input: &MaterializedRealtimeInput<M::Tensor>,
281 mechanisms: &mut M,
282) -> Result<PreparedRealtimeFrame<M::Tensor>, RealtimeFrameInterpretationError<M::Error>>
283where
284 M: RealtimeFrameTensorMechanisms,
285{
286 if input.schedule() != schedule {
287 return Err(RealtimeFrameInterpretationError::InputScheduleMismatch);
288 }
289 schedule_state
290 .validate_schedule(schedule)
291 .map_err(RealtimeFrameInterpretationError::Schedule)?;
292 history
293 .validate_schedule(schedule)
294 .map_err(RealtimeFrameInterpretationError::History)?;
295 let mut schedule_branch = schedule_state.clone();
296 let mut history_branch = history.clone();
297 let transition = schedule_branch
298 .advance(schedule, input.forcing())
299 .map_err(RealtimeFrameInterpretationError::Schedule)?;
300
301 let mut insertions = BTreeMap::new();
302 for (column, coordinate) in transition.input_placements().iter().copied().enumerate() {
303 let payload = mechanisms
304 .column(input.input_audio(), column)
305 .map_err(RealtimeFrameInterpretationError::Mechanism)?;
306 insertions.insert(coordinate, payload);
307 }
308 for coordinate in transition.forced_placements().iter().copied() {
309 insertions.insert(
310 coordinate,
311 forced_payload(input, coordinate.slot(), mechanisms)?,
312 );
313 }
314 for coordinate in transition.warmup_padding().iter().copied() {
315 let payload = mechanisms
316 .filled_column(padding_token(schedule, coordinate.slot())?, input.batch())
317 .map_err(RealtimeFrameInterpretationError::Mechanism)?;
318 insertions.insert(coordinate, payload);
319 }
320 history_branch
321 .overwrite_many(schedule, insertions)
322 .map_err(RealtimeFrameInterpretationError::History)?;
323
324 let temporal = transition
325 .temporal_inputs()
326 .iter()
327 .map(|source| match source {
328 RealtimeTemporalSource::Padding(slot) => mechanisms
329 .filled_column(padding_token(schedule, *slot)?, input.batch())
330 .map_err(RealtimeFrameInterpretationError::Mechanism),
331 RealtimeTemporalSource::Occupied { coordinate, .. } => history_branch
332 .required(schedule, *coordinate)
333 .cloned()
334 .map_err(RealtimeFrameInterpretationError::History),
335 _ => Err(RealtimeFrameInterpretationError::UnsupportedTemporalSource),
336 })
337 .collect::<Result<Vec<_>, _>>()?;
338 let directives = transition
339 .targets()
340 .iter()
341 .map(|target| match target.source() {
342 RealtimeTargetSource::Sampled => Ok(PredictionDirective::Sample),
343 RealtimeTargetSource::Forced(RealtimeForcedSource::CurrentInput) => {
344 forced_payload(input, target.slot(), mechanisms).map(PredictionDirective::Force)
345 }
346 RealtimeTargetSource::Forced(RealtimeForcedSource::Retained) => target
347 .coordinate()
348 .ok_or(RealtimeFrameInterpretationError::MissingTargetCoordinate)
349 .and_then(|coordinate| {
350 history_branch
351 .required(schedule, coordinate)
352 .cloned()
353 .map_err(RealtimeFrameInterpretationError::History)
354 })
355 .map(PredictionDirective::Force),
356 RealtimeTargetSource::Existing(_) => target
357 .coordinate()
358 .ok_or(RealtimeFrameInterpretationError::MissingTargetCoordinate)
359 .and_then(|coordinate| {
360 history_branch
361 .required(schedule, coordinate)
362 .cloned()
363 .map_err(RealtimeFrameInterpretationError::History)
364 })
365 .map(PredictionDirective::Force),
366 _ => Err(RealtimeFrameInterpretationError::UnsupportedTargetSource),
367 })
368 .collect::<Result<Vec<_>, _>>()?;
369
370 *schedule_state = schedule_branch;
371 *history = history_branch;
372 Ok(PreparedRealtimeFrame {
373 schedule: schedule.clone(),
374 transition,
375 temporal,
376 directives,
377 batch: input.batch(),
378 retain_diagnostics: input.retains_diagnostics(),
379 })
380}
381
382fn forced_payload<M>(
383 input: &MaterializedRealtimeInput<M::Tensor>,
384 slot: RealtimeFrameSlot,
385 mechanisms: &mut M,
386) -> Result<M::Tensor, RealtimeFrameInterpretationError<M::Error>>
387where
388 M: RealtimeFrameTensorMechanisms,
389{
390 match slot {
391 RealtimeFrameSlot::Text => input
392 .forced_text()
393 .cloned()
394 .ok_or(RealtimeFrameInterpretationError::MissingForcedPayload { slot }),
395 RealtimeFrameSlot::Audio(codebook) => mechanisms
396 .column(
397 input
398 .forced_audio()
399 .ok_or(RealtimeFrameInterpretationError::MissingForcedPayload { slot })?,
400 codebook,
401 )
402 .map_err(RealtimeFrameInterpretationError::Mechanism),
403 _ => Err(RealtimeFrameInterpretationError::UnsupportedSlot { slot }),
404 }
405}
406
407fn padding_token<E>(
408 schedule: &RealtimeSpeechConfig,
409 slot: RealtimeFrameSlot,
410) -> Result<i32, RealtimeFrameInterpretationError<E>> {
411 match slot {
412 RealtimeFrameSlot::Text => Ok(schedule.text_padding_token()),
413 RealtimeFrameSlot::Audio(codebook) if codebook < schedule.total_audio_codebooks() => {
414 Ok(schedule.audio_padding_token())
415 }
416 _ => Err(RealtimeFrameInterpretationError::UnsupportedSlot { slot }),
417 }
418}
419
420#[derive(Debug, thiserror::Error)]
422#[non_exhaustive]
423pub enum RealtimeFrameInterpretationError<E> {
424 #[error("materialized realtime input does not match the normalized schedule")]
426 InputScheduleMismatch,
427 #[error("prepared realtime frame does not match the normalized schedule")]
429 PreparedScheduleMismatch,
430 #[error(transparent)]
432 Schedule(RealtimeScheduleError),
433 #[error(transparent)]
435 History(RealtimePayloadHistoryError),
436 #[error("realtime frame tensor mechanism failed")]
438 Mechanism(#[source] E),
439 #[error("realtime forced slot {slot:?} has no payload")]
441 MissingForcedPayload {
442 slot: RealtimeFrameSlot,
444 },
445 #[error("realtime existing target has no coordinate")]
447 MissingTargetCoordinate,
448 #[error("realtime model returned {actual} decisions, expected {expected}")]
450 DecisionCount {
451 expected: usize,
453 actual: usize,
455 },
456 #[error("realtime generated decision count {generated} overflowed")]
458 DecisionCountOverflow {
459 generated: usize,
461 },
462 #[error("realtime model returned no text decision")]
464 MissingTextDecision,
465 #[error("realtime model returned {actual} diagnostics, expected {expected}")]
467 DiagnosticCount {
468 expected: usize,
470 actual: usize,
472 },
473 #[error("unsupported realtime temporal source")]
475 UnsupportedTemporalSource,
476 #[error("unsupported realtime target source")]
478 UnsupportedTargetSource,
479 #[error("unsupported realtime frame slot {slot:?}")]
481 UnsupportedSlot {
482 slot: RealtimeFrameSlot,
484 },
485}
486
487#[cfg(test)]
488mod tests {
489 use std::convert::Infallible;
490
491 use eredu_core::{
492 RealtimeFrameConvention, RealtimeInputFrame, RealtimeSlotCoordinate, RealtimeSlotOccupancy,
493 };
494
495 use crate::{
496 RealtimeHostTokenMaterializer, RealtimeIngressContract, RealtimePayloadContract,
497 RealtimePayloadGeneration, RealtimePayloadOwnerIdentity, TokenDomain,
498 };
499
500 use super::*;
501
502 #[derive(Debug, Clone, Eq, PartialEq)]
503 struct Matrix {
504 values: Vec<i32>,
505 shape: [usize; 2],
506 }
507
508 #[derive(Default)]
509 struct Mechanisms {
510 calls: usize,
511 }
512
513 impl RealtimeHostTokenMaterializer for Mechanisms {
514 type Tensor = Matrix;
515 type Error = Infallible;
516
517 fn materialize_i32(
518 &mut self,
519 values: &[i32],
520 shape: [usize; 2],
521 ) -> Result<Self::Tensor, Self::Error> {
522 self.calls += 1;
523 Ok(Matrix {
524 values: values.to_vec(),
525 shape,
526 })
527 }
528 }
529
530 impl RealtimeFrameTensorMechanisms for Mechanisms {
531 type Tensor = Matrix;
532 type Error = Infallible;
533
534 fn column(
535 &mut self,
536 matrix: &Self::Tensor,
537 column: usize,
538 ) -> Result<Self::Tensor, Self::Error> {
539 self.calls += 1;
540 Ok(Matrix {
541 values: matrix
542 .values
543 .chunks_exact(matrix.shape[1])
544 .map(|row| row[column])
545 .collect(),
546 shape: [matrix.shape[0], 1],
547 })
548 }
549
550 fn filled_column(&mut self, token: i32, batch: usize) -> Result<Self::Tensor, Self::Error> {
551 self.calls += 1;
552 Ok(Matrix {
553 values: vec![token; batch],
554 shape: [batch, 1],
555 })
556 }
557
558 fn stack_columns(
559 &mut self,
560 columns: &[Self::Tensor],
561 batch: usize,
562 ) -> Result<Self::Tensor, Self::Error> {
563 self.calls += 1;
564 let mut values = Vec::with_capacity(batch * columns.len());
565 for row in 0..batch {
566 values.extend(columns.iter().map(|column| column.values[row]));
567 }
568 Ok(Matrix {
569 values,
570 shape: [batch, columns.len()],
571 })
572 }
573 }
574
575 fn schedule(convention: RealtimeFrameConvention) -> RealtimeSpeechConfig {
576 RealtimeSpeechConfig::new(2, 1, 1, 1, 9, 8, convention, vec![0, 0, 1]).unwrap()
577 }
578
579 fn payload_history(schedule: &RealtimeSpeechConfig) -> RealtimePayloadHistory<Matrix> {
580 RealtimePayloadHistory::with_contract(
581 RealtimePayloadContract::new(
582 schedule.clone(),
583 1,
584 TokenDomain::new(10),
585 TokenDomain::new(9),
586 RealtimePayloadGeneration::new(1).unwrap(),
587 RealtimePayloadOwnerIdentity::new(1).unwrap(),
588 )
589 .unwrap(),
590 )
591 }
592
593 fn input(
594 schedule: &RealtimeSpeechConfig,
595 mechanisms: &mut Mechanisms,
596 ) -> MaterializedRealtimeInput<Matrix> {
597 materialize_input(
598 schedule,
599 RealtimeInputFrame::new(1, vec![4])
600 .with_forced_text(vec![3])
601 .with_partially_forced_generated_audio(vec![5], vec![false]),
602 mechanisms,
603 )
604 }
605
606 fn materialize_input(
607 schedule: &RealtimeSpeechConfig,
608 input: RealtimeInputFrame,
609 mechanisms: &mut Mechanisms,
610 ) -> MaterializedRealtimeInput<Matrix> {
611 RealtimeIngressContract::new(schedule.clone(), TokenDomain::new(10), TokenDomain::new(9))
612 .unwrap()
613 .validate(&input)
614 .unwrap()
615 .materialize(mechanisms)
616 .unwrap()
617 }
618
619 fn column(value: i32) -> Matrix {
620 Matrix {
621 values: vec![value],
622 shape: [1, 1],
623 }
624 }
625
626 #[test]
627 fn feedback_transition_resolves_temporal_and_ordered_targets_once() {
628 let schedule = schedule(RealtimeFrameConvention::FeedbackAlignedHistory);
629 let mut schedule_state = RealtimeFrameScheduleState::new(schedule.clone());
630 let mut history = payload_history(&schedule);
631 let mut mechanisms = Mechanisms::default();
632 let input = input(&schedule, &mut mechanisms);
633
634 let first = prepare_realtime_frame(
635 &schedule,
636 &mut schedule_state,
637 &mut history,
638 &input,
639 &mut mechanisms,
640 )
641 .unwrap();
642 assert!(first.transition().model_call_required());
643 assert_eq!(first.temporal().len(), 3);
644 assert_eq!(first.directives().len(), 2);
645 assert!(matches!(
646 first.directives()[0],
647 PredictionDirective::Force(_)
648 ));
649 assert!(matches!(first.directives()[1], PredictionDirective::Sample));
650 assert_eq!(schedule_state.frontier(), 1);
651 assert_eq!(
652 history
653 .required(
654 &schedule,
655 RealtimeSlotCoordinate::new(0, RealtimeFrameSlot::Audio(1))
656 )
657 .unwrap()
658 .values,
659 vec![4]
660 );
661 let completed = complete_realtime_frame(
662 &schedule,
663 &mut history,
664 first,
665 vec![column(3), column(6)],
666 Vec::<Matrix>::new(),
667 &mut mechanisms,
668 )
669 .unwrap();
670 assert_eq!(completed.text().values, vec![3]);
671 assert_eq!(completed.decision_audio().values, vec![6]);
672 assert_eq!(completed.sampled_audio().values, vec![6]);
673 assert!(completed.aligned_audio().is_none());
674 assert_eq!(
675 history
676 .required(
677 &schedule,
678 RealtimeSlotCoordinate::new(0, RealtimeFrameSlot::Audio(0))
679 )
680 .unwrap()
681 .values,
682 vec![6]
683 );
684 }
685
686 #[test]
687 fn absolute_initialization_populates_padding_without_model_work() {
688 let schedule = schedule(RealtimeFrameConvention::AbsoluteDelayedSlots);
689 let mut schedule_state = RealtimeFrameScheduleState::new(schedule.clone());
690 let mut history = payload_history(&schedule);
691 let mut mechanisms = Mechanisms::default();
692 let input = input(&schedule, &mut mechanisms);
693 let prepared = prepare_realtime_frame(
694 &schedule,
695 &mut schedule_state,
696 &mut history,
697 &input,
698 &mut mechanisms,
699 )
700 .unwrap();
701 assert!(!prepared.transition().model_call_required());
702 assert!(prepared.temporal().is_empty());
703 assert!(prepared.directives().is_empty());
704 assert_eq!(
705 schedule_state.occupancy(RealtimeSlotCoordinate::new(0, RealtimeFrameSlot::Text)),
706 Some(RealtimeSlotOccupancy::Padding)
707 );
708 assert_eq!(history.len(), 4);
709 let completed = complete_realtime_frame(
710 &schedule,
711 &mut history,
712 prepared,
713 vec![],
714 Vec::<Matrix>::new(),
715 &mut mechanisms,
716 )
717 .unwrap();
718 assert_eq!(completed.text().values, vec![9]);
719 assert!(completed.decision_audio().values.is_empty());
720 assert_eq!(completed.sampled_audio().values, vec![8]);
721 }
722
723 #[test]
724 fn absolute_delayed_frames_retain_forcing_overwrite_targets_and_align_output() {
725 let schedule = RealtimeSpeechConfig::new(
726 2,
727 1,
728 1,
729 1,
730 9,
731 8,
732 RealtimeFrameConvention::AbsoluteDelayedSlots,
733 vec![1, 1, 0],
734 )
735 .unwrap();
736 let mut schedule_state = RealtimeFrameScheduleState::new(schedule.clone());
737 let mut history = payload_history(&schedule);
738 let mut mechanisms = Mechanisms::default();
739
740 let initialization_input = materialize_input(
741 &schedule,
742 RealtimeInputFrame::new(1, vec![4]),
743 &mut mechanisms,
744 );
745 let initialization = prepare_realtime_frame(
746 &schedule,
747 &mut schedule_state,
748 &mut history,
749 &initialization_input,
750 &mut mechanisms,
751 )
752 .unwrap();
753 complete_realtime_frame(
754 &schedule,
755 &mut history,
756 initialization,
757 vec![],
758 Vec::<Matrix>::new(),
759 &mut mechanisms,
760 )
761 .unwrap();
762
763 let second_input = materialize_input(
764 &schedule,
765 RealtimeInputFrame::new(1, vec![6])
766 .with_forced_text(vec![7])
767 .with_forced_generated_audio(vec![6]),
768 &mut mechanisms,
769 );
770 let forced = prepare_realtime_frame(
771 &schedule,
772 &mut schedule_state,
773 &mut history,
774 &second_input,
775 &mut mechanisms,
776 )
777 .unwrap();
778 assert!(matches!(
779 &forced.directives()[0],
780 PredictionDirective::Force(matrix) if matrix.values == vec![9]
781 ));
782 assert!(matches!(
783 &forced.directives()[1],
784 PredictionDirective::Force(matrix) if matrix.values == vec![8]
785 ));
786 let forced_completed = complete_realtime_frame(
787 &schedule,
788 &mut history,
789 forced,
790 vec![column(9), column(8)],
791 Vec::<Matrix>::new(),
792 &mut mechanisms,
793 )
794 .unwrap();
795 assert!(forced_completed.aligned_audio().is_none());
796
797 let next_input = materialize_input(
798 &schedule,
799 RealtimeInputFrame::new(1, vec![7]),
800 &mut mechanisms,
801 );
802 let next = prepare_realtime_frame(
803 &schedule,
804 &mut schedule_state,
805 &mut history,
806 &next_input,
807 &mut mechanisms,
808 )
809 .unwrap();
810 assert_eq!(
811 next.temporal()
812 .iter()
813 .map(|matrix| matrix.values[0])
814 .collect::<Vec<_>>(),
815 vec![9, 8, 6]
816 );
817 assert!(matches!(
818 &next.directives()[0],
819 PredictionDirective::Force(matrix) if matrix.values == vec![7]
820 ));
821 assert!(matches!(
822 &next.directives()[1],
823 PredictionDirective::Force(matrix) if matrix.values == vec![6]
824 ));
825 let next_completed = complete_realtime_frame(
826 &schedule,
827 &mut history,
828 next,
829 vec![column(7), column(6)],
830 Vec::<Matrix>::new(),
831 &mut mechanisms,
832 )
833 .unwrap();
834 assert_eq!(next_completed.aligned_audio().unwrap().values, vec![6]);
835 assert_eq!(next_completed.sampled_audio().values, vec![6]);
836 }
837
838 #[test]
839 fn feedback_frames_overwrite_current_targets_and_align_prior_output() {
840 let schedule = schedule(RealtimeFrameConvention::FeedbackAlignedHistory);
841 let mut schedule_state = RealtimeFrameScheduleState::new(schedule.clone());
842 let mut history = payload_history(&schedule);
843 let mut mechanisms = Mechanisms::default();
844
845 let first_input = materialize_input(
846 &schedule,
847 RealtimeInputFrame::new(1, vec![4])
848 .with_forced_text(vec![3])
849 .with_forced_generated_audio(vec![5]),
850 &mut mechanisms,
851 );
852 let first = prepare_realtime_frame(
853 &schedule,
854 &mut schedule_state,
855 &mut history,
856 &first_input,
857 &mut mechanisms,
858 )
859 .unwrap();
860 complete_realtime_frame(
861 &schedule,
862 &mut history,
863 first,
864 vec![column(3), column(5)],
865 Vec::<Matrix>::new(),
866 &mut mechanisms,
867 )
868 .unwrap();
869
870 let second_input = materialize_input(
871 &schedule,
872 RealtimeInputFrame::new(1, vec![7]).with_forced_text(vec![6]),
873 &mut mechanisms,
874 );
875 let second = prepare_realtime_frame(
876 &schedule,
877 &mut schedule_state,
878 &mut history,
879 &second_input,
880 &mut mechanisms,
881 )
882 .unwrap();
883 assert_eq!(
884 second
885 .temporal()
886 .iter()
887 .map(|matrix| matrix.values[0])
888 .collect::<Vec<_>>(),
889 vec![3, 5, 8]
890 );
891 let second_completed = complete_realtime_frame(
892 &schedule,
893 &mut history,
894 second,
895 vec![column(6), column(2)],
896 Vec::<Matrix>::new(),
897 &mut mechanisms,
898 )
899 .unwrap();
900 assert_eq!(second_completed.aligned_audio().unwrap().values, vec![5]);
901 assert_eq!(
902 history
903 .required(
904 &schedule,
905 RealtimeSlotCoordinate::new(1, RealtimeFrameSlot::Text),
906 )
907 .unwrap()
908 .values,
909 vec![6]
910 );
911 assert_eq!(
912 history
913 .required(
914 &schedule,
915 RealtimeSlotCoordinate::new(1, RealtimeFrameSlot::Audio(0)),
916 )
917 .unwrap()
918 .values,
919 vec![2]
920 );
921 }
922
923 #[test]
924 fn input_schedule_mismatch_fails_before_tensor_mechanisms() {
925 let input_schedule = schedule(RealtimeFrameConvention::FeedbackAlignedHistory);
926 let attempted_schedule = schedule(RealtimeFrameConvention::AbsoluteDelayedSlots);
927 let mut mechanisms = Mechanisms::default();
928 let input = materialize_input(
929 &input_schedule,
930 RealtimeInputFrame::new(1, vec![4]),
931 &mut mechanisms,
932 );
933 let calls_before_prepare = mechanisms.calls;
934 let mut schedule_state = RealtimeFrameScheduleState::new(attempted_schedule.clone());
935 let mut history = payload_history(&attempted_schedule);
936
937 let result = prepare_realtime_frame(
938 &attempted_schedule,
939 &mut schedule_state,
940 &mut history,
941 &input,
942 &mut mechanisms,
943 );
944
945 assert!(matches!(
946 result,
947 Err(RealtimeFrameInterpretationError::InputScheduleMismatch)
948 ));
949 assert_eq!(mechanisms.calls, calls_before_prepare);
950 assert_eq!(schedule_state.frontier(), 0);
951 assert!(history.is_empty());
952 }
953
954 #[test]
955 fn depth_targets_beyond_generated_codebooks_resolve_existing_input() {
956 let schedule = RealtimeSpeechConfig::new(
957 3,
958 2,
959 1,
960 3,
961 9,
962 8,
963 RealtimeFrameConvention::FeedbackAlignedHistory,
964 vec![0, 0, 0, 0],
965 )
966 .unwrap();
967 let mut schedule_state = RealtimeFrameScheduleState::new(schedule.clone());
968 let mut history = payload_history(&schedule);
969 let mut mechanisms = Mechanisms::default();
970 let input = materialize_input(
971 &schedule,
972 RealtimeInputFrame::new(1, vec![4, 7]),
973 &mut mechanisms,
974 );
975
976 let prepared = prepare_realtime_frame(
977 &schedule,
978 &mut schedule_state,
979 &mut history,
980 &input,
981 &mut mechanisms,
982 )
983 .unwrap();
984
985 assert_eq!(prepared.directives().len(), 4);
986 assert!(matches!(
987 &prepared.directives()[0],
988 PredictionDirective::Sample
989 ));
990 assert!(matches!(
991 &prepared.directives()[1],
992 PredictionDirective::Sample
993 ));
994 assert!(matches!(
995 &prepared.directives()[2],
996 PredictionDirective::Force(matrix) if matrix.values == vec![4]
997 ));
998 assert!(matches!(
999 &prepared.directives()[3],
1000 PredictionDirective::Force(matrix) if matrix.values == vec![7]
1001 ));
1002 }
1003}