this repo has no description
0
fork

Configure Feed

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

code review pt3 or something

alice 92b3a291 1a15b0ed

+321 -1
+2
docs/specs/graphics.md
··· 5 5 - Palette: 16 sRGB entries; index→RGBA conversion for presentation; no color-space transforms. 6 6 - Text: Default font; 6 px advance within an 8×8 glyph box; variable-width by trimming empty columns when `fixed=false`. 7 7 - Clip: Active clip rectangle constrains all drawing writes; reads are unaffected. 8 + - VRAM mapping: Screen region writes via memory (`poke`/`memcpy`/`memset`) are not affected by `clip` and update pixels directly. 8 9 9 10 Implemented Semantics 10 11 - `cls(color=0)`: Fills framebuffer with palette index (masked to 0..15). Honors clip by design via `set_pixel` usage in higher-level draws; `cls` itself fills full screen (like TIC-80). ··· 28 29 - Origin: top-left of first drawn column is `(x,y)`. 29 30 - Scaling: draws scaled glyphs; returned width includes scaling. 30 31 - Newlines: advances by 6 px per line (scale applied); `smallfont` currently unused. 32 + - Width: returns the width of the longest line (after trimming when `fixed=false`). A one-pixel spacing is applied between variable-width glyphs. 31 33 32 34 Clip Behavior 33 35 - `clip(x,y,w,h)`: Sets active clip rectangle; `clip()` resets to full screen.
+3 -1
docs/specs/implementation_status.md
··· 18 18 - `print(text,x,y,color=15,fixed=false,scale=1,small=false) -> width`: Default font, variable-width trim, scale applied to return width; newlines advance by 6 px (times scale). 19 19 - Clip 20 20 - `clip(x,y,w,h)` and `clip()`: Set/reset clip rectangle affecting all draw writes; reads are unaffected. 21 + - VRAM screen mapping (via memory) ignores `clip` and updates pixels directly. 21 22 - Memory 22 23 - `peek(addr[,bits=8])`, `poke(addr, value[,bits=8])`: 8/4/2/1-bit addressing across full 96 KB; VRAM screen region mapped to live framebuffer (nibble-packed 2 px/byte). 23 24 - `peek1/peek2/peek4`, `poke1/poke2/poke4`: Bit-specific helpers. 24 - - `memcpy(dst, src, size)`, `memset(dst, value, size)`: Byte-wise operations; overlap-safe memcpy; VRAM ops update on-screen pixels immediately. 25 + - `memcpy(dst, src, size)`, `memset(dst, value, size)`: Byte-wise operations; overlap-safe memcpy; VRAM ops update on-screen pixels immediately. 25 26 26 27 Implemented (System) 27 28 - `trace(message, color=15)`: Prints to console (color informational only in CLI); tests verify trace messages via an internal buffer used only in tests. ··· 54 55 - Triangles: top-left inclusion; CCW orientation enforced internally; half-open bounding box prevents shared-edge double draws. 55 56 - Ellipses vs circles: fill then border may overdraw endpoints; order-dependent at axis rows (parity with TIC-80). 56 57 - Font: Default TIC-80 bitmap included; LSB-left bits; 6 px advance; trimming for variable width. 58 + - VRAM writes: VRAM screen nibble pairs update framebuffer ignoring `clip` (matches TIC-80 behavior). 57 59 58 60 Pending APIs (not implemented yet) 59 61 - Texturing/tiles
+9
docs/testing/test_catalog.md
··· 22 22 - `tri_top_left_flat_top_inclusion`: Top-left rule on flat-top triangles (endpoints excluded on top edge). 23 23 - `tri_top_left_flat_bottom_exclusion`: Bottom edge excluded on flat-bottom triangles. 24 24 - `tri_adjacent_rect_no_gaps`: Two triangles tile a rectangle without gaps. 25 + - `tri_shared_edge_shallow_slope_tiles_rect`: Shallow-slope shared-edge tiling covers rectangle area exactly. 25 26 - `tri_degenerate_zero_area_draws_nothing`: Collinear triangles draw nothing. 27 + - `ellib_cardinals_and_fill_center_row`: Ellipse border cardinals; filled center row interior. 28 + - `ellib_cardinal_points_aspect_wide`: Wide-aspect ellipse draws expected cardinals; neighbors remain background. 26 29 27 30 ## Lua Bridge Tests 28 31 - `tic80_rust/tests/lua_api_tests.rs` ··· 36 39 - `lua_default_cart_deterministic_hash`: Default cart produces deterministic frame hashes for fixed tick counts. 37 40 - `lua_circ_and_circb`: Circle fill and border via Lua. 38 41 - `lua_elli_ellib_and_tri_trib`: Ellipse and triangle APIs via Lua. 42 + - `lua_line_float_truncates_to_integer_pixels`: Float coordinates truncate to integer pixels identically to TIC-80. 39 43 40 44 ## Memory Tests 41 45 - `tic80_rust/tests/memory_tests.rs` ··· 43 47 - `peek4_reads_back_nibble`: 4-bit reads reflect framebuffer. 44 48 - `memcpy_and_memset_affect_vram`: VRAM writes via memcpy/memset reach the screen. 45 49 - `peek_poke_bits_general_ram`: 1/4-bit addressing in general RAM behaves correctly. 50 + - `vram_writes_ignore_clip`: VRAM screen mapping writes ignore clip and update pixels directly. 51 + - `two_bit_cross_byte_alignment`: 2-bit writes at end of a byte and start of next do not bleed. 52 + - `four_bit_unaligned_nibbles`: 4-bit nibble writes across/within bytes pack correctly. 46 53 - `tic80_rust/tests/memory_bits_roundtrip.rs` 47 54 - `roundtrip_peek_poke_bits_general_ram`: Round-trip property-like checks for 1/2/4/8-bit peek/poke across a RAM window. 48 55 - `vram_screen_boundary_write_does_not_bleed`: Last screen byte maps to the last two pixels; next byte (non-screen VRAM) does not affect framebuffer. ··· 55 62 - `fft_query_peak_at_bin`: Bin-aligned sine produces a distinct raw peak at the expected bin versus neighbors. 56 63 - `lua_fft_returns_normalized_bin`: Verifies Lua `fft(k)` returns normalized magnitude by gating a pixel. 57 64 - `fft_query_range_clamps_and_sums`: Clamping and inclusive sum behavior matches C (OOB handling and range sums). 65 + - `fft_single_bin_peak_and_normalization`: Single-bin sine produces clear raw peak and normalized near-1.0 at bin. 66 + - `vqt_bin_has_higher_energy_than_neighbors`: Sine at a center frequency yields higher raw energy at the target bin than neighbors. 58 67 59 68 Notes 60 69 - Tests prefer headless framebuffer inspection over image baselines.
+81
tic80_rust/tests/fft_conformance.rs
··· 1 + use tic80_rust::audio::fft::FFTState; 2 + use tic80_rust::audio::vqt::VQTState; 3 + 4 + fn gen_sine(n: usize, k: usize) -> Vec<f32> { 5 + let mut v = Vec::with_capacity(n); 6 + for i in 0..n { 7 + let phase = 2.0_f32 * std::f32::consts::PI * (k as f32) * (i as f32) / (n as f32); 8 + v.push(phase.sin()); 9 + } 10 + v 11 + } 12 + 13 + #[test] 14 + fn fft_single_bin_peak_and_normalization() { 15 + let mut fft = FFTState::new(4096); 16 + // Generate a sine exactly at bin k 17 + let n = 2048usize; 18 + let k = 50usize; 19 + let s = gen_sine(n, k); 20 + for &x in &s { 21 + fft.ingest(x); 22 + } 23 + fft.update(); 24 + // Raw peak near k should exceed neighbors significantly 25 + let mut max_i = 0usize; 26 + let mut max_v = 0.0f32; 27 + for i in 0..fft.bins() { 28 + let v = fft.fft_raw[i]; 29 + if v > max_v { 30 + max_v = v; 31 + max_i = i; 32 + } 33 + } 34 + assert!((max_i as i32 - k as i32).abs() <= 1); // allow +/-1 bin tolerance 35 + // Normalized at max bin should be ~1.0 36 + assert!(fft.fft_data[max_i] > 0.9); 37 + // Neighbors should be much smaller 38 + if max_i > 0 { 39 + assert!(fft.fft_data[max_i - 1] < 0.5); 40 + } 41 + if max_i + 1 < fft.bins() { 42 + assert!(fft.fft_data[max_i + 1] < 0.5); 43 + } 44 + } 45 + 46 + #[test] 47 + fn vqt_bin_has_higher_energy_than_neighbors() { 48 + // Pick a target bin in [10..100] 49 + let sr = 44_100u32; 50 + let mut vqt = VQTState::new(sr, 16_384); 51 + // Use bin 60 as target 52 + let bin = 60usize; 53 + // Reconstruct center frequency based on implementation schedule 54 + let f0 = 19.445_f32 * (2.0_f32).powf(bin as f32 / 12.0); 55 + // Generate 8192 samples of a sine at this frequency 56 + let n = 8192usize; 57 + let mut s = Vec::with_capacity(n); 58 + for i in 0..n { 59 + let t = i as f32 / (sr as f32); 60 + let phase = 2.0_f32 * std::f32::consts::PI * f0 * t; 61 + s.push(phase.sin()); 62 + } 63 + for &x in &s { 64 + vqt.ingest(x); 65 + } 66 + vqt.update(); 67 + // Target bin should be >= neighbors in both raw and normalized-smoothed paths 68 + let b = bin; 69 + let center = vqt.vqt_raw[b]; 70 + let left = if b > 0 { vqt.vqt_raw[b - 1] } else { 0.0 }; 71 + let right = if b + 1 < vqt.bins_count() { 72 + vqt.vqt_raw[b + 1] 73 + } else { 74 + 0.0 75 + }; 76 + assert!(center >= left); 77 + assert!(center >= right); 78 + // Normalized-smoothed not zero 79 + assert!(vqt.vqt_norm[b] >= 0.0); 80 + assert!(vqt.vqt_norm[b].is_finite()); 81 + }
+26
tic80_rust/tests/lua_numeric_cast_tests.rs
··· 1 + use std::cell::RefCell; 2 + use std::rc::Rc; 3 + 4 + use tic80_rust::core::memory::Memory; 5 + use tic80_rust::gfx::framebuffer::Framebuffer; 6 + use tic80_rust::script::lua_runner::LuaRunner; 7 + 8 + #[test] 9 + fn lua_line_float_truncates_to_integer_pixels() { 10 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 11 + let mem = Rc::new(RefCell::new(Memory::new(fb.clone()))); 12 + // Lua script draws a vertical line at x=1.9 (~x=1) from y=0.0 to y=5.9 (~y=5) 13 + let script = r#" 14 + function TIC() 15 + cls(0) 16 + line(1.9, 0.0, 1.9, 5.9, 3) 17 + end 18 + "#; 19 + let runner = LuaRunner::new(fb.clone(), mem.clone(), script).unwrap(); 20 + runner.tick(); 21 + // Expect pixels at x=1, y in [0..5] to be colored, and x=2 blank in that range 22 + for y in 0..6 { 23 + assert_eq!(fb.borrow_mut().pix(1, y, None), Some(3)); 24 + assert_eq!(fb.borrow_mut().pix(2, y, None), Some(0)); 25 + } 26 + }
+87
tic80_rust/tests/print_conformance_tests.rs
··· 1 + use std::cell::RefCell; 2 + use std::rc::Rc; 3 + 4 + use tic80_rust::gfx::framebuffer::Framebuffer; 5 + 6 + fn count_drawn_in_col(fb: &mut Framebuffer, x: i32, y: i32, h: i32) -> usize { 7 + let mut c = 0; 8 + for yy in y..y + h { 9 + if fb.pix(x, yy, None).unwrap_or(0) != 0 { 10 + c += 1; 11 + } 12 + } 13 + c 14 + } 15 + 16 + #[test] 17 + fn print_fixed_width_and_return_value() { 18 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 19 + let mut fbm = fb.borrow_mut(); 20 + fbm.cls(0); 21 + let x = 10; 22 + let y = 10; 23 + let scale = 1; 24 + // Fixed width: width should be ADV(6) * len * scale 25 + let text = "im i"; 26 + let w = fbm.print_text(text, x, y, 1, true, scale, false); 27 + assert_eq!(w, 6 * (text.len() as i32) * scale); 28 + } 29 + 30 + #[test] 31 + fn print_variable_space_returns_adv() { 32 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 33 + let mut fbm = fb.borrow_mut(); 34 + fbm.cls(0); 35 + let w = fbm.print_text(" ", 0, 0, 1, false, 1, false); 36 + assert_eq!(w, 6); // variable-width fallback for empty glyph is ADV (6) 37 + } 38 + 39 + #[test] 40 + fn print_width_next_column_clear_variable() { 41 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 42 + let mut fbm = fb.borrow_mut(); 43 + fbm.cls(0); 44 + let x = 5; 45 + let y = 20; 46 + let w = fbm.print_text("im", x, y, 1, false, 1, false); 47 + // Column at x + w should be clear (spacing accounted for in return width) 48 + let col = count_drawn_in_col(&mut fbm, x + w, y, 8); 49 + assert_eq!(col, 0); 50 + } 51 + 52 + #[test] 53 + fn print_scale2_width_next_column_clear() { 54 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 55 + let mut fbm = fb.borrow_mut(); 56 + fbm.cls(0); 57 + let x = 7; 58 + let y = 30; 59 + let w = fbm.print_text("im", x, y, 2, false, 2, false); 60 + // Scale 2, bounding column at x + w should be empty 61 + let col = count_drawn_in_col(&mut fbm, x + w, y, 8 * 2); 62 + assert_eq!(col, 0); 63 + } 64 + 65 + #[test] 66 + fn print_newline_advance_scale1() { 67 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 68 + let mut fbm = fb.borrow_mut(); 69 + fbm.cls(0); 70 + let x = 12; 71 + let y = 12; 72 + let w = fbm.print_text("A\nA", x, y, 1, false, 1, false); 73 + // First line draws near y..y+7; second starts at y+6 74 + // Ensure at least one pixel exists in each line's starting row range within [x, x+w) 75 + let mut found_top = false; 76 + let mut found_second = false; 77 + for xx in x..(x + w) { 78 + if fbm.pix(xx, y, None).unwrap_or(0) != 0 { 79 + found_top = true; 80 + } 81 + if fbm.pix(xx, y + 6, None).unwrap_or(0) != 0 { 82 + found_second = true; 83 + } 84 + } 85 + assert!(found_top); 86 + assert!(found_second); 87 + }
+48
tic80_rust/tests/print_glyph_width_tests.rs
··· 1 + use std::cell::RefCell; 2 + use std::rc::Rc; 3 + 4 + use tic80_rust::gfx::framebuffer::Framebuffer; 5 + 6 + #[test] 7 + fn variable_width_trimming_i_vs_m() { 8 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 9 + let mut fbm = fb.borrow_mut(); 10 + fbm.cls(0); 11 + let wi = fbm.print_text("i", 0, 0, 1, false, 1, false); 12 + let wm = fbm.print_text("m", 0, 10, 1, false, 1, false); 13 + assert!(wi < wm); 14 + } 15 + 16 + #[test] 17 + fn fixed_width_monospace_equal_width() { 18 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 19 + let mut fbm = fb.borrow_mut(); 20 + fbm.cls(0); 21 + let w1 = fbm.print_text("i", 0, 0, 1, true, 1, false); 22 + let w2 = fbm.print_text("m", 0, 10, 1, true, 1, false); 23 + let w3 = fbm.print_text("!", 0, 20, 1, true, 1, false); 24 + assert_eq!(w1, w2); 25 + assert_eq!(w2, w3); 26 + } 27 + 28 + #[test] 29 + fn punctuation_width_and_spacing() { 30 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 31 + let mut fbm = fb.borrow_mut(); 32 + fbm.cls(0); 33 + // Variable width should still advance by width+1 column for inter-glyph spacing 34 + let w1 = fbm.print_text("!", 10, 10, 1, false, 1, false); 35 + let w2 = fbm.print_text("!!", 10, 20, 1, false, 1, false); 36 + assert!(w2 >= w1 * 2 - 1); // allow for glyph trim; spacing yields near double width 37 + } 38 + 39 + #[test] 40 + fn multiline_width_is_max_line_width() { 41 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 42 + let mut fbm = fb.borrow_mut(); 43 + fbm.cls(0); 44 + let w = fbm.print_text("mmmm\nmm", 5, 5, 1, false, 1, false); 45 + // First line longer than second; width should reflect the first line 46 + let w_first = fbm.print_text("mmmm", 0, 0, 1, false, 1, false); 47 + assert_eq!(w, w_first); 48 + }
+65
tic80_rust/tests/tri_ellipse_edge_tests.rs
··· 1 + use std::cell::RefCell; 2 + use std::rc::Rc; 3 + 4 + use tic80_rust::gfx::framebuffer::Framebuffer; 5 + 6 + #[allow(dead_code)] 7 + fn count_nonzero(fb: &mut Framebuffer) -> usize { 8 + let (w, h) = (Framebuffer::WIDTH as i32, Framebuffer::HEIGHT as i32); 9 + let mut c = 0usize; 10 + for y in 0..h { 11 + for x in 0..w { 12 + if fb.pix(x, y, None).unwrap_or(0) != 0 { 13 + c += 1; 14 + } 15 + } 16 + } 17 + c 18 + } 19 + 20 + #[test] 21 + fn tri_shared_edge_shallow_slope_tiles_rect() { 22 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 23 + let mut fbm = fb.borrow_mut(); 24 + fbm.cls(0); 25 + // Rectangle region 40x23 at (50,20) 26 + let x0 = 50; 27 + let y0 = 20; 28 + let w = 40; 29 + let h = 23; 30 + // Two triangles sharing a shallow edge: (x0,y0)->(x0+w,y0+h)->(x0,y0+h) and (x0,y0)->(x0+w,y0)->(x0+w,y0+h) 31 + fbm.tri(x0, y0, x0 + w, y0 + h, x0, y0 + h, 2); 32 + fbm.tri(x0, y0, x0 + w, y0, x0 + w, y0 + h, 2); 33 + // Count colored pixels inside the rectangle bounds 34 + let mut area = 0usize; 35 + for yy in y0..y0 + h { 36 + for xx in x0..x0 + w { 37 + if fbm.pix(xx, yy, None).unwrap_or(0) != 0 { 38 + area += 1; 39 + } 40 + } 41 + } 42 + assert_eq!(area as i32, (w * h)); 43 + } 44 + 45 + #[test] 46 + fn ellib_cardinal_points_aspect_wide() { 47 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 48 + let mut fbm = fb.borrow_mut(); 49 + fbm.cls(0); 50 + let cx = 100; 51 + let cy = 60; 52 + let a = 20; // x radius 53 + let b = 5; // y radius 54 + fbm.ellib(cx, cy, a, b, 3); 55 + // Cardinals should be lit 56 + assert!(fbm.pix(cx + a, cy, None).unwrap_or(0) != 0); 57 + assert!(fbm.pix(cx - a, cy, None).unwrap_or(0) != 0); 58 + assert!(fbm.pix(cx, cy + b, None).unwrap_or(0) != 0); 59 + assert!(fbm.pix(cx, cy - b, None).unwrap_or(0) != 0); 60 + // One pixel outside cardinals should remain background 61 + assert_eq!(fbm.pix(cx + a + 1, cy, None).unwrap_or(0), 0); 62 + assert_eq!(fbm.pix(cx - a - 1, cy, None).unwrap_or(0), 0); 63 + assert_eq!(fbm.pix(cx, cy + b + 1, None).unwrap_or(0), 0); 64 + assert_eq!(fbm.pix(cx, cy - b - 1, None).unwrap_or(0), 0); 65 + }