Bisect session with per-suspect kill switches (see BISECT_LOG.md): - appendLog was the confirmed stutter culprit — ran per-message (~110/sec) on the main thread even with the log panel hidden; A/B verified. Disabled via DEBUG_DISABLE_APPEND_LOG; shippable visible-only fix still TODO. - Per-message Log.d also disabled (freebie, no felt difference). - Glow, gradient fill, network flash, feedback setText all exonerated and restored; switches left in place at false. - FocusFeedbackWidgetView rewritten: TextView -> bare View + canvas.drawText. 60hz updates are now field-assign + invalidate, no per-update text Layout. - ContextMenuViewLogic: explicit color-picker branch for the new view type. - Timecode chunkiness root cause was REAPER audio buffer size (source-side burst cadence), documented in PERF_SESSION_2026-07-31.md; stutter bug report stamped RESOLVED. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.5 KiB
Focus Fader Stutter — Bisect Log
Companion to FEEDBACK_STUTTER_BUG.md. One kill-switch per suspect; flip one, rebuild, drag a Focus Fader while 60hz feedback streams, record the result here, then either keep it off or restore it and move to the next.
Ranked suspect list
| # | Suspect | Where | Switch |
|---|---|---|---|
| 1 | appendLog per message (even when panel hidden) | ArrangeFunctionsWebSocket.appendLog | DEBUG_DISABLE_APPEND_LOG |
| 2 | logScrollView.post(fullScroll) per message | inside appendLog | covered by #1 |
| 3 | Runnable flood (~3 posts/msg, 300+/sec) | appendLog + dispatch handlers | mostly covered by #1 |
| 4 | flashCard per message at 60hz | ArrangeFunctionsWebSocket.flashCard → ArrangeCardView.flashActivity | DEBUG_DISABLE_NETWORK_FLASH |
| 5 | setText at 60hz on feedback TextView | FocusFeedbackWidgetView.setTextFromNetwork | DEBUG_DISABLE_FEEDBACK_SETTEXT |
| 6 | LAYER_TYPE_SOFTWARE + blur glow on fader | FocusFaderWidgetView ctor (also FaderWidgetView) | DEBUG_DISABLE_GLOW (one per class) |
| 7 | new LinearGradient every onDraw | FocusFaderWidgetView.onDraw (also FaderWidgetView) | DEBUG_DISABLE_GRADIENT_FILL (one per class) |
| 8 | Log.d full-JSON concat per message | ArrangeFunctionsWebSocket.onMessage | DEBUG_DISABLE_RECEIVE_LOGCAT |
| 9 | Echo stream (sent values come back, run appendLog+flashCard mid-drag) | handleWidgetUpdateF | partially covered by #1 |
| 10 | vibrator.vibrate at value edges | FocusFaderWidgetView.checkEdgeHaptic | not yet added |
Verdict (2026-07-31)
appendLog was the stutter. A/B confirmed: re-enabling it brings the stutter back immediately. Receive-logcat kept off as a freebie (no felt difference). Everything else exonerated and restored to original behavior — including the glow/software-layer rendering, which Paul tested and cleared.
Final switch states: DEBUG_DISABLE_APPEND_LOG = true,
DEBUG_DISABLE_RECEIVE_LOGCAT = true, all others false (original behavior).
Timecode fluidity root cause (found by Paul, same day): REAPER's audio buffer size. REAPER emits OSC feedback per audio block, so a large buffer makes updates arrive in chunky bursts — uneven cadence no client-side rendering can smooth. Lowering the buffer restored fluid feedback. The drawText rewrite of FocusFeedbackWidgetView (done just before this discovery) stays: it's the cheapest render path and collapses same-frame updates to one draw regardless of source pacing.
Test runs
Run 1 — DEBUG_DISABLE_APPEND_LOG = true (suspects 1+2, most of 3)
- Date: 2026-07-31
- Switches off: appendLog (all in/out log lines + auto-scroll posts)
- Side effect while off: debug log panel shows nothing new when opened
- Result: CONFIRMED CULPRIT — "way smoother" with appendLog disabled.
- A/B re-test same day: re-enabled appendLog, stutter came back immediately; disabled again. Not a placebo — appendLog is definitively implicated. Left OFF for now; permanent fix TBD.
Run 2 — DEBUG_DISABLE_RECEIVE_LOGCAT = true (suspect 8)
- Date: 2026-07-31
- Switches off: appendLog (kept off from Run 1) + per-message Log.d in onMessage
- Side effect while off: incoming messages no longer visible in logcat
- Result: No perceptible difference. Kept off anyway (free optimization, nobody reads logcat during performance).
Run 3 — DEBUG_DISABLE_GLOW = true (suspect 6)
- Date: 2026-07-31
- Switches off: appendLog + receive-logcat (from Runs 1-2) + shadow-layer glow and software rendering in FocusFaderWidgetView AND FaderWidgetView
- Side effect while off: faders lose their glow — plain value line + dot
- Result: Exonerated. Still smooth with glow off, but no felt difference attributable to it — Paul: "the glow wasn't an issue." Turned back ON (original software-layer glow restored) at end of bisect.
Run 4 — DEBUG_DISABLE_NETWORK_FLASH = true (suspect 4)
- Date: 2026-07-31
- Switches off: appendLog + receive-logcat + glow (Runs 1-3) + network-triggered activity-ring flashes (local touch flashes still work)
- Side effect while off: cards don't blink green on desktop-driven updates
- Result: SKIPPED — suspect was already inert. The tablet's "Activity: ON/OFF" button (default OFF) was off during all stutter tests, and flashActivity() early-returns on that flag before any timer work. Kill switch left in place (harmless, skips a registry lookup) but this suspect is exonerated for the observed stutter.
Run 5 — DEBUG_DISABLE_FEEDBACK_SETTEXT = true (suspect 5, plus its share of 3)
- Date: 2026-07-31
- Switches off: appendLog + receive-logcat + glow (Runs 1-3) + the ENTIRE widget_feedback UI path: the runOnUiThread post, registry lookup, and setText all skipped (placed before the post, so this also removes 60/sec of the runnable flood)
- Side effect while off: feedback readouts (timecode etc.) freeze at last text
- Result: Nothing earth-shaking — no clear felt difference. Switch turned back OFF (readouts are needed regardless). 60hz setText is tolerable now that appendLog is gone; candidate for once-per-frame conflation later, not a primary culprit.
Run 6 — DEBUG_DISABLE_GRADIENT_FILL = true (suspect 7)
- Date: 2026-07-31
- Switches off: appendLog + receive-logcat + glow (kept from earlier runs); gradient fill replaced with flat translucent fill in both fader classes
- Side effect while off: fader fill is flat color, no fade-to-transparent
- Result: No felt difference — gradient turned back ON (look kept). Create-once gradient (setLocalMatrix) stays on the permanent-fix list as hygiene, not a culprit.