Racket Rankings
Track match results and rankings for singles and doubles tennis or padel matches
翻译准备中
AI 摘要 · 网站内容
尚未检测到更新。GitHub 发布版本后会显示在这里。
You are at the baseball game and you want your phone to show you who is at the plate, who is pitching, and how they do against each other and other relevant stats. Live. Updating every pitch.
翻译准备中
正在加载完整 README。
查看完整 README ↗Live batter-vs-pitcher matchup data for any MLB or MiLB game. Built for at-the-park use.
Free, no ads, no IAP. Uses the MLB Stats API (non-commercial individual use).
brew install xcodegen
cd "/Users/nick/Claude Code/BallparkMatchups"
xcodegen generate
This creates BallparkMatchups.xcodeproj. Open it in Xcode, set your Development Team in Signing & Capabilities, and build.
The app bundles Resources/venues.json (168 venues, fetched 2026-05-12). It refreshes automatically every 30 days.
AppViewModel top-level state machine, session persistence
└── GameViewModel per-game polling loop, diff logic, split selection
MLBAPIClient all network calls (actor, one in-flight request per game)
VenueCache bundled + refreshed venue list, Haversine matching
LocationService CLLocationManager wrapper
SplitPriorityEngine §10.4 priority ranking + §10.5 info-gain filter
LiveFeedStream experimental push path: Gameday socket + diffPatch
├── GamedaySocket reconnecting websocket, heartbeat, dedupe
└── JSONValue raw JSON tree + RFC 6902 patch application
Each poll fetches the entire GUMBO object from
/api/v1.1/game/{gamePk}/feed/live — there is no server-side diff on this path,
and the payload grows all game as plays accumulate. What keeps the cost down is
what happens after the fetch: diffTickState reduces the feed to a nine-field
tuple and classifies the change as none / countOnly / situational / full,
and only the last two trigger the expensive BvP and splits calls. Most polls
resolve to none and cost nothing further.
An alternative to re-downloading the whole game every five seconds: subscribe to the Gameday websocket and fetch only the changes.
wss://ws.statsapi.mlb.com/api/v1/game/push/subscribe/gameday/{gamePk}
https://ws.statsapi.mlb.com/api/v1.1/game/{gamePk}/feed/live/diffPatch
?language=en&startTimecode={ours}&pushUpdateId={theirs}
The socket is a doorbell, not a data feed — it carries an updateId and little
else. The change set is fetched separately, as an array of RFC 6902 operations,
and applied to a locally held copy of the game object.
Three things make this harder than it looks:
startTimecode is our timestamp, not theirs. The server needs to know
where the client is, so it is metaData.timeStamp of the copy already held —
updated after every successful patch.changeEvent.type == "full_refresh" telling
the client to discard its copy. Both must be handled.LiveFeedResponse never decodes. Patching a decoded model would
corrupt it, so JSONValue holds the full tree and LiveFeedResponse is
re-derived after each change.Every failure path ends in a full refetch. Patches are applied to a copy and committed only on success, so a mid-batch throw leaves the live tree untouched — stale-but-correct beats partially-patched.
Polling is not switched off when this is on; it drops to 60s as a backstop. A
dropped socket event is invisible — there is no error to catch and nothing to
retry — so the loop stays as a net, and diffTickState discards the redundant
work for free.
Status: unproven. The path cannot be exercised outside a live game. The patch
algorithm is verified against captured MLB traffic (Tools/diffpatch-verify),
but the socket, the endpoints, and the failure handling have not been watched
through a real one. Enable it from the debug overlay and compare the byte
counters against the polling backstop before trusting it.
First thing to run on a Mac:
cd Tools/swift-verify && swift test # typechecks the patch core under Swift 6
open BallparkMatchups.xcodeproj # then build the app
Tools/swift-verify symlinks BallparkMatchups/API/JSONValue.swift rather than
copying it, so there is one source of truth and swift test compiles the same
code the app ships.
Long-press (1.5s) anywhere on the game screen to toggle the debug overlay. Shows polling interval, last response time, request count, candidate splits vs. shown, last refresh kind.
When the push feed is enabled it also shows socket state, patches applied, full refreshes, patch failures, and bytes over push vs. an estimate of what the same updates would have cost as full polls. Tapping the "Push feed" row toggles the flag; it takes effect when the game screen is reopened.
Headshots, push notifications, favorites, Apple Watch, light mode, pitch-by-pitch detail, multiple simultaneous games.