this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

circles; clippy

alice 70d6aa7b 19d946e6

+178 -4
+2 -2
AGENTS.md
··· 15 15 16 16 **Build Hygiene (always do this)** 17 17 - Fix all compiler warnings before landing changes (treat warnings as errors). 18 - - Run clippy on the crate and keep zero warnings: `cd tic80_rust && cargo clippy --all-targets --all-features -D warnings`. 18 + - Run clippy and keep zero warnings: `cd tic80_rust && cargo clippy -- -D warnings`. 19 19 - Validate with tests: `cd tic80_rust && cargo test` (and run the specific failing test during fixes). 20 20 - Keep changes minimal and focused; don’t expand scope while tests are red. 21 21 22 22 **Testing** 23 23 - Strategy: see `docs/testing/strategy.md` for layers (framebuffer, Lua bridge, deterministic hashes) and future plans (audio, conformance, fuzzing). 24 24 - Catalog: see `docs/testing/test_catalog.md` for a concise list of existing tests and their intent. 25 - - Run: `cd tic80_rust && cargo test` for unit + Lua tests; `cargo clippy --all-targets --all-features -D warnings` for linting. 25 + - Run: `cd tic80_rust && cargo test` for unit + Lua tests; `cargo clippy -- -D warnings` for linting. 26 26 - Determinism: VRAM hashes use FNV-1a over 240×136 palette indices (see strategy doc for rationale and helper snippet). 27 27 28 28 **Decisions (Locked for prototype)**
+6 -1
tic80_rust/assets/default.lua
··· 29 29 30 30 -- Title and legend 31 31 print("TIC-80 Rust Demo", 8, 6, 15) 32 - print("cls pix line rect rectb clip print", 8, 16, 14) 32 + print("cls pix line rect rectb circ circb clip print", 8, 16, 14) 33 33 34 34 -- Static card: rect + rectb 35 35 rectb(20, 28, 48, 22, 12) -- border ··· 39 39 local ax = 90 + (t % 60) - 30 40 40 rectb(ax, 34, 14, 14, 12) 41 41 rect(ax + 1, 35, 12, 12, 3) 42 + 43 + -- Circles: static border + gentle pulsing fill 44 + local rc = 12 + ((t // 60) % 4) -- slow pulse 12..15 45 + circb(180, 56, 14, 14) 46 + circ(180, 56, rc, 10) 42 47 43 48 -- Clip demo toggles every ~2.5 seconds (150 frames @60 FPS) 44 49 local clipped = (t // 150) % 2 == 0
+4
tic80_rust/rust-toolchain.toml
··· 1 + [toolchain] 2 + channel = "stable" 3 + components = ["clippy", "rustfmt"] 4 +
+75
tic80_rust/src/gfx/framebuffer.rs
··· 223 223 } 224 224 } 225 225 226 + // Circle border using 8-way symmetry (integer midpoint algorithm) 227 + pub fn circb(&mut self, cx: i32, cy: i32, r: i32, color: u8) { 228 + if r < 0 { return; } 229 + let c = color & 0x0F; 230 + if r == 0 { 231 + let _ = self.set_pixel(cx, cy, c); 232 + return; 233 + } 234 + let mut x = r; 235 + let mut y = 0; 236 + let mut err = 1 - r; 237 + while x >= y { 238 + // 8 symmetric points 239 + let _ = self.set_pixel(cx + x, cy + y, c); 240 + let _ = self.set_pixel(cx + y, cy + x, c); 241 + let _ = self.set_pixel(cx - y, cy + x, c); 242 + let _ = self.set_pixel(cx - x, cy + y, c); 243 + let _ = self.set_pixel(cx - x, cy - y, c); 244 + let _ = self.set_pixel(cx - y, cy - x, c); 245 + let _ = self.set_pixel(cx + y, cy - x, c); 246 + let _ = self.set_pixel(cx + x, cy - y, c); 247 + 248 + y += 1; 249 + if err < 0 { 250 + err += 2 * y + 1; 251 + } else { 252 + x -= 1; 253 + err += 2 * (y - x) + 1; 254 + } 255 + } 256 + } 257 + 258 + // Filled circle via horizontal spans using symmetry 259 + pub fn circ(&mut self, cx: i32, cy: i32, r: i32, color: u8) { 260 + if r < 0 { return; } 261 + let c = color & 0x0F; 262 + if r == 0 { 263 + let _ = self.set_pixel(cx, cy, c); 264 + return; 265 + } 266 + let mut x = r; 267 + let mut y = 0; 268 + let mut err = 1 - r; 269 + while x >= y { 270 + // Draw horizontal spans for the current y and x offsets 271 + self.hspan(cx - x, cx + x, cy + y, c); 272 + self.hspan(cx - x, cx + x, cy - y, c); 273 + self.hspan(cx - y, cx + y, cy + x, c); 274 + self.hspan(cx - y, cx + y, cy - x, c); 275 + 276 + y += 1; 277 + if err < 0 { 278 + err += 2 * y + 1; 279 + } else { 280 + x -= 1; 281 + err += 2 * (y - x) + 1; 282 + } 283 + } 284 + } 285 + 286 + fn hspan(&mut self, x0: i32, x1: i32, y: i32, color: u8) { 287 + if y < 0 || y as u32 >= HEIGHT { return; } 288 + let start = x0.min(x1); 289 + let end = x0.max(x1); 290 + for x in start..=end { 291 + let _ = self.set_pixel(x, y, color); 292 + } 293 + } 294 + 226 295 // Print text using TIC-80 default font (5x8 glyphs, 1px spacing) 227 296 #[allow(clippy::too_many_arguments)] 228 297 pub fn print_text( ··· 328 397 pub fn dimensions() -> (u32, u32) { 329 398 (Framebuffer::WIDTH, Framebuffer::HEIGHT) 330 399 } 400 + 401 + impl Default for Framebuffer { 402 + fn default() -> Self { 403 + Self::new() 404 + } 405 + }
+1 -1
tic80_rust/src/main.rs
··· 59 59 let mut ticker = Ticker::new(); 60 60 // Program selection: first CLI arg as .lua script, else embedded default 61 61 let args: Vec<String> = std::env::args().skip(1).collect(); 62 - let script = if let Some(first) = args.get(0) { 62 + let script = if let Some(first) = args.first() { 63 63 if first.ends_with(".lua") && Path::new(first).is_file() { 64 64 match fs::read_to_string(first) { 65 65 Ok(s) => s,
+16
tic80_rust/src/script/lua_runner.rs
··· 63 63 )?; 64 64 globals.set("rectb", rectb_fn)?; 65 65 66 + // circ(x, y, r, color) 67 + let fb_circ = fb.clone(); 68 + let circ_fn = lua.create_function(move |_, (x, y, r, color): (i32, i32, i32, u8)| { 69 + fb_circ.borrow_mut().circ(x, y, r, color); 70 + Ok(()) 71 + })?; 72 + globals.set("circ", circ_fn)?; 73 + 74 + // circb(x, y, r, color) 75 + let fb_circb = fb.clone(); 76 + let circb_fn = lua.create_function(move |_, (x, y, r, color): (i32, i32, i32, u8)| { 77 + fb_circb.borrow_mut().circb(x, y, r, color); 78 + Ok(()) 79 + })?; 80 + globals.set("circb", circb_fn)?; 81 + 66 82 // clip(x,y,w,h) or clip() to reset 67 83 let fb_clip = fb.clone(); 68 84 let clip_fn = lua.create_function(move |_, args: MultiValue| {
+54
tic80_rust/tests/gfx_framebuffer_tests.rs
··· 248 248 assert_eq!(fb.pix(6, 0, None), Some(4)); // right edge 249 249 assert_eq!(fb.pix(0, 6, None), Some(4)); // bottom edge 250 250 } 251 + 252 + #[test] 253 + fn circb_cardinals_and_oob() { 254 + let mut fb = Framebuffer::new(); 255 + fb.cls(0); 256 + let cx = 20; 257 + let cy = 20; 258 + let r = 5; 259 + fb.circb(cx, cy, r, 7); 260 + // cardinal points 261 + assert_eq!(fb.pix(cx + r, cy, None), Some(7)); 262 + assert_eq!(fb.pix(cx - r, cy, None), Some(7)); 263 + assert_eq!(fb.pix(cx, cy + r, None), Some(7)); 264 + assert_eq!(fb.pix(cx, cy - r, None), Some(7)); 265 + // just outside should remain background 266 + assert_eq!(fb.pix(cx + r + 1, cy, None), Some(0)); 267 + } 268 + 269 + #[test] 270 + fn circ_fill_center_row_and_clip() { 271 + let mut fb = Framebuffer::new(); 272 + fb.cls(0); 273 + let cx = 30; 274 + let cy = 30; 275 + let r = 4; 276 + fb.circ(cx, cy, r, 5); 277 + // center row should be filled from cx-r .. cx+r 278 + for x in (cx - r)..=(cx + r) { 279 + assert_eq!(fb.pix(x, cy, None), Some(5)); 280 + } 281 + assert_eq!(fb.pix(cx - r - 1, cy, None), Some(0)); 282 + assert_eq!(fb.pix(cx + r + 1, cy, None), Some(0)); 283 + 284 + // clipping restricts drawing to 1x1 285 + let mut fb2 = Framebuffer::new(); 286 + fb2.cls(2); 287 + fb2.clip(0, 0, 1, 1); 288 + fb2.circ(0, 0, 5, 9); 289 + assert_eq!(fb2.pix(0, 0, None), Some(9)); 290 + assert_eq!(fb2.pix(1, 0, None), Some(2)); 291 + assert_eq!(fb2.pix(0, 1, None), Some(2)); 292 + } 293 + 294 + #[test] 295 + fn circ_zero_radius_draws_center() { 296 + let mut fb = Framebuffer::new(); 297 + fb.cls(0); 298 + fb.circ(10, 10, 0, 3); 299 + assert_eq!(fb.pix(10, 10, None), Some(3)); 300 + let mut fb2 = Framebuffer::new(); 301 + fb2.cls(0); 302 + fb2.circb(10, 10, 0, 4); 303 + assert_eq!(fb2.pix(10, 10, None), Some(4)); 304 + }
+20
tic80_rust/tests/lua_api_tests.rs
··· 224 224 // Different frame counts should usually yield different hashes for this cart 225 225 assert_ne!(h1, h2, "different ticks should yield different frame hashes"); 226 226 } 227 + 228 + #[test] 229 + fn lua_circ_and_circb() { 230 + let script = r#" 231 + function BOOT() cls(0) end 232 + function TIC() 233 + circ(20, 20, 4, 6) 234 + circb(30, 20, 3, 9) 235 + end 236 + "#; 237 + let fb = run_lua(script, 1); 238 + let mut fbm = fb.borrow_mut(); 239 + // Filled circle: center row span for r=4 240 + for x in 16..=24 { assert_eq!(fbm.pix(x, 20, None), Some(6)); } 241 + // Border circle: cardinal points for r=3 242 + assert_eq!(fbm.pix(33, 20, None), Some(9)); 243 + assert_eq!(fbm.pix(27, 20, None), Some(9)); 244 + assert_eq!(fbm.pix(30, 23, None), Some(9)); 245 + assert_eq!(fbm.pix(30, 17, None), Some(9)); 246 + }