git.lucas.co / cce-window-manager
window management library
git clone https://git.lucas.co/cce-window-manager.git

commit641d09d9c70c3cc461708fac5116bed5f80cda12
parenta4850bd711
authorLucas Galante <[email protected]>
date2026-07-28 19:21
fix: grid placement is world-true at fractional zooms

At a fractional zoomed period (cell 512 + gap 16 at zoom 0.8 = 422.4px)
the grid quantized everything to the rounded period: cells were spaced
k*422 and the tree phase folded over 422, so the drawn grid drifted
from the world-anchored windows by the rounding error per period and
jumped whenever the fold wrapped — a window edge's neighboring gap line
visibly slid around under pan, reading as inconsistent shadows against
the grid. GridFrame now carries period_px_exact: the tree phase folds
over the exact period (rounded once at the end) and the renderer places
cell k at round(k * exact), keeping every cell within half a pixel of
its world position at any pan. Pinned by a test at the 422.4 case,
including a fold-wrapping pan.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/background.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 47 insertions(+), 9 deletions(-)

diff --git a/src/background.rs b/src/background.rs
index e47cfe4..45766bd 100644
--- a/src/background.rs
+++ b/src/background.rs
@@ -18,12 +18,20 @@ pub fn sanitized_zoom(zoom: f64) -> f64 {
 #[derive(Debug, Clone, PartialEq)]
 pub struct GridFrame {
     /// Grid-tree translation in layout px (includes the output's own
-    /// offset): the pan shift folded modulo one period, minus one period so
-    /// the tree always overhangs the top-left edge. None when the zoomed
-    /// period rounds to zero — the tree keeps its last position.
+    /// offset): the pan shift folded modulo one EXACT period, minus one
+    /// period so the tree always overhangs the top-left edge. None when the
+    /// zoomed period rounds to zero — the tree keeps its last position.
     pub tree_pos: Option<(i32, i32)>,
     /// The zoomed grid period (cell + gap), rounded to px.
     pub period_px: i32,
+    /// The exact (unrounded) zoomed period. Cell k must be placed at
+    /// `round(k * period_px_exact)` tree-local — NOT `k * period_px`: at
+    /// fractional zooms the rounded period drifts from the world-true cell
+    /// positions by its rounding error per period, so the grid slides
+    /// relative to the (world-anchored) windows as the camera pans, and
+    /// anything meant to hug a window edge (a client shadow, a snap)
+    /// visibly jitters against the grid.
+    pub period_px_exact: f64,
     /// Backdrop (gap color) extent: viewport plus one period, so pan shifts
     /// never expose the edge.
     pub backdrop_w: i32,
@@ -73,14 +81,18 @@ pub fn grid_frame(
     };
     let period_px = period_pixels.round() as i32;
 
-    // Modulo shift for infinite scrolling; .floor() matches the truncation
-    // direction of window coordinates.
+    // Modulo shift for infinite scrolling, in EXACT period units: the phase
+    // is folded over the true zoomed period and only rounded once at the
+    // end, so the tree lands within half a pixel of the world-true cell
+    // boundary at any pan. (Folding over the ROUNDED period accumulated its
+    // rounding error into the phase and made the whole grid jump relative
+    // to the windows whenever the fold wrapped.)
     let tree_pos = if period_px > 0 {
-        let origin_x = ((-cam.pan_x) * zoom).floor() as i32;
-        let origin_y = ((-cam.pan_y) * zoom).floor() as i32;
+        let phase_x = ((-cam.pan_x) * zoom).rem_euclid(period_pixels);
+        let phase_y = ((-cam.pan_y) * zoom).rem_euclid(period_pixels);
         Some((
-            output_x + origin_x.rem_euclid(period_px) - period_px,
-            output_y + origin_y.rem_euclid(period_px) - period_px,
+            output_x + (phase_x - period_pixels).round() as i32,
+            output_y + (phase_y - period_pixels).round() as i32,
         ))
     } else {
         None
@@ -115,6 +127,7 @@ pub fn grid_frame(
     GridFrame {
         tree_pos,
         period_px,
+        period_px_exact: period_pixels,
         backdrop_w: viewport_w + period_px,
         backdrop_h: viewport_h + period_px,
         cells,
@@ -192,6 +205,31 @@ mod tests {
         assert_eq!(f.cells.unwrap().fade_inset_px, 45);
     }
 
+    #[test]
+    fn fractional_zoom_keeps_grid_world_true() {
+        // cell 512 + gap 16 = period 528; zoom 0.8 → exact period 422.4
+        // (rounds to 422). The renderer places cell k at round(k * exact):
+        // spacing alternates 422/423 so cells never drift from the
+        // world-anchored windows.
+        let mut s = spec();
+        s.cell_size = 512.0;
+        s.gap_width = 16.0;
+        let f = grid_frame(&s, cam(0.0, 0.0, 0.8), 3840, 2400, 0, 0);
+        assert_eq!(f.period_px, 422);
+        assert!((f.period_px_exact - 422.4).abs() < 1e-9);
+        let pos: Vec<i32> = (0..5).map(|k| (k as f64 * f.period_px_exact).round() as i32).collect();
+        assert_eq!(pos, vec![0, 422, 845, 1267, 1690]);
+        // Tree phase folds over the EXACT period: pan 100 → phase
+        // (-80).rem_euclid(422.4) = 342.4 → tree at round(342.4 - 422.4).
+        let f = grid_frame(&s, cam(100.0, 0.0, 0.8), 3840, 2400, 0, 0);
+        assert_eq!(f.tree_pos.unwrap().0, -80);
+        // A pan that wraps the fold still lands world-true: pan 600 →
+        // -480 px screen shift; phase (-480).rem_euclid(422.4) = 364.8 →
+        // tree at round(-57.6) = -58 (= -480 + one 422.4 period, rounded).
+        let f = grid_frame(&s, cam(600.0, 0.0, 0.8), 3840, 2400, 0, 0);
+        assert_eq!(f.tree_pos.unwrap().0, -58);
+    }
+
     #[test]
     fn degenerate_zoom_and_period_are_safe() {
         // NaN zoom falls back to 1.