Skip to main content

eredu_runtime/
realtime.rs

1//! Atomic runtime ownership for one realtime model-generation transition.
2//!
3//! Architecture equations decide what work to submit. This module owns the
4//! portable publication boundary across model/cache state, delayed-frame
5//! scheduling, sequential sampler state, and backend randomness.
6
7use crate::{
8    Sampler, SamplingBackend, SequentialDecisionDriver, SequentialDecisionError,
9    SequentialDecisionPlan, SequentialDecisionPlanError, SubmissionBackend,
10};
11use eredu_core::{
12    scheduler::SemanticStateTransaction, Completion, RealtimeFrameScheduleState,
13    RealtimeInputFrame, RealtimeSampling, RealtimeScheduleError, RealtimeSpeechConfig,
14};
15use std::marker::PhantomData;
16
17/// Canonical composite state for realtime model generation.
18///
19/// `C` is the exact completion type returned by a concrete
20/// [`SubmissionBackend`]. It is carried at the type level so a branch cannot
21/// be published using an unrelated completion kind.
22#[derive(Debug)]
23pub struct RealtimeGenerationState<M, S, R, C> {
24    model_state: M,
25    schedule_state: RealtimeFrameScheduleState,
26    sampling: RealtimeSampling,
27    samplers: Vec<S>,
28    random_state: Option<R>,
29    completion: PhantomData<fn() -> C>,
30}
31
32/// Unpublished branch of every mutable component in one realtime transition.
33#[derive(Debug)]
34pub struct RealtimeGenerationBranch<MB, S, R, C> {
35    model_state: MB,
36    schedule_state: RealtimeFrameScheduleState,
37    sampling: RealtimeSampling,
38    samplers: Vec<S>,
39    random_state: Option<R>,
40    completion: Option<C>,
41}
42
43/// Additive execution contract for architectures that consume realtime frames.
44///
45/// The causal-text base model contract remains unchanged. Realtime composition
46/// implements this extension only when frame ingress is part of execution.
47pub trait RealtimeFrameTransition<MB, S, R, C> {
48    /// Output causally produced by one frame transition.
49    type Output;
50    /// Architecture or mechanism failure before publication.
51    type Error;
52
53    /// Consumes the portable ingress frame, mutates the unpublished branch,
54    /// and returns the exact completion associated with the submitted work.
55    fn execute(
56        &mut self,
57        frame: &RealtimeInputFrame,
58        branch: &mut RealtimeGenerationBranch<MB, S, R, C>,
59    ) -> Result<(Self::Output, C), Self::Error>;
60}
61
62/// Failure while executing or atomically publishing one realtime frame.
63#[derive(Debug, thiserror::Error)]
64#[non_exhaustive]
65pub enum RealtimeFrameExecutionError<TransitionError, TransactionError> {
66    /// Frame ingress or architecture execution failed before publication.
67    #[error("realtime frame transition failed")]
68    Transition(#[source] TransitionError),
69    /// The returned completion could not be attached to the exact branch.
70    #[error(transparent)]
71    CompletionAttachment(#[from] RealtimeCompletionAttachmentError),
72    /// Exact completion or atomic state publication failed.
73    #[error("realtime frame publication failed")]
74    Publication(#[source] TransactionError),
75}
76
77/// Failure to attach exact backend completion evidence to a branch.
78#[derive(Debug, Clone, Copy, Eq, PartialEq, thiserror::Error)]
79#[non_exhaustive]
80pub enum RealtimeCompletionAttachmentError {
81    /// A transition represents one exact submission and already has evidence.
82    #[error("realtime generation branch already has an exact submission completion")]
83    AlreadyAttached,
84}
85
86/// Invalid composite branch construction or publication.
87#[derive(Debug, thiserror::Error)]
88#[non_exhaustive]
89pub enum RealtimeGenerationTransactionError<ModelError, CompletionError> {
90    /// The model/cache transaction could not branch or publish.
91    #[error("realtime model-state transaction failed: {0}")]
92    Model(#[source] ModelError),
93    /// The portable delayed-frame schedule identity did not match.
94    #[error(transparent)]
95    Schedule(#[from] RealtimeScheduleError),
96    /// Sampler count must equal text plus every depth-codebook decision.
97    #[error("realtime generation requires {expected} sampler states, received {actual}")]
98    SamplerCardinality {
99        /// Required text-plus-depth prediction count.
100        expected: usize,
101        /// Supplied sampler-state count.
102        actual: usize,
103    },
104    /// No exact backend completion was attached to the proposed branch.
105    #[error("realtime generation branch has no exact submission completion")]
106    MissingCompletion,
107    /// The exact backend submission has not completed yet.
108    #[error("realtime generation submission is still pending")]
109    CompletionPending,
110    /// Exact completion observation or successful waiting failed.
111    #[error("realtime generation submission failed: {0}")]
112    Completion(#[source] CompletionError),
113}
114
115impl<M, S, R, C> RealtimeGenerationState<M, S, R, C>
116where
117    M: SemanticStateTransaction,
118    M::Error: 'static,
119    C: Completion,
120{
121    /// Creates canonical state bound to one exact normalized schedule.
122    pub fn new(
123        model_state: M,
124        schedule: RealtimeSpeechConfig,
125        sampling: RealtimeSampling,
126        samplers: Vec<S>,
127        random_state: Option<R>,
128    ) -> Result<Self, RealtimeGenerationTransactionError<M::Error, C::Error>> {
129        Self::from_parts(
130            model_state,
131            &schedule,
132            RealtimeFrameScheduleState::new(schedule.clone()),
133            sampling,
134            samplers,
135            random_state,
136        )
137    }
138
139    /// Validates and adopts an existing portable schedule state.
140    ///
141    /// This constructor is useful when resuming in-memory state: the state must
142    /// carry the same complete schedule identity, not merely equal dimensions.
143    pub fn from_parts(
144        model_state: M,
145        schedule: &RealtimeSpeechConfig,
146        schedule_state: RealtimeFrameScheduleState,
147        sampling: RealtimeSampling,
148        samplers: Vec<S>,
149        random_state: Option<R>,
150    ) -> Result<Self, RealtimeGenerationTransactionError<M::Error, C::Error>> {
151        schedule_state.validate_schedule(schedule)?;
152        validate_sampler_cardinality(schedule, samplers.len())?;
153        Ok(Self {
154            model_state,
155            schedule_state,
156            sampling,
157            samplers,
158            random_state,
159            completion: PhantomData,
160        })
161    }
162
163    /// Borrows canonical model/cache state.
164    pub const fn model_state(&self) -> &M {
165        &self.model_state
166    }
167
168    /// Borrows canonical delayed-frame state.
169    pub const fn schedule_state(&self) -> &RealtimeFrameScheduleState {
170        &self.schedule_state
171    }
172
173    /// Returns the exact portable sampling policy paired with sampler and RNG state.
174    pub const fn sampling(&self) -> RealtimeSampling {
175        self.sampling
176    }
177
178    /// Borrows canonical sampler states in text-plus-depth order.
179    pub fn samplers(&self) -> &[S] {
180        &self.samplers
181    }
182
183    /// Borrows canonical backend random state.
184    pub const fn random_state(&self) -> Option<&R> {
185        self.random_state.as_ref()
186    }
187
188    /// Atomically replaces portable controls, sampler state, and randomness while idle.
189    pub fn replace_sampling(
190        &mut self,
191        sampling: RealtimeSampling,
192        samplers: Vec<S>,
193        random_state: Option<R>,
194    ) -> Result<(), RealtimeGenerationTransactionError<M::Error, C::Error>> {
195        validate_sampler_cardinality(self.schedule_state.schedule(), samplers.len())?;
196        self.sampling = sampling;
197        self.samplers = samplers;
198        self.random_state = random_state;
199        Ok(())
200    }
201
202    /// Executes one ingress-dependent transition and publishes every mutable
203    /// component only after its exact completion succeeds.
204    #[allow(
205        clippy::type_complexity,
206        reason = "the signature preserves the exact transition, model, and completion error types"
207    )]
208    pub fn execute_frame_transition<T>(
209        &mut self,
210        frame: &RealtimeInputFrame,
211        transition: &mut T,
212    ) -> Result<
213        T::Output,
214        RealtimeFrameExecutionError<
215            T::Error,
216            RealtimeGenerationTransactionError<M::Error, C::Error>,
217        >,
218    >
219    where
220        S: Clone,
221        R: Clone,
222        T: RealtimeFrameTransition<M::Branch, S, R, C>,
223    {
224        let mut branch = SemanticStateTransaction::branch(self)
225            .map_err(RealtimeFrameExecutionError::Publication)?;
226        let (output, completion) = match transition.execute(frame, &mut branch) {
227            Ok(submission) => submission,
228            Err(error) => {
229                if let Err(discard) = <Self as SemanticStateTransaction>::discard_branch(branch) {
230                    return Err(RealtimeFrameExecutionError::Publication(discard));
231                }
232                return Err(RealtimeFrameExecutionError::Transition(error));
233            }
234        };
235        if let Err(error) = branch.attach_submission_completion(completion) {
236            if let Err(discard) = <Self as SemanticStateTransaction>::discard_branch(branch) {
237                return Err(RealtimeFrameExecutionError::Publication(discard));
238            }
239            return Err(RealtimeFrameExecutionError::CompletionAttachment(error));
240        }
241        SemanticStateTransaction::commit_branch(self, branch)
242            .map_err(RealtimeFrameExecutionError::Publication)?;
243        Ok(output)
244    }
245
246    /// Publishes a branch only after the concrete backend's exact completion
247    /// has reported completion and successful waiting.
248    ///
249    /// The backend bound proves that `C` is a [`SubmissionBackend::Completion`]
250    /// rather than architecture-owned or family-specific completion metadata.
251    pub fn commit_submission_branch<B>(
252        &mut self,
253        branch: RealtimeGenerationBranch<M::Branch, S, R, C>,
254    ) -> Result<(), RealtimeGenerationTransactionError<M::Error, C::Error>>
255    where
256        B: SubmissionBackend<Completion = C>,
257        S: Clone,
258        R: Clone,
259    {
260        self.commit_branch(branch)
261    }
262}
263
264impl<MB, S, R, C> RealtimeGenerationBranch<MB, S, R, C> {
265    /// Borrows the transition-local model/cache state.
266    pub const fn model_state(&self) -> &MB {
267        &self.model_state
268    }
269
270    /// Mutably borrows the transition-local model/cache state.
271    pub fn model_state_mut(&mut self) -> &mut MB {
272        &mut self.model_state
273    }
274
275    /// Borrows the transition-local delayed-frame state.
276    pub const fn schedule_state(&self) -> &RealtimeFrameScheduleState {
277        &self.schedule_state
278    }
279
280    /// Returns branch-local portable sampling controls.
281    pub const fn sampling(&self) -> RealtimeSampling {
282        self.sampling
283    }
284
285    /// Mutably borrows the transition-local delayed-frame state.
286    pub fn schedule_state_mut(&mut self) -> &mut RealtimeFrameScheduleState {
287        &mut self.schedule_state
288    }
289
290    /// Mutably borrows transition-local model/cache and schedule state together.
291    ///
292    /// Full-frame executors use this split to advance the schedule against the
293    /// same unpublished model branch without shortening either transaction.
294    pub fn state_parts_mut(&mut self) -> (&mut MB, &mut RealtimeFrameScheduleState) {
295        (&mut self.model_state, &mut self.schedule_state)
296    }
297
298    /// Borrows transition-local sampler states in text-plus-depth order.
299    pub fn samplers(&self) -> &[S] {
300        &self.samplers
301    }
302
303    /// Borrows transition-local backend random state.
304    pub const fn random_state(&self) -> Option<&R> {
305        self.random_state.as_ref()
306    }
307
308    /// Returns whether this branch already represents one submitted transition.
309    pub const fn has_submission_completion(&self) -> bool {
310        self.completion.is_some()
311    }
312
313    /// Attaches the one exact completion returned by backend submission.
314    pub fn attach_submission_completion(
315        &mut self,
316        completion: C,
317    ) -> Result<(), RealtimeCompletionAttachmentError> {
318        if self.completion.is_some() {
319            return Err(RealtimeCompletionAttachmentError::AlreadyAttached);
320        }
321        self.completion = Some(completion);
322        Ok(())
323    }
324
325    /// Creates the existing sequential decision driver from cloned branch-local
326    /// sampler and random state.
327    ///
328    /// The driver remains the sole state machine for forced, sampled, and
329    /// diagnostic decisions. State is adopted back only after it finishes.
330    pub fn decision_driver<B>(
331        &self,
332        plan: SequentialDecisionPlan<B::Token>,
333        temperatures: Vec<f32>,
334    ) -> Result<SequentialDecisionDriver<B, S>, SequentialDecisionPlanError>
335    where
336        B: SamplingBackend<RandomState = R>,
337        S: Sampler<B> + Clone,
338        R: Clone,
339    {
340        SequentialDecisionDriver::new(
341            plan,
342            self.samplers.clone(),
343            temperatures,
344            self.random_state.clone(),
345        )
346    }
347
348    /// Atomically adopts sampler and random state from a completed existing
349    /// sequential decision driver.
350    ///
351    /// An incomplete driver returns an error and leaves this branch unchanged.
352    pub fn adopt_decision_driver<B>(
353        &mut self,
354        driver: SequentialDecisionDriver<B, S>,
355    ) -> Result<(), SequentialDecisionError<B::Error>>
356    where
357        B: SamplingBackend<RandomState = R>,
358        S: Sampler<B>,
359    {
360        let (samplers, random_state) = driver.finish_into_sampling_state()?;
361        self.samplers = samplers;
362        self.random_state = random_state;
363        Ok(())
364    }
365}
366
367impl<M, S, R, C> SemanticStateTransaction for RealtimeGenerationState<M, S, R, C>
368where
369    M: SemanticStateTransaction,
370    M::Error: 'static,
371    S: Clone,
372    R: Clone,
373    C: Completion,
374{
375    type Branch = RealtimeGenerationBranch<M::Branch, S, R, C>;
376    type Error = RealtimeGenerationTransactionError<M::Error, C::Error>;
377
378    fn branch(&self) -> Result<Self::Branch, Self::Error> {
379        Ok(RealtimeGenerationBranch {
380            model_state: self.model_state.branch().map_err(Self::Error::Model)?,
381            schedule_state: self.schedule_state.branch()?,
382            sampling: self.sampling,
383            samplers: self.samplers.clone(),
384            random_state: self.random_state.clone(),
385            completion: None,
386        })
387    }
388
389    fn commit_branch(&mut self, branch: Self::Branch) -> Result<(), Self::Error> {
390        let RealtimeGenerationBranch {
391            model_state,
392            schedule_state,
393            sampling,
394            samplers,
395            random_state,
396            completion,
397        } = branch;
398
399        let rollback = |model_state, error| match M::discard_branch(model_state) {
400            Ok(()) => error,
401            Err(error) => Self::Error::Model(error),
402        };
403        let Some(completion) = completion else {
404            return Err(rollback(model_state, Self::Error::MissingCompletion));
405        };
406        let complete = match completion.is_complete() {
407            Ok(complete) => complete,
408            Err(error) => {
409                return Err(rollback(model_state, Self::Error::Completion(error)));
410            }
411        };
412        if !complete {
413            return Err(rollback(model_state, Self::Error::CompletionPending));
414        }
415        if let Err(error) = completion.wait() {
416            return Err(rollback(model_state, Self::Error::Completion(error)));
417        }
418        if let Err(error) = self
419            .schedule_state
420            .validate_schedule(schedule_state.schedule())
421        {
422            return Err(rollback(model_state, error.into()));
423        }
424        if let Err(error) =
425            validate_sampler_cardinality(self.schedule_state.schedule(), samplers.len())
426        {
427            return Err(rollback(model_state, error));
428        }
429
430        self.model_state
431            .commit_branch(model_state)
432            .map_err(Self::Error::Model)?;
433        self.schedule_state.commit_branch(schedule_state)?;
434        self.sampling = sampling;
435        self.samplers = samplers;
436        self.random_state = random_state;
437        Ok(())
438    }
439
440    fn discard_branch(branch: Self::Branch) -> Result<(), Self::Error> {
441        let RealtimeGenerationBranch {
442            model_state,
443            completion,
444            ..
445        } = branch;
446        let completion_error = completion.and_then(|completion| completion.wait().err());
447        M::discard_branch(model_state).map_err(Self::Error::Model)?;
448        if let Some(error) = completion_error {
449            return Err(Self::Error::Completion(error));
450        }
451        Ok(())
452    }
453
454    fn permits_parallel_branches(&self) -> bool {
455        self.model_state.permits_parallel_branches()
456    }
457}
458
459fn validate_sampler_cardinality<ModelError, CompletionError>(
460    schedule: &RealtimeSpeechConfig,
461    actual: usize,
462) -> Result<(), RealtimeGenerationTransactionError<ModelError, CompletionError>> {
463    let expected = schedule.depth_audio_codebooks().checked_add(1).ok_or(
464        RealtimeGenerationTransactionError::SamplerCardinality {
465            expected: usize::MAX,
466            actual,
467        },
468    )?;
469    if actual != expected {
470        return Err(RealtimeGenerationTransactionError::SamplerCardinality { expected, actual });
471    }
472    Ok(())
473}
474
475#[cfg(test)]
476mod tests {
477    use super::*;
478    use crate::{PenaltyConfig, PredictionDirective};
479    use eredu_core::{
480        scheduler::SemanticStateTransaction, RealtimeFrameConvention, RealtimeFrameForcing,
481        RealtimeInputFrame, TokenFilter,
482    };
483    use std::{cell::Cell, convert::Infallible, fmt, rc::Rc};
484
485    #[derive(Debug, Clone, Eq, PartialEq)]
486    struct ModelState {
487        model_step: i32,
488        cache_offset: i32,
489    }
490
491    #[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
492    #[error("model transaction failed")]
493    struct ModelError;
494
495    impl SemanticStateTransaction for ModelState {
496        type Branch = Self;
497        type Error = ModelError;
498
499        fn branch(&self) -> Result<Self::Branch, Self::Error> {
500            Ok(self.clone())
501        }
502
503        fn commit_branch(&mut self, branch: Self::Branch) -> Result<(), Self::Error> {
504            *self = branch;
505            Ok(())
506        }
507    }
508
509    #[derive(Debug, Clone, Copy, Eq, PartialEq)]
510    enum CompletionOutcome {
511        Success,
512        Pending,
513        Failure,
514    }
515
516    #[derive(Debug, Clone)]
517    struct MockCompletion {
518        outcome: CompletionOutcome,
519        waits: Rc<Cell<usize>>,
520    }
521
522    #[derive(Debug, Clone, Eq, PartialEq)]
523    struct CompletionError;
524
525    impl fmt::Display for CompletionError {
526        fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
527            formatter.write_str("mock submission failed")
528        }
529    }
530
531    impl std::error::Error for CompletionError {}
532
533    impl MockCompletion {
534        fn new(outcome: CompletionOutcome) -> (Self, Rc<Cell<usize>>) {
535            let waits = Rc::new(Cell::new(0));
536            (
537                Self {
538                    outcome,
539                    waits: waits.clone(),
540                },
541                waits,
542            )
543        }
544    }
545
546    impl Completion for MockCompletion {
547        type Error = CompletionError;
548
549        fn is_complete(&self) -> Result<bool, Self::Error> {
550            Ok(self.outcome != CompletionOutcome::Pending)
551        }
552
553        fn wait(&self) -> Result<(), Self::Error> {
554            self.waits.set(self.waits.get() + 1);
555            match self.outcome {
556                CompletionOutcome::Success => Ok(()),
557                CompletionOutcome::Failure => Err(CompletionError),
558                CompletionOutcome::Pending => panic!("pending completion must not be waited"),
559            }
560        }
561    }
562
563    struct Backend;
564
565    impl SamplingBackend for Backend {
566        type Logits = i32;
567        type Token = i32;
568        type RandomState = i32;
569        type Context = ();
570        type Error = String;
571
572        fn error(message: String) -> Self::Error {
573            message
574        }
575
576        fn validate_token(
577            token: &Self::Token,
578            domain: crate::TokenDomain,
579            _: &Self::Context,
580        ) -> Result<Self::Token, Self::Error> {
581            usize::try_from(*token)
582                .ok()
583                .filter(|token| *token < domain.cardinality())
584                .map(|_| *token)
585                .ok_or_else(|| "token is outside its decision domain".into())
586        }
587
588        fn scale_temperature(
589            logits: &Self::Logits,
590            _: f32,
591            _: &Self::Context,
592        ) -> Result<Self::Logits, Self::Error> {
593            Ok(*logits)
594        }
595
596        fn apply_penalties(
597            logits: &Self::Logits,
598            _: &[u32],
599            _: PenaltyConfig,
600            _: &Self::Context,
601        ) -> Result<Self::Logits, Self::Error> {
602            Ok(*logits)
603        }
604
605        fn apply_top_k(
606            logits: Self::Logits,
607            _: i32,
608            _: &Self::Context,
609        ) -> Result<Self::Logits, Self::Error> {
610            Ok(logits)
611        }
612
613        fn apply_top_p(
614            logits: Self::Logits,
615            _: f32,
616            _: &Self::Context,
617        ) -> Result<Self::Logits, Self::Error> {
618            Ok(logits)
619        }
620
621        fn apply_min_p(
622            logits: Self::Logits,
623            _: f32,
624            _: &Self::Context,
625        ) -> Result<Self::Logits, Self::Error> {
626            Ok(logits)
627        }
628
629        fn apply_token_filter(
630            logits: &Self::Logits,
631            _: &TokenFilter,
632            _: &Self::Context,
633        ) -> Result<Self::Logits, Self::Error> {
634            Ok(*logits)
635        }
636
637        fn apply_mirostat(
638            logits: &Self::Logits,
639            _: &[u32],
640            _: PenaltyConfig,
641            _: f32,
642            _: f32,
643            _: &Self::Context,
644        ) -> Result<Self::Logits, Self::Error> {
645            Ok(*logits)
646        }
647
648        fn sample_raw(
649            logits: &Self::Logits,
650            _: f32,
651            random: Option<&mut Self::RandomState>,
652            _: &Self::Context,
653        ) -> Result<Self::Token, Self::Error> {
654            if let Some(random) = random {
655                *random += 10;
656            }
657            Ok(*logits)
658        }
659
660        fn sample_processed(
661            logits: &Self::Logits,
662            temperature: f32,
663            random: Option<&mut Self::RandomState>,
664            context: &Self::Context,
665        ) -> Result<Self::Token, Self::Error> {
666            Self::sample_raw(logits, temperature, random, context)
667        }
668
669        fn token_id(token: &Self::Token, _: &Self::Context) -> Result<u32, Self::Error> {
670            u32::try_from(*token).map_err(|error| error.to_string())
671        }
672
673        fn token_probability(
674            _: &Self::Logits,
675            _: u32,
676            _: &Self::Context,
677        ) -> Result<f32, Self::Error> {
678            Ok(1.0)
679        }
680    }
681
682    #[derive(Debug, Clone, Eq, PartialEq)]
683    struct StatefulSampler(i32);
684
685    impl Sampler<Backend> for StatefulSampler {
686        fn sample(
687            &mut self,
688            logits: &i32,
689            _: f32,
690            random: Option<&mut i32>,
691            _: &(),
692        ) -> Result<i32, String> {
693            self.0 += 1;
694            if let Some(random) = random {
695                *random += 10;
696            }
697            Ok(*logits + self.0)
698        }
699    }
700
701    type State = RealtimeGenerationState<ModelState, StatefulSampler, i32, MockCompletion>;
702
703    fn schedule() -> RealtimeSpeechConfig {
704        RealtimeSpeechConfig::new(
705            2,
706            1,
707            1,
708            1,
709            100,
710            64,
711            RealtimeFrameConvention::FeedbackAlignedHistory,
712            vec![0, 0, 1],
713        )
714        .unwrap()
715    }
716
717    fn state() -> State {
718        State::new(
719            ModelState {
720                model_step: 1,
721                cache_offset: 2,
722            },
723            schedule(),
724            RealtimeSampling::greedy(),
725            vec![StatefulSampler(0), StatefulSampler(5)],
726            Some(7),
727        )
728        .unwrap()
729    }
730
731    fn mutate_every_component(
732        frame: &RealtimeInputFrame,
733        branch: &mut RealtimeGenerationBranch<ModelState, StatefulSampler, i32, MockCompletion>,
734    ) {
735        branch.model_state_mut().model_step = frame.input_audio_tokens()[0];
736        branch.model_state_mut().cache_offset = frame.forced_text_tokens().unwrap()[0];
737        branch
738            .schedule_state_mut()
739            .advance(&schedule(), &RealtimeFrameForcing::none(&schedule()))
740            .unwrap();
741        let plan = SequentialDecisionPlan::new(
742            [PredictionDirective::Sample, PredictionDirective::Sample],
743            false,
744            false,
745        )
746        .unwrap();
747        let mut driver = branch
748            .decision_driver::<Backend>(plan, vec![1.0, 1.0])
749            .unwrap();
750        assert_eq!(
751            driver
752                .resolve(0, &10, crate::TokenDomain::new(100), &())
753                .unwrap(),
754            11
755        );
756        assert_eq!(
757            driver
758                .resolve(1, &20, crate::TokenDomain::new(100), &())
759                .unwrap(),
760            26
761        );
762        branch.adopt_decision_driver(driver).unwrap();
763    }
764
765    struct FrameTransition {
766        outcome: CompletionOutcome,
767        waits: Rc<Cell<usize>>,
768    }
769
770    impl RealtimeFrameTransition<ModelState, StatefulSampler, i32, MockCompletion> for FrameTransition {
771        type Output = i32;
772        type Error = Infallible;
773
774        fn execute(
775            &mut self,
776            frame: &RealtimeInputFrame,
777            branch: &mut RealtimeGenerationBranch<ModelState, StatefulSampler, i32, MockCompletion>,
778        ) -> Result<(Self::Output, MockCompletion), Self::Error> {
779            mutate_every_component(frame, branch);
780            let output = branch.model_state.model_step + branch.model_state.cache_offset;
781            Ok((
782                output,
783                MockCompletion {
784                    outcome: self.outcome,
785                    waits: Rc::clone(&self.waits),
786                },
787            ))
788        }
789    }
790
791    #[test]
792    fn realtime_frame_extension_publishes_ingress_and_state_atomically() {
793        let mut state = state();
794        let waits = Rc::new(Cell::new(0));
795        let mut transition = FrameTransition {
796            outcome: CompletionOutcome::Success,
797            waits: Rc::clone(&waits),
798        };
799        let output = state
800            .execute_frame_transition(
801                &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
802                &mut transition,
803            )
804            .unwrap();
805
806        assert_eq!(waits.get(), 1);
807        assert_eq!(output, 19);
808        assert_eq!(
809            state.model_state(),
810            &ModelState {
811                model_step: 9,
812                cache_offset: 10
813            }
814        );
815        assert_eq!(state.schedule_state().frontier(), 1);
816        assert_eq!(state.samplers(), [StatefulSampler(1), StatefulSampler(6)]);
817        assert_eq!(state.random_state(), Some(&27));
818    }
819
820    #[test]
821    fn failed_completion_rolls_back_every_component() {
822        let mut state = state();
823        let waits = Rc::new(Cell::new(0));
824        let mut transition = FrameTransition {
825            outcome: CompletionOutcome::Failure,
826            waits: Rc::clone(&waits),
827        };
828        assert!(matches!(
829            state.execute_frame_transition(
830                &RealtimeInputFrame::new(1, vec![40]).with_forced_text(vec![50]),
831                &mut transition,
832            ),
833            Err(RealtimeFrameExecutionError::Publication(
834                RealtimeGenerationTransactionError::Completion(_)
835            ))
836        ));
837
838        assert_eq!(waits.get(), 1);
839        assert_eq!(
840            state.model_state(),
841            &ModelState {
842                model_step: 1,
843                cache_offset: 2
844            }
845        );
846        assert_eq!(state.schedule_state().frontier(), 0);
847        assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
848        assert_eq!(state.random_state(), Some(&7));
849    }
850
851    #[test]
852    fn pending_completion_is_not_waited_or_published() {
853        let mut state = state();
854        let mut branch = state.branch().unwrap();
855        mutate_every_component(
856            &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
857            &mut branch,
858        );
859        let (completion, waits) = MockCompletion::new(CompletionOutcome::Pending);
860        branch.attach_submission_completion(completion).unwrap();
861        assert!(matches!(
862            state.commit_branch(branch),
863            Err(RealtimeGenerationTransactionError::CompletionPending)
864        ));
865        assert_eq!(waits.get(), 0);
866        assert_eq!(
867            state.model_state(),
868            &ModelState {
869                model_step: 1,
870                cache_offset: 2
871            }
872        );
873        assert_eq!(state.schedule_state().frontier(), 0);
874        assert_eq!(state.random_state(), Some(&7));
875    }
876
877    #[test]
878    fn discard_leaves_canonical_state_unchanged() {
879        let state = state();
880        let mut branch = state.branch().unwrap();
881        mutate_every_component(
882            &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
883            &mut branch,
884        );
885        State::discard_branch(branch).unwrap();
886        assert_eq!(
887            state.model_state(),
888            &ModelState {
889                model_step: 1,
890                cache_offset: 2
891            }
892        );
893        assert_eq!(state.schedule_state().frontier(), 0);
894        assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
895        assert_eq!(state.random_state(), Some(&7));
896    }
897
898    #[test]
899    fn composite_discard_and_failed_commit_delegate_model_rollback() {
900        #[derive(Debug, Clone)]
901        struct TrackingModel(Rc<Cell<usize>>);
902
903        impl SemanticStateTransaction for TrackingModel {
904            type Branch = Self;
905            type Error = ModelError;
906
907            fn branch(&self) -> Result<Self::Branch, Self::Error> {
908                Ok(self.clone())
909            }
910
911            fn commit_branch(&mut self, branch: Self::Branch) -> Result<(), Self::Error> {
912                *self = branch;
913                Ok(())
914            }
915
916            fn discard_branch(branch: Self::Branch) -> Result<(), Self::Error> {
917                branch.0.set(branch.0.get() + 1);
918                Ok(())
919            }
920        }
921
922        type TrackingState =
923            RealtimeGenerationState<TrackingModel, StatefulSampler, i32, MockCompletion>;
924        let rollbacks = Rc::new(Cell::new(0));
925        let mut state = TrackingState::new(
926            TrackingModel(rollbacks.clone()),
927            schedule(),
928            RealtimeSampling::greedy(),
929            vec![StatefulSampler(0), StatefulSampler(0)],
930            None,
931        )
932        .unwrap();
933
934        struct FailingTransition;
935
936        impl RealtimeFrameTransition<TrackingModel, StatefulSampler, i32, MockCompletion>
937            for FailingTransition
938        {
939            type Output = ();
940            type Error = ModelError;
941
942            fn execute(
943                &mut self,
944                _frame: &RealtimeInputFrame,
945                _branch: &mut RealtimeGenerationBranch<
946                    TrackingModel,
947                    StatefulSampler,
948                    i32,
949                    MockCompletion,
950                >,
951            ) -> Result<(Self::Output, MockCompletion), Self::Error> {
952                Err(ModelError)
953            }
954        }
955
956        assert!(matches!(
957            state.execute_frame_transition(
958                &RealtimeInputFrame::new(1, vec![9]),
959                &mut FailingTransition,
960            ),
961            Err(RealtimeFrameExecutionError::Transition(ModelError))
962        ));
963        assert_eq!(rollbacks.get(), 1);
964
965        TrackingState::discard_branch(state.branch().unwrap()).unwrap();
966        assert_eq!(rollbacks.get(), 2);
967
968        let missing_completion = state.branch().unwrap();
969        assert!(matches!(
970            state.commit_branch(missing_completion),
971            Err(RealtimeGenerationTransactionError::MissingCompletion)
972        ));
973        assert_eq!(rollbacks.get(), 3);
974    }
975
976    #[test]
977    fn missing_completion_cannot_publish() {
978        let mut state = state();
979        let mut branch = state.branch().unwrap();
980        mutate_every_component(
981            &RealtimeInputFrame::new(1, vec![9]).with_forced_text(vec![10]),
982            &mut branch,
983        );
984        assert!(matches!(
985            state.commit_branch(branch),
986            Err(RealtimeGenerationTransactionError::MissingCompletion)
987        ));
988        assert_eq!(state.schedule_state().frontier(), 0);
989    }
990
991    #[test]
992    fn schedule_identity_and_sampler_cardinality_are_exact() {
993        let other = RealtimeSpeechConfig::new(
994            2,
995            1,
996            1,
997            1,
998            100,
999            64,
1000            RealtimeFrameConvention::AbsoluteDelayedSlots,
1001            vec![0, 0, 1],
1002        )
1003        .unwrap();
1004        assert!(matches!(
1005            State::from_parts(
1006                ModelState {
1007                    model_step: 1,
1008                    cache_offset: 2,
1009                },
1010                &schedule(),
1011                RealtimeFrameScheduleState::new(other),
1012                RealtimeSampling::greedy(),
1013                vec![StatefulSampler(0), StatefulSampler(0)],
1014                Some(0),
1015            ),
1016            Err(RealtimeGenerationTransactionError::Schedule(
1017                RealtimeScheduleError::ScheduleMismatch
1018            ))
1019        ));
1020        assert!(matches!(
1021            State::new(
1022                ModelState {
1023                    model_step: 1,
1024                    cache_offset: 2,
1025                },
1026                schedule(),
1027                RealtimeSampling::greedy(),
1028                vec![StatefulSampler(0)],
1029                Some(0),
1030            ),
1031            Err(RealtimeGenerationTransactionError::SamplerCardinality {
1032                expected: 2,
1033                actual: 1
1034            })
1035        ));
1036
1037        let mut state = state();
1038        let mut branch = state.branch().unwrap();
1039        branch.samplers.pop();
1040        let (completion, _) = MockCompletion::new(CompletionOutcome::Success);
1041        branch.attach_submission_completion(completion).unwrap();
1042        assert!(matches!(
1043            state.commit_branch(branch),
1044            Err(RealtimeGenerationTransactionError::SamplerCardinality {
1045                expected: 2,
1046                actual: 1
1047            })
1048        ));
1049        assert_eq!(state.samplers().len(), 2);
1050    }
1051
1052    #[test]
1053    fn incomplete_decision_driver_does_not_change_branch_sampling_state() {
1054        let state = state();
1055        let mut branch = state.branch().unwrap();
1056        let plan = SequentialDecisionPlan::new(
1057            [PredictionDirective::Sample, PredictionDirective::Sample],
1058            false,
1059            false,
1060        )
1061        .unwrap();
1062        let mut driver = branch
1063            .decision_driver::<Backend>(plan, vec![1.0, 1.0])
1064            .unwrap();
1065        driver
1066            .resolve(0, &10, crate::TokenDomain::new(100), &())
1067            .unwrap();
1068        assert!(matches!(
1069            branch.adopt_decision_driver(driver),
1070            Err(SequentialDecisionError::Incomplete { .. })
1071        ));
1072        assert_eq!(branch.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
1073        assert_eq!(branch.random_state(), Some(&7));
1074    }
1075
1076    #[test]
1077    fn invalid_decision_tokens_leave_canonical_realtime_state_unchanged() {
1078        let state = state();
1079        let mut branch = state.branch().unwrap();
1080        branch.model_state_mut().model_step = 9;
1081        branch.model_state_mut().cache_offset = 10;
1082        branch
1083            .schedule_state_mut()
1084            .advance(&schedule(), &RealtimeFrameForcing::none(&schedule()))
1085            .unwrap();
1086        let plan = SequentialDecisionPlan::new(
1087            [PredictionDirective::Sample, PredictionDirective::Force(100)],
1088            false,
1089            true,
1090        )
1091        .unwrap();
1092        let mut driver = branch
1093            .decision_driver::<Backend>(plan, vec![1.0, 1.0])
1094            .unwrap();
1095        assert!(matches!(
1096            driver.resolve(0, &100, crate::TokenDomain::new(100), &()),
1097            Err(SequentialDecisionError::Backend(_))
1098        ));
1099        assert!(driver.decisions().is_empty());
1100        drop(driver);
1101        drop(branch);
1102
1103        assert_eq!(state.model_state().model_step, 1);
1104        assert_eq!(state.model_state().cache_offset, 2);
1105        assert_eq!(state.schedule_state().frontier(), 0);
1106        assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
1107        assert_eq!(state.random_state(), Some(&7));
1108
1109        let mut branch = state.branch().unwrap();
1110        branch.model_state_mut().cache_offset = 11;
1111        branch
1112            .schedule_state_mut()
1113            .advance(&schedule(), &RealtimeFrameForcing::none(&schedule()))
1114            .unwrap();
1115        let plan = SequentialDecisionPlan::new(
1116            [
1117                PredictionDirective::Force(1),
1118                PredictionDirective::Force(100),
1119            ],
1120            false,
1121            true,
1122        )
1123        .unwrap();
1124        let driver = branch
1125            .decision_driver::<Backend>(plan, vec![1.0, 1.0])
1126            .unwrap();
1127        assert!(matches!(
1128            driver.forced_tail_tokens(0, 2, [crate::TokenDomain::new(100); 2], &()),
1129            Err(SequentialDecisionError::Backend(_))
1130        ));
1131        drop(driver);
1132        drop(branch);
1133        assert_eq!(state.model_state().cache_offset, 2);
1134        assert_eq!(state.schedule_state().frontier(), 0);
1135        assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
1136        assert_eq!(state.random_state(), Some(&7));
1137    }
1138
1139    #[test]
1140    fn duplicate_completion_evidence_is_rejected() {
1141        let state = state();
1142        let mut branch = state.branch().unwrap();
1143        branch
1144            .attach_submission_completion(MockCompletion::new(CompletionOutcome::Success).0)
1145            .unwrap();
1146        assert_eq!(
1147            branch.attach_submission_completion(MockCompletion::new(CompletionOutcome::Success).0),
1148            Err(RealtimeCompletionAttachmentError::AlreadyAttached)
1149        );
1150    }
1151
1152    #[test]
1153    fn sampling_replacement_binds_controls_samplers_and_randomness_atomically() {
1154        let mut state = state();
1155        let sampling = RealtimeSampling::new(0.25, 0.75, 42)
1156            .unwrap()
1157            .with_top_k(Some(3), Some(5))
1158            .unwrap();
1159        assert!(matches!(
1160            state.replace_sampling(sampling, vec![StatefulSampler(9)], Some(11)),
1161            Err(RealtimeGenerationTransactionError::SamplerCardinality {
1162                expected: 2,
1163                actual: 1
1164            })
1165        ));
1166        assert_eq!(state.sampling(), RealtimeSampling::greedy());
1167        assert_eq!(state.samplers(), [StatefulSampler(0), StatefulSampler(5)]);
1168        assert_eq!(state.random_state(), Some(&7));
1169
1170        state
1171            .replace_sampling(
1172                sampling,
1173                vec![StatefulSampler(9), StatefulSampler(10)],
1174                Some(11),
1175            )
1176            .unwrap();
1177        assert_eq!(state.sampling(), sampling);
1178        assert_eq!(state.samplers(), [StatefulSampler(9), StatefulSampler(10)]);
1179        assert_eq!(state.random_state(), Some(&11));
1180        assert_eq!(state.branch().unwrap().sampling(), sampling);
1181    }
1182}