use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, Color, DotmaxError};
use std::f32::consts::PI;
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(QubitSuperposition),
Box::new(BlochSphere),
Box::new(WavefunctionCollapse),
Box::new(QuantumTunneling),
Box::new(HarmonicOscillator),
Box::new(ParticleInBox),
Box::new(SpinPrecession),
Box::new(EnergyLevels),
Box::new(Decoherence),
Box::new(QuantumWalk),
Box::new(DoubleSlit),
]
}
#[inline]
fn line(grid: &mut BrailleGrid, x0: i32, y0: i32, x1: i32, y1: i32) {
let dx = (x1 - x0).abs();
let dy = (y1 - y0).abs();
let steps = dx.max(dy).max(1);
for i in 0..=steps {
let px = x0 + (x1 - x0) * i / steps;
let py = y0 + (y1 - y0) * i / steps;
draw::dot_i(grid, px, py);
}
}
const KET_FONT: [[u8; 5]; 4] = [
[0b010, 0b010, 0b010, 0b010, 0b010], [0b111, 0b101, 0b101, 0b101, 0b111], [0b010, 0b110, 0b010, 0b010, 0b111], [0b100, 0b010, 0b001, 0b010, 0b100], ];
fn ket(grid: &mut BrailleGrid, x: i32, y: i32, digit: usize) -> i32 {
let glyphs = [0usize, 1 + digit.min(1), 3];
let mut cx = x;
for (n, &g) in glyphs.iter().enumerate() {
for (row, bits) in KET_FONT[g].iter().enumerate() {
for col in 0..3 {
if bits & (0b100 >> col) != 0 {
draw::dot_i(grid, cx + col, y + row as i32);
}
}
}
cx += if n == 0 { 2 } else { 4 } + i32::from(n == 0);
}
cx - x
}
struct QubitSuperposition;
impl ProgressStyle for QubitSuperposition {
fn name(&self) -> &str {
"qubit-superposition"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"Qubit levels |0⟩ and |1⟩: a Rabi wave swings between them, collapsing to a solid |1⟩ behind progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (w, h) = (dw as i32, dh as i32);
if h < 12 || w < 24 {
let front = (ctx.eased * dw as f32) as i32;
for x in 0..w {
let y = if x < front {
0
} else {
let s = ((x as f32 * 0.4 - ctx.time * 6.0).sin() + 1.0) * 0.5;
(s * (h - 1) as f32).round() as i32
};
draw::dot_i(grid, x, y);
}
return Ok(());
}
let (top_y, bot_y) = (2, h - 3);
let lw = ket(grid, 0, 0, 1);
ket(grid, 0, h - 5, 0);
let label_col = Color::rgb(200, 205, 225);
draw::tint_row(grid, 0, 0, (lw / 2) as usize, label_col);
draw::tint_row(
grid,
((h - 1) / 4) as usize,
0,
(lw / 2) as usize,
label_col,
);
let x0 = lw + 2;
let span = (w - x0).max(1);
let front = x0 + (span as f32 * ctx.eased.clamp(0.0, 1.0)) as i32;
let shift = (ctx.time * 12.0) as i32;
if front > x0 {
for y in top_y - 1..=top_y + 1 {
draw::hline(grid, x0 as usize, (front - 1) as usize, y as usize);
}
for cx in (x0 / 2)..=((front - 1) / 2) {
let t = (cx * 2 - x0) as f32 / span as f32;
draw::tint_row(grid, 0, cx as usize, cx as usize, ctx.palette.sample(t));
}
}
let amp = (bot_y - top_y) as f32 * 0.5;
let mid = (top_y + bot_y) as f32 * 0.5;
let mut prev: Option<(i32, i32)> = None;
for x in front.max(x0)..w {
if (x + shift).rem_euclid(4) == 0 {
draw::dot_i(grid, x, top_y);
draw::dot_i(grid, x, bot_y);
}
let y = (mid - amp * (x as f32 * 0.28 - ctx.time * 5.0).sin()).round() as i32;
if let Some((px, py)) = prev {
line(grid, px, py, x, y);
}
prev = Some((x, y));
}
if front > x0 && front < w {
line(grid, front, top_y - 1, front, bot_y);
}
let wave_col = Color::rgb(150, 160, 255);
for cy in 0..(h / 4) {
let lo = (front.max(x0) / 2) as usize;
draw::tint_row(grid, cy as usize, lo, (w / 2) as usize, wave_col);
}
Ok(())
}
}
struct BlochSphere;
impl ProgressStyle for BlochSphere {
fn name(&self) -> &str {
"bloch-sphere"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"Round Bloch sphere: state arrow tips from |0⟩ to |1⟩ with progress and precesses with time as the sphere travels"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (w, h) = (dw as i32, dh as i32);
let r = ((h - 1) / 2).min((w - 1) / 2).max(1);
let rf = r as f32;
let e = ctx.eased.clamp(0.0, 1.0);
let cx = r + ((w - 1 - 2 * r).max(0) as f32 * e) as i32;
let cy = (h - 1) / 2;
let cyf = (h - 1) as f32 / 2.0;
if cx - r > 1 {
draw::hline(grid, 0, (cx - r - 2) as usize, cy as usize);
for c in 0..=((cx - r - 2) / 2) {
let t = c as f32 * 2.0 / w as f32;
draw::tint_row(
grid,
(cy / 4) as usize,
c as usize,
c as usize,
ctx.palette.sample(t),
);
}
}
let steps = (rf * 8.0) as usize + 8;
for s in 0..steps {
let a = s as f32 / steps as f32 * 2.0 * PI;
draw::dot_i(
grid,
cx + (rf * a.cos()).round() as i32,
(cyf + rf * a.sin()).round() as i32,
);
}
for s in 0..steps {
let a = s as f32 / steps as f32 * 2.0 * PI;
if a.sin() < 0.0 && s % 3 != 0 {
continue;
}
draw::dot_i(
grid,
cx + (rf * a.cos()).round() as i32,
(cyf + rf * 0.3 * a.sin()).round() as i32,
);
}
for y in (cy - r)..=(cy + r) {
if y % 2 == 0 {
draw::dot_i(grid, cx, y);
}
}
draw::dot_i(grid, cx - 1, cy - r);
draw::dot_i(grid, cx + 1, cy - r);
let sphere_col = Color::rgb(120, 130, 170);
for c in ((cx - r) / 2)..=((cx + r) / 2) {
for row in 0..=(h - 1) / 4 {
draw::tint_row(
grid,
row as usize,
c.max(0) as usize,
c.max(0) as usize,
sphere_col,
);
}
}
let theta = PI * e;
let phi = ctx.time * 2.5;
let vx = theta.sin() * phi.cos();
let vy = theta.sin() * phi.sin(); let vz = theta.cos();
let tip_x = cx + (vx * rf * 0.95).round() as i32;
let tip_y = (cyf - vz * rf * 0.95 + vy * rf * 0.3).round() as i32;
line(grid, cx, cy, tip_x, tip_y);
line(grid, cx + 1, cy, tip_x + 1, tip_y);
for (dx, dy) in [(0, 0), (1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1)] {
draw::dot_i(grid, tip_x + dx, tip_y + dy);
}
let arrow_col = ctx.palette.sample(e);
let (ax0, ax1) = (cx.min(tip_x) / 2, cx.max(tip_x + 1) / 2);
let (ay0, ay1) = (cy.min(tip_y) / 4, cy.max(tip_y) / 4);
for row in ay0.max(0)..=ay1 {
draw::tint_row(
grid,
row as usize,
ax0.max(0) as usize,
ax1.max(0) as usize,
arrow_col,
);
}
let tip_col = Color::rgb(255, 255, 255);
draw::tint_row(
grid,
(tip_y.max(0) / 4) as usize,
(tip_x.max(0) / 2) as usize,
(tip_x.max(0) / 2) as usize,
tip_col,
);
Ok(())
}
}
struct WavefunctionCollapse;
impl ProgressStyle for WavefunctionCollapse {
fn name(&self) -> &str {
"wavefunction-collapse"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"Wavefunction collapse: |ψ(x)|² spreads as a broad Gaussian then sharpens to a spike at 100%"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let cwf = cw as f32;
let sigma_max = cwf * 0.35;
let sigma_min = cwf.max(4.0) * 0.025;
let sigma = sigma_max * (1.0 - ctx.eased) + sigma_min;
let center_norm = 0.5_f32 + 0.15 * (ctx.time * 1.8).sin() * (1.0 - ctx.eased);
let mean = center_norm * cwf;
for cx in 0..cw {
let xf = cx as f32 + 0.5;
let exponent = -0.5 * ((xf - mean) / sigma.max(0.1)).powi(2);
let amplitude = exponent.exp().clamp(0.0, 1.0);
for cy in 0..ch {
let row_norm = 1.0 - cy as f32 / ch as f32; if amplitude >= row_norm {
draw::vblock(grid, cx, cy, 8);
} else {
let partial = ((amplitude - (row_norm - 1.0 / ch as f32)) * ch as f32 * 8.0)
.clamp(0.0, 8.0) as usize;
if partial > 0 {
draw::vblock(grid, cx, cy, partial);
}
}
}
let t = cx as f32 / cwf;
let col = ctx.palette.sample(t * amplitude + ctx.eased * (1.0 - t));
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, col);
}
}
Ok(())
}
}
struct QuantumTunneling;
impl ProgressStyle for QuantumTunneling {
fn name(&self) -> &str {
"quantum-tunneling"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"Quantum tunneling: wave packet hits a barrier, transmitted amplitude grows with progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let dwf = dw as f32;
let dhf = dh as f32;
let mid_y = (dhf / 2.0) as i32;
let barrier_x0 = (dwf * 0.55) as usize;
let barrier_x1 = (dwf * 0.65) as usize;
let barrier_h = (dhf * 0.7).max(2.0) as usize;
let bar_top = ((dhf - barrier_h as f32) / 2.0) as usize;
for y in bar_top..(bar_top + barrier_h).min(dh) {
draw::dot(grid, barrier_x0.min(dw.saturating_sub(1)), y);
draw::dot(grid, barrier_x1.min(dw.saturating_sub(1)), y);
}
draw::hline(
grid,
barrier_x0,
barrier_x1.min(dw.saturating_sub(1)),
bar_top,
);
draw::hline(
grid,
barrier_x0,
barrier_x1.min(dw.saturating_sub(1)),
(bar_top + barrier_h)
.saturating_sub(1)
.min(dh.saturating_sub(1)),
);
let decay = (1.0 - ctx.eased).max(0.01);
for xi in barrier_x0..=barrier_x1.min(dw.saturating_sub(1)) {
let d = xi.saturating_sub(barrier_x0) as f32;
let envelope =
(-decay * 2.5 * d / (barrier_x1.saturating_sub(barrier_x0) as f32).max(1.0)).exp();
if (d as usize % (2.max((1.0 / envelope.max(0.1)) as usize))) == 0 {
draw::dot(grid, xi, mid_y as usize);
}
}
let k = 4.0 * PI / dwf; let omega = k * 2.0; let refl_amp = (1.0 - ctx.eased).sqrt();
let inc_amp = 1.0_f32;
let wave_amp = (dhf * 0.35).max(1.0);
let mut prev: Option<(i32, i32)> = None;
for xi in 0..barrier_x0 {
let xf = xi as f32;
let incident = inc_amp * (k * xf - omega * ctx.time).sin();
let reflected = refl_amp * (k * xf + omega * ctx.time).sin();
let total = (incident + reflected) * wave_amp;
let py = (mid_y as f32 - total) as i32;
let py = py.clamp(0, dh as i32 - 1);
draw::dot_i(grid, xi as i32, py);
if let Some((lx, ly)) = prev {
line(grid, lx, ly, xi as i32, py);
}
prev = Some((xi as i32, py));
}
let trans_amp = ctx.eased.sqrt();
let mut prev: Option<(i32, i32)> = None;
for xi in (barrier_x1 + 1)..dw {
let xf = xi as f32;
let transmitted = trans_amp * (k * xf - omega * ctx.time).sin() * wave_amp;
let py = (mid_y as f32 - transmitted) as i32;
let py = py.clamp(0, dh as i32 - 1);
draw::dot_i(grid, xi as i32, py);
if let Some((lx, ly)) = prev {
line(grid, lx, ly, xi as i32, py);
}
prev = Some((xi as i32, py));
}
draw::hline(grid, 0, dw.saturating_sub(1), mid_y as usize);
let (cw, ch) = grid.dimensions();
let split_cell = barrier_x1 / 2;
for cx in split_cell.min(cw)..cw {
let t = cx as f32 / cw.max(1) as f32;
let col = ctx.palette.sample(t * ctx.eased);
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, col);
}
}
Ok(())
}
}
struct HarmonicOscillator;
impl ProgressStyle for HarmonicOscillator {
fn name(&self) -> &str {
"harmonic-oscillator"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"QHO eigenstate |ψₙ|² inside its parabolic well; level n climbs the energy ladder with progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let dwf = dw as f32;
let dhf = dh as f32;
let n_max = 6_usize;
let n = (ctx.eased * (n_max + 1) as f32).floor() as usize;
let n = n.min(n_max);
let x_max = 3.5_f32 + n as f32 * 0.4;
let hermite = |x: f32| -> f32 {
if n == 0 {
return 1.0;
}
let mut h_prev2 = 1.0_f32;
let mut h_prev1 = 2.0 * x;
if n == 1 {
return h_prev1;
}
let mut h = 0.0_f32;
for k in 2..=(n as i32) {
h = 2.0 * x * h_prev1 - 2.0 * (k - 1) as f32 * h_prev2;
h_prev2 = h_prev1;
h_prev1 = h;
}
h
};
let mut peak = 0.0_f32;
let norm_samples = 64_usize;
for i in 0..norm_samples {
let xf = -x_max + 2.0 * x_max * i as f32 / norm_samples as f32;
let hn = hermite(xf);
let psi = hn * (-xf * xf / 2.0).exp();
let dens = psi * psi;
if dens > peak {
peak = dens;
}
}
let peak = peak.max(1e-9);
let phase = ctx.time * 0.8;
let mut prev: Option<(i32, i32)> = None;
for xi in 0..dw {
let xf = -x_max + 2.0 * x_max * xi as f32 / dwf;
let hn = hermite(xf);
let psi = hn * (-xf * xf / 2.0).exp();
let density = (psi * psi / peak).clamp(0.0, 1.0);
let shimmer = 1.0 + 0.08 * (2.0 * xf + phase).cos();
let display_h = (density * shimmer.clamp(0.0, 1.1) * dhf).clamp(0.0, dhf);
let bot = dh as i32 - 1;
let top = (bot as f32 - display_h) as i32;
let top = top.clamp(0, bot);
let mid_dot = (top + bot) / 2;
draw::dot_i(grid, xi as i32, mid_dot);
if let Some((lx, ly)) = prev {
line(grid, lx, ly, xi as i32, mid_dot);
}
prev = Some((xi as i32, mid_dot));
}
let bot = dh as i32 - 1;
for xi in (0..dw).step_by(2) {
let u = (2.0 * xi as f32 / dwf - 1.0).abs();
let y = bot - (u * u * bot as f32 * 1.6) as i32;
if y >= 0 {
draw::dot_i(grid, xi as i32, y);
}
}
for k in 0..=n {
let y = bot - 2 - (k as i32 * (bot - 2)) / n_max as i32;
draw::hline(grid, 0, 3, y.max(0) as usize);
}
draw::hline(grid, 0, dw.saturating_sub(1), dh.saturating_sub(1));
let (cw, ch) = grid.dimensions();
for cx in 0..cw {
let t = cx as f32 / cw.max(1) as f32;
let level_t = n as f32 / n_max as f32;
let col = ctx.palette.sample(t * 0.5 + level_t * 0.5);
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, col);
}
}
Ok(())
}
}
struct ParticleInBox;
impl ProgressStyle for ParticleInBox {
fn name(&self) -> &str {
"particle-in-box"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"Particle in a box: sin²(nπx/L) standing mode — mode n steps up with progress, walls visible"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let dhf = dh as f32;
let n_max = 7_usize;
let n = (ctx.eased * n_max as f32).ceil() as usize;
let n = n.clamp(1, n_max);
draw::vline(grid, 0, 0, dh.saturating_sub(1));
draw::vline(grid, dw.saturating_sub(1), 0, dh.saturating_sub(1));
let x_start = 1_usize;
let x_end = dw.saturating_sub(2);
let box_w = x_end.saturating_sub(x_start).max(1);
let phase = ctx.time * 1.2;
let mut prev: Option<(i32, i32)> = None;
for xi in x_start..=x_end {
let xrel = (xi - x_start) as f32 / box_w as f32; let density = (n as f32 * PI * xrel).sin().powi(2).clamp(0.0, 1.0);
let shimmer = 0.85 + 0.15 * (n as f32 * PI * xrel - phase * n as f32).sin().abs();
let display_h = (density * shimmer * (dhf - 2.0)).clamp(0.0, dhf - 2.0);
let base = dh.saturating_sub(2) as i32;
let peak_dot = (base as f32 - display_h) as i32;
let peak_dot = peak_dot.clamp(0, base);
draw::dot_i(grid, xi as i32, peak_dot);
if let Some((lx, ly)) = prev {
line(grid, lx, ly, xi as i32, peak_dot);
}
prev = Some((xi as i32, peak_dot));
}
draw::hline(grid, 0, dw.saturating_sub(1), dh.saturating_sub(1));
let (cw, ch) = grid.dimensions();
let x_start_c = 0_usize;
let x_end_c = cw;
for cx in x_start_c..x_end_c {
let xrel = cx as f32 / cw.max(1) as f32;
let density = (n as f32 * PI * xrel).sin().powi(2).clamp(0.0, 1.0);
let col = ctx.palette.sample(density);
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, col);
}
}
Ok(())
}
}
struct SpinPrecession;
impl ProgressStyle for SpinPrecession {
fn name(&self) -> &str {
"spin-precession"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"Spin arrow cones around a vertical B-field axis; the precessing spin travels with progress, tracing a wave behind it"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (w, h) = (dw as i32, dh as i32);
let e = ctx.eased.clamp(0.0, 1.0);
if h < 8 || w < 16 {
let reach = (e * dw as f32) as usize;
if reach > 0 {
draw::hline(grid, 0, reach - 1, dh - 1);
}
return Ok(());
}
let rim_y = 3.0_f32;
let pivot_y = h - 2;
let ra = (h as f32 * 0.5).min(w as f32 / 4.0);
let rb = 2.0_f32;
let cx = ra.ceil() as i32 + 1 + ((w - 3 - 2 * ra.ceil() as i32).max(0) as f32 * e) as i32;
let cxf = cx as f32;
let phi = ctx.time * 4.0;
let back = cx - ra.ceil() as i32 - 2;
if back > 0 {
draw::hline(grid, 0, back as usize, (h - 1) as usize);
let mut prev: Option<(i32, i32)> = None;
for x in 0..=back {
let a = phi - (cx - x) as f32 * 0.22;
let y = (rim_y + rb * a.cos()).round() as i32;
if let Some((px, py)) = prev {
line(grid, px, py, x, y);
}
prev = Some((x, y));
}
for c in 0..=(back / 2) {
let col = ctx.palette.sample(c as f32 * 2.0 / w as f32);
for row in [0, (h - 1) / 4] {
draw::tint_row(grid, row as usize, c as usize, c as usize, col);
}
}
}
for y in 1..=pivot_y {
if y % 2 == 1 {
draw::dot_i(grid, cx, y);
}
}
draw::dot_i(grid, cx, 0);
draw::dot_i(grid, cx - 1, 1);
draw::dot_i(grid, cx + 1, 1);
let steps = (ra * 6.0) as usize + 8;
for s in 0..steps {
let a = s as f32 / steps as f32 * 2.0 * PI;
if a.cos() < 0.0 && s % 2 == 0 {
continue;
}
let x = (cxf + ra * a.sin()).round() as i32;
let y = (rim_y + rb * a.cos()).round() as i32;
draw::dot_i(grid, x, y);
}
let ri = ra.round() as i32;
for (sx, step) in [(-ri, 3), (ri, 3)] {
let n = (pivot_y - rim_y as i32).max(1);
for i in (0..=n).step_by(step) {
draw::dot_i(grid, cx + sx * (n - i) / n, rim_y as i32 + i);
}
}
let cone_col = Color::rgb(110, 120, 160);
for c in ((cx - ri) / 2).max(0)..=((cx + ri) / 2) {
for row in 0..=(h - 1) / 4 {
draw::tint_row(grid, row as usize, c as usize, c as usize, cone_col);
}
}
let tip_x = (cxf + ra * phi.sin()).round() as i32;
let tip_y = (rim_y + rb * phi.cos()).round() as i32;
line(grid, cx, pivot_y, tip_x, tip_y);
line(grid, cx + 1, pivot_y, tip_x + 1, tip_y);
for (dx, dy) in [(0, 0), (1, 0), (-1, 0), (0, -1), (1, -1), (0, 1), (2, 0)] {
draw::dot_i(grid, tip_x + dx, tip_y + dy);
}
draw::dot_i(grid, cx - 1, pivot_y + 1);
draw::dot_i(grid, cx + 2, pivot_y + 1);
let arrow_col = ctx.palette.sample(1.0);
let steps = (pivot_y - tip_y).max(1);
for i in 0..=steps {
let x = cx + (tip_x - cx) * i / steps;
let y = pivot_y - i;
draw::tint_row(
grid,
(y.max(0) / 4) as usize,
(x.max(0) / 2) as usize,
(x.max(0) / 2) as usize,
arrow_col,
);
}
draw::tint_row(
grid,
(tip_y.max(0) / 4) as usize,
(tip_x.max(0) / 2) as usize,
(tip_x.max(0) / 2) as usize,
Color::rgb(255, 255, 255),
);
Ok(())
}
}
struct EnergyLevels;
impl ProgressStyle for EnergyLevels {
fn name(&self) -> &str {
"energy-levels"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"Energy level diagram: Aufbau filling — electrons populate orbitals from bottom as progress rises"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let dwf = dw as f32;
let dhf = dh as f32;
let n_levels = (dh / 3).clamp(2, 8) as usize;
let n_electrons_max = n_levels * 2;
let n_filled = (ctx.eased * n_electrons_max as f32).round() as usize;
for lv in 0..n_levels {
let y_frac = 1.0 - (lv as f32 + 0.5) / n_levels as f32;
let y_dot = (y_frac * (dhf - 2.0) + 1.0) as usize;
let y_dot = y_dot.min(dh.saturating_sub(1));
let lw = (dwf * 0.4).max(4.0) as usize;
let lx0 = ((dwf - lw as f32) / 2.0) as usize;
let lx1 = (lx0 + lw).min(dw.saturating_sub(1));
draw::hline(grid, lx0, lx1, y_dot);
let elec_base = lv * 2; for spin in 0..2_usize {
let elec_idx = elec_base + spin;
if elec_idx >= n_filled {
break;
}
let ex = if spin == 0 {
lx0.saturating_sub(3)
} else {
(lx1 + 3).min(dw.saturating_sub(1))
};
draw::dot_i(grid, ex as i32, y_dot as i32);
draw::dot_i(grid, ex as i32 - 1, y_dot as i32);
draw::dot_i(grid, ex as i32 + 1, y_dot as i32);
draw::dot_i(grid, ex as i32, y_dot as i32 - 1);
draw::dot_i(grid, ex as i32, y_dot as i32 + 1);
}
draw::dot(grid, 0, y_dot);
draw::dot(grid, 1, y_dot);
}
draw::vline(grid, 0, 0, dh.saturating_sub(1));
let (cw, ch) = grid.dimensions();
let filled_levels = (n_filled + 1) / 2; for lv in 0..filled_levels.min(n_levels) {
let y_frac = 1.0 - (lv as f32 + 0.5) / n_levels as f32;
let cy = (y_frac * (ch as f32 - 1.0)) as usize;
let cy = cy.min(ch.saturating_sub(1));
let t = lv as f32 / n_levels.max(1) as f32;
let col = ctx.palette.sample(1.0 - t); draw::tint_row(grid, cy, 0, cw.saturating_sub(1), col);
}
Ok(())
}
}
struct Decoherence;
impl ProgressStyle for Decoherence {
fn name(&self) -> &str {
"decoherence"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"Decoherence: sharp interference fringes wash out to flat noise as entanglement grows"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let dwf = dw as f32;
let dhf = dh as f32;
let visibility = 1.0 - ctx.eased;
let k = 6.0 * 2.0 * PI / dwf.max(1.0);
let phase = ctx.time * 1.5;
let mut prev: Option<(i32, i32)> = None;
for xi in 0..dw {
let xf = xi as f32;
let intensity = 0.5 * (1.0 + visibility * (k * xf + phase).cos());
let intensity = intensity.clamp(0.0, 1.0);
let display_h = (intensity * (dhf - 1.0)).clamp(0.0, dhf - 1.0);
let py = (dhf - 1.0 - display_h) as i32;
let py = py.clamp(0, dh as i32 - 1);
draw::dot_i(grid, xi as i32, py);
if let Some((lx, ly)) = prev {
line(grid, lx, ly, xi as i32, py);
}
prev = Some((xi as i32, py));
}
let zero_y = (dhf * 0.5) as usize;
for xi in (0..dw).step_by(4) {
draw::dot(grid, xi, zero_y.min(dh.saturating_sub(1)));
}
let (cw, ch) = grid.dimensions();
for cx in 0..cw {
let t = 1.0 - visibility; let col = ctx.palette.sample(t);
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, col);
}
}
Ok(())
}
}
struct QuantumWalk;
impl ProgressStyle for QuantumWalk {
fn name(&self) -> &str {
"quantum-walk"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"Quantum walk on a lattice: twin ballistic peaks race outward from the start site with progress, ripples between"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (w, h) = (dw as i32, dh as i32);
let e = ctx.eased.clamp(0.0, 1.0);
let base = h - 1;
let cx = (w - 1) / 2;
let cxf = (w - 1) as f32 / 2.0;
let spread = 2.0 + e * (cxf - 3.0).max(0.0);
let max_h = (h - 2).max(1) as f32;
for x in (0..w).step_by(2) {
draw::dot_i(grid, x, base);
}
line(grid, cx, base, cx, base - (h / 4).max(1));
draw::dot_i(grid, cx - 1, base - 1);
draw::dot_i(grid, cx + 1, base - 1);
for x in (0..w).step_by(2) {
let d = (x as f32 - cxf).abs();
let p = if d > spread + 1.5 {
0.0
} else {
let edge = (-(d - spread).powi(2) / 3.0).exp();
let inner = if d < spread {
let r = (d * 0.9 - ctx.time * 3.0).sin() * 0.5 + 0.5;
0.12 + 0.2 * r * (d / spread)
} else {
0.0
};
edge.max(inner)
};
let top = base - 1 - (p * max_h).round() as i32;
if top < base - 1 {
line(grid, x, base - 1, x, top);
draw::dot_i(grid, x + 1, top);
let col = if p > 0.6 {
ctx.palette.sample(1.0)
} else {
ctx.palette.sample(p)
};
for row in (top.max(0) / 4)..=(base / 4) {
draw::tint_row(grid, row as usize, (x / 2) as usize, (x / 2) as usize, col);
}
}
}
Ok(())
}
}
struct DoubleSlit;
impl ProgressStyle for DoubleSlit {
fn name(&self) -> &str {
"electron-buildup"
}
fn theme(&self) -> &str {
"quantum"
}
fn describe(&self) -> &str {
"Single-electron double-slit: wavelets spread from two slits and particle hits build up interference fringes with progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let (w, h) = (dw as i32, dh as i32);
let hf = (h - 1) as f32;
let cy = hf / 2.0;
let e = ctx.eased.clamp(0.0, 1.0);
let bx = ((w / 5) & !1).max(2);
let screen_x = w - 2;
let band_x0 = (w * 3 / 4).min(screen_x - 1);
let slit_off = (h / 5).max(1);
let slits = [cy as i32 - slit_off, cy as i32 + slit_off];
for y in 0..h {
if slits.iter().all(|&s| y != s && y != s + 1) {
draw::dot_i(grid, bx, y);
draw::dot_i(grid, bx + 1, y);
}
}
let wall = Color::rgb(140, 145, 165);
for row in 0..=(h - 1) / 4 {
draw::tint_row(
grid,
row as usize,
(bx / 2) as usize,
((bx + 1) / 2) as usize,
wall,
);
}
line(grid, 0, cy as i32 - 1, 0, cy as i32 + 1);
let gap = 6.0_f32;
let drift = (ctx.time * 10.0) % gap;
let mut x = 2.0 + drift;
while (x as i32) < bx - 1 {
for y in ((cy - hf * 0.3) as i32)..=((cy + hf * 0.3) as i32) {
if y % 2 == 0 {
draw::dot_i(grid, x as i32, y);
}
}
x += gap;
}
let reach = (band_x0 - bx - 6).max(1) as f32;
let n_rings = (reach / gap) as i32 + 1;
for &sy in &slits {
for k in 0..n_rings {
let r = k as f32 * gap + drift;
if r < 1.0 || r > reach {
continue;
}
let steps = (r * 3.0) as i32 + 4;
for s in 0..=steps {
let a = -PI / 2.0 + PI * s as f32 / steps as f32;
let px = bx + 2 + (a.cos() * r) as i32;
let py = sy + (a.sin() * r * 0.8) as i32;
if px < band_x0 && (0..h).contains(&py) && s % 3 == 0 {
draw::dot_i(grid, px, py);
}
}
}
}
let wave_col = Color::rgb(120, 150, 255);
for row in 0..=(h - 1) / 4 {
draw::tint_row(
grid,
row as usize,
((bx + 2) / 2) as usize,
((band_x0 - 1).max(0) / 2) as usize,
wave_col,
);
}
line(grid, screen_x, 0, screen_x, h - 1);
line(grid, screen_x + 1, 0, screen_x + 1, h - 1);
let fringe = |y: f32| {
let u = (y - cy) / hf.max(1.0);
let env = (-u * u * 3.0).exp();
(u * PI * 3.0).cos().powi(2) * env
};
let band_w = (screen_x - band_x0).max(1);
let hits = (e * (band_w * h) as f32 * 0.55) as u32;
let mut placed = 0u32;
let mut i = 0u32;
while placed < hits && i < hits.saturating_mul(6) + 16 {
let y = (hashq(i * 2 + 1) * hf).round();
if hashq(i * 2 + 7) < fringe(y) {
let x = band_x0 + (hashq(i * 3 + 5) * band_w as f32) as i32;
draw::dot_i(grid, x.min(screen_x - 2), y as i32);
draw::dot_i(grid, x.min(screen_x - 2) + 1, y as i32);
placed += 1;
}
i += 1;
}
for row in 0..=(h - 1) / 4 {
let yc = (row * 4 + 2) as f32;
let col = ctx.palette.sample(fringe(yc).sqrt());
draw::tint_row(
grid,
row as usize,
(band_x0 / 2) as usize,
(w / 2) as usize,
col,
);
}
if hits > 0 {
let f = (ctx.time * 12.0) as u32;
let y = (hashq(f * 11 + 3) * hf) as i32;
let x = band_x0 + (hashq(f * 13 + 1) * band_w as f32) as i32;
draw::dot_i(grid, x, y);
}
Ok(())
}
}
#[inline]
fn hashq(n: u32) -> f32 {
let mut x = n.wrapping_mul(2_654_435_761);
x ^= x >> 15;
x = x.wrapping_mul(2_246_822_519);
x ^= x >> 13;
(x % 10_000) as f32 / 10_000.0
}