youtube-legend-cli 0.4.0

Non-interactive Rust CLI that downloads YouTube subtitles through third-party providers, using a native Unix stdin/stdout interface.
//! Humanised timing and the seeded randomness behind it.
//!
//! One reason to change: what a delay, a pause or a resting pointer
//! position has to look like to a detector reading the second moment.

// ---------------------------------------------------------------------------
// Deterministic per-process randomness
// ---------------------------------------------------------------------------

/// Small xorshift64* generator.
///
/// A dependency-free PRNG is enough here: nothing security-sensitive
/// depends on it. What *does* matter is that the sequence is stable for
/// the lifetime of one invocation, so canvas and audio noise stay
/// self-consistent across repeated reads within the same page.
#[derive(Debug, Clone)]
pub(crate) struct SessionRng {
    state: u64,
}

impl SessionRng {
    /// Seed the generator from the wall clock and the process id.
    ///
    /// Two concurrent invocations of the binary therefore get different
    /// fingerprints, while a single invocation stays coherent.
    pub(crate) fn from_entropy() -> Self {
        let nanos = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_nanos() as u64)
            .unwrap_or(0x2545_F491_4F6C_DD1D);
        Self::from_seed(nanos ^ (u64::from(std::process::id()) << 32))
    }

    /// Seed the generator explicitly. Used by the unit tests to assert
    /// reproducibility.
    pub(crate) fn from_seed(seed: u64) -> Self {
        Self {
            // A zero state is a fixed point of xorshift; nudge it away.
            state: if seed == 0 {
                0x9E37_79B9_7F4A_7C15
            } else {
                seed
            },
        }
    }

    /// Next raw 64-bit value.
    pub(crate) fn next_u64(&mut self) -> u64 {
        let mut x = self.state;
        x ^= x >> 12;
        x ^= x << 25;
        x ^= x >> 27;
        self.state = x;
        x.wrapping_mul(0x2545_F491_4F6C_DD1D)
    }

    // `next_f64`, `next_range_u64` and `next_range_f64` lived here and
    // were removed on 2026-09-04 with the browser subsystem: their only
    // callers were the humanised input path. `next_u64` stays because
    // `provider::decopy` draws from it to build request identifiers,
    // which is the HTTP path and outlives the browser.
}

static ROOT_SEED: std::sync::LazyLock<std::sync::Mutex<SessionRng>> =
    std::sync::LazyLock::new(|| {
        std::sync::Mutex::new(match crate::config::tuning_u64("stealth.seed") {
            Some(seed) => SessionRng::from_seed(seed),
            None => SessionRng::from_entropy(),
        })
    });

/// Hand out an independent generator derived from the root seed.
///
/// A fork rather than a shared handle, because `&mut SessionRng` is
/// carried across `.await` points inside the humanised input path: a
/// guard living that long would serialise typing against every other
/// task. The lock here is held for one draw and released.
///
/// DECLARED cost: under `--batch` the *order* of forks follows the
/// concurrency, so reproducing a run from `stealth.seed` alone requires
/// `--jobs 1`.
pub(crate) fn session_rng_fork() -> SessionRng {
    let seed = match ROOT_SEED.lock() {
        Ok(mut root) => root.next_u64(),
        // A poisoned mutex means some other thread panicked while
        // holding it. The sequence itself is still perfectly usable, so
        // recover the value rather than propagate an unrelated panic
        // into a cosmetic timing decision.
        Err(poisoned) => poisoned.into_inner().next_u64(),
    };
    SessionRng::from_seed(seed)
}