Something went wrong. Try again.
Codex, Claude, and local AI agents on the Commodore 64 Ultimate.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196pub const TRANSCRIPT_ROWS: usize = 64;pub const TRANSCRIPT_COLUMNS: usize = 40;pub const TRANSCRIPT_BYTES: usize = TRANSCRIPT_ROWS * TRANSCRIPT_COLUMNS;
#[repr(C)]pub struct TranscriptStorage { characters: [u8; TRANSCRIPT_BYTES], colors: [u8; TRANSCRIPT_BYTES],}
impl TranscriptStorage { pub const fn uninitialized() -> Self { Self { characters: [0; TRANSCRIPT_BYTES], colors: [0; TRANSCRIPT_BYTES], } }
fn clear_row(&mut self, physical_row: usize, color: u8) { let first = physical_row * TRANSCRIPT_COLUMNS; let end = first + TRANSCRIPT_COLUMNS; self.characters[first..end].fill(b' '); self.colors[first..end].fill(color); }}
pub struct Transcript { start: usize, count: usize, column: usize, visible: bool,}
impl Transcript { pub const fn new() -> Self { Self { start: 0, count: 0, column: 0, visible: true, } }
pub fn initialize(&mut self, storage: &mut TranscriptStorage, color: u8) { let mut row = 0usize; while row < TRANSCRIPT_ROWS { storage.clear_row(row, color); row += 1; } self.start = 0; self.count = 0; self.column = 0; self.visible = true; }
pub const fn count(&self) -> usize { self.count }
#[cfg(test)] pub const fn column(&self) -> usize { self.column }
pub const fn is_visible(&self) -> bool { self.visible }
pub fn set_visible(&mut self, visible: bool) { self.visible = visible; }
pub fn clear(&mut self, storage: &mut TranscriptStorage, color: u8) { self.initialize(storage, color); }
pub fn speaker(&mut self, storage: &mut TranscriptStorage, text: &[u8], color: u8) { if self.column != 0 { self.newline(storage, color); } self.write(storage, text, color); }
pub fn write(&mut self, storage: &mut TranscriptStorage, text: &[u8], color: u8) { for byte in text { if *byte == b'\n' { self.newline(storage, color); continue; } if self.column == TRANSCRIPT_COLUMNS { self.newline(storage, color); } self.ensure_line(storage, color); let row = self.physical_row(self.count - 1); let offset = row * TRANSCRIPT_COLUMNS + self.column; storage.characters[offset] = *byte; storage.colors[offset] = color; self.column += 1; } }
pub fn newline(&mut self, storage: &mut TranscriptStorage, color: u8) { self.ensure_line(storage, color); if self.count < TRANSCRIPT_ROWS { let row = (self.start + self.count) % TRANSCRIPT_ROWS; self.count += 1; storage.clear_row(row, color); } else { let row = self.start; self.start = (self.start + 1) % TRANSCRIPT_ROWS; storage.clear_row(row, color); } self.column = 0; }
pub fn line<'a>( &self, storage: &'a TranscriptStorage, logical_row: usize, ) -> Option<(&'a [u8], &'a [u8])> { if logical_row >= self.count { return None; } let row = self.physical_row(logical_row); let first = row * TRANSCRIPT_COLUMNS; let end = first + TRANSCRIPT_COLUMNS; Some((&storage.characters[first..end], &storage.colors[first..end])) }
fn ensure_line(&mut self, storage: &mut TranscriptStorage, color: u8) { if self.count == 0 { self.count = 1; storage.clear_row(self.start, color); } }
fn physical_row(&self, logical_row: usize) -> usize { (self.start + logical_row) % TRANSCRIPT_ROWS }}
#[cfg(test)]mod tests { use super::{Transcript, TranscriptStorage, TRANSCRIPT_COLUMNS, TRANSCRIPT_ROWS};
#[test] fn wraps_at_forty_columns_and_sixty_four_rows() { let mut storage = TranscriptStorage::uninitialized(); let mut transcript = Transcript::new(); transcript.initialize(&mut storage, 1); let line = [b'X'; TRANSCRIPT_COLUMNS];
for index in 0..TRANSCRIPT_ROWS + 2 { transcript.write(&mut storage, &line, 1); if index != TRANSCRIPT_ROWS + 1 { transcript.newline(&mut storage, 1); } }
assert_eq!(transcript.count(), TRANSCRIPT_ROWS); assert_eq!(transcript.column(), TRANSCRIPT_COLUMNS); assert_eq!(transcript.line(&storage, 0).unwrap().0, line); }
#[test] fn retains_color_per_character() { let mut storage = TranscriptStorage::uninitialized(); let mut transcript = Transcript::new(); transcript.initialize(&mut storage, 1); transcript.write(&mut storage, b"YOU", 3); transcript.write(&mut storage, b" AI", 13);
let (characters, colors) = transcript.line(&storage, 0).unwrap(); assert_eq!(&characters[..6], b"YOU AI"); assert_eq!(&colors[..3], &[3, 3, 3]); assert_eq!(&colors[3..6], &[13, 13, 13]); }
#[test] fn modal_visibility_does_not_stop_accumulation() { let mut storage = TranscriptStorage::uninitialized(); let mut transcript = Transcript::new(); transcript.initialize(&mut storage, 1); transcript.set_visible(false); transcript.write(&mut storage, b"STREAMED WHILE DASHBOARD OPEN", 13);
assert!(!transcript.is_visible()); assert_eq!( &transcript.line(&storage, 0).unwrap().0[..29], b"STREAMED WHILE DASHBOARD OPEN" ); transcript.set_visible(true); assert!(transcript.is_visible()); }}