Document global contact savings and V3 Full memory qualification

This commit is contained in:
mikecarper committed 2026-10-19 02:57:46 -07:00
1 parent a3f858f6f0
commit a50bee474b
9 files changed
+1063 -3

No files matched your search

+15 -2
View File
@@ -864,8 +864,21 @@ class MotaSource {
};
```
To serve an external mota the node reads its manifest-minus-leaves + `leaves[]` into RAM (<=4 KiB for <=1024
blocks) and streams payload blocks from the source on demand; proofs are generated from the read leaves.
Legacy source paths retain the manifest-minus-leaves and four bytes per source
block, bounded by `OTA_PROOFGEN_SCRATCH`, then stream payload blocks on demand.
Non-PSRAM ESP32 Full instead retains roots of 32-leaf groups and rereads the
selected group. Its complete workspace is bounded to 1,536 bytes while retaining
4,096 blocks and byte-identical proofs. Partial groups use the same odd-node
promotion as the full tree. Receive pipelines, capture, serving and wire formats
remain unchanged; larger custom leaf limits retain the legacy path.
With `OTA_BOUNDED_SOURCE_PROOFS=1`, optional `read_quantum()` and
`read_metadata()` methods bound each metadata step. `SerialMotaSource` uses the
current negotiated single-response capacity and rejects a chunk that no longer
fits after a capacity change. Proof preparation yields between chunks, and
queued jobs retain their buffers across radio backpressure. See the
[V3 Full qualification](test-results/heltec_v3_full_memory_2026-10-19.md) for
allocation bounds and regression coverage.
### 10.2 The `mota-seeder` transport (`MotaSeederProto.h`)
+19
View File
@@ -214,9 +214,28 @@ partition table. Subsequent WiFi updates must fit the new slots. An
application-only update from the ordinary single-slot Full layout cannot
change that layout or enable WiFi OTA.
## Shared contact index storage
Companion and terminal-chat builds share compact contact indexes in
`BaseChatMesh`. Tables with at most 65,535 total slots use 16-bit sort and
matching-result entries; larger configurations retain the original `int` type.
Contact limits, PSRAM allocation fallback and saved/wire formats are unchanged.
On the existing 32-bit platforms, a static 350-contact table saves 764 bytes
from these arrays; a static 100-contact table saves 264 bytes. A 350-contact
PSRAM configuration saves 96 internal bytes and 716 PSRAM bytes. Cached nRF52
records also shrink from 96 to 92 bytes by moving runtime storage bookkeeping
into an alignment gap. The matched RAK4631 Full build saves 2,200 static RAM
bytes in total. These figures do not describe infrastructure ACL tables.
See the [2026-10-19 qualification report](../test-results/contact_memory_2026-10-19.md)
for source-bound builds, tests and remaining board-specific memory limits.
## Regression checks
```sh
python3 -B test/test_contact_indices.py
python3 -B test/test_companion_contact_lifetime.py
python3 -B test/test_contact_cache.py
python3 -B test/test_companion_uncached_storage.py
python3 -B test/test_companion_contact_partial_tail.py
+49 -1
View File
@@ -231,7 +231,12 @@ rate-window counters nor struct padding can make different rules share a slot.
OTA leaf capacity remains controlled by `OTA_PROOFGEN_SCRATCH`, independently
of output-buffer size. The maximum source buffers are 4 KiB plus one block on
ordinary nRF52, 8 KiB plus one block with SD storage, and 16 KiB plus one block
on ESP32. Generic folder sources need no output allocation for raw DATA or
on legacy ESP32 paths. Non-PSRAM ESP32 Full uses grouped source proofs instead:
the complete workspace is bounded to 1,536 bytes, while retaining the same
4,096-block source limit. The check still adds one output block and allocation
metadata; safety reserves and stacks are unchanged. PSRAM and other platforms
retain the legacy policy. Explicit larger custom leaf limits also keep the
legacy implementation. Generic folder sources need no output allocation for raw DATA or
proofs; own-image raw serving retains its caller-owned compatibility buffer.
Its nRF52 allowance remains in the budget even when the device encoder is off.
Queued DATA, PROOF and MANIFEST responses pin their buffers through radio
@@ -812,3 +817,46 @@ images fit. These results qualify linked capacity and host behavior, not
physical USB/RF/MQTT integration, board boot/sleep or a sustained-load soak.
The Mini Repeater remains cable-only (`--allow-no-ota`); no hardware was
flashed, no release was published and existing outputs were not overwritten.
### Shared contact RAM and RAK4631 Full recheck, 2026-10-19 UTC
Companion and `simple_secure_chat` contact sort/match indexes now use `uint16_t`
when the complete table has at most 65,535 slots; larger configurations retain
`int`. Static 350-contact targets save 764 internal bytes; PSRAM targets save
96 internal and 716 external bytes; static 100-contact targets save 264 internal
bytes. Cached nRF52 record placement also saves four bytes per runtime contact.
Capacities, serialization, queues, stacks, features and fallback behavior remain.
At `aed4c22e5`, matched against `31e693ec2`, RAK4631 Full saves 2,200 static RAM
bytes and 488 actual DFU application bytes. Its application is 712,112 of 712,704
bytes (592 free); modeled RAM admission margin rises from 4,516 to 6,716. CRC
sharing and Reader packing preserve formats/text; Full features and existing SD
support remain enabled. These are linked/build capacities, not running-heap or
physical storage measurements.
Five final-source canonical packages pass across nRF52, ESP32 PSRAM, RP2040 and
STM32. RAK3x72 USB has four actual BIN bytes free, requiring unchanged hard gates.
At that revision, V3 Full fails RAM admission on both baseline and final source; its
100-contact wrapper gains 264 bytes, reducing the deficit from 21,288 to 21,024.
The direct 350-contact V3 terminal default build passes separately; it is not a
canonical Full or release-version/preset package. No hardware was flashed.
See the [qualification report](../test-results/contact_memory_2026-10-19.md) and
its source-bound artifacts, software coverage and limits.
### Heltec V3 Full bounded-source repair, 2026-10-19 UTC
At `eb9259f2a`, canonical V3 Full passes with 189,616 internal bytes available
against 183,584 required: 6,032 bytes of modeled margin. The pinned NimBLE
adapter preserves 15 saved phones and 30 CCCDs. Existing Bluedroid pairings need
one-time re-pairing. Its 100 contacts, 40 channels, 256 offline frames, MQTT slot,
Full features and stack sizes remain unchanged.
Grouped source proofs retain the same 4,096-block capacity with a lazy workspace
of 1,004 bytes, statically bounded to 1,536. The guard replaces only the old
16,384-byte leaf allocation with this bound; the output block, metadata,
transient margin and all other allowances remain additive. Legacy/PSRAM paths
and larger custom leaf limits retain the existing policy.
The clean V3 package and constrained RAK regressions pass; physical V3 testing
still awaits USB attachment. See the [V3 qualification report](../test-results/heltec_v3_full_memory_2026-10-19.md)
for artifact hashes, tests, upgrade behavior and hardware limits.
@@ -0,0 +1,414 @@
{
"schema_version": 1,
"date_utc": "2026-10-19",
"source_commit": "aed4c22e51201f0ee3586e26e1007194a74b015d",
"baseline_source_commit": "31e693ec209109a930dcf8aeef89dacbe9116a63",
"hardware_flashed": false,
"release_published": false,
"platformio_single_process": true,
"final_matrix_complete": true,
"all_final_matrix_targets_passed": false,
"packages": [
{
"requested_target": "RAK_4631_companion_radio_full",
"passed": true,
"exit_code": 0,
"packaged_target": "RAK_4631_companion_radio_full",
"build_profile": "full",
"pio_environment": "RAK_4631_companion_radio_full",
"elf_sha256": "f002455df52a9cdf4278461685eaee3082cc14752f741428f6dfe2dcdc2bb2a7",
"all_memory_bound_file_hashes_match": true,
"memory_manifest_sha256": "073bd0eca6c9b233fcb09080991850b61806fddea3a0af31c52b0feb8bed9762",
"files": {
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.uf2": "31d92e0c48378932ec1465bd5542ca696dcf133a9f6555fd9d513691e1fafec0",
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.zip": "d99d904430de75dd5e507a530267d933b8f0bda762afe86cdb150209f4df4499",
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.capabilities.json": "d74ca93392bad5ab7cdbe8e07d24a905dbb570cbea76063ad8d9ab34be9cc788",
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.meshtext-license.txt": "ba558609720fbff1481e236462eea2f9e08a73c1f3c2fd486ea4d9e1d0a6d58f"
},
"capabilities": [
"profile.full",
"companion.meshtext.channel_text",
"ota.update.bluetooth",
"companion.temp_radio",
"companion.ota_cli",
"companion.usb",
"companion.bluetooth",
"companion.usb_logging",
"companion.usb_mota_source",
"companion.mota_sender",
"companion.dedicated_usb_logging",
"companion.ble_mota_source",
"logging.usb.packets",
"logging.usb.control"
],
"capability_verification_count": 13,
"capability_verification_passed": true,
"ordinary_static_ram_bytes": 154084,
"static_sections": {
".data": 1660,
".bss": 152424
},
"static_sections_scope": "ELF .data plus ordinary .bss only; excludes separately named/reserved arenas; null when these exact sections are absent",
"available_internal_bytes": 81292,
"required_heap_bytes": 74576,
"ram_admission_margin_bytes": 6716,
"sd_fat_volume_and_files_reserve_bytes": 2048,
"application_bytes": 712112,
"application_sha256": "c2080afe8874c71eb9a5d1fb253fec06463cac8a774fc776ff46453abf2da420",
"application_source": "DFU application BIN extracted from ZIP",
"uf2_matches_DFU_application": true,
"uf2_blocks": 2782,
"initialized_ELF_flash_load_bytes_match": true,
"initialized_ELF_flash_load_bytes_compared": 712032,
"application_capacity_bytes": 712704,
"application_margin_bytes": 592
},
{
"requested_target": "RAK_3401_companion_radio_usb",
"passed": true,
"exit_code": 0,
"packaged_target": "RAK_3401_companion_radio_usb",
"build_profile": "auto",
"pio_environment": "RAK_3401_companion_radio_usb",
"elf_sha256": "0d4a8c90601fc50b4c44f6ee3f315cc2bdfb5b5365cec2efed9d09d54189b9e2",
"all_memory_bound_file_hashes_match": true,
"memory_manifest_sha256": "e22c31d59175cc10c4ecb4f8d04430a859d4e32b450d8f1cd50480f012fd690d",
"files": {
"RAK_3401_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.uf2": "94469781e5550ff41469a5bf80f6f4dba5fc5d2aa2f9f3e073834b06137ef3d0",
"RAK_3401_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.zip": "0709ee56d7f35ac1c9cfa8e8c997b5e2ebd5990c37d044de0e41c6e638f29587",
"RAK_3401_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.capabilities.json": "088d17a9b1de74a851c5d17807a95a27cb4622e24fa05751ff0ef753b21f5ba7",
"RAK_3401_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.meshtext-license.txt": "ba558609720fbff1481e236462eea2f9e08a73c1f3c2fd486ea4d9e1d0a6d58f"
},
"capabilities": [
"profile.auto",
"ota.update.bluetooth"
],
"capability_verification_count": 1,
"capability_verification_passed": true,
"ordinary_static_ram_bytes": 136564,
"static_sections": {
".data": 1536,
".bss": 135028
},
"static_sections_scope": "ELF .data plus ordinary .bss only; excludes separately named/reserved arenas; null when these exact sections are absent",
"available_internal_bytes": 98812,
"required_heap_bytes": 47872,
"ram_admission_margin_bytes": 50940,
"sd_fat_volume_and_files_reserve_bytes": 0,
"application_bytes": 523864,
"application_sha256": "a7fa19297c0a9a7f34162fbf666c2a9275794e1ff15b68aa0159817fae7a3bc2",
"application_source": "DFU application BIN extracted from ZIP",
"uf2_matches_DFU_application": true,
"uf2_blocks": 2047,
"initialized_ELF_flash_load_bytes_match": true,
"initialized_ELF_flash_load_bytes_compared": 523864
},
{
"requested_target": "Heltec_v3_companion_radio_full",
"passed": false,
"exit_code": 1
},
{
"requested_target": "heltec_v4_2_v4_3_companion_radio_full_femon",
"passed": true,
"exit_code": 0,
"packaged_target": "heltec_v4_2_v4_3_companion_radio_full_femon",
"build_profile": "full",
"pio_environment": "heltec_v4_2_v4_3_companion_radio_full_femon",
"elf_sha256": "2c50274bdc350696299819962592032fab540e63f583f2909baf227f875e8219",
"all_memory_bound_file_hashes_match": true,
"memory_manifest_sha256": "6bdcb095bed1fd784169d3986df1ff4212e84ec94bc69c032a0d2581fc79591f",
"files": {
"heltec_v4_2_v4_3_companion_radio_full_femon-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.bin": "ba264695b5093c643f726d970ef7cbfb29207f86454de8f924220276d0d26d70",
"heltec_v4_2_v4_3_companion_radio_full_femon-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e-merged.bin": "f433d9275edb0b0ef470a2866f82ef948a87b7e328f2098f1e06876e8ef6641c",
"heltec_v4_2_v4_3_companion_radio_full_femon-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.capabilities.json": "08dcf0def4a66ebe628df3635cc2eb4f6e1eeea2b78abe550d1533e4601e2a26",
"heltec_v4_2_v4_3_companion_radio_full_femon-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.meshtext-license.txt": "ba558609720fbff1481e236462eea2f9e08a73c1f3c2fd486ea4d9e1d0a6d58f"
},
"capabilities": [
"profile.full",
"companion.meshtext.channel_text",
"companion.temp_radio",
"companion.ota_cli",
"companion.usb",
"companion.bluetooth",
"companion.usb_logging",
"companion.usb_mota_source",
"companion.mota_sender",
"companion.network_terminal",
"companion.wifi_ota_seeder",
"web.webconfig",
"web.unconfigured_setup_cutoff",
"companion.direct_mqtt",
"logging.usb.packets",
"logging.usb.control",
"companion.app_wifi.get_enabled",
"companion.app_wifi.set_enabled",
"companion.app_wifi.get_ssid",
"companion.app_wifi.set_ssid",
"companion.app_wifi.get_password",
"companion.app_wifi.set_password",
"companion.app_wifi.get_status",
"companion.app_wifi.get_ip",
"ota.update.wifi"
],
"capability_verification_count": 24,
"capability_verification_passed": true,
"ordinary_static_ram_bytes": null,
"static_sections": {},
"static_sections_scope": "ELF .data plus ordinary .bss only; excludes separately named/reserved arenas; null when these exact sections are absent",
"available_internal_bytes": 228872,
"required_heap_bytes": 194336,
"ram_admission_margin_bytes": 34536,
"sd_fat_volume_and_files_reserve_bytes": 0,
"application_bytes": 2558856,
"application_sha256": "ba264695b5093c643f726d970ef7cbfb29207f86454de8f924220276d0d26d70",
"application_source": "application BIN"
},
{
"requested_target": "Xiao_rp2040_companion_radio_usb",
"passed": true,
"exit_code": 0,
"packaged_target": "Xiao_rp2040_companion_radio_usb",
"build_profile": "auto",
"pio_environment": "Xiao_rp2040_companion_radio_usb",
"elf_sha256": "832133d6070f4b1b86ac6a9cbd691b5004025e19f7d853ca0cab2d0755284def",
"all_memory_bound_file_hashes_match": true,
"memory_manifest_sha256": "ecd592a61157de92ceb547984baf34f7a55f1fb8b218e6fece487f4ee4897f08",
"files": {
"Xiao_rp2040_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.bin": "757ccb5adb387dab0fccca1793ed70e0713c8ff13e5801763c8a9250f102d806",
"Xiao_rp2040_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.uf2": "8d9386975f8725f2f7bbe6fb1e9a5f81dff84134c710272e95759bd7872ac56e",
"Xiao_rp2040_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.capabilities.json": "f182d520112f6667afdb45eb64197aa025df44b567dac8e370c6892de2858b34",
"Xiao_rp2040_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.meshtext-license.txt": "ba558609720fbff1481e236462eea2f9e08a73c1f3c2fd486ea4d9e1d0a6d58f"
},
"capabilities": [
"profile.auto"
],
"capability_verification_count": 0,
"capability_verification_passed": true,
"ordinary_static_ram_bytes": 100168,
"static_sections": {
".data": 5372,
".bss": 94796
},
"static_sections_scope": "ELF .data plus ordinary .bss only; excludes separately named/reserved arenas; null when these exact sections are absent",
"available_internal_bytes": 161748,
"required_heap_bytes": 17408,
"ram_admission_margin_bytes": 144340,
"sd_fat_volume_and_files_reserve_bytes": 0,
"application_bytes": 472204,
"application_sha256": "757ccb5adb387dab0fccca1793ed70e0713c8ff13e5801763c8a9250f102d806",
"application_source": "application BIN"
},
{
"requested_target": "RAK_3x72_companion_radio_usb",
"passed": true,
"exit_code": 0,
"packaged_target": "RAK_3x72_companion_radio_usb",
"build_profile": "auto",
"pio_environment": "RAK_3x72_companion_radio_usb",
"elf_sha256": "257b0e46be99095b1e9425bdb581b4639e5ee4f126b524ac997c4ffe123865ac",
"all_memory_bound_file_hashes_match": true,
"memory_manifest_sha256": "19c042cf40b21a068dc83a7e3177c2068167379d2f9f9c1a1a431734491de0a3",
"files": {
"RAK_3x72_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.bin": "3b45baa64a7a5a6bbcce18dc17db70c46afa4321e7fd4369fa47b45d53fa73d0",
"RAK_3x72_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.hex": "bb8198b4e06fe0133e70db53eafc33bfe2acfcafabd26b75ddec76c214b0a861",
"RAK_3x72_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-aed4c22e.capabilities.json": "5df1c74616a9fbcf4253dc5294c2367551b1f78ae19e391e7238aaa4e633edcc"
},
"capabilities": [
"profile.auto"
],
"capability_verification_count": 0,
"capability_verification_passed": true,
"ordinary_static_ram_bytes": 39992,
"static_sections": {
".data": 424,
".bss": 39568
},
"static_sections_scope": "ELF .data plus ordinary .bss only; excludes separately named/reserved arenas; null when these exact sections are absent",
"available_internal_bytes": 24520,
"required_heap_bytes": 17408,
"ram_admission_margin_bytes": 7112,
"sd_fat_volume_and_files_reserve_bytes": 0,
"application_bytes": 229372,
"application_sha256": "3b45baa64a7a5a6bbcce18dc17db70c46afa4321e7fd4369fa47b45d53fa73d0",
"application_source": "application BIN",
"application_capacity_bytes": 229376,
"application_margin_bytes": 4
}
],
"linked_checks": {
"crypto": {
"passed": true,
"elf_sha256": "f002455df52a9cdf4278461685eaee3082cc14752f741428f6dfe2dcdc2bb2a7",
"base_table_bytes": 960,
"reader_data_bytes": 38293,
"vectors": [
{
"rfc8032_vector": 1,
"keypair_stack_bytes": 1152,
"sign_stack_bytes": 1928
},
{
"rfc8032_vector": 2,
"keypair_stack_bytes": 1152,
"sign_stack_bytes": 1928
}
],
"zero_initialized_path_cache_bytes": 5780,
"stack_limit_bytes": 2048,
"scope": "Crypto function frames only; excludes caller/ISR/task stacks and physical timing.",
"evidence_json_sha256": "8ba582cd89bd4d182d5f9e03c70aeeddfee337b401987debcf3f481ec7c59d4e"
},
"workspace": {
"passed": true,
"elf_sha256": "f002455df52a9cdf4278461685eaee3082cc14752f741428f6dfe2dcdc2bb2a7",
"queue_lifetimes": 4,
"workspace_address": "0x200212d4",
"protected_sd_auth_range": [
"0x20006008",
"0x20006050"
],
"scope": "Linked ARM empty-queue OTA acquisition/release; no hardware qualification.",
"evidence_json_sha256": "658e56af82decc3648a99ef432f547577faeb7139f2bd34e74d6744fb0d8a21d"
},
"reader": {
"passed": true,
"elf_sha256": "f002455df52a9cdf4278461685eaee3082cc14752f741428f6dfe2dcdc2bb2a7",
"references_fully_reconstructed": 879,
"calls": 1091,
"canonical_bytes_checked": 95689,
"maximum_reply_verse_stack_bytes": 6016,
"caller_reserve_bytes": 1536,
"loop_stack_bytes": 8192,
"total_arm_instructions": 380103543,
"checker_sha256": "02d0eb4a553c9588c698ca5013c269fae16f863d864b1cac521a18fba065f1be",
"canonical_source_sha256": "916bf9d1feae0ad410d69f61e6c5f328ac6acba10651d751b5bb29719732c49e",
"partition_count": 4,
"disjoint_reference_coverage_verified": true,
"all_pages": true,
"evidence_json_sha256": "740aa99a7a514862f6e597efe56ebca47580416dae28db48016ca333d21d2fe8"
}
},
"adjacent_native_checks": {
"Reader_tests": 16,
"Beacon_tests": 19,
"radio_profile_matrix_scenarios": 32,
"Morse_tests": 7,
"persistent_format_tests": 16
},
"canonical_V3_Full_failure": {
"stage": "runtime RAM admission",
"available_internal_bytes": 177408,
"required_heap_bytes": 198432,
"shortfall_bytes": 21024,
"largest_internal_region_bytes": 144640,
"matched_baseline_control": "verified preexisting RAM admission failure",
"baseline_source_commit": "31e693ec209109a930dcf8aeef89dacbe9116a63",
"baseline_available_internal_bytes": 177144,
"baseline_required_heap_bytes": 198432,
"baseline_shortfall_bytes": 21288,
"available_improvement_bytes": 264,
"profile_contact_capacity": 100,
"final_failed_ELF_hash": null,
"failed_ELF_evidence_scope": "final failed ELF was cleaned by later build; current failure is log-bound only",
"direct_terminal_control": "PASS; direct default build, not a canonical Full package",
"baseline_build_log_sha256": "f5f95ec53b9e472cdd5d46f5ec45d61de68e5eace3c2ead53dec7209e5d0f7c8",
"final_build_log_sha256": "732580a8d70c65b73023aec736a95e961da2451fc7ac9a38efeba9a091027dc4"
},
"focused_contact_checks": {
"unittest_methods_passed": 26,
"method_breakdown": {
"cache_and_persistence_harness": 3,
"persistence_contract": 11,
"stream_contract": 7,
"index_and_layout": 2,
"lifetime_matrix": 1,
"uncached_storage_RP2040_STM32": 2
},
"ASan_UBSan_runtime_configurations": 24,
"runtime_configuration_breakdown": {
"cache": 3,
"index": 11,
"lifetime": 8,
"uncached": 2
},
"actual_ContactInfo_compile_only_layouts": 8,
"lifetime_assertions": 284,
"contact_sources_unchanged_since": "b0f14161506e2a5f6acfbb4b0b1723e7bd7689c3",
"evidence_scope": "completed tool transcript; no standalone contact log retained"
},
"limits": [
"Software/build/linked-ARM qualification only; no board flashed",
"RAM admission and static section totals are not running free heap or physical high-water",
"Physical SD/storage, contact/client behavior, PSRAM pressure, power and radio operation untested"
],
"historical_failures": [
"Intermediate b0f141615 small STM32 build failed size admission; retained separately from final-source matrix",
"Legacy Heltec_v3_companion_radio_ble has identical five WiFiSetupPortal/WiFiOtaSeeder unresolved symbols on matched baseline and intermediate revision",
"Initial V4 invocation rejected unsupported --full-exact argument before target compilation"
],
"baseline_RAK4631_Full": {
"packaged_target": "RAK_4631_companion_radio_full",
"build_profile": "full",
"pio_environment": "RAK_4631_companion_radio_full",
"elf_sha256": "4623af75c924a830415c0a5fd186569b4bbff35a9ce1084438eafe145282058e",
"all_memory_bound_file_hashes_match": true,
"memory_manifest_sha256": "fc1dad5cab6a7deae4ea4042b26dc20231d82710e627fa6e33aef7efecf36c41",
"files": {
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-31e693ec.uf2": "6039f7fe532f121100c1734c9f292ec5f7d7c3ead50b06e7e283fe93569f3fff",
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-31e693ec.zip": "ef641d49dc91db61f0a49912f191f6d81c2887a69950fa66a612f81b31f0e142",
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-31e693ec.capabilities.json": "91711e0da1a8f3ba4b99c24749c1ac78f0862534f436233ff32c867ccbeb9cc9",
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-31e693ec.meshtext-license.txt": "ba558609720fbff1481e236462eea2f9e08a73c1f3c2fd486ea4d9e1d0a6d58f"
},
"capabilities": [
"profile.full",
"companion.meshtext.channel_text",
"ota.update.bluetooth",
"companion.temp_radio",
"companion.ota_cli",
"companion.usb",
"companion.bluetooth",
"companion.usb_logging",
"companion.usb_mota_source",
"companion.mota_sender",
"companion.dedicated_usb_logging",
"companion.ble_mota_source",
"logging.usb.packets",
"logging.usb.control"
],
"capability_verification_count": 13,
"capability_verification_passed": true,
"ordinary_static_ram_bytes": 156284,
"static_sections": {
".data": 1660,
".bss": 154624
},
"static_sections_scope": "ELF .data plus ordinary .bss only; excludes separately named/reserved arenas; null when these exact sections are absent",
"available_internal_bytes": 79092,
"required_heap_bytes": 74576,
"ram_admission_margin_bytes": 4516,
"sd_fat_volume_and_files_reserve_bytes": 2048,
"application_bytes": 712600,
"application_sha256": "520cf40ec5ada891d3fe8081b23546ce777c712383f0479292bb8fac0017d4d7",
"application_source": "DFU application BIN extracted from ZIP",
"uf2_matches_DFU_application": true,
"uf2_blocks": 2784,
"initialized_ELF_flash_load_bytes_match": true,
"initialized_ELF_flash_load_bytes_compared": 712520,
"application_capacity_bytes": 712704,
"application_margin_bytes": 104
},
"direct_terminal_build": {
"target": "Heltec_v3_terminal_chat",
"source_commit": "aed4c22e51201f0ee3586e26e1007194a74b015d",
"passed": true,
"scope": "direct PlatformIO default environment; not a firmware-picker Full or version/preset-qualified release package",
"contact_capacity": 350,
"elf_sha256": "1a193ae1bee829d0bf7d140310b8487d2a4671f0c85715d16cfb764850e06cb6",
"memory_manifest_sha256": "a74079788410d8cdf11e686b62f8aee80537a9743cd0590bf4caf069ab7d038d",
"available_internal_bytes": 263800,
"required_heap_bytes": 77856,
"ram_admission_margin_bytes": 185944,
"build_log_sha256": "31d410458e6e6cd87bce96019e1ce655ab698eeb5e5d720987461c7be07f3fdf"
}
}
@@ -0,0 +1,178 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/test-results/contact_memory_2026-10-19/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Contact RAM savings and RAK4631 Full capacity, 2026-10-19
Shared contact indexes save RAM across Companion profiles and terminal chat.
RAK4631 Full additionally saves **2,200 static RAM bytes and 488 application
bytes**, preserving its existing features and SD support. Five canonical
packages and a direct terminal build pass. Heltec V3 Full failed its preexisting
RAM-admission limit at this revision; the subsequent
[V3 Full repair](heltec_v3_full_memory_2026-10-19.md) is measured separately.
**No hardware was flashed for this contact-memory campaign.**
Baseline source: [`31e693ec209109a930dcf8aeef89dacbe9116a63`](https://github.com/mikecarper/MeshCore/commit/31e693ec209109a930dcf8aeef89dacbe9116a63).
Final source: [`aed4c22e51201f0ee3586e26e1007194a74b015d`](https://github.com/mikecarper/MeshCore/commit/aed4c22e51201f0ee3586e26e1007194a74b015d).
The [machine-readable results](contact_memory_2026-10-19.json) bind package,
ELF, memory-manifest and linked-check hashes to these revisions.
## Shared contact savings
`BaseChatMesh` uses `uint16_t` sort/match indexes when the complete contact table
has at most 65,535 slots; larger configurations keep `int`. API counts retain
their existing types. All Companion profiles and `simple_secure_chat` share this
implementation across nRF52, ESP32, RP2040 and STM32. Infrastructure client/ACL
tables use different record types and are outside this contact-table change.
| Existing configuration | Internal RAM saved by indexes | PSRAM saved by indexes |
| --- | ---: | ---: |
| Static 350-contact table | 764 bytes | None |
| PSRAM 350-contact table with internal fallback | 96 bytes | 716 bytes |
| Static 100-contact table | 264 bytes | None |
These totals include eight anonymous slots and 24 match results. PSRAM retains
its existing 24-slot internal fallback and allocation-failure behavior. Cached
nRF52 `ContactInfo` records also shrink from 96 to 92 bytes by moving the
runtime-only `storage_slot` field into an alignment gap; other record layouts
retain their sizes. Field-based serialization, contact/channel capacities,
features, queues and stack allocations remain unchanged.
## RAK4631 Full comparison
| Measurement | Baseline | Final | Improvement |
| --- | ---: | ---: | ---: |
| Actual application BIN extracted from DFU ZIP | 712,600 | 712,112 | 488 fewer |
| Application-region capacity | 712,704 | 712,704 | Unchanged |
| Actual flash margin | 104 | 592 | 488 more |
| Ordinary static RAM (`.data` + `.bss`) | 156,284 | 154,084 | 2,200 fewer |
| Linked heap capacity | 79,092 | 81,292 | 2,200 more |
| Modeled heap requirement | 74,576 | 74,576 | Unchanged |
| Modeled admission margin | 4,516 | 6,716 | 2,200 more |
`.data` stays at 1,660 bytes; ordinary `.bss` falls from 154,624 to 152,424.
Separately reserved linker arenas are excluded from this section sum. The
measured reduction includes final object alignment. Linked capacities and
admission models do not measure running free heap or physical stack high-water.
Four duplicate CRC loops share the existing implementation without changing
initial values, byte ranges or stored formats. Reader packing increases offline
Zopfli iterations from 15 to 1,000 and selects 16-bit block offsets only when
every block start fits. General 32-bit types/fallback, 4,096-byte blocks,
scratch policy and all canonical text remain unchanged.
| Reader flash data | Baseline | Final | Saving |
| --- | ---: | ---: | ---: |
| Compressed payload | 38,366 | 38,293 | 73 bytes |
| Twenty-four block indexes | 288 | 192 | 96 bytes |
| Payload plus index | 38,654 | 38,485 | 169 bytes |
The complete image saves 488 bytes; Reader payload plus index accounts for 169.
The Full marker and thirteen feature-verification entries are unchanged.
Existing SD support, its 2,048-byte SD/FAT runtime reserve and protected
authentication range remain intact. No physical SD operation was tested.
## Source-bound artifacts
| Artifact | SHA-256 |
| --- | --- |
| Baseline DFU application | `520cf40ec5ada891d3fe8081b23546ce777c712383f0479292bb8fac0017d4d7` |
| Final DFU application | `c2080afe8874c71eb9a5d1fb253fec06463cac8a774fc776ff46453abf2da420` |
| Baseline DFU ZIP | `ef641d49dc91db61f0a49912f191f6d81c2887a69950fa66a612f81b31f0e142` |
| Final DFU ZIP | `d99d904430de75dd5e507a530267d933b8f0bda762afe86cdb150209f4df4499` |
| Final ELF | `f002455df52a9cdf4278461685eaee3082cc14752f741428f6dfe2dcdc2bb2a7` |
Every admitted package's memory-bound file hashes were recomputed. Nordic UF2
payloads match the DFU applications, and initialized ELF flash-load bytes agree.
The JSON records the individual hashes and actual packaged selectors/profiles.
## Software verification
Contact coverage totals **26 Python unittest methods**: three cache/persistence
harness methods, eleven persistence-contract methods, seven stream-contract
methods, two index/layout methods, one lifetime matrix, and two RP2040/STM32
uncached-storage methods. They execute **24 ASan/UBSan C++ configurations**
(three cache, eleven index, eight lifetime, two uncached) and separately compile
eight actual cached/uncached record layouts across the four MCU families. The
lifetime matrix checks 284 assertions. Index execution uses a small stand-in
record; separate layout checks compile the real header. Contact code is
unchanged since `b0f141615`; results are retained in the tool transcript, with
no separate contact log claimed.
Adjacent checks pass: 16 Reader tests, 19 Beacon tests, a radio-profile matrix
with 32 scenarios, seven Morse-codec tests and sixteen persistent-format tests.
These focused counts do not establish a complete hosted CI run.
Final linked ARM checks all bind to the ELF above:
- Crypto: both RFC8032 vectors pass; keypair/sign frames are 1,152/1,928 bytes,
within the existing 2,048-byte function-frame bound.
- OTA workspace: four acquisition/release lifetimes pass, preserving protected
SD authentication range `0x20006008`–`0x20006050`.
- Reader: four disjoint partitions reconstruct all 879 references and every
continuation page: 95,689 canonical bytes through 1,091 calls and 380,103,543
ARM instructions. Exact disjoint reference coverage is verified. Maximum
callee frame is 6,016 bytes; with the existing 1,536-byte caller reserve this
is 7,552 of the 8,192-byte loop-stack model. Canonical source SHA-256 remains
`916bf9d1feae0ad410d69f61e6c5f328ac6acba10651d751b5bb29719732c49e`.
The partitioned campaign invokes the unchanged linked Reader checker with
all-page reconstruction; partitioning is not an additional release gate.
Equivalent complete reproduction against the matching ELF uses Unicorn 2.1.4:
```sh
python scripts/check_nrf52_companion_crypto.py path/to/firmware.elf
python scripts/check_nrf52_companion_ota_workspace.py path/to/firmware.elf
python scripts/check_nrf52_companion_reader.py path/to/firmware.elf --every-page
```
## Final-source build matrix
Canonical packages used `v1.18.0.1-halo-keymind-cascade-dev`, radio preset
`usa-cascade-fixed` and profile `cascade`; Full selections required OTA.
PlatformIO commands ran one at a time. For example:
```sh
bash build.sh build-firmware RAK_4631_companion_radio_full --firmware-version v1.18.0.1-halo-keymind-cascade-dev --radio-preset usa-cascade-fixed --profile cascade --require-ota
```
| Requested target | Actual application bytes | Modeled RAM margin | Result |
| --- | ---: | ---: | --- |
| `RAK_4631_companion_radio_full` | 712,112 | 6,716 | PASS |
| `RAK_3401_companion_radio_usb` | 523,864 | 50,940 | PASS |
| `heltec_v4_2_v4_3_companion_radio_full_femon` | 2,558,856 | 34,536 | PASS, Full PSRAM profile |
| `Xiao_rp2040_companion_radio_usb` | 472,204 | 144,340 | PASS |
| `RAK_3x72_companion_radio_usb` | 229,372 | 7,112 | PASS; four actual flash bytes free |
| `Heltec_v3_companion_radio_full` | No admitted package | -21,024 | Verified preexisting RAM-gate failure |
Matched baseline V3 Full also fails: 177,144 available against 198,432 required
(21,288 short), versus final 177,408 (21,024 short). This wrapper preserves 100
contacts, gaining 264 bytes. The final failed ELF was cleaned by a later build;
the failure is bound to its build log; that ELF hash is not recorded.
The direct `pio run -e Heltec_v3_terminal_chat` default build passes with 350
contacts: 263,800 available, 77,856 required and 185,944 margin. ELF SHA-256 is
`1a193ae1bee829d0bf7d140310b8487d2a4671f0c85715d16cfb764850e06cb6`.
This qualifies a no-PSRAM/shared-terminal compile path; it is not a Full package
or a firmware-picker release-version/preset qualification.
## Retained failures and limits
Intermediate `b0f141615` failed the STM32 size check. Shared little-endian Morse
serialization and an equivalent branchless CRC loop restore the packaged fit.
STM32 static RAM
falls from 40,256 to 39,992 bytes; baseline actual BIN was 229,284/229,376 bytes
(92 free), although PlatformIO's section summary showed 444 free. Acceptance
uses actual BIN/ELF extent. The final four-byte margin and RAK4631 Full's
592-byte margin require the unchanged hard gates for subsequent versions.
The separate legacy V3 BLE target has the same five unresolved WiFi setup/seeder
symbols on matched baseline and intermediate source. Initial disk-quota failures
and an unsupported V4 command flag remain separate failed attempts; successful
retries establish the results above. No features, capacities or budgets were
reduced to obtain these passes. There is no six-canonical-pass claim.
These are software/build/linked-code results. No board was flashed. Physical
contact/client operation, PSRAM pressure/fallback, storage persistence, SD/radio
operation, power, timing and full-firmware ISR/SoftDevice stack high-water remain
outside this qualification.
@@ -0,0 +1,284 @@
{
"schema_version": 1,
"date_utc": "2026-10-19",
"source_commit": "eb9259f2a97113b1d482ca9610661fb24c1c8e98",
"baseline_source_commit": "31e693ec209109a930dcf8aeef89dacbe9116a63",
"all_final_builds_passed": true,
"single_process_platformio": true,
"hardware_validation": {
"performed": false,
"flashed": false,
"status": "Awaiting physical V3 USB attachment to the VM; no V3 enumerated during inventory."
},
"v3_capacity": {
"contacts": 100,
"channels": 40,
"offline_frames": 256,
"active_ble_sessions": 1,
"saved_phone_bonds": 15,
"cccd_records": 30,
"mqtt_runtime_slots": 1
},
"v3_proof_workspace": {
"maximum_source_blocks": 4096,
"actual_xtensa_bytes": 1004,
"reserved_bytes": 1536,
"output_block_bytes": 2048,
"allocation_metadata_bytes": 32,
"legacy_source_leaf_bytes": 16384,
"legacy_larger_custom_limits_retained": true
},
"v3_baseline_memory": {
"available_internal_bytes": 177144,
"required_heap_bytes": 198432,
"margin_bytes": -21288
},
"software_checks": {
"bounded_source_proofs": {
"python_tests": 3,
"exact_wire_proofs": 22240,
"sanitizers": [
"ASan",
"UBSan"
]
},
"firmware_ram_policy": {
"python_tests": 47
},
"nimble_adapter_and_variant": {
"python_tests": 10,
"adapter_sanitizers": [
"ASan",
"UBSan"
]
},
"legacy_ota_cold_buffers": {
"configurations": 4
},
"serial_deadlines": {
"python_tests": 2
},
"source_only_folder_resume": {
"python_tests": 2
}
},
"packages": [
{
"target": "Heltec_v3_companion_radio_full",
"passed": true,
"exit_code": 0,
"elapsed_seconds": 54.22,
"elf_sha256": "3a56805b2d0216026c3710e149db43dedb677684e47ad08ebfe748094c1de55b",
"available_internal_bytes": 189616,
"required_heap_bytes": 183584,
"margin_bytes": 6032,
"memory_manifest_sha256": "dc03ea7ba74caadaf5e5fe43f64affacf14481cdadbfd37ca241eb7bb47c1548",
"memory_bound_file_hashes_verified": true,
"files": {
"Heltec_v3_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2.bin": "c4f276095626560abc4c6875216c147d2fae292bf5c514e5029ef7d03454d3ce",
"Heltec_v3_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2-merged.bin": "8ab56857805925666cf9c8638aa23851bea0d36f01a9f6ca270ad5776c2a0245",
"Heltec_v3_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2.capabilities.json": "d03182ad9405a2cf692687de11cc686fdf3600725d2761db3198cd7236bdba4a",
"Heltec_v3_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2.meshtext-license.txt": "ba558609720fbff1481e236462eea2f9e08a73c1f3c2fd486ea4d9e1d0a6d58f"
},
"capabilities": [
"profile.full",
"companion.meshtext.channel_text",
"companion.temp_radio",
"companion.ota_cli",
"companion.usb",
"companion.bluetooth",
"companion.usb_logging",
"companion.usb_mota_source",
"companion.mota_sender",
"companion.network_terminal",
"companion.wifi_ota_seeder",
"web.webconfig",
"web.unconfigured_setup_cutoff",
"companion.direct_mqtt",
"logging.usb.packets",
"logging.usb.control",
"companion.app_wifi.get_enabled",
"companion.app_wifi.set_enabled",
"companion.app_wifi.get_ssid",
"companion.app_wifi.set_ssid",
"companion.app_wifi.get_password",
"companion.app_wifi.set_password",
"companion.app_wifi.get_status",
"companion.app_wifi.get_ip",
"ota.update.wifi"
],
"feature_checks": 24,
"capabilities_verified": true,
"existing_recipe_reductions": [
"companion.capacity limited to 100 contacts for runtime RAM; 256 queued frames and all Full transports retained"
],
"components": {
"core_tasks_usb_filesystems": 24576,
"wireless_stacks": 81920,
"mqtt_startup_allowance": 24576,
"ota_source_scratch": 8192,
"browser_terminal_session": 2048,
"browser_terminal_scrollback": 4096,
"radio_packet_pool": 5120,
"ota_generic_source_buffers": 3616,
"display_pixels_and_driver": 2048,
"screen_objects_and_history": 8192,
"expanded_message_previews": 2816,
"allocation_and_transient_margin": 16384
},
"required_contiguous_bytes": 11008,
"largest_internal_region_bytes": 156848,
"application_bytes": 2114744,
"application_sha256": "c4f276095626560abc4c6875216c147d2fae292bf5c514e5029ef7d03454d3ce",
"application_capacity_bytes": 3342336,
"application_margin_bytes": 1227592
},
{
"target": "RAK_4631_companion_radio_full",
"passed": true,
"exit_code": 0,
"elapsed_seconds": 34.08,
"elf_sha256": "7f123f4c4a43200fd85fb889bb388b4f22318fd395eeafe64c014af14be3804e",
"available_internal_bytes": 81292,
"required_heap_bytes": 74576,
"margin_bytes": 6716,
"memory_manifest_sha256": "c58fdd1cf010ca8d7c5b0c33549f941482cb6ca11deb361e04c2d4770eaa4f22",
"memory_bound_file_hashes_verified": true,
"files": {
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2.uf2": "b532062fec9d5fe53d490010fea26dfbad334e0ffb66a4f483def390763a31ef",
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2.zip": "dd58370fe7360df078b645813d633a31567780dfab02dbde5e0ec28e120b0018",
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2.capabilities.json": "a54fc3dc71fd31eb5ddfb8c4b7368fcec6446fe41729f05ef3408ca6be270e87",
"RAK_4631_companion_radio_full-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2.meshtext-license.txt": "ba558609720fbff1481e236462eea2f9e08a73c1f3c2fd486ea4d9e1d0a6d58f"
},
"capabilities": [
"profile.full",
"companion.meshtext.channel_text",
"ota.update.bluetooth",
"companion.temp_radio",
"companion.ota_cli",
"companion.usb",
"companion.bluetooth",
"companion.usb_logging",
"companion.usb_mota_source",
"companion.mota_sender",
"companion.dedicated_usb_logging",
"companion.ble_mota_source",
"logging.usb.packets",
"logging.usb.control"
],
"feature_checks": 13,
"capabilities_verified": true,
"existing_recipe_reductions": [
"nRF52 Full: 256 offline frames normally; 128 while mOTA borrows queue storage"
],
"components": {
"loop_and_callback_stacks": 11264,
"core_usb_filesystems_sensors": 12288,
"sd_fat_volume_and_files": 2048,
"ota_target_name_lookup": 4112,
"bluetooth_worker_stacks": 5920,
"radio_packet_pool": 5120,
"ota_generic_source_buffers": 10272,
"ethernet_companion_worker": 4352,
"display_pixels_and_driver": 4096,
"screen_objects_and_history": 8192,
"expanded_message_previews": 2816,
"allocation_and_transient_margin": 4096
},
"required_contiguous_bytes": 11008,
"largest_internal_region_bytes": 81292,
"application_bytes": 712112,
"application_sha256": "2707606a9f4af071c9c4720465a7e7f17132292abc4af9eb468578d456480779",
"application_capacity_bytes": 712704,
"application_margin_bytes": 592
},
{
"target": "RAK_3x72_companion_radio_usb",
"passed": true,
"exit_code": 0,
"elapsed_seconds": 22.35,
"elf_sha256": "1e686c6ad520e84f2ede3f50037c5d1c4c8e4266c0e9fac605d53d67ee4bd593",
"available_internal_bytes": 24520,
"required_heap_bytes": 17408,
"margin_bytes": 7112,
"memory_manifest_sha256": "687267b1bd35019f9683b46fe52ea4c23001dc4ef1f2aa14f51a2e917a9885fc",
"memory_bound_file_hashes_verified": true,
"files": {
"RAK_3x72_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2.bin": "b2770a66e75534fb48d987d2cd1bc71ea178bd7e1e720ebf0d45788efeaa7621",
"RAK_3x72_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2.hex": "a583bc987801281c6c6eb5a4cb9a069e72fd4a5e7b5c3863d8df5f2b31abea58",
"RAK_3x72_companion_radio_usb-v1.18.0.1-halo-keymind-cascade-dev-eb9259f2.capabilities.json": "7977ab1c0bb8f7cb678ae675816dc3a12639d1b2e1d49d6f98bdac1b349bc86f"
},
"capabilities": [
"profile.auto"
],
"feature_checks": 0,
"capabilities_verified": true,
"existing_recipe_reductions": [],
"components": {
"core_filesystems_sensors": 8192,
"radio_packet_pool": 5120,
"allocation_and_transient_margin": 4096
},
"required_contiguous_bytes": 5120,
"largest_internal_region_bytes": 24520,
"application_bytes": 229372,
"application_sha256": "b2770a66e75534fb48d987d2cd1bc71ea178bd7e1e720ebf0d45788efeaa7621",
"application_capacity_bytes": 229376,
"application_margin_bytes": 4
}
],
"rak_linked_arm_checks": {
"crypto": {
"passed": true,
"elf_sha256": "7f123f4c4a43200fd85fb889bb388b4f22318fd395eeafe64c014af14be3804e",
"base_table_bytes": 960,
"reader_data_bytes": 38293,
"vectors": [
{
"rfc8032_vector": 1,
"keypair_stack_bytes": 1152,
"sign_stack_bytes": 1928
},
{
"rfc8032_vector": 2,
"keypair_stack_bytes": 1152,
"sign_stack_bytes": 1928
}
],
"zero_initialized_path_cache_bytes": 5780,
"stack_limit_bytes": 2048,
"scope": "Crypto function frames only; excludes caller/ISR/task stacks and physical timing."
},
"workspace": {
"passed": true,
"elf_sha256": "7f123f4c4a43200fd85fb889bb388b4f22318fd395eeafe64c014af14be3804e",
"queue_lifetimes": 4,
"workspace_address": "0x200212d4",
"protected_sd_auth_range": [
"0x20006008",
"0x20006050"
],
"scope": "Linked ARM empty-queue OTA acquisition/release; no hardware qualification."
},
"reader": {
"passed": true,
"elf_sha256": "7f123f4c4a43200fd85fb889bb388b4f22318fd395eeafe64c014af14be3804e",
"all_pages": true,
"references_fully_reconstructed": 879,
"disjoint_reference_coverage_verified": true,
"calls": 1091,
"canonical_bytes_checked": 95689,
"maximum_reply_verse_stack_bytes": 6016,
"caller_reserve_bytes": 1536,
"loop_stack_bytes": 8192,
"checker_sha256": "02d0eb4a553c9588c698ca5013c269fae16f863d864b1cac521a18fba065f1be",
"canonical_source_sha256": "916bf9d1feae0ad410d69f61e6c5f328ac6acba10651d751b5bb29719732c49e"
}
},
"scope": "Linked capacity and host/linked-code verification; physical V3 boot, transport/load and power-loss behavior are not qualified.",
"linked_ble_store_bytes": {
"ble_store_config_our_secs": 1320,
"ble_store_config_peer_secs": 1320,
"ble_store_config_cccds": 480
}
}
@@ -0,0 +1,96 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/test-results/heltec_v3_full_memory_2026-10-19/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Heltec V3 Full memory qualification, 2026-10-19
Canonical V3 Full now passes its flash and runtime-RAM gates with **6,032 bytes
of modeled RAM margin**, retaining its existing features and capacities.
**Physical testing is pending:** no V3 is currently exposed to the VM, and no
board was flashed during this work.
Measured source: [`eb9259f2a97113b1d482ca9610661fb24c1c8e98`](https://github.com/mikecarper/MeshCore/commit/eb9259f2a97113b1d482ca9610661fb24c1c8e98).
The [machine-readable evidence](heltec_v3_full_memory_2026-10-19.json) records
package hashes, allocation components, capabilities and linked ARM checks.
## What changed
V3 Full uses the existing NimBLE-Arduino 2.5.1 Companion adapter. It preserves
the authenticated NUS service, PIN, address/stealth controls and frame queues.
The canonical profile retains one active BLE session, **15 saved phones and
30 CCCD records** for NUS TX and Service Changed subscriptions. This does not
reuse the smaller default bond store from the optional trial configuration.
**Bluetooth upgrade step:** existing Bluedroid bonds are not migrated. Forget
the previous pairing in the phone's Bluetooth settings and pair again after
installing this image. The PIN setting remains available as before.
Non-PSRAM ESP32 Full also uses a bounded external-source Merkle cache. It keeps
32-leaf groups and their roots, then rereads the selected group to generate the
same wire proof. It supports the same 4,096 source blocks, folder capture,
raw/host-encoded/device-encoded serving, getter requests and transport protocols.
A proof step issues at most one metadata transaction; jobs survive partial
progress and radio backpressure.
The complete workspace is 1,004 bytes in the 32-bit Xtensa layout, with a
production `sizeof` assertion enforcing a 1,536-byte bound. Its output block and
allocation allowance remain separate. The legacy 16 KiB source-leaf allocation
is absent on this path. PSRAM, nRF52 and other unqualified paths retain their
existing implementation, as do explicit larger custom source limits.
The RAM check accounts for this smaller real allocation. The 16 KiB transient
margin, Bluetooth/WiFi allowances, MQTT admission, stacks and minimum floors
remain unchanged. There is no new OTA queue loan or reduction in offline frames.
## Measured image
Both comparisons use the canonical release-length version, Cascade profile and
USA Cascade preset. The baseline is `31e693ec2`; it already failed admission
before the shared contact-memory changes.
| Measurement | Baseline | Final |
| --- | ---: | ---: |
| Internal capacity after linking | 177,144 | 189,616 |
| Modeled allocation requirement | 198,432 | 183,584 |
| Modeled margin | -21,288 | **6,032** |
| Application bytes | Not admitted | 2,114,744 |
| Application-slot capacity | — | 3,342,336 |
| Application-slot margin | — | 1,227,592 |
The existing V3 Full recipe retains **100 contacts, 40 channels, 256 offline
frames and one MQTT runtime slot**. These limits were not reduced by this fix.
All 24 linked/package feature checks pass, alongside the Full profile marker.
USB, Bluetooth, simultaneous WiFi, direct MQTT, WebConfig, logging and OTA remain
in the image. Modeled capacity is not a running free-heap or load measurement.
Final ELF SHA-256: `3a56805b2d0216026c3710e149db43dedb677684e47ad08ebfe748094c1de55b`.
Application SHA-256: `c4f276095626560abc4c6875216c147d2fae292bf5c514e5029ef7d03454d3ce`.
Every file bound by the packaged memory manifest was hash-verified.
## Verification
- New proof tests pass under ASan/UBSan, with 22,240 exact wire-proof comparisons,
partial/failing reads, negotiated transport-size changes, allocation failure,
cancellation, idle cleanup, folder capture, getter bytes and backpressure.
- The RAM-policy suite passes 47 tests. Ten adapter/variant tests pass, including
real adapter behavior under ASan/UBSan and the V3 DIO policy.
- Existing cold-buffer tests pass on four platform configurations. Serial
deadlines and source-only folder-resume tests each pass two test methods.
- Clean-source canonical RAK4631 Full and RAK3x72 USB also build. RAK4631 retains
6,716 modeled RAM bytes and 592 application bytes of margin; RAK3x72 retains
7,112 RAM bytes and four application bytes. Their existing hard gates remain.
- The final-source RAK4631 ELF passes linked RFC8032 crypto, four OTA workspace
lifetimes and every page of all 879 Reader references. Those checks bind to
its own ELF hash in the JSON rather than to an earlier contact-memory image.
PlatformIO commands ran one at a time in a clean checkout. Reproduce the V3
package with:
```sh
bash build.sh build-firmware Heltec_v3_companion_radio_full --firmware-version v1.18.0.1-halo-keymind-cascade-dev --radio-preset usa-cascade-fixed --profile cascade --require-ota
```
Hardware acceptance still requires the physical V3: preserve its flash and
settings, then check boot, live heap under simultaneous transports, contacts,
Bluetooth pairing/reconnect, MQTT, WiFi administration, OTA and persistence.
These software results do not establish that hardware acceptance.
+2
View File
@@ -60,6 +60,8 @@ Completed validation reports, experiments, and test guides are collected here. O
## Build, storage, memory, and checklists
- [Shared contact RAM savings and RAK4631 Full qualification, 2026-10-19](contact_memory_2026-10-19.md)
- [Heltec V3 Full memory qualification, 2026-10-19](heltec_v3_full_memory_2026-10-19.md)
- [Personal room mailbox checks](room_mailboxes.md)
- [Stock nRF52 bootloader version audit](bootloader_version_stock_audit.md)
- [CLI setting dispatch audit](cli_settings_audit.md)
@@ -4,6 +4,12 @@
# ESP32-S3 NimBLE Full Companion trial
Canonical Heltec V3 Full now uses the same pinned NimBLE backend, preserving
its existing 15 saved phones and both notification subscriptions per phone.
Its [Full memory qualification](heltec_v3_full_memory_2026-10-19.md) is separate
from the optional trials below. Switching from Bluedroid requires forgetting
the old phone pairing and pairing again; stored bonds are not migrated.
These optional builds use NimBLE-Arduino 2.5.1 with the existing ESP32 Arduino
2.0.17 toolchain. They are hardware qualification builds, outside the normal
release matrix and firmware picker.