docs(display): the mid-panel tear can be compensated; say how, and when it cannot

main's new section called the 1px step at mid-height a panel-scan effect with
nothing to fix in the render path. The scan explanation holds, but at one
pixel per refresh the step is one refresh of motion and lagging one half
cancels it, which this branch does. Rewrite that paragraph, list what
compensation covers and what it leaves to the old advice (held frames,
other layouts, the emulator), and add a CHANGELOG entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-24 16:52:33 -04:00
co-authored by Claude Opus 5.5
parent 7b16e953d2
commit 6aef54598b
2 changed files with 41 additions and 6 deletions
+35 -6
View File
@@ -325,10 +325,9 @@ of roughly
offset ≈ scroll speed × refresh period
```
Each frame already reaches the panel whole (`SwapOnVSync` swaps complete frames
between refreshes), so there is nothing to fix in the render path; the shift is
created inside a single refresh. Other panel heights show it too, at the point
where their two scan halves meet.
Each frame reaches the panel whole (`SwapOnVSync` swaps complete frames between
refreshes); the shift is created inside a single refresh. Other panel heights
show it too, at the point where their two scan halves meet.
On the 2×128×64 chain above, which refreshes at about 130 Hz flat out
(7.7 ms per pass):
@@ -339,7 +338,37 @@ On the 2×128×64 chain above, which refreshes at about 130 Hz flat out
| 100 px/s | ~0.8 px |
| 150 px/s | ~1.2 px, plainly visible |
### What changes it
### What the display does about it
At one pixel per refresh, the fastest crisp speed, the step is exactly one
refresh's worth of motion, so it can be cancelled: show one half of the panel
a refresh behind the other -- the half whose row at the seam lights at the
start of each refresh. The two rows either side of the seam then show the same
moment again. What is left is a
lean of one pixel per half from top to bottom, continuous across the panel,
which reads as nothing where the step read as a tear. `DisplayManager` does
this while something scrolls at one frame per refresh
(`display.scan_order_compensation`, `"auto"` by default, `"off"` to disable;
the geometry is in `src/scan_order.py`). The lagging rows come from the
previous frame the display presented, so it works for Vegas and every plugin
ticker without knowing how they scroll.
Checked on hdpi (4×128×64 on one chain, rotated 180, 2026-09-24) before it was
written: `scan_mode: 1` (interlaced) made the step vanish but turned moving
edges grainy, and halving the speed halved it, so it is the scan and not a torn
frame. With the compensation the step is gone at 90 px/s.
It is left off where the row order is unknown or the maths does not hold:
- **Slower speeds**, where each frame is held for two or more refreshes. The
offset there is half a pixel or less, and cancelling it would need a lag of
a fraction of a frame.
- **Other layouts:** pixel mappers other than a 0 or 180 degree rotation
(U-mapper, 90/270), non-zero `multiplexing`, interlaced `scan_mode`, and a
canvas remapped to another height (double-sided mode).
- **The emulator,** which has no scan order.
### When it cannot apply
Only a shorter scan period (a faster refresh) or a slower scroll. Measure what
the panel actually achieves first. The library prints the rate with a carriage
@@ -370,7 +399,7 @@ on its own output and set `parallel` to the number of outputs used and
should roughly double the refresh rate and halve the offset. That is a cable
change, so measure again afterwards.
Short of rewiring, keep fast scrolls moderate: at the default 50 px/s the
Short of rewiring, keep fast scrolls moderate on those layouts: at 50 px/s the
offset is under half a pixel.
## Rebuilding the binding