use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, Color, DotmaxError};
use std::f32::consts::PI;
const TINT_START: Color = Color::rgb(228, 84, 38);
const TINT_END: Color = Color::rgb(255, 184, 28);
fn sample_tint(t: f32) -> Color {
let t = t.clamp(0.0, 1.0);
let lerp = |a: u8, b: u8| (f32::from(a) + (f32::from(b) - f32::from(a)) * t) as u8;
Color::rgb(
lerp(TINT_START.r, TINT_END.r),
lerp(TINT_START.g, TINT_END.g),
lerp(TINT_START.b, TINT_END.b),
)
}
struct Tinted<S>(S);
impl<S: ProgressStyle> ProgressStyle for Tinted<S> {
fn name(&self) -> &str {
self.0.name()
}
fn theme(&self) -> &str {
self.0.theme()
}
fn describe(&self) -> &str {
self.0.describe()
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
self.0.render(grid, ctx)?;
grid.enable_color_support();
let (w, h) = grid.dimensions();
for y in 0..h {
for x in 0..w {
let ch = grid.get_char(x, y);
if ch != '\u{2800}' && ch != ' ' {
let t = (x as f32 / w.max(1) as f32 + ctx.time * 0.05).fract();
let tri = 1.0 - (2.0 * t - 1.0).abs();
let _ = grid.set_cell_color(x, y, sample_tint(tri));
}
}
}
Ok(())
}
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(Tinted(Pong)),
Box::new(Tinted(Breakout)),
Box::new(Tinted(Asteroids)),
Box::new(Tinted(MissileCommand)),
Box::new(Tinted(Centipede)),
Box::new(Tinted(Adventure)),
Box::new(Tinted(Pitfall)),
Box::new(Tinted(Combat)),
Box::new(Tinted(Kaboom)),
Box::new(Tinted(YarsRevenge)),
Box::new(Tinted(LunarLander)),
]
}
struct Pong;
impl ProgressStyle for Pong {
fn name(&self) -> &str {
"pong"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Pong: paddles rally a ball across the screen; progress = score"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let score_w = (ctx.eased * w as f32) as usize;
let bar_y = h.saturating_sub(1);
draw::hline(grid, 0, score_w.min(w.saturating_sub(1)), bar_y);
let net_x = w / 2;
let mut y = 0;
while y < h {
draw::dot(grid, net_x, y);
y += 3;
}
let ball_period = 2.0_f32;
let ball_phase = (ctx.time / ball_period).fract();
let ping_pong = if ball_phase < 0.5 {
ball_phase * 2.0
} else {
(1.0 - ball_phase) * 2.0
};
let bx = (ping_pong * w.saturating_sub(1) as f32) as usize;
let by = ((((ctx.time * 1.3).sin() + 1.0) * 0.5) * h.saturating_sub(2) as f32) as usize;
draw::dot(grid, bx, by);
let pad_h = (h / 3).max(1);
let pad_top_left = by.saturating_sub(pad_h / 2).min(h.saturating_sub(pad_h));
draw::vline(
grid,
0,
pad_top_left,
(pad_top_left + pad_h).min(h).saturating_sub(1),
);
let pad_top_right = (h / 2).saturating_sub(pad_h / 2);
draw::vline(
grid,
w.saturating_sub(1),
pad_top_right,
(pad_top_right + pad_h).min(h).saturating_sub(1),
);
Ok(())
}
}
struct Breakout;
impl ProgressStyle for Breakout {
fn name(&self) -> &str {
"breakout"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Breakout: a ball demolishes brick rows; cleared bricks = progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let (dw, dh) = draw::dot_dims(grid);
let brick_rows = ((ch / 2).max(1)).min(ch.saturating_sub(2).max(1));
let total_bricks = cw * brick_rows;
let cleared = (ctx.eased * total_bricks as f32) as usize;
let mut count = 0usize;
'outer: for row in 0..brick_rows {
for col in 0..cw {
if count < cleared {
} else {
draw::shade(grid, col, row, 4); }
count += 1;
if count > total_bricks {
break 'outer;
}
}
}
let pad_w = (cw / 4).max(1);
let pad_x = (cw / 2).saturating_sub(pad_w / 2);
let pad_y = ch.saturating_sub(1);
for px in pad_x..(pad_x + pad_w).min(cw) {
draw::glyph(grid, px, pad_y, '▬');
}
let period = 1.8_f32;
let phase = (ctx.time / period).fract();
let ping = if phase < 0.5 {
phase * 2.0
} else {
(1.0 - phase) * 2.0
};
let bx = (ping * dw.saturating_sub(1) as f32) as usize;
let by_min = brick_rows * 4;
let by_range = dh.saturating_sub(by_min + 1).max(1);
let by = by_min + (((ctx.time * 2.1).sin() + 1.0) * 0.5 * by_range as f32) as usize;
draw::dot(grid, bx, by.min(dh.saturating_sub(1)));
Ok(())
}
}
struct Asteroids;
impl ProgressStyle for Asteroids {
fn name(&self) -> &str {
"asteroids"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Asteroids: vector-wireframe rocks shatter as progress rises; a ship remains"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let draw_ngon = |grid: &mut BrailleGrid, cx: i32, cy: i32, r: i32, n: usize, rot: f32| {
if n < 2 {
return;
}
for i in 0..n {
let a0 = rot + 2.0 * PI * i as f32 / n as f32;
let a1 = rot + 2.0 * PI * (i + 1) as f32 / n as f32;
let x0 = cx + (r as f32 * a0.cos()) as i32;
let y0 = cy + (r as f32 * a0.sin()) as i32;
let x1 = cx + (r as f32 * a1.cos()) as i32;
let y1 = cy + (r as f32 * a1.sin()) as i32;
let mut sx = x0;
let mut sy = y0;
let dx = (x1 - x0).abs();
let dy = (y1 - y0).abs();
let step_x: i32 = if x1 > x0 { 1 } else { -1 };
let step_y: i32 = if y1 > y0 { 1 } else { -1 };
let mut err = dx - dy;
for _ in 0..(dx + dy + 1).min(256) {
draw::dot_i(grid, sx, sy);
if sx == x1 && sy == y1 {
break;
}
let e2 = 2 * err;
if e2 > -dy {
err -= dy;
sx += step_x;
}
if e2 < dx {
err += dx;
sy += step_y;
}
}
}
};
let star_hash = |n: u32| -> u32 {
let mut x = n.wrapping_mul(2_654_435_761);
x ^= x >> 15;
x.wrapping_mul(2_246_822_519)
};
let blink = ((ctx.time * 4.0) as u32).rem_euclid(8);
for i in 0..24u32 {
if (star_hash(i * 5 + 3) + blink) % 8 == 0 {
continue; }
let sx = (star_hash(i * 2 + 1) % w as u32) as i32;
let sy = (star_hash(i * 7 + 5) % h as u32) as i32;
draw::dot_i(grid, sx, sy);
}
let asteroid_data: [(f32, f32, f32, f32, usize); 7] = [
(0.08, 0.32, 0.36, 0.0, 6),
(0.22, 0.70, 0.30, 0.5, 5),
(0.36, 0.26, 0.40, 1.1, 7),
(0.52, 0.68, 0.34, 0.8, 6),
(0.66, 0.28, 0.28, 0.3, 5),
(0.81, 0.68, 0.40, 1.4, 7),
(0.94, 0.30, 0.28, 0.9, 5),
];
let kill_rank: [f32; 7] = [3.0, 0.0, 5.0, 1.0, 6.0, 2.0, 4.0];
let destroyed = ctx.eased * 7.0;
for (i, &(fx, fy, fr, rot_off, sides)) in asteroid_data.iter().enumerate() {
let cx = (fx * w as f32) as i32;
let cy = (fy * h as f32) as i32;
let r = ((h as f32 * fr) as i32).max(2);
let step = 2.0 * PI / sides as f32;
let spin = if i % 2 == 0 { 1.0 } else { -1.0 };
let rot = rot_off + ctx.time * spin * step * 0.25 * (1 + i % 2) as f32;
let frac = destroyed - kill_rank[i];
if frac >= 1.0 {
for j in 0..3u32 {
let dx = (star_hash(i as u32 * 13 + j * 3 + 1) % (r as u32 * 2 + 1)) as i32 - r;
let dy = (star_hash(i as u32 * 17 + j * 5 + 2) % (r as u32 + 1)) as i32 - r / 2;
draw::dot_i(grid, cx + dx, cy + dy);
}
continue;
}
if frac > 0.0 {
for j in 0..sides {
let a = rot + 2.0 * PI * j as f32 / sides as f32;
let d = r as f32 * (0.4 + frac * 2.2);
let px = cx + (d * a.cos()) as i32;
let py = cy + (d * a.sin()) as i32;
draw::dot_i(grid, px, py);
draw::dot_i(grid, px + 1, py);
}
continue;
}
draw_ngon(grid, cx, cy, r, sides, rot);
draw::dot_i(grid, cx - r / 3, cy + r / 4);
draw::dot_i(grid, cx + r / 3, cy - r / 4);
}
let ship_cx = (w / 2) as i32;
let ship_cy = (h * 7 / 10) as i32;
let ship_r = ((h as f32 * 0.22).max(2.0)) as i32;
draw_ngon(grid, ship_cx, ship_cy, ship_r, 3, -PI / 2.0);
if ((ctx.time * 8.0) as i32).rem_euclid(2) == 0 {
draw::dot_i(grid, ship_cx, ship_cy + ship_r + 1);
draw::dot_i(grid, ship_cx - 1, ship_cy + ship_r + 2);
draw::dot_i(grid, ship_cx + 1, ship_cy + ship_r + 2);
}
Ok(())
}
}
struct MissileCommand;
impl ProgressStyle for MissileCommand {
fn name(&self) -> &str {
"missile-command"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Missile Command: interceptor arcs rise to meet incoming threats; arcs = progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
draw::hline(grid, 0, w.saturating_sub(1), h.saturating_sub(1));
let draw_arc =
|grid: &mut BrailleGrid, x0: i32, x1: i32, base: i32, top: i32, fill: f32| {
let steps = (x1 - x0).abs().max(1).min(w as i32);
let drawn_steps = (fill * steps as f32) as i32;
for i in 0..drawn_steps.min(steps) {
let t = i as f32 / steps as f32;
let x = x0 + ((x1 - x0) as f32 * t) as i32;
let arc = 4.0 * t * (1.0 - t);
let y = base - ((base - top) as f32 * arc) as i32;
draw::dot_i(grid, x, y);
}
};
let arc_defs: [(f32, f32, f32); 6] = [
(0.1, 0.35, 0.0),
(0.2, 0.55, 0.15),
(0.3, 0.7, 0.3),
(0.5, 0.85, 0.45),
(0.65, 0.9, 0.6),
(0.8, 0.95, 0.75),
];
let base_y = h.saturating_sub(2) as i32;
let apex_y = (h / 4) as i32;
for &(x_frac, x_end_frac, threshold) in &arc_defs {
if ctx.eased < threshold {
break;
}
let local_fill = ((ctx.eased - threshold) / 0.25).min(1.0);
let x0 = (x_frac * w as f32) as i32;
let x1 = (x_end_frac * w as f32) as i32;
draw_arc(grid, x0, x1, base_y, apex_y, local_fill);
}
let threat_xs = [w / 5, w / 2, 3 * w / 4];
for (i, &tx) in threat_xs.iter().enumerate() {
let phase = ((ctx.time * 0.7 + i as f32 * 0.4) % 2.0) as f32;
let ty = (phase * h as f32 * 0.5) as usize;
draw::dot(grid, tx, ty.min(h.saturating_sub(1)));
}
Ok(())
}
}
struct Centipede;
impl ProgressStyle for Centipede {
fn name(&self) -> &str {
"centipede"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Centipede: a segmented worm winds through the field; cleared segments = progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let total_seg = cw * ch;
let cleared = (ctx.eased * total_seg as f32) as usize;
let head_seg = cleared; for seg in cleared..total_seg {
let row = seg / cw;
let col_idx = seg % cw;
let col = if row % 2 == 0 {
col_idx
} else {
cw.saturating_sub(1).saturating_sub(col_idx)
};
let col = col.min(cw.saturating_sub(1));
let row = row.min(ch.saturating_sub(1));
if seg == head_seg && head_seg < total_seg {
draw::shade(grid, col, row, 4); } else {
draw::shade(grid, col, row, 2); }
}
Ok(())
}
}
struct Adventure;
impl ProgressStyle for Adventure {
fn name(&self) -> &str {
"adventure"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Adventure: a square hero traverses a corridor toward a goal; distance = progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
draw::hline(grid, 0, w.saturating_sub(1), 0);
draw::hline(grid, 0, w.saturating_sub(1), h.saturating_sub(1));
let mid_y = h / 2;
for x in (0..w).step_by(3) {
draw::dot(grid, x, mid_y);
}
let gx = w.saturating_sub(2) as i32;
let gy = mid_y as i32;
draw::dot_i(grid, gx, gy);
draw::dot_i(grid, gx - 1, gy);
draw::dot_i(grid, gx + 1, gy);
draw::dot_i(grid, gx, gy - 1);
draw::dot_i(grid, gx, gy + 1);
let hero_x = (ctx.eased * w.saturating_sub(4) as f32) as usize;
let hero_y = mid_y.saturating_sub(1);
draw::fill_rect(grid, hero_x, hero_y, 2, 2.min(h.saturating_sub(hero_y)));
Ok(())
}
}
struct Pitfall;
impl ProgressStyle for Pitfall {
fn name(&self) -> &str {
"pitfall"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Pitfall: Harry swings a vine pendulum over pits; screens advanced = progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
draw::hline(grid, 0, w.saturating_sub(1), h.saturating_sub(1));
let anchor_x = (w / 5) as i32;
let anchor_y = (h / 4) as i32;
draw::vline(grid, anchor_x as usize, 0, anchor_y as usize);
let solid_end = (ctx.eased * w as f32) as usize;
let ground_y = h.saturating_sub(1);
draw::hline(grid, 0, solid_end.min(w.saturating_sub(1)), ground_y);
let mut px = solid_end;
while px < w {
draw::dot(grid, px, ground_y);
if px + 3 < w {
px += 4;
} else {
break;
}
}
let vine_len = (h as f32 * 0.55).max(2.0);
let angle = (ctx.time * 2.5).sin() * (PI / 4.0);
let vine_dx = (vine_len * angle.sin()) as i32;
let vine_dy = (vine_len * angle.cos()) as i32;
let harry_x = anchor_x + vine_dx;
let harry_y = anchor_y + vine_dy;
let mut lx = anchor_x;
let mut ly = anchor_y;
let dx = (harry_x - anchor_x).abs();
let dy = (harry_y - anchor_y).abs();
let step_x: i32 = if harry_x > anchor_x { 1 } else { -1 };
let step_y: i32 = if harry_y > anchor_y { 1 } else { -1 };
let mut err = dx - dy;
for _ in 0..(dx + dy + 1).min(256) {
draw::dot_i(grid, lx, ly);
if lx == harry_x && ly == harry_y {
break;
}
let e2 = 2 * err;
if e2 > -dy {
err -= dy;
lx += step_x;
}
if e2 < dx {
err += dx;
ly += step_y;
}
}
draw::fill_rect(grid, harry_x.max(0) as usize, harry_y.max(0) as usize, 2, 2);
Ok(())
}
}
struct Combat;
impl ProgressStyle for Combat {
fn name(&self) -> &str {
"combat"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Combat: two tanks exchange shells; hits scored = progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let (dw, dh) = draw::dot_dims(grid);
draw::hbar(grid, 0, ctx.eased);
if ch < 2 {
return Ok(());
}
let left_col = (cw / 8).min(cw.saturating_sub(1));
let right_col = cw
.saturating_sub(cw / 8 + 1)
.max(left_col + 1)
.min(cw.saturating_sub(1));
let tank_row = ch / 2;
draw::shade(grid, left_col, tank_row, 3); draw::shade(grid, right_col, tank_row, 3);
let shell_count = 3usize;
for i in 0..shell_count {
let phase = ((ctx.time * 1.2 + i as f32 * 0.33) % 1.0) as f32;
let (fx, tx) = if i % 2 == 0 {
(left_col as f32 * 2.0 + 2.0, right_col as f32 * 2.0 - 1.0)
} else {
(right_col as f32 * 2.0 - 1.0, left_col as f32 * 2.0 + 2.0)
};
let sx = (fx + (tx - fx) * phase) as usize;
let sy = tank_row * 4 + 1; draw::dot(
grid,
sx.min(dw.saturating_sub(1)),
sy.min(dh.saturating_sub(1)),
);
}
Ok(())
}
}
struct Kaboom;
impl ProgressStyle for Kaboom {
fn name(&self) -> &str {
"kaboom"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Kaboom: bombs drop from a mad bomber; bucket catches = progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let (dw, dh) = draw::dot_dims(grid);
let bomber_cx = cw / 2;
draw::shade(grid, bomber_cx.min(cw.saturating_sub(1)), 0, 3);
let bucket_col =
((((ctx.time * 1.5).sin() + 1.0) * 0.5) * (cw.saturating_sub(2)) as f32) as usize;
let bucket_row = ch.saturating_sub(1);
draw::glyph(grid, bucket_col.min(cw.saturating_sub(1)), bucket_row, '▂');
let bomb_count = 4usize;
for i in 0..bomb_count {
let phase = ((ctx.time * 0.8 + i as f32 * 0.25) % 1.0) as f32;
let bx =
((bomber_cx as f32 + (i as f32 - bomb_count as f32 / 2.0) * 2.0) * 2.0) as usize;
let by = (phase * dh as f32) as usize;
draw::dot(
grid,
bx.min(dw.saturating_sub(1)),
by.min(dh.saturating_sub(1)),
);
}
let score_h = (ctx.eased * dh as f32) as usize;
for sy in (dh.saturating_sub(score_h))..dh {
draw::dot(grid, dw.saturating_sub(1), sy);
}
Ok(())
}
}
struct YarsRevenge;
impl ProgressStyle for YarsRevenge {
fn name(&self) -> &str {
"yars-revenge"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Yars' Revenge: the Qotile shield wall erodes cell by cell; erosion = progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let (dw, _dh) = draw::dot_dims(grid);
let wall_cols = ((cw / 3).max(1)).min(cw);
let total_blocks = wall_cols * ch;
let eroded = (ctx.eased * total_blocks as f32) as usize;
let eroded_cols = eroded / ch.max(1);
let eroded_partial = eroded % ch.max(1);
for col in 0..wall_cols {
for row in 0..ch {
let block_idx = col * ch + row;
if block_idx < eroded {
} else if col == eroded_cols && row < eroded_partial {
} else {
draw::shade(
grid,
col.min(cw.saturating_sub(1)),
row.min(ch.saturating_sub(1)),
3,
);
}
}
}
let yar_x = (((ctx.time * 3.0).sin() + 1.0) * 0.5 * (wall_cols + 2) as f32) as usize;
let yar_y = (ch / 2) * 4; draw::dot(grid, yar_x.min(dw.saturating_sub(1)), yar_y);
draw::dot(
grid,
yar_x.min(dw.saturating_sub(1)),
yar_y.saturating_sub(1),
);
let q_col = cw.saturating_sub(1);
let q_row = (((ctx.time * 0.5).sin() + 1.0) * 0.5 * ch.saturating_sub(1) as f32) as usize;
draw::shade(grid, q_col, q_row.min(ch.saturating_sub(1)), 4);
Ok(())
}
}
struct LunarLander;
impl ProgressStyle for LunarLander {
fn name(&self) -> &str {
"lunar-lander"
}
fn theme(&self) -> &str {
"atari"
}
fn describe(&self) -> &str {
"Lunar Lander: a vector wireframe lander descends; altitude/fuel = progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let gauge_x = w.saturating_sub(2);
let gauge_h = h;
let fuel_h = (ctx.eased * gauge_h as f32) as usize;
draw::vline(grid, gauge_x, 0, gauge_h.saturating_sub(1));
for gy in (gauge_h.saturating_sub(fuel_h))..gauge_h {
draw::dot(grid, gauge_x + 1, gy);
}
let surface_y = h.saturating_sub(2);
draw::hline(grid, 0, gauge_x.saturating_sub(1), surface_y);
let peak_xs = [w / 6, w / 3, w / 2, 2 * w / 3];
for &px in &peak_xs {
if px < gauge_x {
draw::vline(grid, px, surface_y.saturating_sub(2), surface_y);
}
}
let lander_col_centre = (w / 2) as i32;
let descent_range = surface_y.saturating_sub(6);
let lander_y = (ctx.eased * descent_range as f32) as i32;
let bw: i32 = (w as i32 / 8).clamp(2, 5);
let bh: i32 = 2.max((h as i32 / 8).min(3));
let bx0 = lander_col_centre - bw / 2;
let bx1 = lander_col_centre + bw / 2;
let by0 = lander_y;
let by1 = lander_y + bh;
for x in bx0..=bx1 {
draw::dot_i(grid, x, by0);
draw::dot_i(grid, x, by1);
}
for y in by0..=by1 {
draw::dot_i(grid, bx0, y);
draw::dot_i(grid, bx1, y);
}
let leg_len: i32 = bh.max(2);
draw::dot_i(grid, bx0 - leg_len, by1 + leg_len);
draw::dot_i(grid, bx0 - leg_len + 1, by1 + leg_len - 1);
draw::dot_i(grid, bx0 - leg_len + 2, by1 + leg_len - 2);
draw::dot_i(grid, bx1 + leg_len, by1 + leg_len);
draw::dot_i(grid, bx1 + leg_len - 1, by1 + leg_len - 1);
draw::dot_i(grid, bx1 + leg_len - 2, by1 + leg_len - 2);
if ctx.eased < 0.95 {
let pulse = ((ctx.time * 8.0).sin() > 0.0) as i32;
draw::dot_i(grid, lander_col_centre, by1 + 1 + pulse);
}
Ok(())
}
}