Skip to content

Fix periodic coverage-map freeze on large fields (#34)#63

Merged
chriskinal merged 1 commit into
developfrom
fix/coverage-snapshot-freeze
Jul 4, 2026
Merged

Fix periodic coverage-map freeze on large fields (#34)#63
chriskinal merged 1 commit into
developfrom
fix/coverage-snapshot-freeze

Conversation

@chriskinal

Copy link
Copy Markdown
Collaborator

Fixes #34 — the web UI froze for ~1s and jumped the tractor forward periodically once sections were enabled, worse the larger the field. Device-verified on the internal simulator (no-boundary + boundary paths) through multiple bounds expansions.

Root cause

Coverage streaming is normally push-based (the host records every cell it paints and streams incremental deltas — cheap). The freeze came entirely from the full-state rebuild, which fired on grid changes and dumbly scanned the whole field.

Fixes (all in the coverage streaming path)

  • Snapshot is now O(painted), not O(field). GetPaintedDisplayCells walks only the tracked painted bounding box of the display buffer (measured ~130ms → ~9ms on a 40ha field with 0.63ha worked), and it builds off the 10Hz broadcast loop so pose/tick frames never stall.
  • Client drain is time-sliced (COV_DRAIN_BUDGET/frame) so a one-time seed paints over a few frames instead of blocking one.
  • Bounds expansion resends nothing. The added ground is empty, so the host announces the new grid (reset=false) and the client re-anchors its existing coverage into the larger grid via an exact integer-offset pixel copy. A new CoverageInit.reset flag separates grow (re-anchor) from new-field/reload/cell-size-change (tear down + rebuild).
  • Consistent snapshot read. The first no-boundary expansion crosses the 0.1→0.2m resolution band (rebuild path); the lock-free read could catch the display buffer cleared-but-not-yet-resampled and drop pre-expansion coverage permanently. Now materialized under _coverageLock.
  • Edge-ribbon self-heal (pre-existing bug surfaced by the no-boundary test path): auto-init ClearEdges wiped an active section's ribbon while it stayed on, so the crisp worked-area perimeter silently never rendered. AccumulateEdge now re-opens a ribbon on a miss. Logically separable from the freeze fix — flag if you'd prefer it split out.

Testing

  • Full solution test suite green (Models 146 / ViewModels 222 / Services 1026).
  • Desktop head builds.
  • On-device: no freeze/jump through repeated expansions; coverage stays pixel-aligned across each re-anchor; crisp edge renders from the start and survives expansions.

🤖 Generated with Claude Code

The web UI froze for ~1s and jumped the tractor forward whenever the
coverage grid changed, worsening with field size. Root causes + fixes,
all in the coverage streaming path:

- Full-coverage snapshot walked the ENTIRE field bounding box at 0.1m
  (O(field area), ~130ms on a 40ha field even with 0.63ha painted) on
  the 10Hz broadcast loop, stalling pose/tick frames. Now enumerates
  only painted display cells via GetPaintedDisplayCells (O(worked area),
  ~9ms) and builds off the broadcast loop so pose never stalls.

- Client drained a full snapshot in one render frame (unbounded
  per-cell drawRect). Now time-sliced (COV_DRAIN_BUDGET/frame).

- Bounds expansion re-sent the entire accumulated coverage. The added
  ground is empty, so the host now announces the new grid (reset=false)
  and the client re-anchors its existing coverage into the larger grid
  (exact integer-offset pixel copy) — zero rescan, zero resend. A new
  CoverageInit `reset` flag distinguishes grow (re-anchor) from new
  field / reload / cell-size change (tear down + rebuild).

- The first no-boundary expansion crosses the 0.1->0.2m resolution band
  (rebuild path). GetPaintedDisplayCells read the display buffer
  lock-free and could catch it cleared-but-not-yet-resampled, yielding
  an empty snapshot that permanently dropped pre-expansion coverage.
  Now materialized under _coverageLock (alpha folded in) for a
  consistent read.

- Pre-existing edge-ribbon bug surfaced by the no-boundary test path:
  the auto-init SetFieldBounds->ClearEdges wiped an active section's
  ribbon while it stayed on, so StartMapping never re-opened it and the
  crisp worked-area perimeter silently never rendered. AccumulateEdge
  now re-opens a ribbon on a miss (self-heals).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chriskinal
chriskinal merged commit a5649a2 into develop Jul 4, 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.

When enabling sections the system starts to lack every second (running on my brand new HP laptop).

1 participant