captchaforge 0.2.40

Captcha detection and solving for Firefox and BiDi-driven browsers. Detection, vendor solver scaffolding, trusted cross-origin click delivery into nested OOPIFs, and stealth personas are implemented and tested; broad live-vendor solve rates are not yet benchmarked.
Documentation
//! Thompson-sampling bandit demo.
//!
//! ```sh
//! cargo run --example 04_bandit_chooser
//! ```
//!
//! Simulates 1000 rounds of a 5-arm Bernoulli problem with
//! ground-truth means [0.9, 0.6, 0.4, 0.2, 0.1] and prints the
//! per-arm posterior + selection frequency. Demonstrates how the
//! bandit converges to the best arm.

use captchaforge::solver::bandit::Bandit;
use captchaforge::solver::{CaptchaType, SolveMethod};
use rand::{rngs::StdRng, Rng, SeedableRng};

fn main() {
    let true_p = [0.9, 0.6, 0.4, 0.2, 0.1];
    let methods = [
        SolveMethod::AutoPass,
        SolveMethod::BehavioralBypass,
        SolveMethod::VisionLLM,
        SolveMethod::AudioBypass,
        SolveMethod::CrowdSourced,
    ];
    let bandit = Bandit::new();
    let ct = CaptchaType::CloudflareTurnstile;
    let mut rng = StdRng::seed_from_u64(42);
    let mut counts = [0usize; 5];

    println!("Simulating 1000 rounds...");
    for round in 0..1000 {
        let chosen = bandit.choose(&ct, &methods).unwrap();
        let idx = methods.iter().position(|m| m == &chosen).unwrap();
        counts[idx] += 1;
        let succeeded: bool = rng.gen_bool(true_p[idx]);
        bandit.observe(&ct, &chosen, succeeded);
        if round == 99 || round == 499 || round == 999 {
            println!("  after {} rounds: counts={:?}", round + 1, counts);
        }
    }

    println!();
    println!("FINAL POSTERIOR");
    println!("─────────────────────────────────────────────────────");
    for arm in bandit.snapshot() {
        println!(
            "  {:>20} α={:>5.1} β={:>5.1}  mean={:.3}  variance={:.5}",
            arm.solver_method,
            arm.alpha,
            arm.beta,
            arm.posterior_mean(),
            arm.posterior_variance(),
        );
    }
    println!();
    println!("SELECTION FREQUENCY");
    println!("─────────────────────────────────────────────────────");
    for (i, m) in methods.iter().enumerate() {
        println!(
            "  {:?}: {} pulls ({:.1}%)  true_p={:.2}",
            m,
            counts[i],
            100.0 * (counts[i] as f64) / 1000.0,
            true_p[i]
        );
    }
}