fix: count down to a game's reset, not to UTC midnight
A source that prints "August 19, 2026" and no time is stored at 00:00Z, which is a placeholder for "somewhere in this day" rather than a claim about when the day opens. The countdown read it literally, which turns it into exactly that claim — and the UTC day is nobody's. Wuthering Waves events dated the 19th were still running three hours after we had retired them, because a European player's day opens at 04:00 on a UTC+1 server; Asia was four hours out the other way and America nine. Sixty-two of the eighty-six published events were affected, across ten of thirteen games, so the reader was reading a wrong number far more often than a right one. A day-precision boundary now resolves to the reset that opens that game-day on the reader's server — the same grid daily.ts keys every tick by, and the only clock we hold for a game. That is a reading of the date the source printed, not a time invented for it: nothing stored moves, no event ID moves, and the parsers still refuse to guess. Two boundaries are never re-anchored. A regionEnds value is already the instant a source stated per server, so anchoring it would throw a fact away. And an event the reader typed in was resolved by readerInstant in their own timezone, which is a stated time too — theirs. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
00c3f5e2da
commit
4334988ecf
+18
-1
@@ -79,6 +79,23 @@ true`, with `regionEnds` carrying the three resolved UTC instants. The client pi
|
||||
user's stored region (PRD F5). Collapsing these into a single timestamp loses up to 13 hours of
|
||||
accuracy and will make the countdown wrong for two thirds of users.
|
||||
|
||||
**`startPrecision` / `endPrecision`, and what 00:00Z means.** A source that prints a calendar date
|
||||
and no time of day gets `"day"` precision, and the instant stored alongside it is that date at
|
||||
00:00Z. That timestamp is a **placeholder for "somewhere in this day", not a claim that the day
|
||||
begins at UTC midnight** — the parser has declined to invent a time, exactly as it declines to
|
||||
invent a date. Nothing downstream may read it as an instant: `clockFor` in `src/shared/time.ts`
|
||||
resolves a day-precision boundary to the reset that opens that game-day on the reader's server
|
||||
(`dayStartMs`), which is the same clock `daily.ts` keys every tick by, and the countdown and the
|
||||
detail sheet both run off that. Read literally instead, the stored value expires an event up to nine
|
||||
hours early — the whole of Asia and Europe — which is how a Wuthering Waves event dated "August 19"
|
||||
was called over three hours before the game ended it.
|
||||
|
||||
Two boundaries are exempt, for the same reason in both directions. A `regionEnds` value is taken
|
||||
verbatim: that map only exists because a source published a timer per server, so it is already the
|
||||
instant, and re-anchoring it would throw a stated fact away. And an event the reader typed in
|
||||
(`extractionMethod: "manual"`) is taken verbatim too: `readerInstant` resolved it to the instant
|
||||
*they* meant, in their own timezone, when they entered it.
|
||||
|
||||
**`confidence`** is assigned by the parser and adjusted during merge and reconciliation — see
|
||||
`docs/INGESTION.md` § Scoring. It records how firmly the sources pinned the event down.
|
||||
|
||||
@@ -392,7 +409,7 @@ to:
|
||||
| Rule | Why |
|
||||
|---|---|
|
||||
| `endsAt` null pairs with `endPrecision: "unknown"` | The same invariant `GachaEvent` enforces. "I don't know when this ends" is a supported answer for a reader too, and a required one — otherwise entering an unannounced event forces them to invent a date |
|
||||
| A date with no time is `"day"` precision, a date with one is `"exact"` | So the UI's existing "accurate to the day only" note is honest about their input as well |
|
||||
| A date with no time is `"day"` precision, a date with one is `"exact"` | So the UI's existing "accurate to the day only" note is honest about their input as well. Their day-precision boundary is still *their* instant, though — `readerInstant` puts a start at 00:00 and an end at 23:59:59 in their own timezone, so unlike a parser's placeholder it is never re-anchored to a server reset |
|
||||
| `endsAt` must be after `startsAt` | A backwards interval is a typo whoever made it |
|
||||
| `hue` must match `#rrggbb` | It reaches a `style` attribute, and an imported file is not necessarily one the reader wrote |
|
||||
| `regionScoped` is always false | They entered one instant, not a per-region map. Claiming otherwise would fabricate three timestamps out of one |
|
||||
|
||||
@@ -142,6 +142,14 @@ All live in `src/ingest/dates.ts`, each returning null rather than inferring any
|
||||
| `parseSlashClockZone` | `08/17/2026 8:00PM (JST)` (one boundary per column, 12-hour clock, **named** zone) | hololive Dreams |
|
||||
| `parseOpenRange` | `Jul. 24, 2026 - End of 4.6`, `July 10, 2026 - Permanent` | Star Rail, Wuthering Waves |
|
||||
|
||||
**A day-precision result is 00:00Z, and that is a placeholder rather than a time.** Every reader
|
||||
above returns `precision: "day"` when the source printed no clock, and stores the date at UTC
|
||||
midnight because it has to store *something*. It is not a statement that the event begins or ends
|
||||
then, and nothing may count down to it literally: `clockFor` resolves a day-precision boundary to
|
||||
that game-day's server reset for the reader's region (`docs/DATA-MODEL.md` § Field notes). The
|
||||
parsers are unaffected by this and must stay so — resolving here would need a region the parser does
|
||||
not have, and would bake one reader's server into the stored feed.
|
||||
|
||||
`parseOpenRange` is tried last because it is the most permissive — it accepts any leading full date
|
||||
and reports no end.
|
||||
|
||||
|
||||
@@ -287,6 +287,12 @@ The app's entire value is that the dates are right. Therefore:
|
||||
- An event whose confidence is below threshold, or whose sources disagree, is not published at all
|
||||
until a human approves it.
|
||||
- Every event links to its source so a skeptical user can verify in one click.
|
||||
- **A date with no time is resolved on the game's clock, never on UTC's.** Most sources print
|
||||
"August 19, 2026" and no time of day, which is stored as 00:00Z — a placeholder, not an instant.
|
||||
The countdown reads it as that game-day's server reset for the reader's region, because a literal
|
||||
reading retires an event up to nine hours before the game does and the reader is standing in the
|
||||
game while we say it. This is a reading of the date the source printed, not a time invented for
|
||||
it, and the detail sheet says as much.
|
||||
|
||||
An empty calendar is a recoverable disappointment. A confidently wrong end date is the failure this
|
||||
product exists to prevent.
|
||||
|
||||
Reference in New Issue
Block a user