Skip to content

firmware: zero-copy StoredMap/CountingPattern envelopes + RAM tooling (lifted from #45) - #223

Merged
fughilli merged 1 commit into
mainfrom
claude/fug-71-zerocopy-and-ram-tooling
Oct 4, 2026
Merged

fughilli merged 1 commit into
mainfrom
claude/fug-71-zerocopy-and-ram-tooling

Conversation

@fughilli

Copy link
Copy Markdown
Owner

Lifts the still-useful, not-yet-on-main pieces of #45 (FUG-71 device heap pressure) into a clean PR off current main, reconciled with the code as it has since moved. #45 is being closed as partially superseded.

Lifted from #45

Zero-copy protocol envelopes

The by-value micropb decode + reply build in lm_player_handle puts these structs on the heap-allocated loop/httpd task stacks, so shrinking them is heap FUG-71 wants back.

  • handle_get_stored_map — encodes the StoredMapChunk reply zero-copy straight into the output buffer instead of materializing a by-value pb::StoredMapChunk (a 1 KiB inline Vec); the request is hand-decoded too. The firmware profile stubs StoredMapChunk.data to 8 B (gen_main.rs); host keeps the full 1 KiB for the generated encoder / conformance.
  • handle_set_counting_pattern — walks the repeated ColorBlock array (32 × f64-rgb — the fat arm of ClientMessage) zero-copy into pre-reduced 8-bit blocks and installs them via a new shared Player::set_counting_blocks path. The firmware profile stubs SetCountingPattern.blocks to 1; host keeps 32. Reconciliation: main grew a color_order field on this message since FUG-71: solve device heap pressure (release BLE + zero-copy envelopes + RAM tooling) #45, so the walker also parses/forwards it and the by-value host path was refactored (not just replaced) to keep behavior identical.
  • envelope_size_test — a size-regression guard on the shrunk structs.

RAM tooling

  • tools/ram_chart.bzl — a .ram_chart bazel run sibling for firmware_binary that charts static SRAM per symbol/component (backed by fw_memaudit.py, wired onto esp32c6_netstack).
  • fw_memaudit.py --compare — diff a --json snapshot to track a reclaim per-symbol (the fw_memaudit core, --json, and addr2line were already on main; only --compare is new here).
  • docs/design/ram-budget.md — the RAM-budget + tooling writeup.

Deliberately NOT lifted (superseded)

The BLE-release / Bluedroid-lifecycle half of #45 (improv_ble.cpp/.h, main.cpp) is obsolete: main has since replaced Bluedroid entirely with the heapless BLE host (//firmware/blehost:ble_ffi), which removes that permanent BLE heap draw structurally rather than tearing a stack down at runtime.

Reconciliation notes vs #45's original assumptions

  • On current main the ServerMessage oneof is dominated by the newer HardwareConfigState arm (~4 KiB board-capabilities catalog — 31 GPIO pins × 64-B note strings + LED modes, full-size in both profiles), not by StoredMapChunk. So the size guard asserts on the StoredMapChunk / SetCountingPattern structs directly rather than on the whole envelope (which a StoredMapChunk regression could no longer move).
  • The loop/httpd_ssl task-stack-size retune that FUG-71: solve device heap pressure (release BLE + zero-copy envelopes + RAM tooling) #45 did in main.cpp is not included here — main.cpp is on the abandoned (heapless-host) path; the stack retune is a clean follow-up once these frames are small.

Verification

  • envelope_size_test, ffi_test, counting_preempt_test, and firmware/player session_test / arena_upload_test / phone_client_frames_test all pass.
  • fw_memaudit.py --compare exercised on synthetic snapshots; black / buildifier clean.
  • bazel query resolves the new ram_chart target and its ELF filegroup.
  • Heavy firmware-profile link (esp32c6_netstack) is left to CI, per repo convention. The zero-copy code hand-walks bytes so it is profile-agnostic; the host ffi_test compiles the same ffi.rs.

🤖 Generated with Claude Code

… (lifted from #45)

Lifts the still-useful, not-yet-on-main pieces of PR #45 (FUG-71 device heap
pressure) onto current main, reconciled with the code as it has since moved.

Zero-copy protocol envelopes (shrinks the by-value micropb frames that ride the
heap-allocated loop/httpd task stacks in lm_player_handle):

- handle_get_stored_map: encode the StoredMapChunk reply ZERO-COPY straight into
  the output buffer instead of materializing a by-value pb::StoredMapChunk (a
  1 KiB inline Vec). Request is hand-decoded too. Firmware profile stubs
  StoredMapChunk.data to 8 B (gen_main.rs); host keeps 1 KiB for the encoder.
- handle_set_counting_pattern: walk the repeated ColorBlock array (32 x f64-rgb,
  the fat arm of ClientMessage) ZERO-COPY into pre-reduced 8-bit blocks and
  install via the shared Player::set_counting_blocks path; also parse the
  optional color_order string so behavior matches the by-value path exactly.
  Firmware profile stubs SetCountingPattern.blocks to 1; host keeps 32.
- envelope_size_test guards the shrunk structs directly.

RAM tooling:

- tools/ram_chart.bzl: a `.ram_chart` bazel-run sibling for firmware_binary that
  charts static SRAM per symbol/component (backed by fw_memaudit.py; wired onto
  esp32c6_netstack).
- fw_memaudit.py --compare: diff a --json snapshot to track a reclaim per-symbol.
- docs/design/ram-budget.md: the RAM budget + tooling writeup.

The BLE-release / Bluedroid-lifecycle half of #45 is deliberately NOT lifted:
main has since replaced Bluedroid with the heapless BLE host
(//firmware/blehost:ble_ffi), so those changes are obsolete.

Reconciliation notes vs #45's original assumptions:
- The ServerMessage oneof is now dominated by the newer HardwareConfigState arm
  (~4 KiB board-capabilities catalog, full-size in both profiles), NOT by
  StoredMapChunk. So the size guard asserts on the StoredMapChunk /
  SetCountingPattern structs directly rather than on the whole envelope.
- set_counting_pattern gained a color_order field on main; the zero-copy walker
  parses and forwards it so counting_preempt_test still passes.

Verified: envelope_size_test, ffi_test, counting_preempt_test, and the
firmware/player session/arena/phone-frame tests all pass; the ram_chart target
and player_app build graph query clean. The firmware-profile link is left to CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-04 18:27 UTC

@fughilli
fughilli merged commit 87a0300 into main Oct 4, 2026
11 checks passed

This branch was successfully deployed

1 active deployment
HITL — bdbe80d5 Deployed Sep 28, 2026 by fughilli via hitl_tests #650
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