From bbd5b12f4fd454c4c1f016199cf1c408a8286b83 Mon Sep 17 00:00:00 2001 From: Eric J Date: Sat, 29 Aug 2026 23:16:07 -0700 Subject: [PATCH] Grade the overlay water skip and the mark painters in Rust (#227) Second of the cheap-tier delete-with-loss ports, and it closes the same claim in four deleted specs at once. `renderCliffs`, `renderRocks`, `renderEnemies` and `renderResources` each carried a "never paints over water" test. Rust has the predicate - `is_nauvis_water`, derived from the palette so it cannot drift - and every overlay passes it, but the only test of it was the tree one. `paint_mark` and `paint_mark_skipping` had no test at all, though every placement overlay on both planets goes through them. ## Two plants shaped the water test, and both were mine The obvious form of this test is worth writing down as a dead end, because it looks convincing: | attempt | why it does not grade anything | | --- | --- | | render each overlay, assert no water pixel moved | **Nearly vacuous.** Rocks are excluded from water at PLACEMENT time, so in an ordinary window no mark ever gets the chance to spill onto water. Deleting the rock overlay's skip entirely left it green. | | build an all-water base from a colour sampled out of a render | **Vacuous the other way.** The window handed back deepwater, so a predicate that stopped recognising SHALLOW water also passed. | Both were planted and observed, not reasoned about. The shipped test names both tiles from the palette rather than sampling one, and under the shallow-water plant it now fails while all 34 other tests pass. It also paints the same mark on a land base and requires all 25 pixels, so a painter that does nothing cannot satisfy it. ## The rest - `every_nauvis_overlay_paints_and_leaves_water_alone` is kept, and is deliberately described as the weaker check it is. What it does pin is that each overlay paints SOMETHING in a real window. The enemy arm runs at a different origin because enemy bases are excluded from the starting area by design - the anti-vacuity guard caught that rather than passing silently, and that is how the window was chosen. - Three `paint_mark` tests: the `(2r+1)` square, clipping at the edge instead of wrapping, and consulting the skip predicate per pixel rather than per mark. Claude-Session: https://claude.ai/code/session_01UVcbv1pAhPUoCC6aBwZUtg Co-authored-by: Claude Opus 5 (1M context) --- crates/fmw-wasm/src/render.rs | 203 ++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/crates/fmw-wasm/src/render.rs b/crates/fmw-wasm/src/render.rs index f222551..9e65ba9 100644 --- a/crates/fmw-wasm/src/render.rs +++ b/crates/fmw-wasm/src/render.rs @@ -1849,6 +1849,209 @@ mod tests { assert!(water > 0, "the window must contain water to grade the skip"); } + /// A Nauvis request with EVERY overlay lever live, over a window at `origin`. + /// + /// `nauvis_request` leaves the rock, enemy, cliff and resource levers at + /// zero, which would make an overlay paint nothing. The origin is a + /// parameter because the enemy overlay deliberately paints nothing near + /// spawn. + fn nauvis_overlay_request(width: u32, height: u32, view: u32, origin: f64) -> Vec { + let mut b = nauvis_request(width, height); + b[12..16].copy_from_slice(&view.to_le_bytes()); + b[32..40].copy_from_slice(&origin.to_le_bytes()); + b[40..48].copy_from_slice(&origin.to_le_bytes()); + // trees_size, rocks, enemies, cliff frequency/continuity/richness. + for at in [144usize, 152, 160, 200, 208, 216, 224, 248] { + b[at..at + 8].copy_from_slice(&1.0f64.to_le_bytes()); + } + b[232..240].copy_from_slice(&10.0f64.to_le_bytes()); + b[240..248].copy_from_slice(&40.0f64.to_le_bytes()); + // The world box the window covers, grown well past the mark radius, for + // the placement sweep and the cliff cell query alike. `nauvis_request` + // renders at 8 tiles per pixel. + let far = origin + f64::from(width) * 8.0 + 64.0; + for (i, v) in [origin - 64.0, origin - 64.0, far, far].iter().enumerate() { + b[168 + i * 8..176 + i * 8].copy_from_slice(&v.to_le_bytes()); + b[256 + i * 8..264 + i * 8].copy_from_slice(&v.to_le_bytes()); + } + for i in 0..6 { + let at = 288 + i * 24; + for k in 0..3 { + b[at + k * 8..at + k * 8 + 8].copy_from_slice(&1.0f64.to_le_bytes()); + } + } + b + } + + /// The paint-time water skip, graded directly, on BOTH water tiles. + /// + /// Ported from the four specs that each carried a "never paints over water" + /// claim for one overlay - `renderCliffs`, `renderRocks`, `renderEnemies` + /// and `renderResources` - which #227 deletes. Rust has the predicate + /// (`is_nauvis_water`, derived from the palette so it cannot drift) and + /// every overlay passes it, but the only test of it was the tree one. + /// + /// **This grades the mechanism rather than a window, and it names both + /// tiles rather than sampling one.** Two plants shaped it. Rendering each + /// overlay and checking no water pixel moved is nearly vacuous - rocks are + /// excluded from water at PLACEMENT time, so no mark gets the chance to + /// spill, and deleting the rock overlay's skip entirely leaves such a test + /// green. Sampling a water colour out of a render is vacuous the other way: + /// the window handed back deepwater, so a predicate that stopped + /// recognising shallow water also passed. Both were planted, not assumed. + #[test] + fn the_water_skip_spares_both_nauvis_water_tiles() { + const C: [u8; 3] = [10, 20, 30]; + let (w, h) = (5i64, 5i64); + + for (tile, name) in [ + (NauvisTile::Water, "water"), + (NauvisTile::Deepwater, "deepwater"), + ] { + let rgb = nauvis_tile_color(tile); + let mut img = Vec::with_capacity((w * h * 4) as usize); + for _ in 0..(w * h) { + img.extend_from_slice(&[rgb[0], rgb[1], rgb[2], 255]); + } + let before = img.clone(); + paint_mark_skipping(&mut img, (w, h), 2, 2, C, 2, is_nauvis_water); + assert_eq!(img, before, "{name}: a mark painted over it"); + } + + // The same mark on a land base must paint all 25, or the loop above + // would hold for a painter that does nothing at all. + let mut land = vec![0u8; (w * h * 4) as usize]; + paint_mark_skipping(&mut land, (w, h), 2, 2, C, 2, is_nauvis_water); + let painted = (0..land.len()) + .step_by(4) + .filter(|&i| land[i..i + 3] == C) + .count(); + assert_eq!(painted, 25, "the same mark must paint a land base"); + } + + /// Every Nauvis overlay renders, and none of them moves a water pixel. + /// + /// Weaker than it looks - see the test above for why - but it does pin that + /// each overlay paints SOMETHING in a real window, which the four deleted + /// specs also covered and nothing else here does. + #[test] + fn every_nauvis_overlay_paints_and_leaves_water_alone() { + let (w, h) = (48u32, 48u32); + let bytes = (w * h * 4) as usize; + + for (view, name, origin) in [ + (VIEW_CLIFFS, "cliffs", -256.0f64), + (VIEW_ROCKS, "rocks", -256.0), + (VIEW_RESOURCES, "resources", -256.0), + (VIEW_ENEMIES, "enemies", 2048.0), + ] { + let mut terrain = vec![0u8; bytes]; + assert_eq!( + render( + &nauvis_overlay_request(w, h, VIEW_TERRAIN, origin), + &mut terrain + ), + Status::Ok, + "the terrain arm at {origin} must render" + ); + let mut out = vec![0u8; bytes]; + assert_eq!( + render(&nauvis_overlay_request(w, h, view, origin), &mut out), + Status::Ok, + "the {name} arm must render" + ); + + let mut changed = 0usize; + for i in (0..out.len()).step_by(4) { + if is_nauvis_water(terrain[i], terrain[i + 1], terrain[i + 2]) { + assert_eq!( + &out[i..i + 4], + &terrain[i..i + 4], + "{name}: water at byte {i} was painted over" + ); + } else if out[i..i + 4] != terrain[i..i + 4] { + changed += 1; + } + } + // An overlay that painted nothing satisfies the loop above. + assert!(changed > 0, "{name} painted nothing"); + } + } + + /// Whether a pixel carries the colour, for the mark tests below. + fn mark_painted(out: &[u8], w: i64, x: i64, y: i64, color: [u8; 3]) -> bool { + #[allow(clippy::cast_sign_loss)] + let o = ((y * w + x) * 4) as usize; + out[o..o + 3] == color + } + + /// `paint_mark` paints a `(2r+1)` square centred on the pixel. + /// + /// Ported from `test/renderCliffs.spec.ts`'s `paintMark` block for #227. + /// `render.rs` had no test for either painter, though every placement + /// overlay on both planets goes through them. + #[test] + fn a_mark_is_a_2r_plus_1_square_centred_on_the_pixel() { + const C: [u8; 3] = [10, 20, 30]; + let (w, h) = (7i64, 7i64); + let mut out = vec![0u8; (w * h * 4) as usize]; + paint_mark(&mut out, w, h, 3, 3, C, 1); + + let painted = (0..h) + .flat_map(|y| (0..w).map(move |x| (x, y))) + .filter(|&(x, y)| mark_painted(&out, w, x, y, C)) + .count(); + assert_eq!(painted, 9, "a radius of 1 is a 3x3 block"); + for y in 2..=4 { + for x in 2..=4 { + assert!(mark_painted(&out, w, x, y, C), "({x},{y}) must be painted"); + } + } + } + + /// It clips at the edge rather than wrapping. + #[test] + fn a_mark_clips_at_the_image_edge_instead_of_wrapping() { + const C: [u8; 3] = [10, 20, 30]; + let (w, h) = (4i64, 4i64); + let mut out = vec![0u8; (w * h * 4) as usize]; + paint_mark(&mut out, w, h, 0, 0, C, 1); + + let painted = (0..h) + .flat_map(|y| (0..w).map(move |x| (x, y))) + .filter(|&(x, y)| mark_painted(&out, w, x, y, C)) + .count(); + assert_eq!(painted, 4, "a corner mark keeps only its in-image quarter"); + // The opposite edges are what a wrap would have reached. + assert!( + !mark_painted(&out, w, w - 1, 0, C) && !mark_painted(&out, w, 0, h - 1, C), + "the mark wrapped instead of clipping" + ); + } + + /// It consults the skip predicate per pixel, not per mark. + #[test] + fn a_mark_honours_the_skip_predicate_per_pixel() { + const C: [u8; 3] = [10, 20, 30]; + const SENTINEL: [u8; 3] = [99, 0, 0]; + let (w, h) = (3i64, 3i64); + let mut out = vec![0u8; (w * h * 4) as usize]; + // One pixel of the nine carries a colour the predicate rejects. + out[4 * 4] = SENTINEL[0]; + + paint_mark_skipping(&mut out, (w, h), 1, 1, C, 1, |r, _, _| r == SENTINEL[0]); + + let painted = (0..h) + .flat_map(|y| (0..w).map(move |x| (x, y))) + .filter(|&(x, y)| mark_painted(&out, w, x, y, C)) + .count(); + assert_eq!(painted, 8, "the skipped pixel must be the only one left"); + assert!( + mark_painted(&out, w, 1, 1, SENTINEL), + "the skipped pixel must keep its own colour" + ); + } + /// One catalog entry's chart colour, by name. fn resource_color(name: &str) -> [u8; 3] { NAUVIS_RESOURCE_CATALOG