this repo has no description
0
fork

Configure Feed

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

more more more

alice 19d946e6 2922149c

+255 -17
+9 -2
AGENTS.md
··· 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 + **Testing** 23 + - Strategy: see `docs/testing/strategy.md` for layers (framebuffer, Lua bridge, deterministic hashes) and future plans (audio, conformance, fuzzing). 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. 26 + - Determinism: VRAM hashes use FNV-1a over 240×136 palette indices (see strategy doc for rationale and helper snippet). 27 + 22 28 **Decisions (Locked for prototype)** 23 29 - **Presentation:** `winit + pixels` with integer scaling (2x/3x/4x), RGBA palette conversion from 16-color default. 24 - - **Lua Engine:** `mlua` with vendored Lua 5.4, behavior aligned to 5.2 where needed (compat noted in docs). 30 + - **Lua Engine:** `mlua` with vendored Lua 5.3 (ADR 0003), targeting TIC-80 semantics; enable/replicate 5.1/5.2 compatibility where needed and cover with tests. 25 31 - **Framebuffer:** Single VRAM bank, palette indices in CPU memory; palette map/border/vbank deferred. 26 32 27 33 **Current Status** ··· 70 76 - Merged GUI-first docs into `docs/roadmap/gui_first.md`. 71 77 - Renamed API parity to `docs/specs/lua_api_parity.md` and added specs stubs. 72 78 - Added docs index at `docs/README.md`, architecture/testing pages, and ADRs. 79 + - Added detailed testing docs (`docs/testing/strategy.md`) and a test catalog (`docs/testing/test_catalog.md`). 73 80 74 81 **Docs Index** 75 82 - Start here: `docs/README.md` ··· 77 84 - Specs: `docs/specs/memory_map.md`, `docs/specs/lua_api_parity.md`, `docs/specs/graphics.md`, `docs/specs/audio_fft_vqt.md` 78 85 - Architecture: `docs/architecture/workspace.md`, `docs/architecture/runtime.md` 79 86 - Testing: `docs/testing/strategy.md`, `docs/testing/frame_hashes.md` 80 - - ADRs: `docs/adr/0001-winit-pixels.md`, `docs/adr/0002-mlua-lua54-compat.md` 87 + - ADRs: `docs/adr/0001-winit-pixels.md`, `docs/adr/0002-mlua-lua54-compat.md` (superseded), `docs/adr/0003-lua53-with-compat.md`
+3 -2
docs/README.md
··· 19 19 ## Testing 20 20 - `docs/testing/strategy.md`: Testing and validation strategy across API/VRAM/audio. 21 21 - `docs/testing/frame_hashes.md`: Conventions for deterministic frame/audio hashing (stub). 22 + - `docs/testing/test_catalog.md`: Summary of current tests and their intent. 22 23 23 24 ## Decisions (ADR) 24 25 - `docs/adr/0001-winit-pixels.md`: Windowing/presentation stack decision. 25 - - `docs/adr/0002-mlua-lua54-compat.md`: Lua engine choice and compatibility stance. 26 + - `docs/adr/0002-mlua-lua54-compat.md`: Lua 5.4 choice (superseded). 27 + - `docs/adr/0003-lua53-with-compat.md`: Lua 5.3 with 5.1/5.2 compatibility. 26 28 27 29 Notes 28 30 - `MEMORY_MAP.md` at repo root remains the canonical reference for layout. Specs here link to it rather than duplicating. ··· 35 37 - Load a `.lua` file: 36 38 - `cargo run --manifest-path tic80_rust/Cargo.toml -- tic80_rust/assets/alt.lua` 37 39 - In crate dir: `cargo run -- assets/alt.lua` 38 -
+1 -2
docs/adr/0002-mlua-lua54-compat.md
··· 1 1 # ADR 0002: Lua Engine and Compatibility 2 2 3 - Status: Accepted 3 + Status: Superseded by ADR 0003 4 4 Date: 2025-08-26 5 5 6 6 Context ··· 12 12 Consequences 13 13 - Portable across supported platforms; avoids LuaJIT portability trade-offs initially. 14 14 - Add tests around numeric semantics and iteration order to guard against drift. 15 -
+19
docs/adr/0003-lua53-with-compat.md
··· 1 + # ADR 0003: Lua 5.3 With 5.1/5.2 Compatibility 2 + 3 + Status: Accepted (supersedes ADR 0002) 4 + Date: 2025-08-26 5 + 6 + Context 7 + - TIC-80 cartridges historically target Lua 5.3 semantics with selected 5.1/5.2 compatibility. Matching those semantics reduces drift across carts and the reference C build. 8 + 9 + Decision 10 + - Use `mlua` with vendored Lua 5.3: `mlua = { version = "0.9", features = ["lua53", "vendored"] }`. 11 + - Enable 5.1/5.2 compatibility macros during Lua build (LUA_COMPAT_5_2/5_1) where feasible. If the vendored build lacks direct flags, emulate required behaviors at the API boundary and cover with tests. 12 + 13 + Consequences 14 + - Closer behavioral parity with TIC-80 vs. Lua 5.4. 15 + - Some compatibility shims may still be required; capture deltas with tests. 16 + 17 + Notes 18 + - ADR 0002 (Lua 5.4) is superseded by this ADR. Keep it for historical context. 19 +
+53 -9
docs/testing/strategy.md
··· 1 1 # Testing and Validation Strategy 2 2 3 - Layers 4 - - API goldens: Unit tests per API including edge cases and errors. 5 - - Frame hashes: Deterministic VRAM hashes per frame to compare against baselines. 6 - - Audio blocks: Hash mixed audio buffers with tolerances for FP differences. 7 - - Conformance carts: Automated headless runs comparing traces/hashes to reference. 8 - - Fuzzing: `.tic` loader and selected APIs for robustness. 3 + **Layers** 4 + - **Framebuffer unit tests:** Validate drawing primitives (`cls`, `pix`, `line`, `rect`, `rectb`), clipping, palette → RGBA mapping, and OOB behavior. 5 + - **Lua bridge tests:** Verify exposed APIs (`cls/pix/line/rect/print/rectb/clip`) and lifecycle (`BOOT`/`TIC`), and that Lua semantics (e.g., OOB `pix` read returns `nil`) are preserved. 6 + - **Deterministic snapshots:** Compute a simple hash of the 240×136 palette-index framebuffer (per tick) to assert determinism and enable future golden comparisons. 7 + - **Audio blocks (later):** Hash mixed audio blocks with tolerance windows for FP differences. 8 + - **Conformance carts (later):** Automate selected carts (headless) and compare traces/hashes to baselines. 9 + - **Fuzzing (later):** Fuzz `.tic` loader and selected APIs for robustness. 9 10 10 - Notes 11 - - Prefer headless tests with minimal dependencies. 12 - - Keep baselines small and documented; record how they are generated. 11 + **Deterministic Frame Hash** 12 + - We use an FNV‑1a hash over palette indices for the entire 240×136 buffer. 13 + - Rationale: cheap, stable across platforms, and independent of presentation (RGBA conversion is not hashed). 14 + - Helper pseudocode (Rust): 15 + 16 + ```rust 17 + fn fb_hash(fb: &mut Framebuffer) -> u64 { 18 + let (w, h) = dimensions(); 19 + let mut hash: u64 = 0xcbf29ce484222325; // FNV offset 20 + const FNV_PRIME: u64 = 0x00000100000001B3; 21 + for y in 0..(h as i32) { 22 + for x in 0..(w as i32) { 23 + let b = fb.pix(x, y, None).unwrap_or(0); 24 + hash ^= b as u64; 25 + hash = hash.wrapping_mul(FNV_PRIME); 26 + } 27 + } 28 + hash 29 + } 30 + ``` 31 + 32 + **What We Test Today** 33 + - Framebuffer 34 + - **cls/pix:** Fill/reads; OOB reads return `None`; writes masked to 0..15. 35 + - **rect (fill):** In-bounds, clipping to viewport and active clip rectangle. 36 + - **rectb (border):** One‑pixel perimeter; clipped to viewport and active clip. 37 + - **line:** Endpoints and pixel counts for axis-aligned and sloped lines; robustness with far‑OOB endpoints. 38 + - **clip:** Set/reset and enforcement in `set_pixel`, `rect`, `rectb`, and `pix` write mode. 39 + - **palette blit:** Index→RGBA conversion correctness for selected samples. 40 + - Lua bridge 41 + - **API wiring:** `cls/pix/line/rect/print/rectb/clip` exposed and working. 42 + - **OOB semantics:** `pix` OOB read returns `nil` in Lua. 43 + - **File loading:** Alternate cart (`assets/alt.lua`) loads and runs via in-memory string. 44 + - **Determinism:** Default cart’s frame hash is stable for N ticks; different tick counts produce different hashes. 45 + 46 + See `docs/testing/test_catalog.md` for the current test list and intent. 47 + 48 + **How To Run** 49 + - Unit + Lua tests: `cd tic80_rust && cargo test` 50 + - Clippy (treat warnings as errors): `cd tic80_rust && cargo clippy --all-targets --all-features -D warnings` 51 + 52 + **Future Additions** 53 + - Expand primitives coverage (`circb/circ/elli/ellib/tri/trib`, `print` edge cases: scale>1 areas, baseline/advance, small font). 54 + - Add frame-hash goldens for selected demo sequences (stable seeds and scripts). 55 + - Introduce error-path tests for Lua type/arity mismatches and unknown APIs. 56 + - Add input semantics tests (`key/keyp/btn/btnp/mouse`) with fixed-step repeat timing. 13 57
+32
docs/testing/test_catalog.md
··· 1 + # Test Catalog 2 + 3 + This document summarizes the current test coverage with file paths and intent. 4 + 5 + ## Framebuffer Unit Tests 6 + - `tic80_rust/tests/gfx_framebuffer_tests.rs` 7 + - `cls_fills_entire_buffer`: `cls` fills the entire VRAM with the color index. 8 + - `pix_read_write_and_bounds`: `pix` read/write semantics; OOB reads return `None` and writes are ignored. 9 + - `rect_fill_and_clipping`: `rect` fills and clips to viewport; fully OOB rects are no‑ops. 10 + - `line_basic_counts_and_endpoints`: Line endpoints colored; counts match `max(dx,dy)+1` both directions. 11 + - `blit_to_rgba_maps_palette`: Palette index→RGBA mapping matches expected sRGB bytes. 12 + - `rectb_draws_border`: `rectb` draws a 1‑px border; interior remains unchanged. 13 + - `clip_limits_drawing_and_reset`: Clip restricts drawing; reset restores full viewport. 14 + - `print_width_fixed_vs_variable_and_newline`: `print_text` width (fixed vs variable), newline row advance, and scale behavior. 15 + - `clip_affects_pix_write`: `pix(x,y,color)` respects the active clip. 16 + - `robust_oob_line_and_rectb`: OOB line still draws in-bounds; border rect crossing viewport produces in-bounds edges. 17 + 18 + ## Lua Bridge Tests 19 + - `tic80_rust/tests/lua_api_tests.rs` 20 + - `lua_cls_and_pix`: `BOOT` clears; `TIC` writes a pixel; verify both states. 21 + - `lua_line_and_rect`: Lines and filled rect via Lua. 22 + - `lua_print_width_marker`: `print` returns width; script uses it to place a marker. 23 + - `lua_print_defaults_and_pix_read`: `print` defaults and `pix` read mode; verifies glyphs drawn near origin. 24 + - `lua_clip_and_rectb`: `clip()` and `rectb` via Lua; clip restricts drawing. 25 + - `lua_runs_alt_cart_file`: Loads `assets/alt.lua`, checks background/marker/square. 26 + - `lua_pix_oob_read_returns_nil`: OOB `pix` read returns `nil` in Lua. 27 + - `lua_default_cart_deterministic_hash`: Default cart produces deterministic frame hashes for fixed tick counts. 28 + 29 + Notes 30 + - Tests prefer headless framebuffer inspection over image baselines. 31 + - Hashing uses FNV‑1a over VRAM palette indices for portability and stability. 32 +
+1 -1
tic80_rust/Cargo.toml
··· 6 6 [dependencies] 7 7 winit = "0.28" 8 8 pixels = "0.14" 9 - mlua = { version = "0.9", features = ["lua54", "vendored"] } 9 + mlua = { version = "0.9", features = ["lua53", "vendored"] }
+1 -1
tic80_rust/src/main.rs
··· 45 45 let (width, height) = dimensions(); 46 46 let size = LogicalSize::new((width as f64) * SCALE, (height as f64) * SCALE); 47 47 let window = WindowBuilder::new() 48 - .with_title("tic80_rust – Milestone 1 (GUI + cls/pix)") 48 + .with_title("rustic") 49 49 .with_inner_size(size) 50 50 .with_min_inner_size(size) 51 51 .build(&event_loop)
+77
tic80_rust/tests/gfx_framebuffer_tests.rs
··· 83 83 c 84 84 } 85 85 86 + #[allow(dead_code)] 87 + fn fb_hash(fb: &mut Framebuffer) -> u64 { 88 + // Simple FNV-1a over pixel indices via pix reads 89 + let (w, h) = dimensions(); 90 + let mut hash: u64 = 0xcbf29ce484222325; 91 + const FNV_PRIME: u64 = 0x00000100000001B3; 92 + for y in 0..(h as i32) { 93 + for x in 0..(w as i32) { 94 + let b = fb.pix(x, y, None).unwrap_or(0); 95 + hash ^= b as u64; 96 + hash = hash.wrapping_mul(FNV_PRIME); 97 + } 98 + } 99 + hash 100 + } 101 + 86 102 #[test] 87 103 fn line_basic_counts_and_endpoints() { 88 104 // Horizontal line ··· 171 187 assert_eq!(fb.pix(0, 0, None), Some(9)); 172 188 assert_eq!(fb.pix(1, 1, None), Some(9)); 173 189 } 190 + 191 + #[test] 192 + fn print_width_fixed_vs_variable_and_newline() { 193 + let mut fb = Framebuffer::new(); 194 + fb.cls(0); 195 + // Fixed width: width = len * 6 * scale 196 + let w1 = fb.print_text("AB", 10, 10, 1, true, 1, false); 197 + assert_eq!(w1, 2 * 6); 198 + let w2 = fb.print_text("AB", 10, 10, 1, true, 2, false); 199 + assert_eq!(w2, 2 * 6 * 2); 200 + // Variable width should be <= fixed width for same string/scale 201 + let v1 = fb.print_text("AB", 10, 10, 1, false, 1, false); 202 + assert!(v1 <= w1); 203 + 204 + // Newlines: expect drawing on initial row and on y+6 (scale=1) 205 + let mut fb2 = Framebuffer::new(); 206 + fb2.cls(0); 207 + let _ = fb2.print_text("A\nA", 0, 0, 15, true, 1, false); 208 + // Something on row 0 209 + let mut any_row0 = false; 210 + for x in 0..8 { 211 + if fb2.pix(x, 0, None) == Some(15) { any_row0 = true; break; } 212 + } 213 + assert!(any_row0); 214 + // And something on row 6 215 + let mut any_row6 = false; 216 + for x in 0..8 { 217 + if fb2.pix(x, 6, None) == Some(15) { any_row6 = true; break; } 218 + } 219 + assert!(any_row6); 220 + } 221 + 222 + #[test] 223 + fn clip_affects_pix_write() { 224 + let mut fb = Framebuffer::new(); 225 + fb.cls(2); 226 + fb.clip(1, 1, 1, 1); // only (1,1) 227 + // Write outside clip 228 + let _ = fb.pix(0, 0, Some(7)); 229 + // Write inside clip 230 + let _ = fb.pix(1, 1, Some(7)); 231 + // Validate 232 + assert_eq!(fb.pix(0, 0, None), Some(2)); 233 + assert_eq!(fb.pix(1, 1, None), Some(7)); 234 + } 235 + 236 + #[test] 237 + fn robust_oob_line_and_rectb() { 238 + let mut fb = Framebuffer::new(); 239 + fb.cls(0); 240 + // Very long line across/outside bounds 241 + fb.line(-100, -100, 1000, 2000, 9); 242 + // Should produce some in-bounds pixels 243 + assert!(count_color(&mut fb, 9) > 0); 244 + 245 + // Rect border with negative origin that crosses viewport 246 + fb.rectb(-5, -5, 12, 12, 4); 247 + // Perimeter segments that lie in-bounds should be colored 248 + assert_eq!(fb.pix(6, 0, None), Some(4)); // right edge 249 + assert_eq!(fb.pix(0, 6, None), Some(4)); // bottom edge 250 + }
+59
tic80_rust/tests/lua_api_tests.rs
··· 165 165 assert_eq!(fbm.pix(1, 0, None), Some(1)); 166 166 assert_eq!(fbm.pix(0, 1, None), Some(1)); 167 167 } 168 + 169 + #[test] 170 + fn lua_pix_oob_read_returns_nil() { 171 + let script = r#" 172 + function BOOT() cls(0) end 173 + function TIC() 174 + local ok = true 175 + if pix(-1, 0) ~= nil then ok = false end 176 + if pix(0, -1) ~= nil then ok = false end 177 + if pix(240, 0) ~= nil then ok = false end 178 + if pix(0, 136) ~= nil then ok = false end 179 + if ok then pix(0, 0, 5) end 180 + end 181 + "#; 182 + let fb = run_lua(script, 1); 183 + // Marker set only if nil checks passed 184 + assert_eq!(fb.borrow_mut().pix(0, 0, None), Some(5)); 185 + } 186 + 187 + fn fb_hash(fb: &mut Framebuffer) -> u64 { 188 + let (w, h) = dimensions(); 189 + let mut hash: u64 = 0xcbf29ce484222325; 190 + const FNV_PRIME: u64 = 0x00000100000001B3; 191 + for y in 0..(h as i32) { 192 + for x in 0..(w as i32) { 193 + let b = fb.pix(x, y, None).unwrap_or(0); 194 + hash ^= b as u64; 195 + hash = hash.wrapping_mul(FNV_PRIME); 196 + } 197 + } 198 + hash 199 + } 200 + 201 + #[test] 202 + fn lua_default_cart_deterministic_hash() { 203 + // Load default cart and run N frames twice; hashes should match 204 + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 205 + path.push("assets/default.lua"); 206 + let script = std::fs::read_to_string(&path).expect("read default.lua"); 207 + 208 + let run_hash = |ticks: usize| -> u64 { 209 + let fb = Rc::new(RefCell::new(Framebuffer::new())); 210 + let runner = LuaRunner::new(fb.clone(), &script).expect("lua init"); 211 + for _ in 0..ticks { runner.tick(); } 212 + let mut borrowed = fb.borrow_mut(); 213 + fb_hash(&mut *borrowed) 214 + }; 215 + 216 + let h1 = run_hash(1); 217 + let h1_again = run_hash(1); 218 + assert_eq!(h1, h1_again, "hash should be deterministic for 1 tick"); 219 + 220 + let h2 = run_hash(2); 221 + let h2_again = run_hash(2); 222 + assert_eq!(h2, h2_again, "hash should be deterministic for 2 ticks"); 223 + 224 + // Different frame counts should usually yield different hashes for this cart 225 + assert_ne!(h1, h2, "different ticks should yield different frame hashes"); 226 + }