Skip to content

fix(odds-ticker): publish the scroll cache array before the image - #471

Merged
ChuckBuilds merged 1 commit into
mainfrom
fix/odds-ticker-cache-publish-order
Sep 8, 2026
Merged

fix(odds-ticker): publish the scroll cache array before the image#471
ChuckBuilds merged 1 commit into
mainfrom
fix/odds-ticker-cache-publish-order

Conversation

@ChuckBuilds

Copy link
Copy Markdown
Owner

Follow-up to #469, which merged while I was reviewing it. One finding, one line.

The problem

#469 moved the strip rebuild onto a worker thread — right call — but left _create_ticker_image writing the two helper fields image-first:

self.scroll_helper.cached_image = strip        # manager.py:2422
self.scroll_helper.cached_array = strip_array  # manager.py:2423

That is the reverse of the order the same PR established as correct in display()'s re-seed at 2982-2983, with a comment explaining exactly why:

Array first, then image. get_visible_portion reads cached_image.width and cached_array separately, so a new image against an old array is short by the difference.

_get_visible_portion_integer reads the two in separate statements:

img_w = self.cached_image.width
...
frame_array = np.ascontiguousarray(self.cached_array[:, start_x:end_x])

Why it is reachable now and was not before

It was safe while _create_ticker_image ran with display() blocked on q.get() — no concurrent reader. #469 removed that block. The rebuild now runs on a worker, and the new re-seed path deliberately keeps display() scrolling the previous strip while it runs, so the render thread reads in exactly that window.

Publishing ticker_image last does not protect it: that gate is already passed when the old strip is on screen.

Reproduced against the real ScrollHelper:

image=6392 array=6312, scrolled near the end
  ValueError: could not broadcast input array from shape (64,12,3) into shape (64,92,3)

image=6392 array=6392, same position
  ok, (128, 64)

Two adjacent statements, which is why #469's 30-minute soak saw none of it and reported zero not enough image data.

The test that should have caught it

test_ticker_strip_is_published_only_once_it_is_finished asserts min(publishes) > max(caches)ticker_image after cached_array. It says nothing about the two cache fields relative to each other, so it passed throughout.

test_cached_array_is_written_before_cached_image walks both _create_ticker_image and display() in source order and requires every cached_image write to be preceded by a cached_array write. Against the pre-fix code it fails naming the line:

_create_ticker_image: cached_image is assigned at line 2422 without cached_array
being written first. A reader between the two sees a new image against an old
array and the slice is short. Write cached_array first.

Verification

odds-ticker tests 3 passed (7 assertions in test_display_defers_network), safety harness PASS at all 8 sizes, manifest gate OK. Manifest 1.4.3 → 1.4.4.

Not addressed here

_pump_background("image-rebuild", ..., min_interval=2.0) logs at INFO on every start, and a persistent no-games state leaves ticker_image None so it retries every 2s — roughly 30 INFO lines/minute for as long as it lasts. That is a large improvement on the per-frame warning it replaced (~6,000/min), so it is not a regression, but the data-update pump uses min_interval=30.0 and this one would read better at debug or with a longer backoff. Left alone rather than widening a one-line fix.

🤖 Generated with Claude Code

https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9

#469 moved the strip rebuild onto a worker thread, which is right, but
left _create_ticker_image writing the two helper fields image-first:

    self.scroll_helper.cached_image = strip
    self.scroll_helper.cached_array = strip_array

That is the reverse of the order the same PR established as correct in
display()'s re-seed, with a comment explaining why:
_get_visible_portion_integer reads cached_image.width and cached_array as
two separate statements, so a reader between the two assignments sees the
new (wider) image against the old (shorter) array.

It was safe while _create_ticker_image ran with display() blocked on a
queue. It is not now: the rebuild runs on a worker, and the re-seed keeps
display() scrolling the previous strip while it runs, so the render
thread reads in exactly that window. Reproduced against the real helper:

    image=6392 array=6312, scrolled near the end
      ValueError: could not broadcast input array from shape (64,12,3)
                  into shape (64,92,3)
    image=6392 array=6392, same position
      ok, (128, 64)

Two adjacent statements, which is why 30 minutes of soak on #469 saw
none of it.

The existing test pins ticker_image relative to cached_array and says
nothing about the two cache fields relative to each other, so it passed
throughout. test_cached_array_is_written_before_cached_image walks both
_create_ticker_image and display() in source order and requires every
cached_image write to be preceded by a cached_array write. It fails on
the pre-fix code naming the exact line.

Manifest 1.4.3 -> 1.4.4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9
@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 822e9679-a7cc-4f3f-ae99-f450b13db79b


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 10 complexity

Metric Results
Complexity 10

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@ChuckBuilds
ChuckBuilds merged commit 3b1088a into main Sep 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants