docs(gamestate): win_prob/momentum fields + WS-vs-SSE delivery semantics#278
Merged
Merged
Conversation
…mestate payload Adds a "Win probability (tennis)" subsection + sport-specific table row for the live-only win_prob_home / win_prob_away / win_prob_book / win_prob_at / momentum / momentum_window_s fields that have been on the gamestate wire since the momentum meter shipped (sharp-api-go gamestate_winprob.go) but were never in the customer docs (2026-07-03 gamestate architecture review, F8). States the ground truth precisely: tennis-only today; server-computed at serve time from the de-vigged live moneyline (Pinnacle anchor → consensus median → single-book fallback), identical on REST + WS + SSE; present only while live with a fresh moneyline, omitted otherwise (no null placeholders); win_prob calibrated (Brier ~0.02-0.06 in ongoing grading); momentum explicitly framed as a UI/trend signal with no predictive value. Example uses clearly generic placeholder players. en locale only — de/es/pt-BR sync is a manual translate-content.mjs pass (not CI-automated), to be run at ship time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…removed+resume vs full-slate re-emit The gamestate streaming section previously described one merged model (snapshot -> changed rows -> gamestate:removed) that is only true for WebSocket; the 2026-07-03 gamestate architecture review (F7) flagged the real transport asymmetry as deliberate but unwritten. gamestate.mdx: new "Delivery semantics: WebSocket vs SSE" table — WS = incremental gamestate:update frames + gamestate:removed id lists, resumable via resume=true&from_seq=N (best-effort ~2-min replay buffer); SSE = full current slate re-emitted per update cycle, no removed events (removal = absence from the next slate), not resumable (Last-Event-ID covers odds only; reconnects re-bootstrap with resumed:false + fallback_reason:"channel_unsupported" and a fresh gamestate:snapshot). Plus a client-pattern callout: keyed map replaced wholesale on SSE, deltas applied on WS. stream.mdx: drop the incorrect gamestate:removed from the SSE channel table and add gamestate:snapshot / gamestate:update event sections (full-slate warning, no id: lines — matches the non-resumable wire). en locale only — de/es/pt-BR sync is a manual translate-content.mjs pass (not CI-automated), to be run at ship time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pty-slate caveat, ~5-min WS replay, prematch winprob fields - SSE gamestate reconnect: resumed:false + fallback_reason:"channel_unsupported" appear on `connected` only when the reconnect presents a Last-Event-ID (e.g. on the `all` channel, or a hand-set header). Gamestate frames and heartbeats carry no SSE id: line, so a plain EventSource on the gamestate channel reconnects without a Last-Event-ID and gets neither key — the fresh gamestate:snapshot re-bootstrap is the unconditional part. (sse.go resumeAttempted gating; streaming.go sseClientTrySend emits no id: line.) Fixed in gamestate.mdx + stream.mdx. - WS replay buffer: "~2-minute window (2000 events)" was pre-#388 sizing — the default is TTL-bound at 300s (10k entries / 64 MiB caps, replay.go), no prod override. Say ~5 minutes in gamestate.mdx and fix the same stale figure at its source (api-reference/websocket.mdx:635/680, streaming/websocket.mdx:141/150). - Document prematch_winprob_home/away + winprob_dip — shipped alongside the win_prob fields since PR#1070 (gamestate_winprob.go), emitted only when a prematch baseline was captured before go-live. - "well-calibrated" -> "sharp ... with a known conservative tilt": ongoing grading shows favorite underconfidence ~6.5pp / calibration gap ~0.13; the Brier range stays. - Filtered SSE streams receive no gamestate:update at all on cycles matching zero events (streaming.go handleGamestateUpdate skips empty selections), so ended-event-stops-appearing fails for the last matching event — documented the edge case + client mitigation in gamestate.mdx + stream.mdx. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ecision 2026-07-03) Matches enforcement PR sharp-api-go#1168 — Game State add-on keys receive every other gamestate field; the nine win-prob family fields are omitted for non-Enterprise keys. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collaborator
Author
|
Merge gate (auto-classified): gate=docs — auto-classified by the gate-log enforcer (14min after merge, no Posted by |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Documents two gamestate API surfaces that existed in production but were undocumented or documented wrong:
content/en/api-reference/gamestate.mdx) — new "Win probability (tennis)" section coveringwin_prob_home/win_prob_away/win_prob_book/win_prob_at,momentum/momentum_window_s, the prematch baseline fieldsprematch_winprob_home/prematch_winprob_away, andwinprob_dip. Includes sourcing (de-vigged live moneyline, Pinnacle-anchored with cross-book consensus fallback), grading honesty (Brier ~0.02–0.06;momentumis a trend/display signal with no measurable predictive value — explicitly warned against building betting logic on it), and absence semantics (fields omitted when not computable — live tennis with a fresh live moneyline only, nonullplaceholders).gamestate.mdx,stream.mdx) — the two transports were documented as if they behaved identically; they do not:gamestate:update(changed rows only) + explicitgamestate:removedid lists + resumable viaresume=true&from_seq=N.gamestate:updatere-emits the full current slate; there is nogamestate:removedon SSE; the channel is not resumable (Last-Event-IDreplay covers odds only — gamestate frames carry noid:line, and theresumed: false/fallback_reason: "channel_unsupported"ack only appears when a reconnect actually presents aLast-Event-ID).gamestate:updateat all, so clients should expire unrefreshed rows when heartbeats continue but updates stop.websocket.mdxx2) — the documented "2-minute (max 2000 events)" replay buffer is actually TTL-bound at roughly ~5 minutes, best-effort with entry/byte caps; corrected in both the API reference and the streaming guide.Why
Findings from the 2026-07-03 gamestate architecture end-to-end review (wiki:
operations/2026-07-03 Gamestate Architecture End-to-End Review.md). The review traced gamestate from adapters through sharp-api-go to both streaming transports and found the public docs diverged from observed production behavior in exactly these places. An SSE consumer following the previous docs would implement delta-merging and wait forgamestate:removedframes that never arrive, leaving ended matches stuck in local state; the win-prob fields were shipping to Enterprise customers entirely undocumented.Includes review fixes from the same session (SSE reconnect ack gating, filtered empty-slate caveat, ~5-min WS replay wording, prematch winprob fields).
Rollout
Docs-only change — no code, no new env flags, nothing gated. Merge is behavior-neutral for the API itself; the only effect of merging is the Vercel docs-site deploy publishing these pages. Nothing to activate.
Testing
Repo has no pytest/go suites; the deploy gate (
deploy-prod.ymlvalidate job) is typecheck + build + link check, run locally from the worktree:Build-side regeneration churn (
public/openapi.json/openapi-version.jsontimestamp stamps,sitemap.xmllastmod bumps) was discarded rather than committed — this PR intentionally contains only the four content.mdxfiles, so theopenapi-version-checkgate does not trigger.Sibling PRs
Same review produced two api-adapters gamestate PRs (league-prefix fragment collapse — api-adapters#2014 — and tier0 authority/guards). Those two touch the same api-adapters file in different regions and can merge in either order; this docs PR is independent of both — it documents current production behavior, not anything those shadow-gated PRs change. No ordering constraint.
Type: docs
🤖 Generated with Claude Code