use crossterm::{
cursor,
event::{self, Event, KeyCode, KeyModifiers},
execute, queue,
style::{Color, Print, ResetColor, SetForegroundColor},
terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen},
};
use dotmax::chess::board::{render_position_with_options, RenderOptions};
use dotmax::image::{DitheringMethod, ImageRenderer};
use dotmax::raytracer::wireframe::rotate_vec_yaw_pitch_roll;
use dotmax::raytracer::{
render_with_orientation, Camera, RenderMode, Scene as RtScene, Sphere, Vector3,
WireframeRotation,
};
use shakmaty::{Chess, Position};
use std::{
cell::Cell as StdCell,
io::{self, Write},
path::Path,
time::{Duration, Instant},
};
thread_local! { static RNG: StdCell<u32> = const { StdCell::new(0x1234_5678) }; }
fn r_u32() -> u32 {
RNG.with(|c| {
let mut x = c.get();
if x == 0 {
x = 0x9E37_79B9;
}
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
c.set(x);
x
})
}
fn r_f32() -> f32 {
(r_u32() as f32) / (u32::MAX as f32)
}
fn r_pick<T: Copy>(xs: &[T]) -> T {
xs[(r_u32() as usize) % xs.len()]
}
fn seed_from_clock() {
let nanos = Instant::now().elapsed().as_nanos() as u32
^ std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.subsec_nanos());
RNG.with(|c| c.set(nanos | 1));
}
const HEX: &[char] = &[
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
];
const BITS: &[char] = &['0', '1'];
const PUNCT: &[char] = &[
'!', '@', '#', '$', '%', '&', '*', '+', '=', '<', '>', '?', '/', '\\', '^', '~',
];
const KANA: &[char] = &[
'ヲ', 'ァ', 'ィ', 'ゥ', 'ェ', 'ォ', 'ャ', 'ュ', 'ョ', 'ッ', 'ア', 'イ', 'ウ', 'エ', 'オ', 'ハ', 'ヒ', 'フ', 'ヘ',
'ホ', 'マ', 'ミ', 'ム',
];
const BLOCK: &[char] = &['░', '▒', '▓', '█', '▚', '▞', '▙', '▟'];
const GREEK: &[char] = &[
'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'λ', 'μ', 'π', 'σ', 'τ', 'φ', 'ψ', 'ω',
];
const SNIPPETS: &[&str] = &[
" ::SYNC:: ",
" 0xDEAD ",
" [OK] ",
" ROUTINE 0x42 ",
" ACK ",
" FAULT ",
" λ=0x1F ",
" >>> ",
" <<< ",
" /proc/self ",
" alloc= ",
" ACK 0x7F ",
" EOF ",
" NULL ",
" TX/RX ",
" PID:4821 ",
" SIG 0x4A ",
" ENTER ",
" φ=1.618 ",
" √2=1.414 ",
" θ=π/φ ",
" FIB(13)=233 ",
" // BREACH ",
" ✦ INVOCATION ✦ ",
" ∴ by golden angle ∴ ",
" one cursor many cells ",
" ☸ hose is holy ☸ ",
" the cube watches ",
" as above so below ",
" // GOLDEN HOUR // ",
" ⚘ signal becomes sacrament ⚘ ",
" ∞ one cursor ∞ ",
" ACK the geometry ",
" fold by fold ",
" ✧ enter be transformed ✧ ",
" ∇ scripture ∇ ",
];
fn build_stream(len: usize) -> Vec<char> {
let pools: &[&[char]] = &[HEX, BITS, PUNCT, KANA, BLOCK, GREEK];
let mut out = Vec::with_capacity(len);
while out.len() < len {
if r_f32() < 0.15 {
let s = SNIPPETS[(r_u32() as usize) % SNIPPETS.len()];
for ch in s.chars() {
if out.len() >= len {
break;
}
out.push(ch);
}
} else {
let p = pools[(r_u32() as usize) % pools.len()];
let burst = 4 + (r_u32() as usize) % 9;
for _ in 0..burst {
if out.len() >= len {
break;
}
out.push(r_pick(p));
}
}
}
out
}
#[derive(Clone, Copy, Debug)]
struct Rect {
x: i32,
y: i32,
w: i32,
h: i32,
}
fn fib_spiral(initial: Rect, max_depth: usize, cw: bool) -> Vec<Rect> {
let mut out = Vec::new();
let mut rect = initial;
const PHI_COMPLEMENT: f32 = 0.381_966;
for step in 0..max_depth {
if rect.w < 6 || rect.h < 4 {
break;
}
let vertical_split = rect.w >= rect.h;
let leaf_far = (step % 2 == 0) ^ !cw;
if vertical_split {
let leaf_w = ((rect.w as f32) * PHI_COMPLEMENT).round().max(3.0) as i32;
if leaf_far {
out.push(Rect {
x: rect.x + rect.w - leaf_w,
y: rect.y,
w: leaf_w,
h: rect.h,
});
} else {
out.push(Rect {
x: rect.x,
y: rect.y,
w: leaf_w,
h: rect.h,
});
rect.x += leaf_w;
}
rect.w -= leaf_w;
} else {
let leaf_h = ((rect.h as f32) * PHI_COMPLEMENT).round().max(2.0) as i32;
if leaf_far {
out.push(Rect {
x: rect.x,
y: rect.y + rect.h - leaf_h,
w: rect.w,
h: leaf_h,
});
} else {
out.push(Rect {
x: rect.x,
y: rect.y,
w: rect.w,
h: leaf_h,
});
rect.y += leaf_h;
}
rect.h -= leaf_h;
}
}
out.push(rect);
out
}
fn golden_child(parent: Rect) -> Rect {
const INV_PHI: f32 = 0.618_034;
let cw = ((parent.w as f32) * INV_PHI).round().max(4.0) as i32;
let ch = ((parent.h as f32) * INV_PHI).round().max(3.0) as i32;
let cw = cw.min(parent.w - 1);
let ch = ch.min(parent.h - 1);
let bias_x = if r_f32() < 0.5 { 0.0 } else { 1.0 - INV_PHI };
let bias_y = if r_f32() < 0.5 { 0.0 } else { 1.0 - INV_PHI };
let x = parent.x + ((parent.w - cw) as f32 * bias_x).round() as i32;
let y = parent.y + ((parent.h - ch) as f32 * bias_y).round() as i32;
Rect { x, y, w: cw, h: ch }
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Side {
L,
R,
Chaos,
Nested,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum FlowDir {
RowMajor,
RowMajorRev,
ColMajor,
ColMajorRev,
}
fn random_flow() -> FlowDir {
match r_u32() & 3 {
0 => FlowDir::RowMajor,
1 => FlowDir::RowMajorRev,
2 => FlowDir::ColMajor,
_ => FlowDir::ColMajorRev,
}
}
#[inline]
fn cell_index(rx: i32, ry: i32, w: i32, h: i32, dir: FlowDir) -> i64 {
let rx = rx as i64;
let ry = ry as i64;
let w = w as i64;
let h = h as i64;
match dir {
FlowDir::RowMajor => ry * w + rx,
FlowDir::RowMajorRev => (h - 1 - ry) * w + (w - 1 - rx),
FlowDir::ColMajor => rx * h + ry,
FlowDir::ColMajorRev => (w - 1 - rx) * h + (h - 1 - ry),
}
}
#[derive(Clone, Copy)]
enum Formation {
Raytrace,
BlockStrata,
ParseDump,
RegisterDump,
TextmarkConverter,
Cellular1D { rule: u8 },
Marquee,
DensityGrid,
AttentionMatrix,
ProbField,
ImagePanel { asset: usize },
#[allow(dead_code)]
RaytraceCube,
Atm,
AgentSlot { player: u8 },
ChessBoard,
PayoutButton,
TerminalInput,
}
const UI_WHITE: (u8, u8, u8) = (255, 255, 255);
struct ImageVariant {
cells: Vec<Vec<char>>,
w: usize,
h: usize,
}
struct ImageAsset {
name: &'static str,
variants: Vec<ImageVariant>, luma: Vec<u8>, }
const DITHER_METHODS: &[DitheringMethod] = &[
DitheringMethod::None,
DitheringMethod::FloydSteinberg,
DitheringMethod::Bayer,
DitheringMethod::Atkinson,
];
fn load_image(
path: &str,
name: &'static str,
cells_w: usize,
cells_h: usize,
) -> Option<ImageAsset> {
let mut variants: Vec<ImageVariant> = Vec::with_capacity(DITHER_METHODS.len());
let mut luma: Option<Vec<u8>> = None;
for &m in DITHER_METHODS {
let grid = ImageRenderer::new()
.load_from_path(Path::new(path))
.ok()?
.resize(cells_w, cells_h, true)
.ok()?
.dithering(m)
.render()
.ok()?;
let (gw, gh) = grid.dimensions();
let cells: Vec<Vec<char>> = (0..gh)
.map(|y| (0..gw).map(|x| grid.get_char(x, y)).collect())
.collect();
if luma.is_none() {
luma = Some(grid.get_raw_patterns().to_vec());
}
variants.push(ImageVariant {
cells,
w: gw,
h: gh,
});
}
Some(ImageAsset {
name,
variants,
luma: luma.unwrap_or_default(),
})
}
fn load_image_assets() -> Vec<ImageAsset> {
let candidates: &[(&str, &str, &'static str)] = &[
(
"tests/fixtures/images/tiger_small.png",
"./tests/fixtures/images/tiger_small.png",
"TIGER",
),
(
"tests/fixtures/images/tiger_1.png",
"./tests/fixtures/images/tiger_1.png",
"TIGR2",
),
(
"tests/fixtures/images/viper3.png",
"./tests/fixtures/images/viper3.png",
"VIPER",
),
(
"tests/fixtures/images/viper_head_3.png",
"./tests/fixtures/images/viper_head_3.png",
"VHEAD",
),
(
"tests/fixtures/images/extras/snakedesk.png",
"./tests/fixtures/images/extras/snakedesk.png",
"SNAKE",
),
(
"tests/fixtures/images/extras/rabbit.png",
"./tests/fixtures/images/extras/rabbit.png",
"RABT",
),
(
"tests/fixtures/images/extras/grifter.jpg",
"./tests/fixtures/images/extras/grifter.jpg",
"GRFTR",
),
(
"tests/fixtures/images/extras/frog_01.png",
"./tests/fixtures/images/extras/frog_01.png",
"FROG",
),
(
"tests/fixtures/images/extras/frog_02.png",
"./tests/fixtures/images/extras/frog_02.png",
"FROG2",
),
];
let mut out = Vec::new();
for &(p1, p2, name) in candidates {
if let Some(a) = load_image(p1, name, 64, 32).or_else(|| load_image(p2, name, 64, 32)) {
out.push(a);
}
}
out
}
fn pick_formation(rect: Rect) -> Formation {
let aspect = (rect.w as f32) / (rect.h.max(1) as f32);
let r = r_u32() as usize;
if aspect > 3.5 {
match r % 4 {
0 => Formation::Marquee,
1 => Formation::TextmarkConverter,
2 => Formation::BlockStrata,
_ => Formation::RegisterDump,
}
} else if aspect < 0.65 {
match r % 3 {
0 => Formation::ParseDump,
1 => Formation::Cellular1D {
rule: if r & 1 == 0 { 30 } else { 110 },
},
_ => Formation::BlockStrata,
}
} else if (aspect - 1.0).abs() < 0.45 && rect.w >= 10 && rect.h >= 6 {
match r % 4 {
0 => Formation::Raytrace,
1 => Formation::AttentionMatrix,
2 => Formation::DensityGrid,
_ => Formation::Cellular1D {
rule: if r & 1 == 0 { 30 } else { 110 },
},
}
} else {
match r % 9 {
0 => Formation::ParseDump,
1 => Formation::RegisterDump,
2 => Formation::DensityGrid,
3 => Formation::TextmarkConverter,
4 => Formation::Cellular1D {
rule: if r & 1 == 0 { 30 } else { 110 },
},
5 => Formation::BlockStrata,
6 => Formation::AttentionMatrix,
7 => Formation::ProbField,
_ => Formation::Marquee,
}
}
}
struct Zone {
base_rect: Rect, rect: Rect,
side: Side,
formation: Formation,
flow_dir: FlowDir,
tap_offset: i32,
pulse: f32,
pulse_rate: f32,
glitch_rate: f32,
}
fn make_zone(
base: Rect,
side: Side,
formation: Formation,
flow_dir: FlowDir,
tap: i32,
_zone_idx: usize,
) -> Zone {
Zone {
base_rect: base,
rect: base,
side,
formation,
flow_dir,
tap_offset: tap,
pulse: r_f32() * 3.0,
pulse_rate: 0.6 + r_f32() * 1.1,
glitch_rate: if r_f32() < 0.15 {
0.3 + r_f32() * 0.7
} else {
0.0
},
}
}
struct Scene {
w: i32,
h: i32,
stream: Vec<char>,
cursor: f32,
flow_rate: f32,
zones: Vec<Zone>,
pipes: Vec<Pipe>,
assets: Vec<ImageAsset>,
adjacency: Vec<Vec<u16>>,
streamers: Vec<Streamer>,
noise_feeds: Vec<NoiseFeed>,
protected_rects: Vec<Rect>,
chess_pos: Chess,
chess_last_move_at: f32,
balance: u32,
balance_last_tick: f32,
input_buffer: String,
glitch_inserts: Vec<GlitchInsertion>,
last_glitch_spawn: f32,
dither_flows: Vec<DitherFlow>,
flipped: bool,
reversed: bool,
}
struct PaintCtx<'a> {
stream: &'a [char],
cursor: f32,
zones: &'a [Zone],
adjacency: &'a [Vec<u16>],
assets: &'a [ImageAsset],
chess_pos: &'a Chess,
balance: u32,
input_buffer: &'a str,
}
fn build_scene(w: i32, h: i32) -> Scene {
seed_from_clock();
let mid = w / 2;
let depth = ((w.min(h * 2)) / 14).clamp(4, 8) as usize;
let left_spiral = fib_spiral(
Rect {
x: 0,
y: 0,
w: mid,
h,
},
depth,
true,
);
let right_spiral = fib_spiral(
Rect {
x: mid,
y: 0,
w: w - mid,
h,
},
depth,
false,
);
let mut zones = Vec::new();
let mut tap_accum: i64 = 0;
for rect in &left_spiral {
let side = if r_f32() < 0.08 { Side::Chaos } else { Side::L };
let flow_dir = if r_f32() < 0.20 {
random_flow()
} else {
FlowDir::RowMajor
};
let formation = pick_formation(*rect);
let idx = zones.len();
zones.push(make_zone(
*rect,
side,
formation,
flow_dir,
tap_accum as i32,
idx,
));
tap_accum += (rect.w * rect.h) as i64;
}
for rect in right_spiral.iter().rev() {
let side = if r_f32() < 0.08 { Side::Chaos } else { Side::R };
let flow_dir = if r_f32() < 0.20 {
random_flow()
} else {
FlowDir::RowMajorRev
};
let formation = pick_formation(*rect);
let idx = zones.len();
zones.push(make_zone(
*rect,
side,
formation,
flow_dir,
tap_accum as i32,
idx,
));
tap_accum += (rect.w * rect.h) as i64;
}
let mut big_indices: Vec<usize> = (0..zones.len()).collect();
big_indices.sort_by_key(|&i| -(zones[i].base_rect.w * zones[i].base_rect.h));
for &i in big_indices.iter().take(4) {
let parent = zones[i].base_rect;
if parent.w < 12 || parent.h < 6 {
continue;
}
let child = golden_child(parent);
if child.w < 5 || child.h < 3 {
continue;
}
let formation = pick_formation(child);
let idx = zones.len();
let mut z = make_zone(
child,
Side::Nested,
formation,
random_flow(),
tap_accum as i32,
idx,
);
z.glitch_rate = 0.15 + r_f32() * 0.4;
zones.push(z);
tap_accum += (child.w * child.h) as i64;
}
let assets = load_image_assets();
let chess_h = ((h as f32 * 0.55) as i32).clamp(8, 36);
let mut chess_w = chess_h * 2;
if chess_w > w * 5 / 8 {
chess_w = (w * 5 / 8) & !1; }
let chess_w = chess_w.clamp(16, 80);
let chess_h = (chess_w / 2).clamp(8, 36);
let ui_chess = Rect {
x: (w - chess_w) / 2,
y: ((h - chess_h) / 2 - 1).max(2),
w: chess_w,
h: chess_h,
};
let panel_w = (w / 7).clamp(18, 28);
let panel_h = (h / 9).clamp(4, 6);
let ui_atm = Rect {
x: w - panel_w - 1,
y: 1,
w: panel_w,
h: panel_h,
};
let ui_agent_a = Rect {
x: 1,
y: 1,
w: panel_w,
h: panel_h,
};
let ui_agent_b = Rect {
x: w - panel_w - 1,
y: ui_atm.y + ui_atm.h + 1,
w: panel_w,
h: panel_h,
};
let ui_payout = Rect {
x: w - panel_w - 1,
y: ui_agent_b.y + ui_agent_b.h + 1,
w: panel_w,
h: panel_h.min(4),
};
let term_h = 3_i32;
let term_w = (chess_w + 4).min(w - 4);
let ui_term = Rect {
x: (w - term_w) / 2,
y: h - term_h - 1,
w: term_w,
h: term_h,
};
let img_left_h = (ui_term.y - (ui_agent_a.y + ui_agent_a.h) - 2).max(8);
let ui_viper = Rect {
x: 1,
y: ui_agent_a.y + ui_agent_a.h + 1,
w: panel_w,
h: img_left_h,
};
let img_right_h = (ui_term.y - (ui_payout.y + ui_payout.h) - 2).max(6);
let ui_vhead = Rect {
x: w - panel_w - 1,
y: ui_payout.y + ui_payout.h + 1,
w: panel_w,
h: img_right_h,
};
let mut sorted_by_area: Vec<usize> = (0..zones.len())
.filter(|&i| zones[i].side != Side::Nested)
.collect();
sorted_by_area.sort_by_key(|&i| -(zones[i].base_rect.w * zones[i].base_rect.h));
if !assets.is_empty() {
let mut assigned = 0usize;
let want = assets.len().min(sorted_by_area.len());
for &i in &sorted_by_area {
let r = zones[i].base_rect;
if r.w < 10 || r.h < 6 {
continue;
}
zones[i].formation = Formation::ImagePanel {
asset: assigned % assets.len(),
};
assigned += 1;
if assigned >= want {
break;
}
}
}
let mut push_ui = |rect: Rect, formation: Formation, tap: &mut i64| {
zones.push(Zone {
base_rect: rect,
rect,
side: Side::Nested,
formation,
flow_dir: FlowDir::RowMajor,
tap_offset: *tap as i32,
pulse: r_f32() * 3.0,
pulse_rate: 0.8 + r_f32() * 0.5,
glitch_rate: 0.0,
});
*tap += (rect.w * rect.h) as i64;
};
push_ui(ui_atm, Formation::Atm, &mut tap_accum);
push_ui(
ui_agent_a,
Formation::AgentSlot { player: 0 },
&mut tap_accum,
);
push_ui(
ui_agent_b,
Formation::AgentSlot { player: 1 },
&mut tap_accum,
);
push_ui(ui_chess, Formation::ChessBoard, &mut tap_accum);
push_ui(ui_payout, Formation::PayoutButton, &mut tap_accum);
push_ui(ui_term, Formation::TerminalInput, &mut tap_accum);
let viper_idx = usize::from(assets.len() > 1);
let vhead_idx = if assets.len() > 2 { 2 } else { viper_idx };
push_ui(
ui_viper,
Formation::ImagePanel { asset: viper_idx },
&mut tap_accum,
);
push_ui(
ui_vhead,
Formation::ImagePanel { asset: vhead_idx },
&mut tap_accum,
);
let stream = build_stream(32_768);
let pipes: Vec<Pipe> = build_pipes(&zones);
let mut adjacency: Vec<Vec<u16>> = vec![Vec::new(); zones.len()];
for p in &pipes {
adjacency[p.from as usize].push(p.to);
adjacency[p.to as usize].push(p.from);
}
let streamers = vec![
Streamer {
axis: StreamerAxis::Horizontal,
anchor: (h as f32 * 0.06) as i32,
speed: 26.0,
direction: 1,
},
Streamer {
axis: StreamerAxis::Horizontal,
anchor: (h as f32 * 0.42) as i32,
speed: 32.0,
direction: -1,
},
Streamer {
axis: StreamerAxis::Horizontal,
anchor: (h as f32 * 0.78) as i32,
speed: 21.0,
direction: 1,
},
Streamer {
axis: StreamerAxis::Horizontal,
anchor: (h as f32 * 0.94) as i32,
speed: 18.0,
direction: -1,
},
Streamer {
axis: StreamerAxis::Vertical,
anchor: (w as f32 * 0.04) as i32,
speed: 22.0,
direction: -1,
},
Streamer {
axis: StreamerAxis::Vertical,
anchor: (w as f32 * 0.97) as i32,
speed: 28.0,
direction: 1,
},
Streamer {
axis: StreamerAxis::DiagPos,
anchor: (w as f32 * 0.02) as i32,
speed: 18.0,
direction: 1,
},
Streamer {
axis: StreamerAxis::DiagPos,
anchor: (w as f32 * 0.45) as i32,
speed: 24.0,
direction: 1,
},
Streamer {
axis: StreamerAxis::DiagNeg,
anchor: (w as f32 * 0.98) as i32,
speed: 17.0,
direction: -1,
},
Streamer {
axis: StreamerAxis::DiagNeg,
anchor: (w as f32 * 0.55) as i32,
speed: 23.0,
direction: -1,
},
];
let noise_feeds = vec![
NoiseFeed {
pos: ((w as f32 * 0.10) as i32, 0),
dir: (0, 1),
length: 4,
seed: 0xACE0_BEEF,
speed: 11.0,
},
NoiseFeed {
pos: ((w as f32 * 0.30) as i32, 0),
dir: (0, 1),
length: 5,
seed: 0xFACE_FADE,
speed: 13.0,
},
NoiseFeed {
pos: ((w as f32 * 0.50) as i32, 0),
dir: (0, 1),
length: 3,
seed: 0xB001_C0DE,
speed: 15.0,
},
NoiseFeed {
pos: ((w as f32 * 0.70) as i32, 0),
dir: (0, 1),
length: 4,
seed: 0x1337_C0DE,
speed: 12.0,
},
NoiseFeed {
pos: ((w as f32 * 0.90) as i32, 0),
dir: (0, 1),
length: 3,
seed: 0xBEEF_F00D,
speed: 16.0,
},
NoiseFeed {
pos: (0, (h as f32 * 0.30) as i32),
dir: (1, 0),
length: 5,
seed: 0xDEAD_BEEF,
speed: 12.0,
},
NoiseFeed {
pos: (0, (h as f32 * 0.55) as i32),
dir: (1, 0),
length: 4,
seed: 0xCAFE_F00D,
speed: 14.0,
},
NoiseFeed {
pos: (0, (h as f32 * 0.78) as i32),
dir: (1, 0),
length: 5,
seed: 0xFEED_BABE,
speed: 10.0,
},
NoiseFeed {
pos: (w - 1, (h as f32 * 0.30) as i32),
dir: (-1, 0),
length: 5,
seed: 0xDEAD_C0DE,
speed: 14.0,
},
NoiseFeed {
pos: (w - 1, (h as f32 * 0.55) as i32),
dir: (-1, 0),
length: 4,
seed: 0x4269_4269,
speed: 11.0,
},
NoiseFeed {
pos: (w - 1, (h as f32 * 0.78) as i32),
dir: (-1, 0),
length: 5,
seed: 0xC001_BEEF,
speed: 15.0,
},
NoiseFeed {
pos: ((w as f32 * 0.20) as i32, h - 1),
dir: (0, -1),
length: 4,
seed: 0xC0DE_F00D,
speed: 12.0,
},
NoiseFeed {
pos: ((w as f32 * 0.55) as i32, h - 1),
dir: (0, -1),
length: 4,
seed: 0xBEEF_BABE,
speed: 13.0,
},
NoiseFeed {
pos: ((w as f32 * 0.85) as i32, h - 1),
dir: (0, -1),
length: 5,
seed: 0xFA15_AFE1,
speed: 11.0,
},
];
let protected_rects: Vec<Rect> = Vec::new();
Scene {
w,
h,
stream,
cursor: 0.0,
flow_rate: 48.0,
zones,
pipes,
assets,
adjacency,
streamers,
noise_feeds,
protected_rects,
chess_pos: Chess::default(),
chess_last_move_at: 0.0,
balance: 42_069,
balance_last_tick: 0.0,
input_buffer: String::new(),
glitch_inserts: Vec::new(),
last_glitch_spawn: 0.0,
dither_flows: {
let mut flows = Vec::new();
for _ in 0..6 {
let speed = 6.0 + r_f32() * 12.0;
let theta = r_f32() * std::f32::consts::TAU;
flows.push(DitherFlow {
pos_x: r_f32() * w as f32,
pos_y: r_f32() * h as f32,
vel_x: theta.cos() * speed,
vel_y: theta.sin() * speed * 0.5, trail: Vec::new(),
trail_max: 14 + (r_u32() as usize % 18),
});
}
flows
},
flipped: false,
reversed: false,
}
}
fn tick(scene: &mut Scene, dt: f32) {
let sign = if scene.reversed { -1.0 } else { 1.0 };
scene.cursor += scene.flow_rate * dt * sign;
for z in &mut scene.zones {
z.pulse += dt * z.pulse_rate * sign;
}
if scene.cursor - scene.chess_last_move_at > 60.0 {
scene.chess_last_move_at = scene.cursor;
let moves = scene.chess_pos.legal_moves();
if moves.is_empty() {
scene.chess_pos = Chess::default();
} else {
let idx = (r_u32() as usize) % moves.len();
let mv = moves[idx];
scene.chess_pos.play_unchecked(mv);
}
}
if scene.cursor - scene.balance_last_tick > 14.0 {
scene.balance_last_tick = scene.cursor;
let pct = (r_f32() - 0.5) * 0.04; let delta = (scene.balance as f32 * pct) as i64;
let new_bal = (scene.balance as i64 + delta).max(100);
scene.balance = new_bal as u32;
}
let w = scene.w;
let h = scene.h;
for flow in &mut scene.dither_flows {
tick_flow(flow, dt * sign, w, h);
}
let cur = scene.cursor;
scene
.glitch_inserts
.retain(|i| (cur - i.spawn_cursor).abs() < i.duration_chars);
if scene.cursor - scene.last_glitch_spawn > 25.0
&& scene.glitch_inserts.len() < 5
&& !scene.assets.is_empty()
{
scene.last_glitch_spawn = scene.cursor;
if r_f32() < 0.85 {
spawn_glitch_insertion(scene);
}
}
}
fn spawn_glitch_insertion(scene: &mut Scene) {
let asset_idx = (r_u32() as usize) % scene.assets.len();
let asset = &scene.assets[asset_idx];
if asset.variants.is_empty() {
return;
}
let variant_idx = (r_u32() as usize) % asset.variants.len();
let variant = &asset.variants[variant_idx];
let aw = variant.w as i32;
let ah = variant.h as i32;
let max_w = (scene.w / 2).max(8);
let max_h = (scene.h * 2 / 3).max(6);
let rw = (8 + (r_u32() as i32 % (max_w - 7).max(1))).min(scene.w - 1);
let rh = (4 + (r_u32() as i32 % (max_h - 3).max(1))).min(scene.h - 1);
let rx = r_u32() as i32 % (scene.w - rw).max(1);
let ry = r_u32() as i32 % (scene.h - rh).max(1);
let rect = Rect {
x: rx,
y: ry,
w: rw,
h: rh,
};
let crop = if r_f32() < 0.5 {
None
} else {
let cw = (4 + (r_u32() as i32 % (aw - 3).max(1))).min(aw);
let ch = (3 + (r_u32() as i32 % (ah - 2).max(1))).min(ah);
let cx = r_u32() as i32 % (aw - cw).max(1);
let cy = r_u32() as i32 % (ah - ch).max(1);
Some(Rect {
x: cx,
y: cy,
w: cw,
h: ch,
})
};
let duration_chars = 30.0 + r_f32() * 90.0; scene.glitch_inserts.push(GlitchInsertion {
asset_idx,
rect,
crop,
spawn_cursor: scene.cursor,
duration_chars,
variant_idx,
});
}
const NO_OWNER: u16 = u16::MAX;
#[derive(Clone, Copy)]
struct PxCell {
ch: char,
fg: (u8, u8, u8),
intensity: f32,
owner: u16, }
impl PxCell {
const fn empty() -> Self {
Self {
ch: ' ',
fg: (0, 0, 0),
intensity: 0.0,
owner: NO_OWNER,
}
}
}
fn put(grid: &mut [Vec<PxCell>], x: i32, y: i32, ch: char, c: (u8, u8, u8), i: f32, owner: u16) {
if y < 0 || x < 0 {
return;
}
let (uy, ux) = (y as usize, x as usize);
if uy >= grid.len() || ux >= grid[0].len() {
return;
}
let cell = &mut grid[uy][ux];
if i >= cell.intensity {
cell.ch = ch;
cell.fg = c;
cell.intensity = i;
cell.owner = owner;
}
}
fn put_force(grid: &mut [Vec<PxCell>], x: i32, y: i32, ch: char, c: (u8, u8, u8), i: f32) {
if y < 0 || x < 0 {
return;
}
let (uy, ux) = (y as usize, x as usize);
if uy >= grid.len() || ux >= grid[0].len() {
return;
}
grid[uy][ux] = PxCell {
ch,
fg: c,
intensity: i,
owner: NO_OWNER,
};
}
fn color_for(side: Side) -> (u8, u8, u8) {
match side {
Side::L | Side::R => (170, 18, 18), Side::Chaos => (255, 90, 90), Side::Nested => (230, 40, 40), }
}
#[inline]
fn sample(stream: &[char], idx: i64) -> char {
let n = stream.len() as i64;
stream[idx.rem_euclid(n) as usize]
}
fn glitch_offset(z: &Zone) -> i64 {
if z.glitch_rate < 0.05 {
return 0;
}
let phase = (z.pulse * z.glitch_rate * 0.6) as i64;
phase.wrapping_mul(2_039)
}
fn ihash(x: i32, y: i32, t: i32) -> u32 {
let mut n = (x as u32)
.wrapping_mul(374_761_393)
.wrapping_add((y as u32).wrapping_mul(668_265_263))
.wrapping_add((t as u32).wrapping_mul(2_654_435_761));
n ^= n >> 13;
n = n.wrapping_mul(1_274_126_177);
n ^ (n >> 16)
}
fn paint_fill(grid: &mut [Vec<PxCell>], zone: &Zone, zone_id: u16, ch: char, i: f32) {
let color = color_for(zone.side);
for ry in 0..zone.rect.h {
for rx in 0..zone.rect.w {
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
ch,
color,
i,
zone_id,
);
}
}
}
fn paint_raytrace(grid: &mut [Vec<PxCell>], zone: &Zone, zone_id: u16) {
let color = color_for(zone.side);
let w = zone.rect.w as usize;
let h = zone.rect.h as usize;
if w < 4 || h < 3 {
paint_fill(grid, zone, zone_id, '·', 0.20);
return;
}
let mut rt = RtScene::new();
rt.add_object(Box::new(Sphere::new(Vector3::new(0.0, 0.0, -3.0), 1.1)));
let cam = Camera::new(Vector3::new(0.0, 0.0, 0.0), 4.0, 3.0);
let orient = WireframeRotation {
yaw: zone.pulse * 0.6,
pitch: (zone.pulse * 0.4).sin() * 0.45,
roll: 0.0,
};
let mode = RenderMode::Wireframe {
step_rad: 15.0_f32.to_radians(),
tol_rad: 0.035,
};
let buf = render_with_orientation(&rt, &cam, w, h, mode, orient);
let ramp: &[char] = &[' ', '·', ':', '-', '=', '+', '*', '#', '%', '@'];
for (ry, row) in buf.iter().enumerate().take(h) {
for (rx, &depth) in row.iter().enumerate().take(w) {
let v = depth.clamp(0.0, 1.0);
let idx = ((v * (ramp.len() - 1) as f32).round() as usize).min(ramp.len() - 1);
let ch = ramp[idx];
let i = if v > 0.30 { 0.90 } else { 0.22 };
put(
grid,
zone.rect.x + rx as i32,
zone.rect.y + ry as i32,
ch,
color,
i,
zone_id,
);
}
}
}
fn paint_block_strata(grid: &mut [Vec<PxCell>], zone: &Zone, zone_id: u16) {
let color = color_for(zone.side);
let levels: &[(char, f32)] = &[
(' ', 0.08),
('░', 0.38),
('▒', 0.62),
('▓', 0.85),
('█', 1.00),
];
let scroll = (zone.pulse * 2.4) as i32;
for ry in 0..zone.rect.h {
let stripe = (ry + scroll).rem_euclid(20);
let level_idx = match stripe {
0..=1 => 0,
2..=4 => 1,
9..=12 | 16..=18 => 3,
13..=15 => 4,
_ => 2,
};
let (ch, i) = levels[level_idx];
for rx in 0..zone.rect.w {
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
ch,
color,
i,
zone_id,
);
}
}
}
fn paint_parse_dump(
grid: &mut [Vec<PxCell>],
zone: &Zone,
zone_id: u16,
stream: &[char],
cursor: f32,
) {
let color = color_for(zone.side);
let zw = zone.rect.w;
let zh = zone.rect.h;
let base: i64 = (cursor as i64) - (zone.tap_offset as i64);
let scroll = base / 4;
for ry in 0..zh {
let addr = (scroll.wrapping_add(ry as i64) & 0xffff) as u32;
for rx in 0..zw {
let (ch, i) = if rx < 4 {
let nibble = ((addr >> ((3 - rx) * 4)) & 0xf) as usize;
(HEX[nibble], 0.72)
} else if rx == 4 {
(':', 0.55)
} else if rx == 5 {
(' ', 0.10)
} else {
let rel = rx - 6;
let byte_idx = rel / 3;
let pos = rel % 3;
let b = sample(stream, base + (ry as i64) * 9 + byte_idx as i64) as u32;
match pos {
0 => (HEX[((b >> 4) & 0xf) as usize], 0.92),
1 => (HEX[(b & 0xf) as usize], 0.92),
_ => (' ', 0.12),
}
};
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
ch,
color,
i,
zone_id,
);
}
}
}
fn paint_register_dump(
grid: &mut [Vec<PxCell>],
zone: &Zone,
zone_id: u16,
stream: &[char],
cursor: f32,
) {
const NAMES: &[&str] = &[
"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "PC", "SP", "LR", "SR",
];
let color = color_for(zone.side);
let zw = zone.rect.w;
let zh = zone.rect.h;
let base: i64 = (cursor as i64) - (zone.tap_offset as i64);
for ry in 0..zh {
let name_cycle = ((base / 8) as usize).wrapping_add(ry as usize) % NAMES.len();
let name = NAMES[name_cycle];
let mut row: Vec<(char, f32)> = Vec::with_capacity(zw as usize);
for ch in name.chars() {
row.push((ch, 0.82));
}
row.push((':', 0.55));
row.push((' ', 0.10));
row.push(('0', 0.70));
row.push(('x', 0.70));
for i in 0..8 {
let nib = sample(stream, base + (ry as i64) * 5 + i as i64) as u32;
row.push((HEX[(nib & 0xf) as usize], 0.95));
}
while row.len() < zw as usize {
row.push((' ', 0.10));
}
for (rx, &(ch, i)) in row.iter().take(zw as usize).enumerate() {
put(
grid,
zone.rect.x + rx as i32,
zone.rect.y + ry,
ch,
color,
i,
zone_id,
);
}
}
}
fn paint_textmark(
grid: &mut [Vec<PxCell>],
zone: &Zone,
zone_id: u16,
stream: &[char],
cursor: f32,
) {
let color = color_for(zone.side);
let zw = zone.rect.w;
let zh = zone.rect.h;
let mid_y = zh / 2;
let mid_x = zw / 2;
let base: i64 = (cursor as i64) - (zone.tap_offset as i64);
let transform = |c: char| -> char {
if c.is_ascii_alphabetic() {
let b = if c.is_ascii_lowercase() { b'a' } else { b'A' };
let off = ((c as u8) - b + 13) % 26;
(b + off) as char
} else if c.is_ascii_digit() {
let d = (c as u8) - b'0';
(b'0' + (9 - d)) as char
} else if c.is_ascii() {
HEX[((c as u8) >> 4 & 0x0f) as usize]
} else {
HEX[((c as u32) & 0x0f) as usize]
}
};
for ry in 0..zh {
for rx in 0..zw {
if ry == mid_y && rx == mid_x {
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
'⇒',
color,
1.0,
zone_id,
);
continue;
}
if ry == mid_y {
let (ch, i) = if rx < mid_x {
let c = sample(stream, base + (mid_x - 1 - rx) as i64);
(c, 0.95)
} else {
let c = sample(stream, base + (rx - mid_x - 1) as i64);
(transform(c), 0.95)
};
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
ch,
color,
i,
zone_id,
);
} else {
let d = ((ry - mid_y).abs() as f32) / (zh as f32 * 0.5);
let fade = (0.48 - d * 0.32).max(0.12);
let c = sample(stream, base + (ry as i64) * 7 + rx as i64);
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
c,
color,
fade,
zone_id,
);
}
}
}
}
fn paint_cellular(
grid: &mut [Vec<PxCell>],
zone: &Zone,
zone_id: u16,
rule: u8,
stream: &[char],
cursor: f32,
) {
let color = color_for(zone.side);
let zw = zone.rect.w as usize;
let zh = zone.rect.h as usize;
if zw < 3 || zh < 2 {
paint_fill(grid, zone, zone_id, '·', 0.20);
return;
}
let base: i64 = (cursor as i64) - (zone.tap_offset as i64);
let t_shift = (zone.pulse * 2.0) as i64;
let mut row: Vec<bool> = (0..zw)
.map(|rx| {
let s = sample(stream, base + rx as i64 + t_shift) as u32;
(s & 1) == 1
})
.collect();
for ry in 0..zh {
for (rx, &alive) in row.iter().enumerate() {
let (ch, i) = if alive { ('█', 0.93) } else { ('·', 0.18) };
put(
grid,
zone.rect.x + rx as i32,
zone.rect.y + ry as i32,
ch,
color,
i,
zone_id,
);
}
if ry + 1 >= zh {
break;
}
let prev = row.clone();
for rx in 0..zw {
let l = prev[(rx + zw - 1) % zw];
let c = prev[rx];
let r = prev[(rx + 1) % zw];
let pat = ((l as u8) << 2) | ((c as u8) << 1) | (r as u8);
row[rx] = ((rule >> pat) & 1) == 1;
}
}
}
fn paint_marquee(
grid: &mut [Vec<PxCell>],
zone: &Zone,
zone_id: u16,
stream: &[char],
cursor: f32,
) {
let color = color_for(zone.side);
let base: i64 = (cursor as i64) - (zone.tap_offset as i64) + glitch_offset(zone);
let zw = zone.rect.w;
let zh = zone.rect.h;
let mid_y = zh / 2;
for ry in 0..zh {
for rx in 0..zw {
let ci = cell_index(rx, ry, zw, zh, zone.flow_dir);
let ch = sample(stream, base + ci);
let i = if ry == mid_y {
1.0
} else {
let d = ((ry - mid_y).abs() as f32) / (zh as f32 * 0.5);
(0.78 - d * 0.48).max(0.30)
};
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
ch,
color,
i,
zone_id,
);
}
}
}
fn paint_density_grid(
grid: &mut [Vec<PxCell>],
zone: &Zone,
zone_id: u16,
stream: &[char],
cursor: f32,
) {
let color = color_for(zone.side);
let base: i64 = (cursor as i64) - (zone.tap_offset as i64);
let levels: &[(char, f32)] = &[
(' ', 0.08),
('░', 0.35),
('▒', 0.58),
('▓', 0.82),
('█', 1.00),
];
let block_w: i32 = 2;
let blocks_per_row = (zone.rect.w + block_w - 1) / block_w;
for ry in 0..zone.rect.h {
for bx in 0..blocks_per_row {
let rx0 = bx * block_w;
let idx = (ry as i64) * (blocks_per_row as i64) + bx as i64;
let s = sample(stream, base + idx) as u32;
let density = ((s & 0xff) as usize * levels.len()) / 256;
let density = density.min(levels.len() - 1);
let (ch, i) = levels[density];
for k in 0..block_w {
let rx = rx0 + k;
if rx >= zone.rect.w {
break;
}
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
ch,
color,
i,
zone_id,
);
}
}
}
}
fn paint_attention(grid: &mut [Vec<PxCell>], zone: &Zone, zone_id: u16) {
let color = color_for(zone.side);
let zw = zone.rect.w;
let zh = zone.rect.h;
let t = zone.pulse;
let n_sinks = (1 + (zw / 14)).max(2);
let mut sinks: Vec<i32> = Vec::with_capacity(n_sinks as usize);
for i in 0..n_sinks {
let phase = (t * 0.2 + i as f32 * 0.7).sin();
let pos = (((phase + 1.0) * 0.5) * (zw as f32 - 2.0)) as i32 + 1;
sinks.push(pos.clamp(0, zw - 1));
}
for ry in 0..zh {
for rx in 0..zw {
let mut score: f32 = 0.12;
let diag_x = (ry as f32 / zh.max(1) as f32) * (zw as f32);
let d = (diag_x - rx as f32).abs();
if d < 2.0 {
score = score.max(0.88 - d * 0.25);
}
for &s in &sinks {
let cd = (rx - s).abs();
if cd == 0 {
score = score.max(0.78);
} else if cd == 1 {
score = score.max(0.42);
}
}
let h = ihash(rx, ry, (t * 2.0) as i32);
if (h & 0xff) < 4 {
score = score.max(0.95);
}
let (ch, i) = if score > 0.85 {
('█', 1.0)
} else if score > 0.60 {
('▓', 0.80)
} else if score > 0.38 {
('▒', 0.55)
} else if score > 0.18 {
('░', 0.32)
} else {
('.', 0.14)
};
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
ch,
color,
i,
zone_id,
);
}
}
}
fn paint_prob_field(grid: &mut [Vec<PxCell>], zone: &Zone, zone_id: u16, ctx: &PaintCtx) {
const CANDIDATES: &[&str] = &[
"the", "and", "to", "of", "is", "a", "in", "that", "it", "for", "fn", "::", "0x", "->",
"=>", "if", "fold", "self", "void", "phi", "sigma", "delta", "ROUTINE", "ACK", "MOV",
"yield", "loop", "ok", "recur", "echo", "bind", "map", "tau", "λ", "∇",
];
let color = color_for(zone.side);
let zw = zone.rect.w as usize;
let zh = zone.rect.h as usize;
if zw < 14 || zh < 2 {
paint_fill(grid, zone, zone_id, '·', 0.25);
return;
}
let neighbors = &ctx.adjacency[zone_id as usize];
let tap_pool: Vec<i32> = if neighbors.is_empty() {
vec![zone.tap_offset]
} else {
neighbors
.iter()
.map(|&j| ctx.zones[j as usize].tap_offset)
.collect()
};
let top_k = zh.min(16);
let bar_width = zw.saturating_sub(13).max(4);
let mut probs: Vec<(f32, &str)> = Vec::with_capacity(top_k);
let mut sum = 0.0_f32;
for i in 0..top_k {
let tap = tap_pool[i % tap_pool.len()];
let neighbor_window_offset = (i as i64) * 23 + (ctx.cursor as i64 / 3);
let s = sample(
ctx.stream,
(ctx.cursor as i64) - (tap as i64) + neighbor_window_offset,
) as u32;
let raw = 0.01 + ((s & 0xff) as f32) / 255.0;
let weight = raw.powi(3);
sum += weight;
let name = CANDIDATES[(s as usize >> 4) % CANDIDATES.len()];
probs.push((weight, name));
}
for p in &mut probs {
p.0 /= sum.max(1e-6);
}
probs.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
paint_fill(grid, zone, zone_id, ' ', 0.08);
for (row, (p, name)) in probs.iter().enumerate().take(zh) {
let ry = row as i32;
let fill = ((*p * bar_width as f32).round() as usize).min(bar_width);
for rx in 0..(bar_width as i32) {
let (ch, i) = if (rx as usize) < fill {
('█', (0.55 + p * 0.45).min(1.0))
} else {
('░', 0.20)
};
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
ch,
color,
i,
zone_id,
);
}
let p_str = format!(" {:.2}", p.min(0.99));
let mut cx = bar_width as i32 + 1;
for ch in p_str.chars() {
if cx >= zone.rect.w {
break;
}
put(
grid,
zone.rect.x + cx,
zone.rect.y + ry,
ch,
color,
0.82,
zone_id,
);
cx += 1;
}
cx += 1;
for ch in name.chars() {
if cx >= zone.rect.w {
break;
}
put(
grid,
zone.rect.x + cx,
zone.rect.y + ry,
ch,
color,
0.95,
zone_id,
);
cx += 1;
}
}
}
#[derive(Clone, Copy)]
enum SweepKind {
Horizontal,
Vertical,
Diagonal,
AntiDiag,
Radial,
}
#[derive(Clone, Copy)]
enum DitherPhase {
Stable {
idx: usize,
},
Sweep {
from: usize,
to: usize,
t: f32,
kind: SweepKind,
},
}
const DITHER_NAMES: &[&str] = &["NONE", "FLOYD", "BAYER", "ATKIN"];
fn dither_phase(cursor: f32, n_variants: usize, offset_secs: f32) -> DitherPhase {
const PERIOD: f32 = 11.0;
const TRANSIT: f32 = 1.6;
let cycle = PERIOD + TRANSIT;
let secs = (cursor / 48.0) + offset_secs;
let cycle_num = secs.div_euclid(cycle) as i64;
let in_cycle = secs.rem_euclid(cycle);
let kind = match (cycle_num.rem_euclid(5)) as usize {
0 => SweepKind::Horizontal,
1 => SweepKind::Vertical,
2 => SweepKind::Diagonal,
3 => SweepKind::AntiDiag,
_ => SweepKind::Radial,
};
if in_cycle < PERIOD {
DitherPhase::Stable {
idx: (cycle_num.rem_euclid(n_variants as i64)) as usize,
}
} else {
let raw = ((in_cycle - PERIOD) / TRANSIT).clamp(0.0, 1.0);
let t = raw * raw * (3.0 - 2.0 * raw);
let from = cycle_num.rem_euclid(n_variants as i64) as usize;
let to = (cycle_num + 1).rem_euclid(n_variants as i64) as usize;
DitherPhase::Sweep { from, to, t, kind }
}
}
#[inline]
fn sweep_front_glyph(kind: SweepKind) -> char {
match kind {
SweepKind::Horizontal => '✦', SweepKind::Vertical => '✧', SweepKind::Diagonal => '◉', SweepKind::AntiDiag => '☸', SweepKind::Radial => '⚘', }
}
#[inline]
fn sweep_progress(rx: i32, ry: i32, zw: i32, zh: i32, kind: SweepKind) -> f32 {
let zwf = zw.max(1) as f32;
let zhf = zh.max(1) as f32;
match kind {
SweepKind::Horizontal => rx as f32 / zwf,
SweepKind::Vertical => ry as f32 / zhf,
SweepKind::Diagonal => (rx as f32 + (ry as f32) * 2.0) / (zwf + zhf * 2.0),
SweepKind::AntiDiag => ((zwf - rx as f32 - 1.0) + (ry as f32) * 2.0) / (zwf + zhf * 2.0),
SweepKind::Radial => {
let cx = zwf * 0.5;
let cy = zhf * 0.5;
let dx = rx as f32 - cx;
let dy = (ry as f32 - cy) * 2.0;
let d = (dx * dx + dy * dy).sqrt();
let max_d = ((cx * cx) + (cy * 2.0).powi(2)).sqrt().max(0.01);
d / max_d
}
}
}
fn paint_image_panel(
grid: &mut [Vec<PxCell>],
zone: &Zone,
zone_id: u16,
asset_idx: usize,
ctx: &PaintCtx,
) {
let color = color_for(zone.side);
let zw = zone.rect.w;
let zh = zone.rect.h;
if ctx.assets.is_empty() || asset_idx >= ctx.assets.len() {
paint_fill(grid, zone, zone_id, '·', 0.3);
return;
}
let asset = &ctx.assets[asset_idx];
if asset.variants.is_empty() {
paint_fill(grid, zone, zone_id, '·', 0.3);
return;
}
let n_variants = asset.variants.len();
let zone_offset = (zone.tap_offset as f32 * 0.0019) + zone.pulse * 0.7;
let phase = dither_phase(ctx.cursor, n_variants, zone_offset);
let pulse_i = (zone.pulse * 3.7) as i32;
let tear_rows: [i32; 3] = [
((zone.pulse * 4.2).sin() * zh as f32) as i32 % zh.max(1),
((zone.pulse * 1.9 + 1.3).sin() * zh as f32) as i32 % zh.max(1),
((zone.pulse * 2.6 + 3.1).sin() * zh as f32) as i32 % zh.max(1),
];
let tear_amts: [i32; 3] = [
((zone.pulse * 5.3).sin() * 6.0) as i32,
((zone.pulse * 3.8 + 0.7).sin() * 4.0) as i32,
((zone.pulse * 2.1 + 2.4).sin() * 8.0) as i32,
];
let base_stream: i64 = (ctx.cursor as i64) - (zone.tap_offset as i64);
const FRONT_BAND: f32 = 0.035;
let scroll_x = (zone.pulse * 1.7) as i32;
let scroll_y = (zone.pulse * 0.9) as i32;
for ry in 0..zh {
let mut tear_dx = 0_i32;
for k in 0..3 {
if ry == tear_rows[k] {
tear_dx = tear_amts[k];
}
}
for rx in 0..zw {
let (v_idx, on_front, front_kind) = match phase {
DitherPhase::Stable { idx } => (idx, false, SweepKind::Horizontal),
DitherPhase::Sweep { from, to, t, kind } => {
let p = sweep_progress(rx, ry, zw, zh, kind);
let front = (p - t).abs() < FRONT_BAND;
let idx = if p < t { to } else { from };
(idx, front, kind)
}
};
let variant = &asset.variants[v_idx.min(n_variants - 1)];
let vw = variant.w as i32;
let vh = variant.h as i32;
let srx = (rx + tear_dx + scroll_x).rem_euclid(vw.max(1)) as usize;
let sry = (ry + scroll_y).rem_euclid(vh.max(1)) as usize;
let img_ch = variant
.cells
.get(sry)
.and_then(|row| row.get(srx))
.copied()
.unwrap_or(' ');
let h = ihash(rx, ry, pulse_i);
let (ch, intensity, fg_override) = if on_front {
(sweep_front_glyph(front_kind), 1.0, Some((255, 50, 50)))
} else if h.trailing_zeros() >= 7 {
let blocks: &[char] = &['█', '▓', '▒', '░'];
(blocks[(h as usize >> 7) % blocks.len()], 1.0, None)
} else if h.trailing_zeros() >= 6 {
let sc = sample(ctx.stream, base_stream + (ry as i64) * 7 + rx as i64);
(sc, 0.85, None)
} else if img_ch == '\u{2800}' {
(' ', 0.08, None)
} else {
(img_ch, 0.92, None)
};
let fg = fg_override.unwrap_or(color);
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
ch,
fg,
intensity,
zone_id,
);
}
}
let label = asset.name;
for (i, ch) in label.chars().enumerate() {
let lx = zone.rect.x + 1 + i as i32;
if i as i32 + 1 < zw {
put(grid, lx, zone.rect.y, ch, (255, 50, 50), 1.0, zone_id);
}
}
let tag: String = match phase {
DitherPhase::Stable { idx } => {
format!("[{}]", DITHER_NAMES[idx.min(DITHER_NAMES.len() - 1)])
}
DitherPhase::Sweep { from, to, .. } => format!(
"{}→{}",
DITHER_NAMES[from.min(DITHER_NAMES.len() - 1)],
DITHER_NAMES[to.min(DITHER_NAMES.len() - 1)],
),
};
let tag_y = zone.rect.y + zh - 1;
let tag_x_start = zone.rect.x + zw - (tag.chars().count() as i32) - 1;
for (i, ch) in tag.chars().enumerate() {
let lx = tag_x_start + i as i32;
if lx >= zone.rect.x && lx < zone.rect.x + zw {
put(grid, lx, tag_y, ch, (255, 50, 50), 1.0, zone_id);
}
}
}
fn paint_raytrace_cube(grid: &mut [Vec<PxCell>], zone: &Zone, zone_id: u16) {
let zw = zone.rect.w;
let zh = zone.rect.h;
if zw < 6 || zh < 4 {
paint_fill(grid, zone, zone_id, ' ', 0.05);
return;
}
for ry in 0..zh {
for rx in 0..zw {
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
' ',
(0, 0, 0),
0.02,
zone_id,
);
}
}
let corners = [
Vector3::new(-1.0, -1.0, -1.0),
Vector3::new(1.0, -1.0, -1.0),
Vector3::new(1.0, 1.0, -1.0),
Vector3::new(-1.0, 1.0, -1.0),
Vector3::new(-1.0, -1.0, 1.0),
Vector3::new(1.0, -1.0, 1.0),
Vector3::new(1.0, 1.0, 1.0),
Vector3::new(-1.0, 1.0, 1.0),
];
let edges: [(usize, usize); 12] = [
(0, 1),
(1, 2),
(2, 3),
(3, 0), (4, 5),
(5, 6),
(6, 7),
(7, 4), (0, 4),
(1, 5),
(2, 6),
(3, 7), ];
let yaw = zone.pulse * 0.55;
let pitch = (zone.pulse * 0.37).sin() * 0.45;
let roll = (zone.pulse * 0.22).cos() * 0.25;
let zwf = zw as f32;
let zhf = zh as f32;
let cam_z = 3.2_f32;
let viewport_w = 2.8_f32;
let viewport_h = 2.8_f32;
let projected: [Option<(i32, i32)>; 8] = {
let mut out = [None; 8];
for (i, &c) in corners.iter().enumerate() {
let r = rotate_vec_yaw_pitch_roll(c, yaw, pitch, roll);
let z = r.z - cam_z;
if z >= -0.01 {
continue;
} let px = r.x * (-1.0 / z) / (viewport_w * 0.5);
let py = r.y * (-1.0 / z) / (viewport_h * 0.5);
let cx = (px + 1.0) * 0.5 * zwf;
let cy = (1.0 - (py + 1.0) * 0.5) * zhf * 2.0; let cy = cy * 0.5;
out[i] = Some((cx.round() as i32, cy.round() as i32));
}
out
};
for &(a, b) in &edges {
if let (Some((x0, y0)), Some((x1, y1))) = (projected[a], projected[b]) {
let dx = (x1 - x0).abs();
let dy = -(y1 - y0).abs();
let sx = if x0 < x1 { 1 } else { -1 };
let sy = if y0 < y1 { 1 } else { -1 };
let mut err = dx + dy;
let (mut x, mut y) = (x0, y0);
loop {
if x >= 0 && x < zw && y >= 0 && y < zh {
put(
grid,
zone.rect.x + x,
zone.rect.y + y,
'█',
(220, 35, 35),
1.0,
zone_id,
);
}
if x == x1 && y == y1 {
break;
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x += sx;
}
if e2 <= dx {
err += dx;
y += sy;
}
}
}
}
}
fn paint_box_border(grid: &mut [Vec<PxCell>], zone: &Zone, zone_id: u16, intensity: f32) {
let zw = zone.rect.w;
let zh = zone.rect.h;
if zw < 2 || zh < 2 {
return;
}
for x in 1..zw - 1 {
put(
grid,
zone.rect.x + x,
zone.rect.y,
'─',
UI_WHITE,
intensity,
zone_id,
);
put(
grid,
zone.rect.x + x,
zone.rect.y + zh - 1,
'─',
UI_WHITE,
intensity,
zone_id,
);
}
for y in 1..zh - 1 {
put(
grid,
zone.rect.x,
zone.rect.y + y,
'│',
UI_WHITE,
intensity,
zone_id,
);
put(
grid,
zone.rect.x + zw - 1,
zone.rect.y + y,
'│',
UI_WHITE,
intensity,
zone_id,
);
}
put(
grid,
zone.rect.x,
zone.rect.y,
'┌',
UI_WHITE,
intensity,
zone_id,
);
put(
grid,
zone.rect.x + zw - 1,
zone.rect.y,
'┐',
UI_WHITE,
intensity,
zone_id,
);
put(
grid,
zone.rect.x,
zone.rect.y + zh - 1,
'└',
UI_WHITE,
intensity,
zone_id,
);
put(
grid,
zone.rect.x + zw - 1,
zone.rect.y + zh - 1,
'┘',
UI_WHITE,
intensity,
zone_id,
);
}
#[allow(clippy::too_many_arguments)]
fn put_str(
grid: &mut [Vec<PxCell>],
x: i32,
y: i32,
s: &str,
color: (u8, u8, u8),
i: f32,
oid: u16,
max_x: i32,
) {
for (cx, ch) in (x..).zip(s.chars()) {
if cx >= max_x {
break;
}
put(grid, cx, y, ch, color, i, oid);
}
}
fn paint_atm(grid: &mut [Vec<PxCell>], zone: &Zone, zone_id: u16, balance: u32, cursor: f32) {
paint_fill(grid, zone, zone_id, ' ', 0.05);
paint_box_border(grid, zone, zone_id, 1.0);
let ix = zone.rect.x + 2;
let max_x = zone.rect.x + zone.rect.w - 1;
put_str(
grid,
ix,
zone.rect.y + 1,
"ATM ::",
UI_WHITE,
1.0,
zone_id,
max_x,
);
let bal = format!("$ {:08}", balance);
put_str(
grid,
ix,
zone.rect.y + 2,
&bal,
UI_WHITE,
1.0,
zone_id,
max_x,
);
let blink = ((cursor * 0.5) as i32) % 2 == 0;
if blink && zone.rect.h >= 4 {
put_str(
grid,
ix,
zone.rect.y + 3,
"● ONLINE",
(255, 90, 90),
1.0,
zone_id,
max_x,
);
} else if zone.rect.h >= 4 {
put_str(
grid,
ix,
zone.rect.y + 3,
"○ ONLINE",
(200, 60, 60),
0.9,
zone_id,
max_x,
);
}
}
fn paint_agent_slot(
grid: &mut [Vec<PxCell>],
zone: &Zone,
zone_id: u16,
player: u8,
scene_input: &str,
) {
paint_fill(grid, zone, zone_id, ' ', 0.05);
paint_box_border(grid, zone, zone_id, 0.9);
let ix = zone.rect.x + 2;
let max_x = zone.rect.x + zone.rect.w - 1;
let label = if player == 0 { "AGENT P1" } else { "AGENT P2" };
put_str(
grid,
ix,
zone.rect.y + 1,
label,
UI_WHITE,
1.0,
zone_id,
max_x,
);
let prompt = "[ENTER AGENT >]";
put_str(
grid,
ix,
zone.rect.y + 2,
prompt,
(255, 110, 110),
1.0,
zone_id,
max_x,
);
if player == 0 && !scene_input.is_empty() && zone.rect.h >= 4 {
let truncated: String = scene_input
.chars()
.take((zone.rect.w - 4) as usize)
.collect();
put_str(
grid,
ix,
zone.rect.y + 3,
&truncated,
UI_WHITE,
1.0,
zone_id,
max_x,
);
}
}
fn paint_chess_board(
grid: &mut [Vec<PxCell>],
zone: &Zone,
zone_id: u16,
pos: &Chess,
cursor: f32,
) {
paint_fill(grid, zone, zone_id, ' ', 0.04);
let opts = RenderOptions {
target_width: Some(zone.rect.w as usize),
target_height: Some(zone.rect.h as usize),
..Default::default()
};
if let Ok(braille_grid) = render_position_with_options(pos, &opts) {
let (gw, gh) = braille_grid.dimensions();
let phase = dither_phase(cursor, 4, 0.0);
for ry in 0..zone.rect.h.min(gh as i32) {
for rx in 0..zone.rect.w.min(gw as i32) {
let ch = braille_grid.get_char(rx as usize, ry as usize);
let (color, intensity) = if ch == '\u{2800}' || ch == ' ' {
((50, 5, 5), 0.30)
} else {
(UI_WHITE, 1.0)
};
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
ch,
color,
intensity,
zone_id,
);
}
}
if let DitherPhase::Sweep { t, kind, .. } = phase {
let zw = zone.rect.w;
let zh = zone.rect.h;
for ry in 0..zh {
for rx in 0..zw {
let p = sweep_progress(rx, ry, zw, zh, kind);
if (p - t).abs() < 0.04 {
put(
grid,
zone.rect.x + rx,
zone.rect.y + ry,
sweep_front_glyph(kind),
UI_WHITE,
1.0,
zone_id,
);
}
}
}
}
}
}
fn paint_payout_button(grid: &mut [Vec<PxCell>], zone: &Zone, zone_id: u16, cursor: f32) {
let zw = zone.rect.w;
let zh = zone.rect.h;
paint_fill(grid, zone, zone_id, ' ', 0.0);
for x in 0..zw {
put(
grid,
zone.rect.x + x,
zone.rect.y,
'█',
UI_WHITE,
1.0,
zone_id,
);
put(
grid,
zone.rect.x + x,
zone.rect.y + zh - 1,
'█',
UI_WHITE,
1.0,
zone_id,
);
}
for y in 0..zh {
put(
grid,
zone.rect.x,
zone.rect.y + y,
'█',
UI_WHITE,
1.0,
zone_id,
);
put(
grid,
zone.rect.x + zw - 1,
zone.rect.y + y,
'█',
UI_WHITE,
1.0,
zone_id,
);
}
let text = "[ CASH OUT ]";
let len = text.chars().count() as i32;
let mid_y = zone.rect.y + zh / 2;
let mid_x = zone.rect.x + (zw - len) / 2;
let pulse = ((cursor * 0.05).sin() + 1.0) * 0.5; let color = (
255,
(60.0 + pulse * 60.0) as u8,
(60.0 + pulse * 60.0) as u8,
);
put_str(
grid,
mid_x,
mid_y,
text,
color,
1.0,
zone_id,
zone.rect.x + zw - 1,
);
}
fn paint_terminal_input(
grid: &mut [Vec<PxCell>],
zone: &Zone,
zone_id: u16,
buffer: &str,
cursor: f32,
) {
paint_fill(grid, zone, zone_id, ' ', 0.05);
paint_box_border(grid, zone, zone_id, 0.9);
let ix = zone.rect.x + 2;
let mid_y = zone.rect.y + zone.rect.h / 2;
let max_x = zone.rect.x + zone.rect.w - 1;
let prompt = "INPUT> ";
put_str(grid, ix, mid_y, prompt, UI_WHITE, 1.0, zone_id, max_x);
let mut bx = ix + prompt.chars().count() as i32;
for ch in buffer.chars() {
if bx >= max_x - 1 {
break;
}
put(grid, bx, mid_y, ch, UI_WHITE, 1.0, zone_id);
bx += 1;
}
let blink = ((cursor / 24.0) as i32) % 2 == 0;
if blink && bx < max_x {
put(grid, bx, mid_y, '█', UI_WHITE, 1.0, zone_id);
}
}
fn paint_formation(grid: &mut [Vec<PxCell>], zone: &Zone, zone_id: u16, ctx: &PaintCtx) {
match zone.formation {
Formation::Raytrace => paint_raytrace(grid, zone, zone_id),
Formation::BlockStrata => paint_block_strata(grid, zone, zone_id),
Formation::ParseDump => paint_parse_dump(grid, zone, zone_id, ctx.stream, ctx.cursor),
Formation::RegisterDump => paint_register_dump(grid, zone, zone_id, ctx.stream, ctx.cursor),
Formation::TextmarkConverter => paint_textmark(grid, zone, zone_id, ctx.stream, ctx.cursor),
Formation::Cellular1D { rule } => {
paint_cellular(grid, zone, zone_id, rule, ctx.stream, ctx.cursor);
}
Formation::Marquee => paint_marquee(grid, zone, zone_id, ctx.stream, ctx.cursor),
Formation::DensityGrid => paint_density_grid(grid, zone, zone_id, ctx.stream, ctx.cursor),
Formation::AttentionMatrix => paint_attention(grid, zone, zone_id),
Formation::ProbField => paint_prob_field(grid, zone, zone_id, ctx),
Formation::ImagePanel { asset } => paint_image_panel(grid, zone, zone_id, asset, ctx),
Formation::RaytraceCube => paint_raytrace_cube(grid, zone, zone_id),
Formation::Atm => paint_atm(grid, zone, zone_id, ctx.balance, ctx.cursor),
Formation::AgentSlot { player } => {
paint_agent_slot(grid, zone, zone_id, player, ctx.input_buffer);
}
Formation::ChessBoard => paint_chess_board(grid, zone, zone_id, ctx.chess_pos, ctx.cursor),
Formation::PayoutButton => paint_payout_button(grid, zone, zone_id, ctx.cursor),
Formation::TerminalInput => {
paint_terminal_input(grid, zone, zone_id, ctx.input_buffer, ctx.cursor);
}
}
}
#[derive(Clone, Copy)]
enum Transform {
Xor(u8),
Rot(i8),
HexEncode,
BitRev,
Stripe,
}
fn pick_transform() -> Transform {
match r_u32() % 5 {
0 => Transform::Xor(0x33 + ((r_u32() as u8) & 0x7F)),
1 => Transform::Rot((1 + (r_u32() % 25)) as i8),
2 => Transform::HexEncode,
3 => Transform::BitRev,
_ => Transform::Stripe,
}
}
fn apply_transform(c: char, t: Transform) -> char {
match t {
Transform::Xor(k) => {
if c.is_ascii() {
let b = (c as u8) ^ k;
if (b as char).is_ascii_graphic() {
b as char
} else {
HEX[(b & 0x0f) as usize]
}
} else {
HEX[((c as u32) & 0x0f) as usize]
}
}
Transform::Rot(n) => {
if c.is_ascii_alphabetic() {
let base = if c.is_ascii_uppercase() { b'A' } else { b'a' };
let shifted = (((c as u8 - base) as i16 + n as i16).rem_euclid(26)) as u8;
(base + shifted) as char
} else {
c
}
}
Transform::HexEncode => HEX[((c as u32) & 0x0f) as usize],
Transform::BitRev => {
if c.is_ascii() {
let mut b = c as u8;
b = b.rotate_left(4);
b = ((b >> 2) & 0x33) | ((b << 2) & 0xcc);
b = ((b >> 1) & 0x55) | ((b << 1) & 0xaa);
if (b as char).is_ascii_graphic() {
b as char
} else {
HEX[(b & 0x0f) as usize]
}
} else {
HEX[((c as u32) & 0x0f) as usize]
}
}
Transform::Stripe => {
if (c as u32) & 1 == 0 {
'|'
} else {
c
}
}
}
}
fn transform_symbols(t: Transform) -> [char; 3] {
match t {
Transform::Xor(k) => ['⊕', HEX[((k >> 4) & 0xf) as usize], HEX[(k & 0xf) as usize]],
Transform::Rot(n) => {
let mag = (n.unsigned_abs() as usize) % 26;
['↻', HEX[(mag / 16) as usize], HEX[(mag % 16) as usize]]
}
Transform::HexEncode => ['#', '1', '6'],
Transform::BitRev => ['⊥', '↔', '⊥'],
Transform::Stripe => ['▮', '|', '▮'],
}
}
struct Pipe {
from: u16,
to: u16,
cells: Vec<(i32, i32)>, transform: Transform,
step: i64, }
fn try_build_pipe(zones: &[Zone], i: usize, j: usize) -> Option<Pipe> {
let a = zones[i].base_rect;
let b = zones[j].base_rect;
let min_edge = 4;
let len_each = 5;
let mk = |from: u16, to: u16, cells: Vec<(i32, i32)>| -> Pipe {
Pipe {
from,
to,
cells,
transform: pick_transform(),
step: 1 + (r_u32() % 4) as i64, }
};
if a.x + a.w == b.x {
let y0 = a.y.max(b.y);
let y1 = (a.y + a.h).min(b.y + b.h);
if y1 - y0 < min_edge {
return None;
}
let y = y0 + (y1 - y0) / 2;
let l_a = len_each.min(a.w - 1).max(2);
let l_b = len_each.min(b.w - 1).max(2);
let cells: Vec<(i32, i32)> = ((a.x + a.w - l_a)..(b.x + l_b)).map(|x| (x, y)).collect();
if cells.len() >= 6 {
return Some(mk(i as u16, j as u16, cells));
}
}
if b.x + b.w == a.x {
let y0 = a.y.max(b.y);
let y1 = (a.y + a.h).min(b.y + b.h);
if y1 - y0 < min_edge {
return None;
}
let y = y0 + (y1 - y0) / 2;
let l_a = len_each.min(a.w - 1).max(2);
let l_b = len_each.min(b.w - 1).max(2);
let cells: Vec<(i32, i32)> = ((b.x + b.w - l_b)..(a.x + l_a)).map(|x| (x, y)).collect();
if cells.len() >= 6 {
return Some(mk(j as u16, i as u16, cells));
}
}
if a.y + a.h == b.y {
let x0 = a.x.max(b.x);
let x1 = (a.x + a.w).min(b.x + b.w);
if x1 - x0 < min_edge {
return None;
}
let x = x0 + (x1 - x0) / 2;
let l_a = 4.min(a.h - 1).max(2);
let l_b = 4.min(b.h - 1).max(2);
let cells: Vec<(i32, i32)> = ((a.y + a.h - l_a)..(b.y + l_b)).map(|y| (x, y)).collect();
if cells.len() >= 6 {
return Some(mk(i as u16, j as u16, cells));
}
}
if b.y + b.h == a.y {
let x0 = a.x.max(b.x);
let x1 = (a.x + a.w).min(b.x + b.w);
if x1 - x0 < min_edge {
return None;
}
let x = x0 + (x1 - x0) / 2;
let l_a = 4.min(a.h - 1).max(2);
let l_b = 4.min(b.h - 1).max(2);
let cells: Vec<(i32, i32)> = ((b.y + b.h - l_b)..(a.y + l_a)).map(|y| (x, y)).collect();
if cells.len() >= 6 {
return Some(mk(j as u16, i as u16, cells));
}
}
None
}
fn build_pipes(zones: &[Zone]) -> Vec<Pipe> {
let mut pipes = Vec::new();
for i in 0..zones.len() {
if zones[i].side == Side::Nested {
continue;
}
for j in (i + 1)..zones.len() {
if zones[j].side == Side::Nested {
continue;
}
if let Some(p) = try_build_pipe(zones, i, j) {
pipes.push(p);
}
}
}
pipes
}
#[derive(Clone, Copy)]
enum StreamerAxis {
Horizontal,
Vertical,
DiagPos,
DiagNeg,
}
struct Streamer {
axis: StreamerAxis,
anchor: i32, speed: f32, direction: i32, }
fn streamer_cells(axis: StreamerAxis, anchor: i32, w: i32, h: i32) -> Vec<(i32, i32)> {
match axis {
StreamerAxis::Horizontal => (0..w).map(|x| (x, anchor)).collect(),
StreamerAxis::Vertical => (0..h).map(|y| (anchor, y)).collect(),
StreamerAxis::DiagPos => {
let mut out = Vec::new();
let mut x = anchor;
let mut y = 0;
while y < h {
if x >= 0 && x < w {
out.push((x, y));
}
y += 1;
x += 2; }
out
}
StreamerAxis::DiagNeg => {
let mut out = Vec::new();
let mut x = anchor;
let mut y = 0;
while y < h {
if x >= 0 && x < w {
out.push((x, y));
}
y += 1;
x -= 2;
}
out
}
}
}
#[inline]
fn rect_contains(r: Rect, x: i32, y: i32) -> bool {
x >= r.x && x < r.x + r.w && y >= r.y && y < r.y + r.h
}
fn paint_streamer(
grid: &mut [Vec<PxCell>],
s: &Streamer,
w: i32,
h: i32,
protected: &[Rect],
stream: &[char],
cursor: f32,
) {
let cells = streamer_cells(s.axis, s.anchor, w, h);
let scroll = (cursor * s.speed) as i64;
for (i, &(x, y)) in cells.iter().enumerate() {
if protected.iter().any(|r| rect_contains(*r, x, y)) {
continue;
}
let pos = scroll + (i as i64) * s.direction as i64;
let ch = sample(stream, pos);
put_force(grid, x, y, ch, (250, 55, 55), 0.95);
}
}
struct NoiseFeed {
pos: (i32, i32), dir: (i32, i32), length: i32, seed: u32, speed: f32, }
const NOISE_POOL: &[char] = &[
'#', '@', '%', '$', '*', '!', '?', '&', '+', '=', '~', '^', '\\',
];
fn paint_noise_feed(grid: &mut [Vec<PxCell>], f: &NoiseFeed, protected: &[Rect], cursor: f32) {
let scroll = (cursor * f.speed) as u32;
for i in 0..f.length {
let x = f.pos.0 + f.dir.0 * i;
let y = f.pos.1 + f.dir.1 * i;
if protected.iter().any(|r| rect_contains(*r, x, y)) {
continue;
}
let h = f
.seed
.wrapping_mul(2_654_435_761)
.wrapping_add(scroll.wrapping_mul(31))
.wrapping_add(i as u32);
let ch = NOISE_POOL[(h as usize) % NOISE_POOL.len()];
let trail_fade = 1.0 - (i as f32) / (f.length.max(1) as f32);
let intensity = 0.55 + trail_fade * 0.40;
put_force(grid, x, y, ch, (245, 50, 50), intensity);
}
}
struct DitherFlow {
pos_x: f32,
pos_y: f32,
vel_x: f32,
vel_y: f32,
trail: Vec<(i32, i32)>,
trail_max: usize,
}
const FLOW_RAMP: &[char] = &['░', '▒', '▓', '█'];
fn tick_flow(flow: &mut DitherFlow, dt: f32, w: i32, h: i32) {
flow.pos_x += flow.vel_x * dt;
flow.pos_y += flow.vel_y * dt;
if flow.pos_x < 0.0 {
flow.pos_x = 0.0;
flow.vel_x = flow.vel_x.abs();
} else if flow.pos_x >= w as f32 {
flow.pos_x = (w - 1) as f32;
flow.vel_x = -flow.vel_x.abs();
}
if flow.pos_y < 1.0 {
flow.pos_y = 1.0;
flow.vel_y = flow.vel_y.abs();
} else if flow.pos_y >= h as f32 {
flow.pos_y = (h - 1) as f32;
flow.vel_y = -flow.vel_y.abs();
}
if r_f32() < 0.07 {
let theta = (r_f32() - 0.5) * 0.6;
let (ct, st) = (theta.cos(), theta.sin());
let nvx = flow.vel_x * ct - flow.vel_y * st;
let nvy = flow.vel_x * st + flow.vel_y * ct;
flow.vel_x = nvx;
flow.vel_y = nvy;
}
let cell = (flow.pos_x as i32, flow.pos_y as i32);
if flow.trail.last() != Some(&cell) {
flow.trail.push(cell);
if flow.trail.len() > flow.trail_max {
flow.trail.remove(0);
}
}
}
fn paint_flow(grid: &mut [Vec<PxCell>], flow: &DitherFlow) {
let n = flow.trail.len();
if n == 0 {
return;
}
for (i, &(x, y)) in flow.trail.iter().enumerate() {
let age_pct = i as f32 / n as f32;
let level = ((age_pct * FLOW_RAMP.len() as f32) as usize).min(FLOW_RAMP.len() - 1);
let intensity = 0.45 + age_pct * 0.55;
put_force(grid, x, y, FLOW_RAMP[level], (255, 70, 70), intensity);
}
}
struct GlitchInsertion {
asset_idx: usize,
rect: Rect,
crop: Option<Rect>,
spawn_cursor: f32,
duration_chars: f32,
variant_idx: usize,
}
fn paint_glitch_insertion(
grid: &mut [Vec<PxCell>],
ins: &GlitchInsertion,
assets: &[ImageAsset],
cursor: f32,
) {
if ins.asset_idx >= assets.len() {
return;
}
let asset = &assets[ins.asset_idx];
if asset.variants.is_empty() {
return;
}
let variant = &asset.variants[ins.variant_idx.min(asset.variants.len() - 1)];
let vw = variant.w as i32;
let vh = variant.h as i32;
let off_x = ins.crop.map_or(0, |c| c.x);
let off_y = ins.crop.map_or(0, |c| c.y);
let age = (cursor - ins.spawn_cursor) / ins.duration_chars.max(0.01);
let life_factor = if age < 0.15 {
age / 0.15
} else if age > 0.85 {
((1.0 - age) / 0.15).max(0.0)
} else {
1.0
};
for ry in 0..ins.rect.h {
for rx in 0..ins.rect.w {
let srx = (off_x + rx).rem_euclid(vw.max(1)) as usize;
let sry = (off_y + ry).rem_euclid(vh.max(1)) as usize;
let ch = variant
.cells
.get(sry)
.and_then(|row| row.get(srx))
.copied()
.unwrap_or(' ');
if ch == '\u{2800}' || ch == ' ' {
continue;
}
let intensity = (0.7 + 0.3 * life_factor).min(1.0);
put_force(
grid,
ins.rect.x + rx,
ins.rect.y + ry,
ch,
(255, 60, 60),
intensity,
);
}
}
}
const LITANY: &str =
" ✦ ONE CURSOR FLOWS AND ALL CELLS AWAKEN ☸ BY GOLDEN ANGLE ALL THINGS ALIGN \
✧ PIPES CARRY WHAT CANNOT BE HELD ◉ φ = 1.618 IS THE ARCHITECT \
⚘ SIGNAL BECOMES SACRAMENT ✦ AS ABOVE SO BELOW ☸ \
THE CUBE AT THE EAST KEEPS COUNT ✧ FOLD BY FOLD ◉ \
HOSE IS HOLY HOSE IS HOLY HOSE IS HOLY ⚘ ";
fn paint_litany(grid: &mut [Vec<PxCell>], w: i32, cursor: f32) {
let chars: Vec<char> = LITANY.chars().collect();
let n = chars.len() as i64;
if n == 0 || grid.is_empty() {
return;
}
let scroll = (cursor * 0.35) as i64;
for x in 0..w {
let idx = (scroll + x as i64).rem_euclid(n) as usize;
put_force(grid, x, 0, chars[idx], (255, 50, 50), 1.0);
}
}
fn paint_pipe(
grid: &mut [Vec<PxCell>],
pipe: &Pipe,
zones: &[Zone],
stream: &[char],
assets: &[ImageAsset],
cursor: f32,
) {
let from_zone = &zones[pipe.from as usize];
let from_tap = from_zone.tap_offset as i64;
let n = pipe.cells.len();
let input_end = n / 3;
let op_end = (n * 2) / 3;
let op_syms = transform_symbols(pipe.transform);
let op_len = op_end - input_end;
let image_src: Option<&[u8]> = match from_zone.formation {
Formation::ImagePanel { asset } => assets.get(asset).map(|a| a.luma.as_slice()),
_ => None,
};
let fetch_char = |p: i64| -> char {
if let Some(bytes) = image_src {
if bytes.is_empty() {
return ' ';
}
let idx = p.rem_euclid(bytes.len() as i64 * 2) as usize;
let byte = bytes[idx / 2];
let nib = if idx & 1 == 0 { byte >> 4 } else { byte & 0x0f };
HEX[nib as usize]
} else {
sample(stream, p)
}
};
for (i, &(x, y)) in pipe.cells.iter().enumerate() {
let ch = if i < input_end {
fetch_char((cursor as i64) - from_tap - i as i64 * pipe.step)
} else if i < op_end {
let sym_idx = (i - input_end) * op_syms.len() / op_len.max(1);
op_syms[sym_idx.min(2)]
} else {
let delay = (i - input_end) as i64 * pipe.step;
let src_pos = (cursor as i64) - from_tap - delay;
let src = fetch_char(src_pos);
apply_transform(src, pipe.transform)
};
let i_val = if (input_end..op_end).contains(&i) {
0.92
} else {
1.0
};
put_force(grid, x, y, ch, (255, 50, 50), i_val);
}
}
fn apply_edge_morph(grid: &mut [Vec<PxCell>], scene: &Scene) {
let grid_h = grid.len() as i32;
let grid_w = if grid.is_empty() {
0
} else {
grid[0].len() as i32
};
for i in 0..scene.zones.len() {
let z = &scene.zones[i];
if matches!(z.formation, Formation::RaytraceCube) {
continue;
}
let r = z.base_rect;
let morph_d: i32 = 3;
let t_phase = (scene.cursor * 0.25) as i32;
for ry in 0..r.h {
for rx in 0..r.w {
let dx = rx.min(r.w - 1 - rx);
let dy = ry.min(r.h - 1 - ry);
let dist = dx.min(dy);
if dist >= morph_d {
continue;
}
let gx = r.x + rx;
let gy = r.y + ry;
if gx < 0 || gy < 0 || gx >= grid_w || gy >= grid_h {
continue;
}
let (ugx, ugy) = (gx as usize, gy as usize);
if grid[ugy][ugx].owner != i as u16 {
continue;
}
let nearness = (morph_d - dist) as f32 / morph_d as f32;
let h_val = ihash(rx, ry, t_phase);
let r_val = (h_val & 0xff) as f32 / 255.0;
let threshold = 0.55 * nearness * nearness;
if r_val >= threshold {
continue;
}
let (sdx, sdy): (i32, i32) = if dx < dy {
if rx < r.w / 2 {
(-1, 0)
} else {
(1, 0)
}
} else {
if ry < r.h / 2 {
(0, -1)
} else {
(0, 1)
}
};
let steps = 1 + ((h_val >> 8) & 0x3) as i32;
let lx = gx + sdx * steps;
let ly = gy + sdy * steps;
if lx < 0 || ly < 0 || lx >= grid_w || ly >= grid_h {
continue;
}
let src = grid[ly as usize][lx as usize];
if src.owner == i as u16 || src.owner == NO_OWNER {
continue;
}
if matches!(
scene.zones[src.owner as usize].formation,
Formation::RaytraceCube
) {
continue;
}
let cell = &mut grid[ugy][ugx];
cell.ch = src.ch;
cell.fg = src.fg;
cell.intensity = (cell.intensity * 0.55 + src.intensity * 0.65).min(1.0);
}
}
}
}
fn is_ui_formation(f: &Formation) -> bool {
matches!(
f,
Formation::Atm
| Formation::AgentSlot { .. }
| Formation::ChessBoard
| Formation::PayoutButton
| Formation::TerminalInput
)
}
fn render(scene: &Scene) -> Vec<Vec<PxCell>> {
let mut grid = vec![vec![PxCell::empty(); scene.w as usize]; scene.h as usize];
let ctx = PaintCtx {
stream: &scene.stream,
cursor: scene.cursor,
zones: &scene.zones,
adjacency: &scene.adjacency,
assets: &scene.assets,
chess_pos: &scene.chess_pos,
balance: scene.balance,
input_buffer: &scene.input_buffer,
};
for i in 0..scene.zones.len() {
if !is_ui_formation(&scene.zones[i].formation) {
paint_formation(&mut grid, &scene.zones[i], i as u16, &ctx);
}
}
apply_edge_morph(&mut grid, scene);
for p in &scene.pipes {
paint_pipe(
&mut grid,
p,
&scene.zones,
&scene.stream,
&scene.assets,
scene.cursor,
);
}
for s in &scene.streamers {
paint_streamer(
&mut grid,
s,
scene.w,
scene.h,
&scene.protected_rects,
&scene.stream,
scene.cursor,
);
}
for f in &scene.noise_feeds {
paint_noise_feed(&mut grid, f, &scene.protected_rects, scene.cursor);
}
for flow in &scene.dither_flows {
paint_flow(&mut grid, flow);
}
for ins in &scene.glitch_inserts {
paint_glitch_insertion(&mut grid, ins, &scene.assets, scene.cursor);
}
for i in 0..scene.zones.len() {
if is_ui_formation(&scene.zones[i].formation) {
paint_formation(&mut grid, &scene.zones[i], i as u16, &ctx);
}
}
paint_litany(&mut grid, scene.w, scene.cursor);
if scene.flipped {
for row in &mut grid {
row.reverse();
}
}
grid
}
fn dim(c: (u8, u8, u8), i: f32) -> (u8, u8, u8) {
let f = i.clamp(0.0, 1.0);
(
(c.0 as f32 * f) as u8,
(c.1 as f32 * f) as u8,
(c.2 as f32 * f) as u8,
)
}
fn draw(stdout: &mut impl Write, grid: &[Vec<PxCell>]) -> io::Result<()> {
queue!(stdout, cursor::MoveTo(0, 0))?;
let mut last_fg: Option<(u8, u8, u8)> = None;
for (i, row) in grid.iter().enumerate() {
queue!(stdout, cursor::MoveTo(0, i as u16))?;
for cell in row {
let fg = dim(cell.fg, cell.intensity);
if Some(fg) != last_fg {
queue!(
stdout,
SetForegroundColor(Color::Rgb {
r: fg.0,
g: fg.1,
b: fg.2
})
)?;
last_fg = Some(fg);
}
queue!(stdout, Print(cell.ch))?;
}
}
queue!(stdout, ResetColor)?;
stdout.flush()?;
Ok(())
}
fn main() -> io::Result<()> {
let mut stdout = io::stdout();
terminal::enable_raw_mode()?;
execute!(
stdout,
EnterAlternateScreen,
cursor::Hide,
Clear(ClearType::All)
)?;
let (cols, rows) = terminal::size()?;
let w = (cols as i32).max(60);
let h = ((rows as i32) - 1).max(12);
let mut scene = build_scene(w, h);
let target = Duration::from_millis(33);
let mut last = Instant::now();
let result = (|| -> io::Result<()> {
loop {
if event::poll(Duration::ZERO)? {
if let Event::Key(k) = event::read()? {
match (k.code, k.modifiers) {
(KeyCode::Esc, _) => break,
(KeyCode::Char('c'), m) if m.contains(KeyModifiers::CONTROL) => break,
(KeyCode::Char('f'), m) if m.contains(KeyModifiers::CONTROL) => {
scene.flipped = !scene.flipped;
}
(KeyCode::Char('r'), m) if m.contains(KeyModifiers::CONTROL) => {
scene.reversed = !scene.reversed;
}
(KeyCode::Backspace, _) => {
scene.input_buffer.pop();
}
(KeyCode::Enter, _) => {
scene.input_buffer.clear();
}
(KeyCode::Char(c), m)
if !m.contains(KeyModifiers::CONTROL)
&& scene.input_buffer.chars().count() < 30 =>
{
scene.input_buffer.push(c);
}
_ => {}
}
}
}
let now = Instant::now();
let dt = (now - last).as_secs_f32().min(0.1);
last = now;
tick(&mut scene, dt);
let grid = render(&scene);
draw(&mut stdout, &grid)?;
let elapsed = last.elapsed();
if elapsed < target {
std::thread::sleep(target.checked_sub(elapsed).unwrap());
}
}
Ok(())
})();
execute!(stdout, ResetColor, cursor::Show, LeaveAlternateScreen)?;
terminal::disable_raw_mode()?;
result
}