[DO NOT MERGE - TEST ONLY] fix(pad/sound): composite #340 fix -- async pad re-init + off-thread SFX + miss-pause - #358
[DO NOT MERGE - TEST ONLY] fix(pad/sound): composite #340 fix -- async pad re-init + off-thread SFX + miss-pause#358NathanNeurotic wants to merge 9 commits into
Conversation
…ss counters (#340) A pad-read MISS (connected pad, no fresh sample this poll) leaves the held buttons out of global paddata for the frame while edgedata still carries the last valid sample. The baseline repeat loop treated that as a release and re-armed the full 3x initial delay (300-1500 ms depending on scroll speed), so recurring misses on real hardware starved auto-repeat entirely -- the 'hangs on the 3rd highlighted item' pattern in #340/#272. Pause the countdown for keys whose edgedata bit survives a miss poll instead; a key genuinely released during a blind window still resets on the first good read, and an emulator (which never misses) sees byte-for-byte baseline behavior. This restores the hold-repeat feel HW-validated at Beta-3442 and lost in the PR #328 revert. Also: readMisses/missBurst/missBurstMax had no writers since that revert (the Debug HUD always rendered miss:0) -- count them again, and treat a miss poll with a carried held sample as input activity so the 250-600 ms inline initializePad self-heal cannot fire mid-navigation off misses that masked the user's own presses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fafc14d shipped the toggle with no reader anywhere -- readLeftJoy never checked gEnableAnalogNav and guiShowControllerConfig never synced the dialog row, so the checkbox always rendered off and its edits went nowhere. Stick sensitivity already carries the real control: Off maps to a 128 deadzone which fully gates an axis out of d-pad navigation, per axis. Remove the dead row, enum id, global, and config key rather than wiring a redundant switch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first cut decided 'miss' per poll: any pad producing a fresh sample masked another pad's miss, so with two pads -- or a PADEMU ds34 alongside a native pad, exactly the USB-heavy setup that starves SIO2 the most -- the pause never engaged and the HUD counted nothing. Track the held bits of each ready pad that produced no sample (pollMissedHeld) and drive the repeat pause, the activity stamp, and the miss counters from that. Also drop a carried sample once nothing supplies its port any more (a ds34 pad that stopped reporting), so a stale press cannot pin the repeat pause or the self-heal idle gate open forever. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rame state machine (#340) The HW Debug-HUD photos finally measured the felt hangs: initializePad ran 5-9 times PER SESSION (init:N) at 306-908 ms each -- every run a GUI-thread blackout from DelayThread polling loops, triggered ~1 s apart by a chronically flapping freepad. Unroll the same init sequence into a state machine advanced one status sample per rendered frame: identical stage semantics (retryable half-built mode tables, best-effort pressure/ actuator setup, PR #151's >=100 ms request budgets), zero GUI stall. Stage budgets are wall-clock milliseconds (frame counts would inflate under exactly the stretched frames being fixed) with a 2 s total cap so a wedged freepad can never hold a port unread for long. The pad is not read while its machine runs (matching the old semantics); its carried held sample feeds the repeat pause, not the miss counters, and a mid- init unplug performs the full unplug housekeeping the machine's own state sampling would otherwise swallow. Failed self-heals back off exponentially (60 -> 960 good reads): each attempt adds mode traffic to the contended bus that caused it, and the photos showed heal storms against a pad that never recovers analog. Reconnect-edge runs do not consume backoff steps, and a fresh physical connection resets it. Boot-time init keeps the proven blocking sequence (no GUI exists to stall yet). Also stop the worst-poll HUD tracker from inheriting the whole boot (photos read poll:8-13 s, hiding real in-session stalls): reseed the poll clock when the main loop starts polling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hread (#340) The HW photos measured a single cursor-tick audsrv_ch_play_adpcm RPC blocking the GUI thread for 352 ms under IOP contention (SFX:0/352ms). Queue sounds to a small dedicated thread (prio 45, below GUI/IO) via a ring instead; a full ring drops the sound -- a skipped tick beats a stalled menu. Rumble hooks, gates, the 45 ms cursor rate limit, and channel rotation stay on the producer side; the RPC wall time is still measured into the same HUD fields, now reporting IOP congestion without stalling anyone. Concurrency contract (per adversarial review): sfxPlay has TWO producers -- the GUI thread and the prio-30 ioman worker (BD connect/ disconnect sounds), which preempts it -- so the slot claim is DIntr/ EIntr-bracketed and the thread+semaphore are created once from audioInit, which happens-before any producer (both gate on audio_initialized). sfxInit (theme reload rewrites sfx[]) and audioEnd (audsrv_quit / NBD IOP reset) park the consumer via a bounded quiesce so no RPC can cross either. Stale queued cursor ticks (>100 ms) are dropped unplayed so a cleared IOP stall does not chirp the backlog. Synchronous fallback if the thread cannot be created. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Zack's first composite-build HUD line found the last hang: 'init:3 (2002/2002ms)' -- his freepad never completes the re-init sequence, so every heal run burned to the 2 s total cap, and because readPad suspended reads for the machine's lifetime each run was a 2 s hold-scroll freeze, while everything else finally measured clean (miss:2, poll:34ms, SFX:0/1ms; taps 'tight, chances to miss near none'). The no-reads-during-init rule was inherited from the blocking design, where it was an accident of the thread being busy -- the protocol has no such rule. freepad keeps serving reads across a mode change, and the miss/digital frames it may return during the transition are exactly what the per-pad miss machinery and repeat pause already absorb. So: tick the machine, then read normally. A wedged re-init now costs literally nothing felt; the machine idles to its cap in the background while input flows. Drops the pollPadsInit special-casing (reads during init now count honestly in the miss/burst diagnostics). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
v2 pushed ( His line Expected on v2: hold-scroll smooth end-to-end, including the moments 🤖 Generated with Claude Code |
…ge (#340) Zack's v2 HUD line closed the case on the last hang: with reads kept live through the re-init machine, the reads MISS for the window anyway (miss:2 -> 47, burst 28) -- freepad's vblank task stops serving READ_DATA while a mode-change request is in flight, and his pad never completes one (three runs per session, all at the 2002 ms total cap). So every heal attempt is a ~2 s input outage that no EE-side reading, pausing, or scheduling can bridge: the heal itself is the hang. Stop healing after PAD_HEAL_GIVE_UP (2) consecutive failed heals. The d-pad is fully functional in digital mode, so on a pad that cannot negotiate DualShock mode this trades a dead analog re-arm nobody was winning for uninterrupted input. A reconnect edge (real replug) resets healFailures and re-arms healing; healthy pads that heal on the first or second try are unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
v3 pushed ( v2's HUD line was decisive: with reads kept live through the re-init machine, they miss anyway ( v3: after 2 consecutive failed heals, healing stops for the session (a real replug re-arms it). The D-pad is fully functional in digital mode, so this trades an analog re-arm that was never succeeding for uninterrupted input. Expected on v3: at most ~2 brief hitches early in a session while the give-up threshold is reached, then hold-scroll continuous for the rest of the session. 🤖 Generated with Claude Code |
#340 v4) The v3 give-up was dead code on a flappy pad: every DISCONN->ready reconnect edge reset healFailures and launched a full, un-counted init run, so each flap re-armed up to three ~2 s mode-window read outages (the hangs), and the DISCONN transition dropped the held sample at once, forging a release+re-press on 1-2-poll flaps (the skips and phantom confirms). v4 discriminates by dwell time -- the one observable at the edge. freepad's error-driven flaps reconnect within 1-2 polls (~16-33 ms); a human replug dwells hundreds of ms. Short-dwell DISCONN polls are now treated exactly like read misses (held sample carried: repeat pauses, no forged edge) and a short-dwell reconnect runs NO init and does NOT reset the give-up budget. Only a dwell past PAD_FLAP_DWELL_MS (300) does the clean-slate housekeeping, and only its reconnect edge grants a fresh init + give-up reset. Every failed init run now counts toward the give-up regardless of trigger, and the machine aborts un-counted if the user resumed input before the mode request was actually issued. Net effect on a chronically flapping pad: at most 2 failed init runs per session (plus any real replugs), flaps degrade to ordinary miss-pauses, and no pad mode request is ever issued during or within a second of user input. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
v4 pushed ( The reframeZack's direct tests: current official OPL main is smooth on his console (and sOPL — identified as The prime suspect:
|
TEST BUILD D — the composite fix the HUD photos dictated. Do not merge; a clean PR replaces it if it wins.
What the photos showed (the payoff of the counters)
init:9(306/306ms)andinit:5(~308-908ms)— the inlineinitializePadre-init ran 5–9 times per session at 0.3–0.9 s each, every run a GUI-thread blackout: that is the felt hang. Plusbrst:~10-25(0.2–0.4 s continuous blind windows swallowing whole taps) andSFX:0/352ms(one cursor-sound RPC blocking the GUI for a third of a second). The symptom was a composite — which is why every single-layer fix 'didn't do it'.What this build changes (stacked on #355's per-pad miss-pause + counters)
poll:HUD field fixed: it inherited the whole boot (8–13 s readings); now it starts counting when the main loop starts polling, so in-session GUI stalls are finally measurable.For Zack
Same torture test, Debug Colors ON. Expected: hold-scroll smooth, no 0.3–0.9 s freezes, no dead zones after pausing;
init:may still count runs but their (…ms) wall costs no longer stall input;poll:should now read ~16–33 ms (a big number = real GUI stall, finally visible). Misses/bursts will still occur (transport-level; the pause absorbs them). Falsified if hangs persist withinitclimbing — then the machine's SIO2 traffic itself is the problem, not its blocking.Adversarially reviewed before push: two-producer race and lazy-create double-thread corruption fixed, frame-budget inflation replaced with wall-clock budgets, mid-init unplug housekeeping, quiesce across audsrv shutdown/theme reload.
🤖 Generated with Claude Code