helena 0.1.0

Core types and component interfaces for helena, a latent data-to-waveform generation platform.
Documentation
//! The latent-kind axis, as types.
//!
//! [`Continuous`] and [`Tokens`] are uninhabited markers; [`LatentKind`]
//! binds each to its host-side payload ([`FrameSeq`] / [`TokenGrid`]) and its
//! serialization tag. The trait is sealed: the kind axis is a closed product
//! decision (PRD §9.2 resolved continuous as the engine, tokens as the
//! de-prioritized alternative), not a plugin surface — extensibility lives at
//! the *seams*, which are generic over the kind.

use serde::{Deserialize, Serialize, de::DeserializeOwned};

use super::frame_seq::FrameSeq;
use super::tokens::TokenGrid;
use super::value::{AnyLatent, Latent};

mod sealed {
    /// Seals [`super::LatentKind`]: the kind axis is closed by design.
    pub trait Sealed {}
    impl Sealed for super::Continuous {}
    impl Sealed for super::Tokens {}
}

/// The dynamic kind tag, used only at serialization edges (artifact
/// metadata, cache records, CLI reports). Inside the pipeline the kind is a
/// type parameter; the tag is *stored* where it must be dynamic, never
/// guessed from structure (the old single-codebook-RVQ-vs-discrete inference
/// is gone — codebook count is a [`TokenGrid`] fact, not a kind).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum KindTag {
    /// Dense continuous `[frames, dim]` latents.
    Continuous,
    /// Discrete codec tokens (one or more codebooks).
    Tokens,
}

impl std::fmt::Display for KindTag {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            KindTag::Continuous => write!(f, "continuous"),
            KindTag::Tokens => write!(f, "tokens"),
        }
    }
}

/// A framed sequence: countable and sliceable along the frame axis.
///
/// The capability the streaming machinery needs from any latent payload —
/// both [`FrameSeq`] (rows) and [`TokenGrid`] (columns) implement it, so the
/// streaming law and block iteration are written once, kind-generically.
pub trait Framed {
    /// Number of frames along the time axis.
    fn frames(&self) -> usize;

    /// A copy of frames `start .. start + len`.
    ///
    /// Callers stay in bounds: `start + len <= self.frames()`. Implementations
    /// may panic on out-of-range slices, as slice indexing does.
    fn slice_frames(&self, start: usize, len: usize) -> Self;
}

/// One side of the latent-kind axis: binds a marker to its payload and tag.
///
/// Sealed (see the module doc). `#[doc(hidden)]` items are wiring between
/// the static and dynamic representations; they are not API.
pub trait LatentKind: sealed::Sealed + Send + Sync + 'static {
    /// Host-side payload for this kind.
    type Repr: Framed
        + Clone
        + PartialEq
        + std::fmt::Debug
        + Serialize
        + DeserializeOwned
        + Send
        + Sync
        + 'static;

    /// The dynamic tag stored at serialization edges.
    const TAG: KindTag;

    /// Wiring: wrap a typed latent into the dynamic representation.
    #[doc(hidden)]
    fn wrap_any(latent: Latent<Self>) -> AnyLatent
    where
        Self: Sized;

    /// Wiring: recover the typed latent if the dynamic value holds this kind.
    #[doc(hidden)]
    fn unwrap_any(any: AnyLatent) -> Option<Latent<Self>>
    where
        Self: Sized;
}

/// Marker: the continuous latent kind — the control surface (PRD §9.2).
///
/// Uninhabited; derives exist only to satisfy derive-generated bounds on
/// types generic over the kind.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Continuous {}

impl LatentKind for Continuous {
    type Repr = FrameSeq;
    const TAG: KindTag = KindTag::Continuous;

    fn wrap_any(latent: Latent<Self>) -> AnyLatent {
        AnyLatent::Continuous(latent)
    }

    fn unwrap_any(any: AnyLatent) -> Option<Latent<Self>> {
        match any {
            AnyLatent::Continuous(latent) => Some(latent),
            AnyLatent::Tokens(_) => None,
        }
    }
}

/// Marker: the token latent kind (de-prioritized backend, PRD §9.3).
///
/// Uninhabited; derives exist only to satisfy derive-generated bounds on
/// types generic over the kind.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Tokens {}

impl LatentKind for Tokens {
    type Repr = TokenGrid;
    const TAG: KindTag = KindTag::Tokens;

    fn wrap_any(latent: Latent<Self>) -> AnyLatent {
        AnyLatent::Tokens(latent)
    }

    fn unwrap_any(any: AnyLatent) -> Option<Latent<Self>> {
        match any {
            AnyLatent::Tokens(latent) => Some(latent),
            AnyLatent::Continuous(_) => None,
        }
    }
}