From 29854848834028b1f4f1bd8172d1bfb84e687e3a Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Wed, 19 Aug 2026 04:23:24 +0200 Subject: [PATCH] feat: rebuild Infinity Nikki on a live source, at day precision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Game8 page stopped being updated on 31 August 2025 and mentions 2026 zero times, and it had been publishing five year-old events with endsAt: null — which the app renders as live-with-unknown-end indefinitely. That is worse than an empty lane, and nothing in the pipeline catches it, because a stale page is not a broken one: it fetches, it parses, it passes every test. The replacement is the game's Fandom wiki, permitted by the same standard robots.txt that cleared Nikke, maintained to within two days, and shaped as Current / Upcoming / Permanent / Past tables of Event | Duration | Description | Type. Its durations state a wall clock on both sides and no zone anywhere for that column, so parseZonelessClockRange reads the clock and discards it, publishing the printed date at day precision. That invents nothing and treats these cells exactly as every Game8 date already is. Converting instead would mean picking an offset, and the offset moves the day: July 16 20:00 read as UTC-7 is July 17, and the start's day is half of every event ID this game will ever have. The Game8 entry is removed rather than kept as a second opinion, since a source whose every row is wrong is not corroboration. Its fixture stays: it is the only page here carrying the labelled "Start: ... End: Permanent" shape, and the test now drives it through the parser directly. Titles come from a link's title attribute, which never passes through text(), so they are entity-decoded by hand — otherwise "Alison's Travel Shop" becomes a slug, and a slug is a localStorage key. The ingest sanitiser caught exactly that during the build. Co-Authored-By: Claude Opus 5 (1M context) --- docs/DATA-MODEL.md | 5 + docs/INGESTION.md | 5 +- .../fandom-events-2026-08-19.expected.json | 149 +++++++++++ fixtures/nikki/fandom-events-2026-08-19.html | 1 + src/ingest/adapters/index.ts | 21 +- src/ingest/dates.ts | 42 ++++ src/ingest/parsers/fandom.ts | 234 +++++++++++++++++- test/adapters/game8.test.ts | 134 +++++++++- 8 files changed, 560 insertions(+), 31 deletions(-) create mode 100644 fixtures/nikki/fandom-events-2026-08-19.expected.json create mode 100644 fixtures/nikki/fandom-events-2026-08-19.html diff --git a/docs/DATA-MODEL.md b/docs/DATA-MODEL.md index a929065..7df5378 100644 --- a/docs/DATA-MODEL.md +++ b/docs/DATA-MODEL.md @@ -319,6 +319,11 @@ Note also that `nikke` and `nikki` are one letter apart and are different games Nikke and Infinity Nikki. Both are the first segment of every completion key their game will ever have, so a typo in either direction is silent data loss for real readers. +Infinity Nikki's source changed on 2026-08-19 and its silence changed shape with it: the Fandom wiki +states a wall clock on every boundary and no zone for it, so there is still nothing to read a server +map off — the clock is discarded at ingest and only the printed day is published. An offset invented +here would move real readers' day keys. + Infinity Nikki, P5X, Blue Archive, Chaos Zero Nightmare and Umamusume carry **no `resetOffsets` entry**, so they take the regional default. That is an assumption, not a verified server map — none of those sources states one. Two of the 2026-08-19 additions are worth separating out, because they diff --git a/docs/INGESTION.md b/docs/INGESTION.md index 1d7f4da..2c0244f 100644 --- a/docs/INGESTION.md +++ b/docs/INGESTION.md @@ -48,10 +48,10 @@ Consequences worth internalising: | Parser | Site | Sources using it | |---|---|---| -| `game8` | game8.co article calendars | Genshin, Star Rail, Wuthering Waves, ZZZ, Endfield, NTE, Infinity Nikki, Persona 5: The Phantom X, Chaos Zero Nightmare, Umamusume | +| `game8` | game8.co article calendars | Genshin, Star Rail, Wuthering Waves, ZZZ, Endfield, NTE, Persona 5: The Phantom X, Chaos Zero Nightmare, Umamusume | | `wikigg` | wiki.gg MediaWiki `mp-event` templates | Endfield | | `akwiki` | arknights.wiki.gg's `mrfz-wtable` "Ongoing/upcoming" table | Arknights | -| `fandom` | Fandom wikis via the MediaWiki `action=parse` API — three page templates: `Event \| Time Period \| Version` wikitables, FGO's picture-fenced `ONGOING EVENTS` blocks, and Nikke's `Event \| Start(UTC+9) \| End(UTC+9)` tables | Reverse: 1999, Fate/Grand Order, Nikke | +| `fandom` | Fandom wikis via the MediaWiki `action=parse` API — four page templates: `Event \| Time Period \| Version` wikitables, FGO's picture-fenced `ONGOING EVENTS` blocks, Nikke's `Event \| Start(UTC+9) \| End(UTC+9)` tables, and Infinity Nikki's `Event \| Duration \| Description \| Type` article-tables | Reverse: 1999, Fate/Grand Order, Nikke, Infinity Nikki | | `bawiki` | bluearchive.wiki's rendered `/wiki/Events` — a JP/Global tabber over `Name (EN) \| Start date \| End date \| Notes` wikitables | Blue Archive | | `holodoriwiki` | holodori.wiki's rendered `/wiki/Events` — `Current Events` and `Past Events` wikitables over `Event \| Type \| Start Date \| End Date` | hololive Dreams | | `iopwiki` | iopwiki.com's `gf-table event-period` tables — `Title \| Period (start/end) \| Server \| Type \| Comment`, one table per event and one row per server | Girls' Frontline 2 | @@ -145,6 +145,7 @@ All live in `src/ingest/dates.ts`, each returning null rather than inferring any | `parseIsoClockRangeUtc` | `2026-08-06 13:00 - 2026-08-26 22:59 (UTC)` (whole range in one cell, **zone required**, nothing converted) | Girls' Frontline 2 | | `parseIsoOffsetInstant` | `2026-08-03T21:00-07:00` (a machine-readable `