forked from unom/punktfunk
Supersedes the parse gate in26b0819f. The off-thread read, the claim token, the teardown ordering and its bounded wait all stand — only what happens in the gap changes.26b0819fheld every report back until the calibration read came home, so a pad that stalled on EP0 could feel dead for up to the link's 250 ms timeout: no buttons, no sticks, nothing. Reports are now forwarded immediately and their motion scaled by the nominal calibration until the real one lands. That gap is exactly the behaviour that shipped beforef6de620f— acceleration ~18% short, gyro unscaled — for about a millisecond. Nobody can feel that. A controller that ignores a button press for a quarter of a second is not in the same category, and it is the only one of the two a user would ever report. It is also the safer of the two conservatisms available here. The rejected third option, forwarding motion as zeroes until the real numbers arrive, would have the host read a still pad as being in free fall — a lie about the physical world rather than an imprecision about it. The nominal constants are merely a slightly wrong scale. The token is more load-bearing under this, not less. With a gate, an unpublished calibration meant "parse nothing"; now it means "scale nominally", so begin() clearing the previous pad's value is the whole reason a re-claim falls back to the nominal constants instead of silently inheriting factory numbers belonging to a different unit — which are, in general, further off than nominal. The fallback therefore lives in the hand-off itself (MotionCalHandoff.effective) rather than as an elvis at the call site: restoring the gate now means changing the type's API, not deleting three characters in onReport. The tests moved with the contract. They assert the nominal calibration is what is in effect during the gap, rather than merely that the slot is empty — an empty slot is now compatible with either behaviour, so asserting on it would have let a regression pass. Added the case the change exists for: the same raw report, parsed either side of publication, forwards identical buttons and sticks while its gyro and acceleration convert differently. Mutation-checked three ways — dropping the nominal fallback fails all five cases, dropping begin's clear fails the inheritance case, dropping the token check fails three. Gate: `:kit:compileDebugKotlin`, `:kit:testDebugUnitTest` and `:app:compileDebugKotlin` green on a forced clean rerun, 62 cases across the module, 0 failed, with the five hand-off cases read back out of the JUnit XML. The on-glass re-verificationf6de620fowes is still owed and unchanged.