fix(mhires): pad absent percell slots with bg0 to stop out-of-palette (green) flicker on slow transports#40
Merged
Merged
Conversation
… (green) flicker on slow transports
The percell path picks each 4×8 cell's top-3 non-bg0 colors via argpartition.
A cell with fewer than 3 distinct non-bg0 colors present — mostly-bg0 cells,
which are the norm under a small forced palette like [0,4,6,14] — left the
surplus slots holding ARBITRARY zero-count palette indices. For an all-bg0
cell argpartition literally picks index 5 (green), plus 1/15, into the
screen/color RAM nibbles.
In steady state those filler slots are never selected (present pixels resolve
to their own in-set color), so on the U64's fast TCP DMA the garbage stays
invisible. But push() ships screen ($0400) / color ($D800) / bitmap ($2000)
as three NON-ATOMIC writes; on the TeensyROM serial transport (~10 KB/frame,
ack-gated, far slower) the VIC reads a new bitmap byte against a still-stale
screen/color byte mid-frame and briefly renders the garbage filler — the
green-square flicker, and on letterboxed video the all-bg0 edge cells flashing
("flashing border").
Fix: replace any per-cell pick whose smoothed count is 0 (never present in the
cell) with bg0. screen/color RAM then only ever carries colors genuinely
present in the cell — nothing outside the source's set can leak — and the
absent slots become a deterministic bg0, so present colors stop churning slots.
bg0 in a filler slot is a harmless duplicate (the %00 code already reaches it;
argmin breaks ties to the real bg0 at slot 0). No change to rendered output on
fast transports — the filler slots were never selected.
Verified offline against the actual config (mhires + percell + force_palette
[0,4,6,14], Rush - Subdivisions): pre-fix 200/200 frames leak out-of-palette
indices into screen/color RAM (incl. 5=green); post-fix 0/200.
Tests: new PercellFillerSafetyTest (no out-of-present-set leak; all-bg0 cell is
solid bg0); rewrote the stale "excludes bg0 from per-cell picks" test to the
correct invariant (bg0 fills only unused slots, never displaces a real color).
test_staged_bitmap_skips_panel exercised the menu's "panel not drawn on
REU-staged bitmap scenes" path, which logs a one-shot WARNING — left
unasserted, it printed on every full-suite run and buried real failures.
Wrap the call in assertLogs("c64cast.menu", WARNING) so the message is
verified AND kept off the console.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #40 +/- ##
=======================================
Coverage 79.56% 79.57%
=======================================
Files 68 68
Lines 12858 12861 +3
Branches 1898 1898
=======================================
+ Hits 10231 10234 +3
Misses 2188 2188
Partials 439 439 ☔ View full report in Codecov by Harness. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On the
mhirespercellpath with a small forced palette (e.g.force_palette_indices = [0, 4, 6, 14]), cells flicker with green squares — a color that isn't in the allowed set — and letterboxed video shows the all-bg0 edge cells flashing (reads as a "flashing border"). Pronounced on the TeensyROM serial backend.Root cause
Each 4×8 cell picks its top-3 non-bg0 colors with
argpartition. A cell with fewer than 3 distinct non-bg0 colors present — the norm under a small forced palette, where most cells are mostly bg0 with 0–2 accents — left the surplus slots holding arbitrary zero-count palette indices. For an all-bg0 cell,argpartitionreturns index 5 (green) (among others) straight into the screen/color-RAM nibbles.In steady state those filler slots are never selected (a present pixel resolves to its own in-set color), so on a fast transport the garbage stays invisible. But
push()ships screen ($0400) / color ($D800) / bitmap ($2000) as three non-atomic writes. On a slow, ack-gated transport the VIC can read a new bitmap byte against a still-stale screen/color byte mid-frame and briefly render the garbage filler — the green-square flicker and the flashing letterbox edges.Fix
Replace any per-cell pick whose smoothed count is
0(never present in that cell) with bg0. Screen/color RAM then only ever carries colors genuinely present in the cell — nothing outside the source's set can leak — and the absent slots become a deterministic bg0, so present colors stop churning slots frame-to-frame. bg0 in a filler slot is a harmless duplicate (the%00code already reaches it; the per-pixel argmin breaks ties to the real bg0 at slot 0).No change to rendered output on fast transports — the filler slots were never selected there.
Verification
Offline, driving the real compose path with an
mhires+percell+force_palette [0,4,6,14]config over 200 frames:[1, 5, 15](5 = green)Tests
PercellFillerSafetyTest: screen/color RAM never carries a color outside the cell's present set; an all-bg0 cell collapses to solid bg0.excludes_bg0_from_per_cell_pickstest to the correct invariant: bg0 fills only unused slots and never displaces a genuinely-present color.Also in this PR
test(menu): wrap an expected REU-staged "panel not drawn" WARNING inassertLogs— it was printing on every full-suite run and burying real failures.