Skip to main content

eredu_runtime/
realtime_interpreter.rs

1//! Family-blind interpretation of portable realtime schedule transitions.
2
3use 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
15/// Narrow tensor mechanisms needed to populate and resolve scheduled slots.
16pub trait RealtimeFrameTensorMechanisms {
17    /// Opaque/native token payload.
18    type Tensor: Clone;
19    /// Mechanism failure.
20    type Error;
21
22    /// Selects one token column while retaining the batch and singleton axes.
23    fn column(&mut self, matrix: &Self::Tensor, column: usize)
24        -> Result<Self::Tensor, Self::Error>;
25
26    /// Creates one batch-by-one column containing an architecture-selected token.
27    fn filled_column(&mut self, token: i32, batch: usize) -> Result<Self::Tensor, Self::Error>;
28
29    /// Stacks batch-by-one columns into one batch-by-column matrix.
30    fn stack_columns(
31        &mut self,
32        columns: &[Self::Tensor],
33        batch: usize,
34    ) -> Result<Self::Tensor, Self::Error>;
35}
36
37/// Fully resolved temporal inputs and ordered target directives for one step.
38pub 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    /// Returns the authoritative portable schedule transition.
49    pub const fn transition(&self) -> &RealtimeFrameTransition {
50        &self.transition
51    }
52
53    /// Returns temporal text-plus-audio payloads in canonical slot order.
54    pub fn temporal(&self) -> &[T] {
55        &self.temporal
56    }
57
58    /// Returns ordered text-then-depth forced or sampled directives.
59    pub fn directives(&self) -> &[PredictionDirective<T>] {
60        &self.directives
61    }
62
63    /// Returns the validated positive batch size.
64    pub const fn batch(&self) -> usize {
65        self.batch
66    }
67
68    /// Returns whether complete ordered logits diagnostics are requested.
69    pub const fn retains_diagnostics(&self) -> bool {
70        self.retain_diagnostics
71    }
72
73    /// Consumes the step into schedule, inputs, directives, and output policy.
74    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
95/// Opaque output tensors produced by one completely interpreted frame.
96pub 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    /// Returns one text-token column.
106    pub const fn text(&self) -> &T {
107        &self.text
108    }
109
110    /// Returns every depth decision in codebook order.
111    pub const fn decision_audio(&self) -> &T {
112        &self.decision_audio
113    }
114
115    /// Returns generated-audio decisions in generated-codebook order.
116    pub const fn sampled_audio(&self) -> &T {
117        &self.sampled_audio
118    }
119
120    /// Returns delay-aligned generated audio when the schedule exposes it.
121    pub const fn aligned_audio(&self) -> Option<&T> {
122        self.aligned_audio.as_ref()
123    }
124
125    /// Returns ordered per-decision diagnostics.
126    pub fn diagnostics(&self) -> &[D] {
127        &self.diagnostics
128    }
129
130    /// Consumes every opaque output and diagnostic.
131    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
142/// Records sampled decisions, resolves aligned output, and prunes history.
143pub 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
272/// Advances one neutral schedule and resolves all pre-model opaque payloads.
273///
274/// Schedule and history publication is atomic. Sampled target values are
275/// recorded later, after the ordered model decision driver completes.
276pub 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/// Stable failure while interpreting one portable schedule transition.
421#[derive(Debug, thiserror::Error)]
422#[non_exhaustive]
423pub enum RealtimeFrameInterpretationError<E> {
424    /// Opaque input was validated under a different normalized schedule.
425    #[error("materialized realtime input does not match the normalized schedule")]
426    InputScheduleMismatch,
427    /// Prepared frame was resolved under a different normalized schedule.
428    #[error("prepared realtime frame does not match the normalized schedule")]
429    PreparedScheduleMismatch,
430    /// Portable schedule state rejected the transition.
431    #[error(transparent)]
432    Schedule(RealtimeScheduleError),
433    /// Delayed-coordinate payload history rejected an operation.
434    #[error(transparent)]
435    History(RealtimePayloadHistoryError),
436    /// A narrow opaque tensor mechanism failed.
437    #[error("realtime frame tensor mechanism failed")]
438    Mechanism(#[source] E),
439    /// A forced schedule target had no validated opaque payload.
440    #[error("realtime forced slot {slot:?} has no payload")]
441    MissingForcedPayload {
442        /// Missing forced slot.
443        slot: RealtimeFrameSlot,
444    },
445    /// An existing target did not retain its required coordinate.
446    #[error("realtime existing target has no coordinate")]
447    MissingTargetCoordinate,
448    /// Model execution returned the wrong number of ordered decisions.
449    #[error("realtime model returned {actual} decisions, expected {expected}")]
450    DecisionCount {
451        /// Required decision count.
452        expected: usize,
453        /// Actual decision count.
454        actual: usize,
455    },
456    /// Generated decision cardinality overflowed its text prefix.
457    #[error("realtime generated decision count {generated} overflowed")]
458    DecisionCountOverflow {
459        /// Generated-audio decision count.
460        generated: usize,
461    },
462    /// A model-required transition did not return its leading text decision.
463    #[error("realtime model returned no text decision")]
464    MissingTextDecision,
465    /// Diagnostic retention did not match the exact ordered decision count.
466    #[error("realtime model returned {actual} diagnostics, expected {expected}")]
467    DiagnosticCount {
468        /// Required diagnostic count.
469        expected: usize,
470        /// Actual diagnostic count.
471        actual: usize,
472    },
473    /// A schedule exposed a temporal source unknown to this runtime version.
474    #[error("unsupported realtime temporal source")]
475    UnsupportedTemporalSource,
476    /// A schedule exposed a target source unknown to this runtime version.
477    #[error("unsupported realtime target source")]
478    UnsupportedTargetSource,
479    /// A schedule exposed a frame slot unknown to this runtime version.
480    #[error("unsupported realtime frame slot {slot:?}")]
481    UnsupportedSlot {
482        /// Unsupported slot.
483        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}