Releases
1.9.5
For you
- A forecast you already saw for a game that has kicked off can no longer be quietly replaced by one of these new runs; the one you saw before kickoff is the one that stands.
- Nothing changes for you. This corrects two comments that explain how the site reaches live scores.
- Nothing changes for you. This is an internal measurement collector, not part of the site you read.
- Nothing changes for you. This is a maintenance job that keeps an internal lookup table fresh so player matching behind the scenes does not quietly go stale.
Under the hood
- The injury and inactives run kinds existed as accepted names with no behaviour of their own: both took the full path, so an inactives run during the afternoon wave would have re-forecast games that kicked off at one o'clock. Both now freeze games that have started and skip the batch narratives, the way a refresh does. Their firing times are derived rather than written by hand, because the useful window is ninety minutes wide, there are three kickoff waves, and the season spans two clock offsets, with the evening wave crossing midnight UTC in both; a test computes every window and fails if the committed times miss one. A kind that fires while another run is in flight now waits once and then starts, logging loudly instead if the run is still busy, where before it was a silent no-op that looked like a quiet Sunday.
- The three firing times above were not the whole week. They reached Sunday and no other day, so a game kicking off Thursday, Monday, Saturday, or the occasional Friday or Wednesday, got no dedicated firing at all, and neither did the handful of international games that kick off in the late morning. 18 of the 37 games in the Sunday late afternoon wave went uncovered too, every one of them in the winter half of the season and none in the summer half, because one firing had been assumed to also catch a second wave that actually starts a few minutes later. Every game of the season now gets a dedicated firing, fifteen in place of three, computed from a committed fixture of that season's real kickoffs; the fixture names its own season, and the build now fails loudly if that name stops matching the season running, rather than counting on next year's fixture being regenerated on time.
- Two comments in the live score client described requests the code does not make. One blamed a refused hostname on the network our container runs in. Measured again this week, that hostname refuses a home laptop and a worker alike, on every path tried, while two sibling hostnames serve both callers the same bytes, so the refusal belongs to the hostname and to nothing else. The original reading came from a comparison that changed two things at once. The other comment said a descriptive user agent is always turned away where a plain one is accepted; re-measured, both are accepted. The request itself does not change, because what we send today works and has worked continuously since August. What changes is what a future reader may infer from it while diagnosing a refusal: check the hostname first. Both corrections are dated beside the text they correct, which stays in place.
- The Phase 0 measurement collector was pointed at hosts that answer. It had been running since 2026-09-10 reporting success on every run while recording nothing but refusals from two endpoints that cannot serve it, so the measurement window had not started. A run that observes nothing now fails loudly instead of passing quietly, and its parsing moved into a tested module rather than an untested inline script.
- The same collector's measurement can no longer clear its own floors on data it does not have. Its observation span counted every tick it ran, including the refused ones, so twelve days of recording nothing followed by two days of real observation read as a fortnight and cleared a floor that exists to require two full game weeks; the span now counts only the ticks that observed something, and the report states each tier's span and the largest gap between consecutive observations beside it. The schedule was four days a week for eight hours a day, which left two blind stretches of about 65 hours in which only the newest official release is ever seen, so every lead first published inside one was credited to a later release and overstated, always in the direction of building the thing. It polls every fifteen minutes every day now, four requests an hour per host, inside the rate this project's own spec declares. The floors, the delivery promises and the verdict moved out of the report script into the tested module, unchanged in value, because nothing lints or tests that script and it owned the decision; a test now pins the three floor values. The release request carries the workflow's own token, and an absent token is a recorded failure rather than a quiet fall back to a rate budget shared with every other job on the runner.
- The player id crosswalk now refreshes on the nightly schedule and lands somewhere a Worker can read it. Until now the only copy lived wherever it was last built by hand, so its match rate would have decayed with no visible symptom.