From 63af6838fc4c786d79d3cfac70a6a0512d6d7de0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 21:49:56 -0400 Subject: [PATCH 1/4] fix(flights): compose the map at the width the ticker asked for Noticed on a live rig: the flight map came back at two different sizes. Vegas asks a plugin for a narrower render so a layout built for the full panel does not read as sparse in the ticker. It normally delivers that by narrowing the shared canvas for the duration of the call, which a plugin sizing itself from matrix.width picks up with no changes of its own. But it cannot narrow the canvas in offscreen mode -- _render_at swaps the shared canvas, which is unsafe there -- so it only sets the hint, and notes that a plugin reading get_vegas_render_width() still gets the narrow size while one that reads only matrix.width "renders full width and is trimmed instead". This plugin relied on the canvas being narrowed; its own docstring said the projection scales "without any extra plumbing". Measured over two hours on the rig, that assumption held 3 times out of 12: Native: requesting 256px instead of 512px Native: SUCCESS - 1 images, 512px total width <- cropped afterwards Native: SUCCESS - 1 images, 256px total width so which width came back depended on a path the plugin cannot see, and the full-width renders composed a map twice the needed width to have most of it thrown away. The width property now reads the hint. That covers both paths and falls back to the panel width outside a Vegas request, so the rotation is unaffected. The composite cache is already keyed by (width, height) and its comment already says "Rotation and Vegas render the same map at different widths", so this adds a cache entry rather than invalidating one. No plugin in the repo read get_vegas_render_width() before this, despite core providing and documenting it. Nine other plugins currently render full-width frames that the adapter then trims by 56-83%; they are candidates for the same treatment but each needs its own layout thought, so this fixes the one with a measured problem. test_vegas_render_width.py asserts the width follows the request, returns to the panel width afterwards, and falls back on a nonsense hint, plus that the cache still keys on size. Mutation-checked: reverting to matrix.width fails it. The harness sets both display_manager and _display_manager_ref deliberately: this plugin keeps the private name while BasePlugin stores the same object under the public one, and get_vegas_render_width()'s fallback reads the public one. A stub with only the private name makes the fallback miss the matrix and return its hard-coded 128, which looks exactly like the property being broken. --- plugins.json | 4 +- plugins/ledmatrix-flights/manager.py | 23 ++++ plugins/ledmatrix-flights/manifest.json | 8 +- .../test_vegas_render_width.py | 117 ++++++++++++++++++ 4 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 plugins/ledmatrix-flights/test_vegas_render_width.py diff --git a/plugins.json b/plugins.json index fe71fed6..cdf6b706 100644 --- a/plugins.json +++ b/plugins.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "last_updated": "2026-08-20", + "last_updated": "2026-08-19", "plugins": [ { "id": "cricket-scoreboard", @@ -439,7 +439,7 @@ "last_updated": "2026-08-03", "verified": true, "screenshot": "", - "latest_version": "1.12.13" + "latest_version": "1.12.14" }, { "id": "march-madness", diff --git a/plugins/ledmatrix-flights/manager.py b/plugins/ledmatrix-flights/manager.py index 06385747..094c07c3 100644 --- a/plugins/ledmatrix-flights/manager.py +++ b/plugins/ledmatrix-flights/manager.py @@ -591,6 +591,29 @@ def _resolve_metar_airports(self, codes) -> list: @property def display_width(self) -> int: + """Width to render at: the panel, or the slice Vegas asked for. + + Vegas requests a narrower render so a layout built for the full panel + does not read as sparse in the ticker. It normally delivers that by + narrowing the shared canvas for the duration of the call, which this + property picks up for free through matrix.width -- but it cannot narrow + the canvas in offscreen mode, where it only sets the hint. Reading the + hint covers both paths. + + Without this the map was composed at the full panel width and then + cropped by the adapter: on one rig, of twelve narrow requests only + three were honoured and nine rendered full width. The composite cache + below is already keyed by size and expects both widths, so honouring + the request adds an entry rather than thrashing. + """ + hint = getattr(self, "get_vegas_render_width", None) + if callable(hint): + try: + requested = int(hint()) + if requested > 0: + return requested + except (TypeError, ValueError): + pass return self._display_manager_ref.matrix.width @property diff --git a/plugins/ledmatrix-flights/manifest.json b/plugins/ledmatrix-flights/manifest.json index a293588b..01e4c977 100644 --- a/plugins/ledmatrix-flights/manifest.json +++ b/plugins/ledmatrix-flights/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-flights", "name": "Flight Tracker", - "version": "1.12.13", + "version": "1.12.14", "description": "Real-time aircraft tracking with ADS-B/FlightRadar24/OpenSky/adsb.fi/adsb.lol data, map backgrounds, area mode, flight tracking, anchor airport, flight records, and optional airport weather (METAR/TAF/PIREP/SIGMET via the free NOAA Aviation Weather Center API)", "author": "ChuckBuilds", "entry_point": "manager.py", @@ -37,6 +37,12 @@ "min_ledmatrix_version": "2.0.0", "max_ledmatrix_version": "3.0.0", "versions": [ + { + "version": "1.12.14", + "released": "2026-08-19", + "ledmatrix_min_version": "2.0.0", + "notes": "Compose the map at the width the ticker asked for. Vegas requests a narrower render so a layout built for the full panel does not read as sparse in the scroll, and normally delivers it by narrowing the shared canvas -- which this plugin picked up for free through matrix.width. It cannot narrow the canvas in offscreen mode, though, where it only sets the hint, so the same map came back at two different widths depending on which path the adapter took: on one rig only three of twelve narrow requests were honoured and the other nine rendered the full panel and were cropped afterwards. The width property now reads the hint, which covers both paths and falls back to the panel width outside a Vegas request. The composite cache is already keyed by size and expects both widths, so this adds an entry rather than invalidating one." + }, { "version": "1.12.13", "released": "2026-08-19", diff --git a/plugins/ledmatrix-flights/test_vegas_render_width.py b/plugins/ledmatrix-flights/test_vegas_render_width.py new file mode 100644 index 00000000..78f22c93 --- /dev/null +++ b/plugins/ledmatrix-flights/test_vegas_render_width.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""The map must be composed at the width the ticker asked for. + +Vegas asks a plugin for a narrower render so a layout built for the full panel +does not read as sparse in the ticker. It normally delivers that by narrowing +the shared canvas for the duration of the call, which a plugin sizing itself +from ``matrix.width`` picks up for free. But it cannot narrow the canvas in +offscreen mode -- ``_render_at`` swaps the shared canvas, which is unsafe +there -- so it only sets ``_vegas_render_width`` and notes that a plugin +reading ``get_vegas_render_width()`` still gets the narrow size while one that +only reads ``matrix.width`` "renders full width and is trimmed instead". + +This plugin relied on the canvas being narrowed. Its own docstring said the +projection scales "without any extra plumbing". On a live rig that assumption +held three times out of twelve: + + [ledmatrix-flights] Native: requesting 256px instead of 512px + [ledmatrix-flights] Native: SUCCESS - 1 images, 512px total width + [ledmatrix-flights] Native: SUCCESS - 1 images, 256px total width + +so the same map came back at two different widths depending on which path the +adapter took, and the full-width renders were cropped afterwards. + +No plugin in the repo read get_vegas_render_width() before this. + +Run: /bin/python plugins/ledmatrix-flights/test_vegas_render_width.py +""" + +import sys +from pathlib import Path + +plugin_dir = Path(__file__).resolve().parent +sys.path.insert(0, str(plugin_dir)) +for candidate in (Path("/home/rackpi/projects/LEDMatrix"), + plugin_dir.parents[2] / "LEDMatrix"): + if (candidate / "src" / "plugin_system" / "base_plugin.py").exists(): + sys.path.insert(0, str(candidate)) + break + +failures = [] + + +def check(label, ok): + print((" PASS " if ok else " FAIL ") + label) + if not ok: + failures.append(label) + + +class _Matrix: + width, height = 512, 64 + + +class _DisplayManager: + matrix = _Matrix() + + +def _plugin(): + """A FlightTracker carrying what the width property reads. + + Both names are set on purpose. This plugin keeps its own + ``_display_manager_ref``, while BasePlugin.__init__ stores the same object + as ``display_manager`` -- and get_vegas_render_width()'s fallback reads the + latter. A stub with only the private name makes the fallback miss the + matrix and return its hard-coded 128, which looks exactly like the property + being broken. + """ + from manager import FlightTrackerPlugin as _P + obj = object.__new__(_P) + dm = _DisplayManager() + obj._display_manager_ref = dm + obj.display_manager = dm + return obj + + +def main(): + print("the render width follows the ticker's request") + from src.plugin_system.base_plugin import BasePlugin + + p = _plugin() + check("the plugin inherits get_vegas_render_width from BasePlugin", + isinstance(p, BasePlugin) or hasattr(p, "get_vegas_render_width")) + + check("outside a Vegas request it is the panel width (%d)" % p.display_width, + p.display_width == 512) + + # Exactly what PluginAdapter does before calling get_vegas_content(), and + # the only thing it can do in offscreen mode. + p._vegas_render_width = 256 + check("during a narrow request it is the requested width (%d)" % p.display_width, + p.display_width == 256) + + # And exactly what the adapter's finally clause does afterwards. + p._vegas_render_width = None + check("afterwards it is the panel width again (%d)" % p.display_width, + p.display_width == 512) + + print("\nnonsense hints fall back rather than propagating") + for bad in (0, -1, "wide", None): + p._vegas_render_width = bad + ok = p.display_width == 512 + check(f"a hint of {bad!r} falls back to the panel width", ok) + + print("\nthe composite cache keys on the size, so both widths coexist") + source = (plugin_dir / "manager.py").read_text(encoding="utf-8") + check("cached_map_bgs is keyed by (width, height)", + "current_size = (self.display_width, self.display_height)" in source) + check("and it is only cleared when the view itself moves", + "if self.last_map_center != current_center or self.last_map_zoom != zoom:" + in source) + + print("\n%s" % ("FAILED: %d" % len(failures) if failures + else "All checks passed")) + return 1 if failures else 0 + + +if __name__ == "__main__": + sys.exit(main()) From 5f7dc7d831afe206956b0d96d9d083e908cf31cf Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 22:19:07 -0400 Subject: [PATCH 2/4] fix(flights): stop writing debug images on every map composite _get_map_background() saved two PNGs every time it composed a map, with nothing gating it -- no debug flag, no config check -- under a comment reading "Debug: Save composite image to see what's happening". Left-over debugging shipped as production behaviour. Measured on a live rig: debug_composite.png 5.36 MB debug_cropped.png 0.04 MB composites in 6h 5 That is roughly 108 MB a day written to an SD card for files nothing reads, on a device whose cards have already failed twice with unreadable-block-device symptoms. It also gets worse, not better, with the change that makes this plugin honour the ticker's narrower render request: composing at two widths instead of one roughly doubles the count. They also landed in the process's working directory, which is the install root, where a pre-commit hook has previously swept them into a commit. Now written only when debug logging is enabled, and into the plugin's own tile cache directory rather than wherever the process happens to be running. For comparison, the health-record churn fixed in the core repo was ~6,300 small writes a day; this is 108 MB of large ones from a single plugin. test_no_debug_image_writes.py walks the module's AST and asserts every save() sits under an isEnabledFor(DEBUG) guard and that no bare working-directory path remains. Mutation-checked: ungating the writes fails two checks, and restoring the bare CWD path fails another. All 10 flights suites pass. --- plugins.json | 4 +- plugins/ledmatrix-flights/manager.py | 34 +++++--- plugins/ledmatrix-flights/manifest.json | 8 +- .../test_no_debug_image_writes.py | 83 +++++++++++++++++++ 4 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 plugins/ledmatrix-flights/test_no_debug_image_writes.py diff --git a/plugins.json b/plugins.json index fe71fed6..cdf6b706 100644 --- a/plugins.json +++ b/plugins.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "last_updated": "2026-08-20", + "last_updated": "2026-08-19", "plugins": [ { "id": "cricket-scoreboard", @@ -439,7 +439,7 @@ "last_updated": "2026-08-03", "verified": true, "screenshot": "", - "latest_version": "1.12.13" + "latest_version": "1.12.14" }, { "id": "march-madness", diff --git a/plugins/ledmatrix-flights/manager.py b/plugins/ledmatrix-flights/manager.py index 06385747..6a2eb31a 100644 --- a/plugins/ledmatrix-flights/manager.py +++ b/plugins/ledmatrix-flights/manager.py @@ -2406,17 +2406,29 @@ def _get_map_background(self, center_lat: float, center_lon: float, self.logger.debug(f"[Flight Tracker] Map displays {desired_miles_wide:.1f} miles wide x {desired_miles_high:.1f} miles high (no stretching)") self.logger.debug(f"[Flight Tracker] Native tile scale: {pixels_per_mile_at_zoom:.3f} pixels/mile, cropped {crop_width_needed}x{crop_height_needed} pixels, scaled to {self.display_width}x{self.display_height}") - # Debug: Save composite image to see what's happening - try: - debug_composite = Path("debug_composite.png") - composite.save(debug_composite) - self.logger.debug(f"[Flight Tracker] Saved composite to: {debug_composite}") - - debug_cropped = Path("debug_cropped.png") - cropped.save(debug_cropped) - self.logger.debug(f"[Flight Tracker] Saved cropped to: {debug_cropped}") - except Exception as e: - self.logger.debug(f"[Flight Tracker] Could not save debug images: {e}") + # Debug aid, off unless debug logging is on. This used to run + # unconditionally, writing both PNGs on every composite: measured on a + # live rig at 5.36 MB for the composite and 0.04 MB for the crop, five + # composites in six hours -- about 108 MB a day written to an SD card + # for files nothing reads. They also landed in the process's working + # directory, which is the install root, where a pre-commit hook has + # previously swept them into a commit. + if self.logger.isEnabledFor(logging.DEBUG): + debug_dir = getattr(self, "tile_cache_dir", None) + if debug_dir is None: + self.logger.debug( + "[Flight Tracker] No cache directory; skipping debug images") + else: + try: + composite_path = Path(debug_dir) / "debug_composite.png" + composite.save(composite_path) + cropped_path = Path(debug_dir) / "debug_cropped.png" + cropped.save(cropped_path) + self.logger.debug( + "[Flight Tracker] Saved debug images to: %s", debug_dir) + except OSError as e: + self.logger.debug( + f"[Flight Tracker] Could not save debug images: {e}") return cropped diff --git a/plugins/ledmatrix-flights/manifest.json b/plugins/ledmatrix-flights/manifest.json index a293588b..fbe8ad66 100644 --- a/plugins/ledmatrix-flights/manifest.json +++ b/plugins/ledmatrix-flights/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-flights", "name": "Flight Tracker", - "version": "1.12.13", + "version": "1.12.14", "description": "Real-time aircraft tracking with ADS-B/FlightRadar24/OpenSky/adsb.fi/adsb.lol data, map backgrounds, area mode, flight tracking, anchor airport, flight records, and optional airport weather (METAR/TAF/PIREP/SIGMET via the free NOAA Aviation Weather Center API)", "author": "ChuckBuilds", "entry_point": "manager.py", @@ -37,6 +37,12 @@ "min_ledmatrix_version": "2.0.0", "max_ledmatrix_version": "3.0.0", "versions": [ + { + "version": "1.12.14", + "released": "2026-08-19", + "ledmatrix_min_version": "2.0.0", + "notes": "Stop writing debug images on every map composite. Two PNGs were saved unconditionally -- no debug flag, no config check, under a comment reading \"Debug: Save composite image to see what's happening\". Measured on a live rig they are 5.36 MB and 0.04 MB, written five times in six hours: about 108 MB a day onto an SD card for files nothing reads. They also landed in the process working directory, which is the install root. They are now written only when debug logging is enabled, and into the plugin's own tile cache directory." + }, { "version": "1.12.13", "released": "2026-08-19", diff --git a/plugins/ledmatrix-flights/test_no_debug_image_writes.py b/plugins/ledmatrix-flights/test_no_debug_image_writes.py new file mode 100644 index 00000000..dff0249d --- /dev/null +++ b/plugins/ledmatrix-flights/test_no_debug_image_writes.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Composing the map must not write debug PNGs unless debug logging is on. + +_get_map_background() saved two images on every composite, ungated -- no debug +flag, no config check, and the comment above it read "Debug: Save composite +image to see what's happening". Left-over debugging shipped as production +behaviour. + +Measured on a live rig: debug_composite.png is 5.36 MB and debug_cropped.png +0.04 MB, with five composites in six hours. That is roughly 108 MB a day +written to an SD card for files nothing reads, on a device whose cards have +already failed twice. Honouring the ticker's narrower render request makes the +plugin compose at two widths rather than one, so the count roughly doubles. + +They also landed in the process's working directory -- the install root -- +where a pre-commit hook has previously swept them into a commit. + +Run: /bin/python plugins/ledmatrix-flights/test_no_debug_image_writes.py +""" + +import ast +import sys +from pathlib import Path + +PLUGIN = Path(__file__).resolve().parent +MANAGER = PLUGIN / "manager.py" +failures = [] + + +def check(label, ok): + print((" PASS " if ok else " FAIL ") + label) + if not ok: + failures.append(label) + + +def _save_calls(tree): + """Every `.save(...)` in the module, with its line number.""" + return [n for n in ast.walk(tree) + if isinstance(n, ast.Call) + and getattr(n.func, "attr", None) == "save"] + + +def _guarding_debug_check(tree, lineno): + """True when the statement at `lineno` sits under an isEnabledFor guard.""" + for node in ast.walk(tree): + if not isinstance(node, ast.If): + continue + test = ast.dump(node.test) + if "isEnabledFor" not in test: + continue + for child in ast.walk(node): + if getattr(child, "lineno", None) == lineno: + return True + return False + + +def main(): + tree = ast.parse(MANAGER.read_text(encoding="utf-8")) + + print("debug images are written only when debug logging is enabled") + saves = _save_calls(tree) + check(f"{len(saves)} save() call(s) found in manager.py", bool(saves)) + for call in saves: + guarded = _guarding_debug_check(tree, call.lineno) + check(f"the save at line {call.lineno} is behind a debug-level guard", + guarded) + + print("\nand not into the process working directory") + source = MANAGER.read_text(encoding="utf-8") + check('no bare Path("debug_composite.png")', + 'Path("debug_composite.png")' not in source) + check('no bare Path("debug_cropped.png")', + 'Path("debug_cropped.png")' not in source) + check("debug images go under the tile cache directory", + "tile_cache_dir" in source.split("debug_composite.png")[0][-600:]) + + print("\n%s" % ("FAILED: %d" % len(failures) if failures + else "All checks passed")) + return 1 if failures else 0 + + +if __name__ == "__main__": + sys.exit(main()) From 2bfdf679d30a6d57494906e05583939dd62ba88e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 06:30:46 -0400 Subject: [PATCH 3/4] perf(flights): stop the tracker writing 690 log lines every half hour The flight tracker logged two unconditional INFO lines on every poll -- one naming the aircraft count it was about to process, one summarising the result. Polls run about every five seconds, so on a live rig that was 686 lines per half hour, roughly 86% of the device's entire log volume, and a steady trickle of journal writes to the SD card for a line that mostly repeated itself. The "Processing N aircraft" line is trace: the summary immediately below reports the same total. Demoted to debug, with lazy %-args so a disabled level costs nothing to skip. The summary is worth keeping, so it is now reported when it changes. Which fields to key on mattered more than expected. Total and With-position jitter on almost every poll as distant traffic drifts through the receiver's edge, and keying on the whole line collapsed 343 samples to 210 -- barely worth doing. Keying on what the plugin actually displays, aircraft in range and aircraft tracked, collapses the same samples to 67. The jittery counts still ride along in the message, where they cost nothing. A 300-second heartbeat keeps a quiet sky from looking like a stalled tracker. Measured by replaying 343 real polls captured from a running rig through the committed logic: 686 lines become 67, a 90% reduction, with the heartbeat never needing to fire. Safety harness passes at all eight sizes. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW --- plugins.json | 2 +- plugins/ledmatrix-flights/manager.py | 31 +++++++++++++++++++++++-- plugins/ledmatrix-flights/manifest.json | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/plugins.json b/plugins.json index fe71fed6..3ff2a51e 100644 --- a/plugins.json +++ b/plugins.json @@ -439,7 +439,7 @@ "last_updated": "2026-08-03", "verified": true, "screenshot": "", - "latest_version": "1.12.13" + "latest_version": "1.12.16" }, { "id": "march-madness", diff --git a/plugins/ledmatrix-flights/manager.py b/plugins/ledmatrix-flights/manager.py index 06385747..30a86a99 100644 --- a/plugins/ledmatrix-flights/manager.py +++ b/plugins/ledmatrix-flights/manager.py @@ -1703,7 +1703,11 @@ def _process_aircraft_data(self, data: Dict) -> None: return total_aircraft = len(data['aircraft']) - self.logger.info(f"[Flight Tracker] Processing {total_aircraft} aircraft from SkyAware") + # Trace, not news: the Summary line below reports the same total, and + # this pair ran every few seconds. Lazy %-args so a disabled level + # costs nothing. + self.logger.debug("[Flight Tracker] Processing %d aircraft from SkyAware", + total_aircraft) current_time = time.time() active_icao = set() @@ -1820,7 +1824,30 @@ def _process_aircraft_data(self, data: Dict) -> None: for icao in stale_all: del self.all_aircraft_data[icao] - self.logger.info(f"[Flight Tracker] Summary - Total: {total_aircraft}, With position: {aircraft_with_position}, In range ({self.map_radius_miles}mi): {aircraft_in_range}, Tracking: {len(self.aircraft_data)}, Removed stale: {len(stale_icao)}") + # This ran on every poll -- roughly every five seconds, so ~690 lines + # per half hour, most of the device's log volume and a steady trickle + # of SD writes for a line that usually repeats itself. + # + # Keyed on what the plugin actually shows: aircraft in range and + # tracked. Total and With-position jitter every poll as distant + # traffic drifts in and out of the receiver, so keying on them + # collapsed almost nothing (343 lines -> 210 on measured data); + # keying on these two gives 343 -> 67. The jittery counts still ride + # along in the message, where they cost nothing. + summary = (aircraft_in_range, len(self.aircraft_data)) + last_logged = getattr(self, '_last_summary_log', 0.0) + message = ("[Flight Tracker] Summary - Total: %d, With position: %d, " + "In range (%smi): %d, Tracking: %d, Removed stale: %d") + args = (total_aircraft, aircraft_with_position, self.map_radius_miles, + aircraft_in_range, len(self.aircraft_data), len(stale_icao)) + # The heartbeat keeps a quiet sky from looking like a stalled tracker. + if summary != getattr(self, '_last_summary', None) or \ + current_time - last_logged >= 300: + self.logger.info(message, *args) + self._last_summary_log = current_time + else: + self.logger.debug(message, *args) + self._last_summary = summary self._update_flight_records() def _altitude_to_color(self, altitude: float) -> Tuple[int, int, int]: diff --git a/plugins/ledmatrix-flights/manifest.json b/plugins/ledmatrix-flights/manifest.json index a293588b..c23b2f80 100644 --- a/plugins/ledmatrix-flights/manifest.json +++ b/plugins/ledmatrix-flights/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-flights", "name": "Flight Tracker", - "version": "1.12.13", + "version": "1.12.16", "description": "Real-time aircraft tracking with ADS-B/FlightRadar24/OpenSky/adsb.fi/adsb.lol data, map backgrounds, area mode, flight tracking, anchor airport, flight records, and optional airport weather (METAR/TAF/PIREP/SIGMET via the free NOAA Aviation Weather Center API)", "author": "ChuckBuilds", "entry_point": "manager.py", From ceedf7e60d31834df5479b5226d647364861dc92 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 16:18:10 -0400 Subject: [PATCH 4/4] chore(flights): drop the committed debug images debug_composite.png and debug_cropped.png are output, not assets. The map composer writes them with Path("debug_composite.png") -- a relative path, so on an install they land in the process working directory, which is the core checkout. A running rig accordingly carries two untracked files in its LEDMatrix repo, and copies of them were committed here as well. Untracked files in a checkout are harmless until upstream adds a file at the same path, at which point the pull refuses. There is no reason to leave that waiting. Removed and added to .gitignore so they cannot come back. #305 is what stops them being written at all unless debug logging is on, and puts them under the tile cache directory rather than the CWD; this only clears what was already committed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW --- .gitignore | 5 +++++ plugins.json | 2 +- plugins/ledmatrix-flights/debug_composite.png | Bin 27124 -> 0 bytes plugins/ledmatrix-flights/debug_cropped.png | Bin 156 -> 0 bytes plugins/ledmatrix-flights/manifest.json | 2 +- 5 files changed, 7 insertions(+), 2 deletions(-) delete mode 100644 plugins/ledmatrix-flights/debug_composite.png delete mode 100644 plugins/ledmatrix-flights/debug_cropped.png diff --git a/.gitignore b/.gitignore index 973c7a2a..64aa9684 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,8 @@ plugins/*/assets/**/*_backup/ # ledmatrix-music/emulator_config.json is committed on purpose and stays # tracked -- .gitignore does not apply to files already in the index. emulator_config.json + +# Debug output the flights map composer writes when debug logging is on. +# It lands in the process CWD, which on an install is the core checkout. +debug_composite.png +debug_cropped.png diff --git a/plugins.json b/plugins.json index fe71fed6..0873cd64 100644 --- a/plugins.json +++ b/plugins.json @@ -439,7 +439,7 @@ "last_updated": "2026-08-03", "verified": true, "screenshot": "", - "latest_version": "1.12.13" + "latest_version": "1.12.17" }, { "id": "march-madness", diff --git a/plugins/ledmatrix-flights/debug_composite.png b/plugins/ledmatrix-flights/debug_composite.png deleted file mode 100644 index c899e0e35cb6d105866e189760832840e62dc87a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27124 zcmeI4y)Q#i7{*UONVS@XFp%8jCRPKwinLOpBCUvB2Z?ku63bPSt%FheAE-$POGg_6 z0}F$g8iS#OL7n&QIs6auJHzeC?MKolzvsE{bJO&Ay}X=FPo|AA+0sgJ!??JhoV~>S{(N}EZS&2T ze3q=+TwWjTVSg~?w&9)e?|PdE-N|S^k#yU}PULpxlMn4nfur-#%|L8wPUps$y2g8 zDJmd{r~nMA0x$prQE3^#2p9n)U<7r6JuoYZ5~QN?01UtoDGBxf48Q;k7(rZM7newt jfm8&BNJ+2FSZ!bFXGAIZfFnCb; z>ce_A$=VHC5u0qw!{#hzuDi~#hB1P916u;`0jUO4hHQpwqzTwE`ZFxC`C5<%w2r~k L)z4*}Q$iB}Ls%{5 diff --git a/plugins/ledmatrix-flights/manifest.json b/plugins/ledmatrix-flights/manifest.json index a293588b..cc586c16 100644 --- a/plugins/ledmatrix-flights/manifest.json +++ b/plugins/ledmatrix-flights/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-flights", "name": "Flight Tracker", - "version": "1.12.13", + "version": "1.12.17", "description": "Real-time aircraft tracking with ADS-B/FlightRadar24/OpenSky/adsb.fi/adsb.lol data, map backgrounds, area mode, flight tracking, anchor airport, flight records, and optional airport weather (METAR/TAF/PIREP/SIGMET via the free NOAA Aviation Weather Center API)", "author": "ChuckBuilds", "entry_point": "manager.py",