d82e744d87
PlayEventsReporter used to make the skip-vs-ended call client-side: if the play head reached within 3s of duration -> playEnded with full duration; otherwise -> playSkipped (forces was_skipped=true on the server regardless of actual play time). That left the server's configurable skip rule (skip_max_completion_ratio + skip_max_duration_played_ms, default 50%/30s) dead code and meant any "next" tap registered as a skip even after most of the track played - too strict per operator 2026-06-01. PlayEventsReporter now always calls playEnded with the actual position played to. Server's RecordPlayEnded applies the rule and decides was_skipped. If the play ran to completion, the last observed position is approximately the track duration -> ratio ~1 -> not skipped. If the user hit next mid-track, the duration the server sees is the real listen time and the rule fires normally. Drops the curReachedEnd field and COMPLETION_TOLERANCE_MS constant that backed the prior classification. playSkipped wire endpoint stays in place for a future explicit-dislike affordance (not wired to track-change transitions any more). Parity-map row updated. Web backflow tracked as task #57 (Scribe 521). Flutter not touched - deprecated per M8.