add Genshin Impact Fandom event source

Add genshin-impact.fandom.com as a second source for Genshin Impact,
which has been fixture-backed in CI because Game8 CloudFront-blocks the
Actions runner with 202s.

- Add fifth Fandom template parser in src/ingest/parsers/fandom.ts
  extracting titles from caption display text, stripping dated subpage
  suffixes, and checking currency via latestBoundaryMs.
- Register genshin-fandom-events in src/ingest/adapters/index.ts and
  set genshin-game8-events priority: 10 so existing event IDs survive
  near-match ties, preventing localStorage completion marks from
  orphaning.
- Explicitly sort adapter groups by priority descending in
  scripts/build-feed.ts to align with parseGame.
- Pin fixtures and add test coverage in test/adapters/game8.test.ts.
- Update AGENTS.md, README.md, docs/INGESTION.md, and docs/SOURCES.md.
This commit is contained in:
Lucas Winther
2026-09-12 05:20:56 +02:00
parent 0f224b62f3
commit 81ac9fb3dd
10 changed files with 771 additions and 22 deletions
+8 -8
View File
@@ -51,7 +51,7 @@ Consequences worth internalising:
| `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 — 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 |
| `fandom` | Fandom wikis via the MediaWiki `action=parse` API — five page templates: `Event \| Time Period \| Version` wikitables, FGO's picture-fenced `ONGOING EVENTS` blocks, Nikke's `Event \| Start(UTC+9) \| End(UTC+9)` tables, Infinity Nikki's `Event \| Duration \| Description \| Type` article-tables, and Genshin Impact's `Event \| Duration \| Type(s)` tables | Reverse: 1999, Fate/Grand Order, Nikke, Infinity Nikki, Genshin Impact |
| `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 |
@@ -140,7 +140,7 @@ All live in `src/ingest/dates.ts`, each returning null rather than inferring any
|---|---|---|
| `parseMonthDayYear` | `August 12, 2026` | Genshin detail rows |
| `parseMonthDayRange` | `August 12 - September 21, 2026` (year on the end only) | Genshin, NTE |
| `parseFullRange` | `Aug. 14, 2026 - Aug. 24, 2026` (a year each side) | Star Rail, Wuthering Waves, Fate/Grand Order |
| `parseFullRange` | `Aug. 14, 2026 - Aug. 24, 2026` (a year each side) | Star Rail, Wuthering Waves, Fate/Grand Order, Genshin Impact (Fandom) |
| `parseShortSlashRange` | `08/09/26 - 08/30/26` | Endfield |
| `parseSlashDateTimeRange` | `2021/01/16 04:00 - 2021/01/31 03:59` | Genshin past events |
| `parseLabelledStartEnd` | `Start: January 24, 2025 End: Permanent` | Infinity Nikki |
@@ -161,15 +161,15 @@ midnight because it has to store *something*. It is not a statement that the eve
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).
**No parser may store a resolved boundary, and three of them must read one to decide inclusion.**
**No parser may store a resolved boundary, and four of them must read one to decide inclusion.**
The stored value stays the printed day at 00:00Z: resolving it here would need a region the parser
does not have, and would bake one reader's server into the feed everybody downloads. But a parser
whose page carries no "ongoing" heading it can trust decides currency against `ctx.now` itself —
`bawiki.ts`, and the Fate/Grand Order and Infinity Nikki branches of `fandom.ts` and comparing the
placeholder to `now` retires a row at UTC midnight, hours before `clockFor` calls it over for
anybody. The reader does not see a stale row; they watch the deadline they were counting down to
disappear on its last day, which is the silent drop AGENTS.md § Working on parsers calls the
dangerous failure. So those three ask `latestBoundaryMs` (`src/shared/time.ts`) when the boundary is
`bawiki.ts`, and the Fate/Grand Order, Infinity Nikki and Genshin Impact branches of `fandom.ts`
and comparing the placeholder to `now` retires a row at UTC midnight, hours before `clockFor` calls
it over for anybody. The reader does not see a stale row; they watch the deadline they were counting
down to disappear on its last day, which is the silent drop AGENTS.md § Working on parsers calls the
dangerous failure. So those four ask `latestBoundaryMs` (`src/shared/time.ts`) when the boundary is
day-precision: the last region's reset, and therefore the instant the row is history for every
reader rather than for the earliest of them. Being generous by nine hours costs one expired row at
the bottom of a list; being strict costs a live one.
+18 -1
View File
@@ -59,6 +59,7 @@ Two things this method cannot tell you, and both matter:
| Infinity Nikki | **Rebuilt** (2026-08-19) on `infinity-nikki.fandom.com` at day precision, replacing a Game8 page stale since August 2025 — see § 11 |
| Silver Palace | Unreleased |
| Honkai Impact 3rd | **Built** (2026-08-27) on `arustats.com`, and the only lane here whose dates are **estimates** — see § 14 |
| Genshin Impact (Fandom) | **Built** (2026-09-12) on `genshin-impact.fandom.com`, second source for a blind lane — see § 15 |
## Summary of findings
@@ -74,6 +75,7 @@ Two things this method cannot tell you, and both matter:
| Azur Lane | none | — | — | **Still declined** — the Fandom alternatives are 2021 archives |
| Aether Gazer | — | — | — | **Do not build. The game is shutting down.** |
| Silver Palace | — | — | — | Unreleased; beta only |
| Genshin Impact (Fandom) | `genshin-impact.fandom.com/api.php?…page=Event` | `fandom`, fifth template | yes (via `api.php`) | **BUILT** 2026-09-12 — adds 11 events Game8 missed; Game8 priority preserves localStorage IDs |
---
@@ -484,7 +486,7 @@ table.
**One cost applies to both builds and should be in the commit message, not discovered later.** Both
candidates are game8.co, which does not answer the Actions runner (`AGENTS.md` § Scraping conduct).
Nine of the twenty sources are game8 pages today, now that Infinity Nikki has moved to Fandom
Nine of the twenty-one sources are game8 pages today, now that Infinity Nikki has moved to Fandom
(§ 11); these would make ten and eleven. Each is a lane fixture-backed in CI from day one and only
ever as fresh as someone's last manual `bun run refresh`, and one more request to a single host every
cycle — the per-host arithmetic § Scraping conduct already calls uncomfortable.
@@ -767,6 +769,21 @@ Phase B's first target in
changed on the ingest side yet — `GachaEvent` does not carry a rule, and this
note is what stops the next person re-deriving the reason it does not.
## 15. Genshin Impact (Fandom) — BUILT 2026-09-12, adding a second source for a blind lane
**Source:** `https://genshin-impact.fandom.com/api.php?action=parse&page=Event&prop=text&formatversion=2&format=json` (page at `https://genshin-impact.fandom.com/wiki/Event`).
**Conduct:** Standard Fandom `robots.txt` (`Allow: /api.php?action=`, no `Disallow: /` for `*`, no `Content-Signal`). Read with the runner's client; CI can fetch it.
**Why built:** Genshin's Game8 page has never been fetchable from CI (CloudFront returns a `202` on all runners), so until now Genshin was fixture-backed in CI and could only age. Fandom provides a second, active source that scheduled refreshes can update.
**The template:** Fifth Fandom template. `Current Events` and `Upcoming Events` wikitables under `h3` headings with columns `Event | Duration | Type(s)`.
- **Title extraction:** Display text of the caption link beneath the banner image is taken. Link `title` (parent subpage) and `img alt` (uploaded image file name) are rejected to prevent corrupting event titles and IDs.
- **Run-date suffixes:** Recurring event run dates (`2026-09-14`) in subpage names are stripped so dates aren't duplicated.
- **Currency:** `latestBoundaryMs` is checked for day-precision boundaries so events aren't retired before all server regions roll.
- **Priority & IDs:** Game8 is assigned `priority: 10` while Fandom is default/0. Two live events differ slightly in naming (`To Temper Thyself and Journey Far` vs `… Cycle 5`, and `Stygian Onslaught` vs `…: Battle of the Starburst`). Game8's higher priority breaks near-match ties in `merge.ts`, ensuring existing localStorage event IDs survive and reader completion marks remain intact. Fandom contributes 11 net-new events Game8 missed, 0 date conflicts occur, and overlapping events gain corroboration bonuses.
- **336-day event:** Genshin's anniversary 5-star selection runs 336 days (`2025-10-22``2026-09-23`), prompting the test duration ceiling to be raised from 180 to 365 days across the test suite and ingestion specs.
## What every one of these costs, beyond the source
Adding a game is never only a `SOURCES` entry: