//! AGI's core rendering trick: every room is drawn into TWO buffers at once. //! //! * the **visual** buffer is what the player sees (palette indices), and //! * the **priority** buffer is invisible — it encodes depth bands and //! control lines so the ego can walk *behind* a column and be blocked by a //! wall, all without any per-object Z bookkeeping. //! //! Values 4..=15 in the priority buffer are depth bands (higher = nearer the //! camera = drawn on top). Values 0..=3 are "control" lines with special //! meaning to the movement system. pub const PIC_W: usize = 160; pub const PIC_H: usize = 168; /// The "open" color the visual buffer clears to (white, like AGI fills). pub const BG_VISUAL: u8 = 15; // --- Control-line values (priority buffer 0..=3) --------------------------- /// Unconditional barrier — nothing may step here. pub const CTRL_BLOCK: u8 = 0; /// Water: sets the on-water state for the ego. (reserved for later) pub const CTRL_WATER: u8 = 1; /// Signal line: stepping on it can trigger room logic. (reserved for later) pub const CTRL_TRIGGER: u8 = 2; /// Conditional barrier. (reserved for later) pub const CTRL_COND: u8 = 3; /// Depth bands run from here up to 15. pub const FIRST_BAND: u8 = 4; /// Map a screen row to its default depth priority: 4 at the top of the play /// area down to 15 at the very bottom. This is what makes a flat floor sort /// correctly with no extra work from the room author. #[inline] pub fn band(y: usize) -> u8 { let b = FIRST_BAND as usize + (y * 12) / PIC_H; b.min(15) as u8 } pub struct FrameBuffer { pub visual: Vec, pub priority: Vec, } impl FrameBuffer { pub fn new() -> Self { let mut fb = FrameBuffer { visual: vec![BG_VISUAL; PIC_W * PIC_H], priority: vec![FIRST_BAND; PIC_W * PIC_H], }; fb.clear(); fb } /// Reset to a blank room: white visual, default depth bands on priority. pub fn clear(&mut self) { for y in 0..PIC_H { let p = band(y); for x in 0..PIC_W { let i = y * PIC_W + x; self.visual[i] = BG_VISUAL; self.priority[i] = p; } } } #[inline] pub fn in_bounds(x: i32, y: i32) -> bool { x >= 0 && y >= 0 && (x as usize) < PIC_W && (y as usize) < PIC_H } /// Priority at a point; off-screen reads as a solid wall. #[inline] pub fn priority_at(&self, x: i32, y: i32) -> u8 { if Self::in_bounds(x, y) { self.priority[y as usize * PIC_W + x as usize] } else { CTRL_BLOCK } } } impl Default for FrameBuffer { fn default() -> Self { Self::new() } }