Let a stack measurement start from a known point, and run it at the real work factor
Right-sizing the task stacks needs two things the tree did not have.
FreeRTOS derives a high-water mark by scanning the fill pattern below the deepest
stack pointer a task ever reached, so the figure is cumulative since the task was
created and the kernel offers no way to reset it. Measuring one workload rather
than everything since boot therefore means re-filling the dead region, and doing
that safely means doing it from inside the target task, where its own stack
pointer is readable. A new platform helper does exactly that, reached through
each worker's existing trampoline — the vault executor, the admin-crypto runner,
the BLE command queue — with a new operation added to the PBKDF2 worker, whose
request type had no generic entry point. It refuses at runtime unless a wide
margin below the current stack pointer is free, because getting that wrong
corrupts a live frame. It also scrubs residual PIN and key bytes out of the dead
region, which is worth having on its own.
The second is a work factor that means something. Debug builds force PBKDF2 to a
single iteration, so a derivation measured there does not hold the deep call
chain for a representative length of time. A `<variant>_stress` env extends
`_debug`, keeping the console and the reset command, and drops that override so
the board's production count applies through the variant's own #ifndef default:
175,000 on the S3, 40,000 on classic. Wired for sticks3 and m5stickc_plus2.
Invariant 19 keeps the shape honest: a stress env must extend its `_debug` twin
rather than re-derive it, and must not carry the debug iteration override.
Verified: sticks3_debug and sticks3_stress build clean, check_variants passes
across 40 environments, check_layout is green and native tests pass 2861/2861.
Claude-Session: https://claude.ai/code/session_01Q2J5gQSFMTDLVzPUYog51r
Raise the per-variant capacities, and stop shipping unusable vault UI
Every board now pins the capacity its flash and RAM actually support:
tdeck and cores3_se 700, core2_v13 500, sticks3 and cardputer 250,
m5stickc_plus2 180. The two 4 MB boards stay at 100 — their rekey-safe
ceiling is 139, and staging a second vault for a change-PIN on a fuller
one would run the filesystem out of space.
m5stickc_plus2 could not honestly hold 180: it had 3,132 B of internal
DRAM left against the 4,096 B bar. The slack came from a real absurdity.
Every button board compiled BOTH vault-menu layouts and picked one at
runtime from whether a third button existed, though a board's buttons are
fixed at manufacture. Each unused layout carries a row array sized by
capacity, so plus2 was paying 5 KB for a screen it can never draw. Boards
now declare which layout they use and only that one is compiled: plus2
goes to 9,196 B, sticks3 to 143,141, cores3_se to 78,985. core2_v13
recovers only the view object because its row arrays already live in
PSRAM, which is the placement working as intended.
The declaration is deliberate rather than inferred. Button count looked
like the discriminator only because today's fleet correlates: cardputer
and tdeck are wide-screen boards with no third button, and the old rule
would have handed them the narrow layout if their keyboards ever went
away. What actually decides the layout is width — a sliding pill
indicator is what fits 135 px, while a top tab bar plus an action bar
need room, and a past CoreInk header garble came from that view
hardcoding 320x240. Input decides only how you move between tabs. So the
variant states its choice, with no default, and a build-time tie rejects
a board declaring a layout its hardware cannot drive.
The names were lying too. Both layouts draw tabs, so "Tabbed"
distinguished nothing: they are now NarrowVaultView and WideVaultView,
after the constraint that actually separates them. KeyboardVaultView
keeps its name on purpose — cardputer is 240x135 and tdeck is 320x240,
one narrow and one wide, and both use it, so there the discriminator
really is the input device.
Review of the change caught a T-Deck pin that had never been raised while
five other artifacts already advertised 700, a NAV TOTP macro that
regressed on the wide boards so the screenshot suite filed a vault-list
capture as the TOTP screen, a selector whose "no silent default" promise
had a hole (an unknown token preprocesses to zero, which was the one
value exempted, so a typo compiled no view at all), and a variant guard
whose regex rejected correct declarations that carried a trailing
comment.
Verified: native 2861/2861, all three repo guards, and all eight boards
build with their declared layout confirmed present and its siblings
absent in each map.
Claude-Session: https://claude.ai/code/session_01Q2J5gQSFMTDLVzPUYog51r