feat(messages): add OnMapTrace for mower firmware (LZMA-wrapped variant)#1567
feat(messages): add OnMapTrace for mower firmware (LZMA-wrapped variant)#1567Beennnn wants to merge 12 commits into
Conversation
GetMapTrace V2 format is better handled by upstream PRs DeebotUniverse#1565 and DeebotUniverse#1567. The xmp9ds test duplicates existing coverage per maintainer feedback on DeebotUniverse#1564. https://claude.ai/code/session_01YFYjxwixRZrtjfv1aUfoVQ
| def _ecovacs_encode(payload: bytes) -> str: | ||
| raw = lzma.compress( | ||
| payload, | ||
| format=lzma.FORMAT_RAW, | ||
| filters=[{"id": lzma.FILTER_LZMA1, | ||
| "preset": lzma.PRESET_DEFAULT, | ||
| "dict_size": _DICT_SIZE}], | ||
| ) | ||
| header = bytes([0x5D]) + _DICT_SIZE.to_bytes(4, "little") + len(payload).to_bytes(4, "little") | ||
| return base64.b64encode(header + raw).decode("ascii") |
There was a problem hiding this comment.
We should not encode the data at runtime. Just use the encoded as static string for the test input
There was a problem hiding this comment.
Addressed in d88f690 (May 5) — the test inputs are now static pre-encoded base64 constants (_SINGLE_GROUP, _MULTI_GROUP, _EMPTY_GROUPS, _SERIAL_TEST, _NON_JSON at the top of the test file). No runtime encoding happens.
Address review feedback from edenhaus on DeebotUniverse#1567: - Replace runtime LZMA encoding in tests with pre-computed static base64 strings. Test inputs are now constants, not computed at test time. - Remove mid/batid from debug log message to satisfy CodeQL "clear-text logging of sensitive information" alert. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Pushed Addresses @edenhaus' review on home-assistant/core#169590 ("the creation of the map should go into the library"): moves the rendering and FIFO-cap accumulation out of the HA integration and into this lib. 9 unit tests added ( |
Mower devices (Ecovacs GOAT family) do not expose a ``map=`` capability in their hardware definitions, so the existing ``EcovacsMap`` image entity is not created for them — yet recent mower firmwares actively push trajectory points via ``MapTraceEvent``. This adds a sibling ``EcovacsMowerTraceMap`` image entity that is created for any device whose ``capabilities.device_type is MOWER``. It subscribes directly to ``MapTraceEvent`` on the device's event bus, parses the ``"x,y;x,y;..."`` payload into a list of points, and renders a simple SVG polyline of the cumulative trajectory. Implementation notes: - The trace is accumulated in memory (cap at 5 000 points to bound memory; older points are dropped FIFO). - Y axis is flipped because the mower coordinate frame is bottom-up while SVG is top-down. - ``viewBox`` is computed per push from the bounding box, with a 5% padding, so the entire trajectory remains visible regardless of garden size. - ``stroke-width`` scales with the bounding-box width so the line remains visible on both small and large lawns. Requires ``deebot-client>=18.3.0`` (which ships ``OnMapTrace`` — DeebotUniverse/client.py#1567). Tests pending — opening as draft for design feedback first.
5d454e6 to
6fed632
Compare
| @@ -0,0 +1,98 @@ | |||
| """Mower trajectory accumulator and SVG renderer. | |||
|
|
|||
| Mowers (e.g. Ecovacs GOAT family) do not expose the regular ``map`` | |||
There was a problem hiding this comment.
We should use the map capability. MapTraceEvent is also used by the vacuum bots. I also know that mower have a full map, so we should implement that one instead creating a new workaround just for the traces
There was a problem hiding this comment.
Agreed — pushed 550a750 that removes mower_trace.py (and its tests) from this PR entirely. This scope-narrows #1567 to just the parser side: it stops the ~217k warning storm and emits structured events. The rendering / full-map capability for mowers is the right home for the SVG side of things — I'll open a separate PR for that once we have the static map (onMI/onArI/getAreaSet) parsing done. @monsivar generously offered sanitised MQTT captures + a Python viewer to validate the anchor-matching approach for zone registration, so that follow-up work has a real reference to build against.
|
Thanks for working on this. I have been analysing a separate GOAT MQTT capture (O1200 LiDAR Pro) containing both the static map and live mowing data. I think a few observations may be relevant to this PR. 1.
|
|
Thanks for the extremely detailed review — these observations land squarely on the limits of the current PR. Your captures of an O1200 LiDAR Pro reach further than the single A1600 firmware sample I had, and 1–3 + 5 are clearly blocking issues for a correct implementation. Here is what I plan to revise on this PR, plus what I think belongs in a follow-up: 1. Chunk assembly before LZMA decompression — will fixYou are right. The current handler treats every key = (mid, batid, serial, type_)
chunks[key][int(index)] = base64.b64decode(info)
if sum(len(c) for c in chunks[key].values()) >= info_size:
compressed = b"".join(chunks[key][i] for i in sorted(chunks[key]))
decoded = decompress_lzma(compressed)
assert len(decoded) == info_size
chunks.pop(key)with a bounded buffer (drop incomplete keys after N seconds or M kB) so a missed final chunk doesn't leak memory across mowing cycles. 2.
|
…onMapTrace Address review feedback from @monsivar on DeebotUniverse#1567 based on real O1200 LiDAR Pro MQTT captures. 1. Reassemble chunked LZMA streams before decompression. Firmware paginates one contiguous stream across `index` values; only `index=0` carries the LZMA header. Previously each chunk was treated as standalone, which only worked for single-chunk captures. Now buffered per `(mid, batid, serial, type)` and decompressed once `len(decoded) >= infoSize`. 2. Stop using `serial` as `MapTraceEvent.start`. Real captures reuse the same serial across different batid values; the previous behaviour bypassed the `Map` Rust helper's reset logic by accident. Use a stable non-zero constant (always ≠ 0 so the Map helper appends rather than clearing). 3. Add `MowerMapTraceEvent` to preserve `group_id` and per-segment boundaries from the firmware payload (zone / layer / cycle semantics). The legacy `MapTraceEvent` with a flattened compatibility string is still emitted for the Rust `Map` renderer; structure-aware consumers subscribe to the new event instead. 4. Static maps and live traces stay distinct concepts. The handler now emits one event per concept rather than collapsing groups across cycles into a single polyline. Safety: - Per-key buffer cap (512 kB) drops runaway buffers when a firmware never sends the final chunk. - Total buffer cap (2 MB) bounds memory across in-flight cycles; oldest cycle is evicted on overflow. - Out-of-order chunk arrivals reassemble in sorted index order, so the LZMA header always lands first. - A second `index=0` for an already-completed-or-started key resets just that key's buffer (firmware retry). Tests: - 13 OnMapTrace tests (single/multi group, chunk reassembly, out-of-order arrivals, per-key cap, fresh-cycle reset, independent concurrent keys, corrupt payload paths). - 3 new MowerMapTrace.add_groups tests for structured consumption. - Full suite: 711 passed (11 docker-marked tests skipped). Refs: DeebotUniverse#1567 review comment 4826137614 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fd24877 to
144807b
Compare
Address review feedback from edenhaus on DeebotUniverse#1567: - Replace runtime LZMA encoding in tests with pre-computed static base64 strings. Test inputs are now constants, not computed at test time. - Remove mid/batid from debug log message to satisfy CodeQL "clear-text logging of sensitive information" alert. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…onMapTrace Address review feedback from @monsivar on DeebotUniverse#1567 based on real O1200 LiDAR Pro MQTT captures. 1. Reassemble chunked LZMA streams before decompression. Firmware paginates one contiguous stream across `index` values; only `index=0` carries the LZMA header. Previously each chunk was treated as standalone, which only worked for single-chunk captures. Now buffered per `(mid, batid, serial, type)` and decompressed once `len(decoded) >= infoSize`. 2. Stop using `serial` as `MapTraceEvent.start`. Real captures reuse the same serial across different batid values; the previous behaviour bypassed the `Map` Rust helper's reset logic by accident. Use a stable non-zero constant (always ≠ 0 so the Map helper appends rather than clearing). 3. Add `MowerMapTraceEvent` to preserve `group_id` and per-segment boundaries from the firmware payload (zone / layer / cycle semantics). The legacy `MapTraceEvent` with a flattened compatibility string is still emitted for the Rust `Map` renderer; structure-aware consumers subscribe to the new event instead. 4. Static maps and live traces stay distinct concepts. The handler now emits one event per concept rather than collapsing groups across cycles into a single polyline. Safety: - Per-key buffer cap (512 kB) drops runaway buffers when a firmware never sends the final chunk. - Total buffer cap (2 MB) bounds memory across in-flight cycles; oldest cycle is evicted on overflow. - Out-of-order chunk arrivals reassemble in sorted index order, so the LZMA header always lands first. - A second `index=0` for an already-completed-or-started key resets just that key's buffer (firmware retry). Tests: - 13 OnMapTrace tests (single/multi group, chunk reassembly, out-of-order arrivals, per-key cap, fresh-cycle reset, independent concurrent keys, corrupt payload paths). - 3 new MowerMapTrace.add_groups tests for structured consumption. - Full suite: 711 passed (11 docker-marked tests skipped). Refs: DeebotUniverse#1567 review comment 4826137614 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
144807b to
6e9eea0
Compare
Address review feedback from edenhaus on DeebotUniverse#1567: - Replace runtime LZMA encoding in tests with pre-computed static base64 strings. Test inputs are now constants, not computed at test time. - Remove mid/batid from debug log message to satisfy CodeQL "clear-text logging of sensitive information" alert. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…onMapTrace Address review feedback from @monsivar on DeebotUniverse#1567 based on real O1200 LiDAR Pro MQTT captures. 1. Reassemble chunked LZMA streams before decompression. Firmware paginates one contiguous stream across `index` values; only `index=0` carries the LZMA header. Previously each chunk was treated as standalone, which only worked for single-chunk captures. Now buffered per `(mid, batid, serial, type)` and decompressed once `len(decoded) >= infoSize`. 2. Stop using `serial` as `MapTraceEvent.start`. Real captures reuse the same serial across different batid values; the previous behaviour bypassed the `Map` Rust helper's reset logic by accident. Use a stable non-zero constant (always ≠ 0 so the Map helper appends rather than clearing). 3. Add `MowerMapTraceEvent` to preserve `group_id` and per-segment boundaries from the firmware payload (zone / layer / cycle semantics). The legacy `MapTraceEvent` with a flattened compatibility string is still emitted for the Rust `Map` renderer; structure-aware consumers subscribe to the new event instead. 4. Static maps and live traces stay distinct concepts. The handler now emits one event per concept rather than collapsing groups across cycles into a single polyline. Safety: - Per-key buffer cap (512 kB) drops runaway buffers when a firmware never sends the final chunk. - Total buffer cap (2 MB) bounds memory across in-flight cycles; oldest cycle is evicted on overflow. - Out-of-order chunk arrivals reassemble in sorted index order, so the LZMA header always lands first. - A second `index=0` for an already-completed-or-started key resets just that key's buffer (firmware retry). Tests: - 13 OnMapTrace tests (single/multi group, chunk reassembly, out-of-order arrivals, per-key cap, fresh-cycle reset, independent concurrent keys, corrupt payload paths). - 3 new MowerMapTrace.add_groups tests for structured consumption. - Full suite: 711 passed (11 docker-marked tests skipped). Refs: DeebotUniverse#1567 review comment 4826137614 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #1567 +/- ##
==========================================
+ Coverage 95.02% 95.13% +0.11%
==========================================
Files 159 161 +2
Lines 6234 6439 +205
Branches 353 376 +23
==========================================
+ Hits 5924 6126 +202
- Misses 248 249 +1
- Partials 62 64 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Mower firmwares (observed: GOAT A1600 RTK fw 1.15.13) push spontaneous
``onMapTrace`` messages whose body schema is completely different from
the existing ``GetMapTrace`` response:
{
"header": {"fwVer": "1.15.13", ...},
"body": {"data": {
"mid": "...", "batid": "...", "serial": "1",
"index": "0", "type": "4",
"info": "<base64 of LZMA1 compressed JSON>",
"infoSize": 3455
}}
}
The compressed ``info`` field, once decompressed, is a JSON list of
trajectory groups: ``[[group_id, "0;x1,y1;x2,y2;...;", "0;x,y;..."], ...]``
with negative-and-positive integer coordinates (relative to a map origin).
This adds a dedicated ``OnMapTrace`` message handler that:
1. Detects the new format via the presence of ``info``.
2. Decompresses via the existing Rust ``decompress_base64_data`` helper
(which already handles the firmware's trimmed 9-byte LZMA header).
3. Parses the JSON, drops the leading ``"0"`` anchor of each segment,
and concatenates the remaining points across groups.
4. Notifies ``MapTraceEvent`` using the firmware ``serial`` as ``start``
so the ``Map`` Rust helper does not clear the trace on every push.
Registered alongside the other JSON map messages so it is dispatched
*before* the legacy ``getMapTrace`` fallback (which still serves vacuum
firmwares unchanged).
Tests:
- Happy paths (single group, multi-group, multi-segment).
- ``info`` missing → ANALYSE (defer to legacy handler).
- Empty groups → ANALYSE (no event emitted).
- Corrupt ``info`` (invalid base64, too short, decompresses to non-JSON) → ANALYSE (no exception escapes).
- ``serial`` propagates as ``MapTraceEvent.start``.
- Full suite: 705/705 pass, no regression.
Refs:
- DeebotUniverse#1376 (Disable getMapTrace for Goat) — this PR
is the proper alternative: instead of disabling, the message is now
parsed and surfaces as a usable trajectory.
- Companion to DeebotUniverse#1565 (skip legacy fallback for mowers) and DeebotUniverse#1566
(warn-once rate limit). DeebotUniverse#1565 still serves as a safety net for any
remaining unhandled map messages on mowers.
Address review feedback from edenhaus on DeebotUniverse#1567: - Replace runtime LZMA encoding in tests with pre-computed static base64 strings. Test inputs are now constants, not computed at test time. - Remove mid/batid from debug log message to satisfy CodeQL "clear-text logging of sensitive information" alert. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mowers don't expose the regular map capability used by vacuums; their trajectory only comes through MapTraceEvent. Move accumulation, FIFO cap and SVG rendering into the library so consumers only forward the event payload and read back an SVG.
- json → orjson per TID251 (banned import) - mower_trace: rename for-loop var to avoid PLW2901 reassignment - restore `except (TypeError, ValueError):` parens (ruff format had stripped them, breaking Python 3 syntax) - imports reorganised by ruff - ruff format applied to test_on_map_trace.py and the map __init__.py 8 unit tests for OnMapTrace still pass. Addresses CI 'Run prek checks' fail.
ruff-format 0.15.11 (pinned in .pre-commit-config.yaml) incorrectly rewrites `except (TypeError, ValueError):` to `except TypeError, ValueError:` which is invalid Python 3 syntax. Confirmed reproducible locally with `uvx ruff@0.15.11 format --diff`. Newer ruff releases are fine. Wrap the offending block with `# fmt: off` / `# fmt: on` so prek doesn't strip the tuple parens. The semantics (single int() call, two distinct exception types caught) are unchanged.
…onMapTrace Address review feedback from @monsivar on DeebotUniverse#1567 based on real O1200 LiDAR Pro MQTT captures. 1. Reassemble chunked LZMA streams before decompression. Firmware paginates one contiguous stream across `index` values; only `index=0` carries the LZMA header. Previously each chunk was treated as standalone, which only worked for single-chunk captures. Now buffered per `(mid, batid, serial, type)` and decompressed once `len(decoded) >= infoSize`. 2. Stop using `serial` as `MapTraceEvent.start`. Real captures reuse the same serial across different batid values; the previous behaviour bypassed the `Map` Rust helper's reset logic by accident. Use a stable non-zero constant (always ≠ 0 so the Map helper appends rather than clearing). 3. Add `MowerMapTraceEvent` to preserve `group_id` and per-segment boundaries from the firmware payload (zone / layer / cycle semantics). The legacy `MapTraceEvent` with a flattened compatibility string is still emitted for the Rust `Map` renderer; structure-aware consumers subscribe to the new event instead. 4. Static maps and live traces stay distinct concepts. The handler now emits one event per concept rather than collapsing groups across cycles into a single polyline. Safety: - Per-key buffer cap (512 kB) drops runaway buffers when a firmware never sends the final chunk. - Total buffer cap (2 MB) bounds memory across in-flight cycles; oldest cycle is evicted on overflow. - Out-of-order chunk arrivals reassemble in sorted index order, so the LZMA header always lands first. - A second `index=0` for an already-completed-or-started key resets just that key's buffer (firmware retry). Tests: - 13 OnMapTrace tests (single/multi group, chunk reassembly, out-of-order arrivals, per-key cap, fresh-cycle reset, independent concurrent keys, corrupt payload paths). - 3 new MowerMapTrace.add_groups tests for structured consumption. - Full suite: 711 passed (11 docker-marked tests skipped). Refs: DeebotUniverse#1567 review comment 4826137614 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CodeQL flagged the log message in OnMapTrace as 'Clear-text logging of sensitive information' because the formatted tuple included mid/batid. Same class of issue as the earlier d88f690 fix on this branch — only the cap value is needed for diagnosis, the device identifiers are not. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A new test asserts that get_message(name, static) returns the right handler per device class. With the OnMapTrace handler registered for mowers (xmp9ds), the expected value flips from None to OnMapTrace. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6e9eea0 to
0874e65
Compare
Per @edenhaus review on mower_trace.py:3 ("we should use the map capability … instead of creating a workaround just for the traces"), the SVG accumulator/renderer is out of scope for this PR. Kept in this PR: - The onMapTrace message handler that stops the 217k-warning log storm - The MowerMapTraceEvent structure that preserves group/segment semantics (needed by any future consumer, whether the map capability or a downstream renderer) - The compatibility MapTraceEvent flat projection so today's Rust Map renderer keeps working for vacuums Removed: - deebot_client/mower_trace.py (MowerMapTrace accumulator + SVG renderer) - tests/test_mower_trace.py (12 tests for the renderer) The full-map capability for mowers (onMI / onArI / getAreaSet static map + zone registration) is the right home for rendering. That work is tracked separately with @monsivar sharing sanitised MQTT captures + a Python viewer for anchor-matching validation — planned as a follow-up PR once this parser lands. Suite: 705/705 pass (was 717, minus the 12 removed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses Codecov patch coverage report (was 87.43% patch on this PR): - test_OnMapTrace_parse_groups_handles_malformed_input: exercises the non-list-group / empty-group / non-string-segment / invalid-point-token skip branches inside _parse_groups. Also invalid-JSON and non-list-root guard paths. - test_OnMapTrace_evict_to_make_room_drops_oldest_by_total_bytes: covers the while _total() + incoming > _MAX_TOTAL_BYTES loop that per-key eviction alone doesn't hit. - test_OnMapTrace_evict_to_make_room_caps_keys_tracked: covers the _MAX_KEYS_TRACKED eviction path. Suite: 708 pass (was 705, +3 targeted tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Thanks for the response. I have prepared a sanitised package containing the material I have collected and the tools I have used so far: The package includes:
The most important findings in the package are:
A small disclaimer: I am not a software developer and do not have a strong background in protocol reverse engineering. I have worked through this using the limited technical knowledge I have, a lot of testing and comparison, and assistance from AI tools. The viewers and documentation should therefore be treated as reference material and experimental findings rather than production-ready code. I am happy to collect more captures, test hypotheses or inspect specific app actions. It would help me greatly if requests could be made as clearly and specifically as possible, for example:
With a clear description of the required action and the data you need, I will do my best to provide additional sanitised captures and results. |
|
Thanks for the sanitised package — this is genuinely more than I was hoping for, and it lines up cleanly with what's already in this PR after the refactor. Cross-checking your §1 — LZMA-alone header repair. The insert-four-zeros-at-byte-8 step is already what the existing Rust helper §2 — Chunk assembly with §7 — preserve structure. The refactor now emits §3, §4, §5, §6, §8 — static main map, Two small pinpoint questions to make sure I map them correctly:
If you're OK with it, I'd like to open the follow-up PR referencing |
|
Thanks, that all sounds good to me, and I am happy for you to reference Also good to hear that the current refactor already matches the chunking and structure-preserving parts of the findings. I do not currently see anything in #1567 that obviously contradicts the captures I shared. Regarding your two questions: 1. Route identity in
|
Downstream consumers can re-check assumptions about the leading marker and the x,y split as the format is reverse-engineered further. Per monsivar's format notes on DeebotUniverse#1567.
|
Thanks, that's exactly the recipes I needed. I just pushed d521cca which adds On your two recipes — both go verbatim into the follow-up static-map PR, no reinterpretation needed on my side: Route identity — triple Zero-shared-sequence fallback — no coercion of local coordinates into the main-map frame. I'll model each area as I'll also lean on And nothing to excuse — the notes are precise and the failure modes you called out (per-batch identity, degenerate anchor, reversed traversal) are the ones that would have bitten a first-pass implementation. Much easier to design the follow-up against them than to discover them after the fact. |
…h paths Both branches (except KeyError/TypeError/ValueError on envelope fields, same on int(infoSize)) were flagged by codecov/patch on d521cca as the missing coverage delta on DeebotUniverse#1567.
Summary
Mower firmwares (observed: GOAT A1600 RTK fw
1.15.13) push spontaneousonMapTraceMQTT messages whose body schema is completely different from the existingGetMapTraceresponse — sameNAME, different shape:{ "header": {"fwVer": "1.15.13", "tzm": 120, "ts": "...", ...}, "body": {"data": { "mid": "...", "batid": "...", "serial": "1", "index": "0", "type": "4", "info": "<base64 of LZMA1-compressed JSON>", "infoSize": 3455 }} }Decompressing the
infofield yields a small JSON document of the form:— a list of trajectory groups, each holding one or more
;-separated coordinate strings.The current
GetMapTrace._handle_body_data_dictexpectstraceValueand raises aKeyError, which the legacy fallback catches and logs asWARNING — Could not parse getMapTrace. In one observed setup this produced 217 520 identical warnings in 3 days (~70k/day, ~50/min during mowing).Scope (narrowed after review)
This PR is now strictly the parser side. Per @edenhaus review,
mower_trace.py(the SVG accumulator/renderer that used to be here) has been removed — rendering belongs with a proper map capability, not a mower-only workaround.Kept in this PR:
OnMapTracehandler that recognises the mower-flavoured envelope, reassembles chunks acrossindexvalues (per @monsivar's O1200 LiDAR Pro capture analysis), decompresses LZMA, and parses the payload.MowerMapTraceEventcarrying the structured{group_id, segments[]}shape — semantic information that any downstream consumer (map capability, custom renderer, dashboard) can consume.MapTraceEventwith a flattened compat projection so the RustMaprenderer used by vacuums keeps working unaffected.Not in this PR
onMI/onArI/getAreaSet) — planned as a follow-up PR with @monsivar sharing sanitised MQTT captures + a Python viewer for anchor-matching validation.Compatibility with companion PRs
xmp9ds) — independent.Test plan
test_OnMapTrace_emits_structured_event_and_flat_compat— single-group happy pathtest_OnMapTrace_preserves_groups_and_segments— 2 groups × 2-3 segmentstest_OnMapTrace_no_info_field_returns_analyse— defer to legacy handlertest_OnMapTrace_empty_groups_returns_analyse— no event emitted when there are no pointstest_OnMapTrace_corrupt_info_returns_analyse— 3 corruption modes, no exception escapestest_OnMapTrace_uses_constant_compat_start_not_serial— start=1 constant, not the reusedserialtest_OnMapTrace_chunk_in_progress_emits_nothing_then_completes— multi-chunk reassemblytest_OnMapTrace_chunks_out_of_order_still_reassemble— sorted-index reassemblytest_OnMapTrace_new_cycle_drops_partial_previous_buffer— retry resettest_OnMapTrace_different_keys_have_independent_buffers— concurrent mowing cyclestest_OnMapTrace_per_key_byte_cap_drops_runaway_buffer— memory safetyRefs
getMapTrace()for GOAT. This PR resolves the underlying issue rather than disabling.