Skip to content

fix(ledmatrix-flights): key the map background cache on display size - #243

Merged
ChuckBuilds merged 1 commit into
mainfrom
claude/coderabbit-pr-comment-e4et98
Aug 3, 2026
Merged

fix(ledmatrix-flights): key the map background cache on display size#243
ChuckBuilds merged 1 commit into
mainfrom
claude/coderabbit-pr-comment-e4et98

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

The map background cache was keyed on centre and zoom, but the image it stores has already been cropped to the display aspect ratio and resized to the panel — so the display size is part of its identity too. Vegas narrows the display manager while requesting content, so the rotation and the ticker ask for the same view at different widths, and whichever rendered second was handed the other one's image. The cache now keys on size as well.

Follow-up to #231, which is where the two paths started sharing a renderer.

Type of change

  • Bug fix in an existing plugin
  • New plugin (also fill out the SUBMISSION.md checklist below)
  • New feature for an existing plugin
  • Documentation only
  • Repo-wide change (registry script, hook, top-level docs)

Plugin(s) affected

ledmatrix-flights (1.12.6 → 1.12.7)

Related issues

Refs #231. Found while checking the CodeRabbit review comment on that PR — see "Notes for reviewer" for where that comment landed.

The bug

_get_map_background() stores its result after cropping to the display aspect ratio and resizing to the panel:

crop_height_needed = int(crop_width_needed * (self.display_height / self.display_width))
...
cropped = cropped.resize((self.display_width, self.display_height), Image.Resampling.LANCZOS)

but the memo that guards all of that only looked at centre and zoom:

current_center = (round(center_lat, 4), round(center_lon, 4))
if (self.cached_map_bg is not None and
    self.last_map_center == current_center and
    self.last_map_zoom == zoom):
    return self.cached_map_bg

Vegas narrows the display manager while it requests content, so display_width differs between the two paths — on a panel with vegas_width_pct: 50 the ticker asks at half the width. Whichever path rendered first populated the single slot; the other got that image back at the wrong size.

_render_map_image() does img = map_bg.copy(), so the mismatch propagates to the whole frame: the returned image is the wrong size, and the aircraft, trails and centre marker on it were projected by _latlon_to_pixel for the size it didn't get.

This is the gap in #231's "sizing needs no extra plumbing" note. That's true of the projection — it reads display_width on every call — but not of the cached background, which was baked at one size and reused at another.

Fix

One entry per size, dropped wholesale when the view moves:

current_size = (self.display_width, self.display_height)
if self.last_map_center != current_center or self.last_map_zoom != zoom:
    self.cached_map_bgs.clear()
else:
    cached = self.cached_map_bgs.get(current_size)
    if cached is not None:
        return cached

Keeping every size rather than a single slot re-keyed on size is deliberate: the two paths alternate every rotation cycle, so a single slot would re-crop, re-resize and re-run the brightness/contrast/saturation enhancers on each Vegas↔rotation swap. Tiles come off the disk cache in that case, so it wouldn't hit the network — but it's avoidable work in the render path.

cached_map_bgcached_map_bgs, which touches the config-reload test that asserts the cache is invalidated on change.

Test plan

  • Loaded the plugin in LEDMatrix on real hardware
  • Loaded the plugin in LEDMatrix emulator mode (EMULATOR=true python3 run.py)
  • Rendered the plugin in the dev preview server (scripts/dev_server.py)
  • Verified the web UI configuration form against the schema
  • Unit tests (see below)
  • Safety harness — green via the safety CI job on this PR

Four new cases in test_vegas_map_parity.py:

Test Asserts
test_background_matches_the_requested_size_after_a_narrower_render the background comes back at the size that was asked for
test_each_size_is_still_cached neither size re-tiles when the two alternate
test_moving_the_centre_drops_every_cached_size a centre change invalidates all sizes, not just the current one
test_rendered_map_is_the_display_size_across_a_size_switch the end-to-end symptom: the composed frame tracks the display through a size switch

They stub _fetch_tile with a gradient tile, so the compositing path actually runs without touching the network, and chdir to a tmp dir because the renderer drops debug_composite.png/debug_cropped.png in the cwd.

Verified load-bearing: reverting the memo to its old behaviour fails 3 of the 4. The existing parity tests can't catch this — make_plugin() sets map_bg_enabled = False, so the tile path never executes and the byte-identity assertion is satisfied by the memo regardless.

  • test_vegas_map_parity.py: 21 passed
  • test_config_reload.py: 15 passed
  • test_overhead_radius.py: passed
  • scripts/check_module_collisions.py: OK across 42 plugins

Required for plugin changes

  • Bumped version in plugins/<id>/manifest.json (1.12.6 → 1.12.7)
  • class_name in manifest.json matches the actual class in manager.py exactly
  • entry_point matches the real file
  • Updated the plugin's README.md if config keys changed — N/A, no config keys changed
  • config_schema.json is the source of truth for the web UI form — N/A, no new options
  • Pre-commit hook ran successfully (auto-synced plugins.json)

Checklist

  • My commits follow the message convention in CONTRIBUTING.md
  • I read CONTRIBUTING.md and CODE_OF_CONDUCT.md
  • I've not committed any secrets

Notes for reviewer

On the CodeRabbit comment from #231. It flagged _get_map_background() being reachable from the render path as a stability risk and asked for tile fetching to move into update(). That concern is real — _fetch_tile does a blocking requests.get(url, timeout=10), and a cold cache can walk 16 tiles × 4 URL fallbacks — but it wasn't introduced by #231. Both call sites on the old main already reached it from render code (_display_map and get_vegas_content); #231 reduced that to one. It's mitigated in practice by the disk tile cache, the in-memory composite memo, and the disable_on_cache_error / >50%-failure circuit breakers, so it bites on first render rather than per frame.

I've left that as-is — moving the fetch into update() is a real restructure of a 200-line method and deserves its own PR. This PR fixes the different, concrete bug I found in the same memo while checking that comment.

On the docstring-coverage pre-merge warning. Not addressed deliberately. The functions it counts are the four new test methods and a nested fake_fetch helper; this file documents intent at the class level and relies on sentence-style method names, and the three existing test classes have no method docstrings either. Adding them to clear the threshold would diverge from the file's idiom without helping a reader.

Also spotted, not fixed here: cached_pixels_per_mile (manager.py:156) is dead — set to None in two places and never read or assigned. And _get_map_background unconditionally writes debug_composite.png and debug_cropped.png to the process cwd on every cold render. Both are pre-existing and out of scope; happy to fold either in if you'd prefer.

The cached map composite is already cropped to the display aspect ratio
and resized to the panel before it is stored, but the memo was keyed on
centre and zoom alone. Vegas narrows the display manager while it
requests content, so the rotation and the ticker ask for the same view at
different widths — and whichever rendered second was handed the other
one's image.

_render_map_image() copies that background, so the symptom is a whole
frame at the wrong size, with aircraft and trails projected for the size
it didn't get. This is why #231's "sizing needs no extra plumbing" only
held for _latlon_to_pixel: the projection follows the display, the cached
background didn't.

The cache now holds one entry per size and clears them all when the
centre or zoom moves. Keeping every size rather than a single slot keyed
on size matters because the two paths alternate: a single slot would
re-crop and re-resize on every Vegas/rotation swap.

Also renames cached_map_bg -> cached_map_bgs, updating the config-reload
test that asserts the cache is invalidated on change.

Tests: three cases in test_vegas_map_parity.py covering the size after a
narrower render, per-size cache retention, and the end-to-end frame size
across a size switch. All three fail against the old memo. The existing
parity tests can't catch this — they set map_bg_enabled = False, so the
tile path never runs. Full suite: 21 passed, plus test_config_reload.py
(15) and test_overhead_radius.py green.

check_module_collisions.py: OK across 42 plugins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZQZi3TiR77t2k5QjPix3E
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Map background cache update

Layer / File(s) Summary
Cache model and invalidation
plugins/ledmatrix-flights/manager.py
Map backgrounds are cached by display width and height. Center, zoom, and live configuration changes clear all cached sizes.
Size-aware rendering and tests
plugins/ledmatrix-flights/manager.py, plugins/ledmatrix-flights/test_config_reload.py, plugins/ledmatrix-flights/test_vegas_map_parity.py
Rendering stores composites for the current display size. Tests cover size switching, cache reuse, invalidation, and rendered dimensions.
Release metadata
plugins.json, plugins/ledmatrix-flights/manifest.json, plugins/ledmatrix-flights/CHANGELOG.md
Release metadata and documentation identify version 1.12.7 and describe the cache correction.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Display
  participant _render_map_image
  participant cached_map_bgs
  participant tile_fetcher
  Display->>_render_map_image: Request frame at display size
  _render_map_image->>cached_map_bgs: Check size-specific composite
  alt Cache hit
    cached_map_bgs-->>_render_map_image: Return cached composite
  else Cache miss
    _render_map_image->>tile_fetcher: Fetch map tiles
    tile_fetcher-->>_render_map_image: Return tile images
    _render_map_image->>cached_map_bgs: Store composite by display size
  end
  _render_map_image-->>Display: Render frame at display size
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: making the map background cache key depend on display size.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/coderabbit-pr-comment-e4et98

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 8 complexity

Metric Results
Complexity 8

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 5ebd488 into main Aug 3, 2026
4 checks passed
@ChuckBuilds
ChuckBuilds deleted the claude/coderabbit-pr-comment-e4et98 branch August 3, 2026 18:02
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