返回 CodeWhale
main.rs
根目录 / pet / tui / src / main.rs
1 //! The dot-whale pet as a ratatui widget.
2 //!
3 //! `pet_core` is the same `rs/pet_sim.rs` the standalone runner compiles —
4 //! one file, one authority. This crate adds only the render path: particles →
5 //! braille cells → ratatui `Buffer`, coloured with the frame's computed RGB.
6 //!
7 //! Demo mode (no real terminal needed — renders through TestBackend and emits
8 //! truecolor ANSI so you can see the actual frame):
9 //! cargo run --offline -- ../tape.tsv [--frame N]
10 //! With `--live` it runs the tape looped on a real crossterm-less ANSI stream.
11
12 use ratatui::buffer::Buffer;
13 use ratatui::layout::Rect;
14 use ratatui::style::Color;
15
16 #[path = "../../rs/pet_sim.rs"]
17 mod pet_sim;
18 use pet_sim::*;
19
20 #[path = "../../../crates/tui/src/tui/ambient_life/pet_widget.rs"]
21 mod pet_widget;
22 use pet_widget::PetWidget;
23
24 fn parse_row(c: Vec<&str>) -> Option<(f64, PetState)> {
25 if c.len() < 10 || c[0] == "dt" { return None; }
26 let channel = ChannelId::from_key(c[4]).expect("known tape channel");
27 Some((c[0].parse().unwrap(), PetState {
28 activity: c[1].parse().unwrap(), coherence: c[2].parse().unwrap(),
29 attention: c[3].parse().unwrap(), channel,
30 observed: c[5].parse().unwrap(), roam_x: c[6].parse().unwrap(),
31 roam_y: c[7].parse().unwrap(), flip: c[8].parse().unwrap(), lit: c[9].parse().unwrap(),
32 }))
33 }
34
35 /// Render one frame through ratatui's TestBackend and emit it as truecolor
36 /// ANSI — what a real terminal would show, without needing a PTY.
37 fn emit_ansi(buf: &Buffer, area: Rect) {
38 for y in 0..area.height {
39 for x in 0..area.width {
40 let cell = &buf[(x, y)];
41 let sym = cell.symbol();
42 if sym == " " { print!(" "); continue; }
43 match cell.fg {
44 Color::Rgb(r, g, b) => print!("\x1b[38;2;{r};{g};{b}m{sym}\x1b[0m"),
45 _ => print!("{sym}"),
46 }
47 }
48 println!();
49 }
50 }
51
52 fn main() {
53 let args: Vec<String> = std::env::args().collect();
54 let motion = !args.iter().any(|s| s == "--reduced-motion");
55 let tape_path = args.get(1).map(|s| s.as_str()).unwrap_or("../tape.tsv");
56 let frame_target: Option<usize> = args.iter().position(|a| a == "--frame")
57 .and_then(|i| args.get(i + 1)).and_then(|s| s.parse().ok());
58 let tape = std::fs::read_to_string(tape_path).expect("tape.tsv");
59 let rows: Vec<(f64, PetState)> =
60 tape.trim().lines().filter_map(|l| parse_row(l.split('\t').collect())).collect();
61
62 let mut sim = PetSim::whale();
63 let (w, h) = (90u16, 30u16);
64 let backend = ratatui::backend::TestBackend::new(w, h);
65 let mut terminal = ratatui::Terminal::new(backend).unwrap();
66
67 let frames: Vec<usize> = match frame_target {
68 Some(f) => vec![f],
69 None => vec![60, 240, 480, 780, 1080, 1320, 1560, 1800],
70 };
71
72 let mut next = frames.iter().peekable();
73 for (i, (dt, s)) in rows.iter().enumerate() {
74 sim.step(*dt, s, motion, 1.0);
75 if next.peek() == Some(&&i) {
76 terminal.draw(|f| {
77 let wgt = PetWidget { sim: &sim, state: s };
78 f.render_widget(wgt, f.area());
79 }).unwrap();
80 println!("── tape frame {i} · {} · {} ──", sim.frame.channel, sim.frame.arch);
81 emit_ansi(terminal.backend().buffer(), Rect::new(0, 0, w, h));
82 next.next();
83 if next.peek().is_none() { break; }
84 }
85 }
86 }
87
87 lines RUST