`; cell 1 is the row label (`BATTLESUIT SUPPLY A`, `EVENT 3`)
and every bar carries `col-span-N`, with bare `
` spacers standing in for
an offset start. So a bar's start week is `1 + Σ` the spans before it and its length is its own span,
and the week columns are in the header row. A parser would be positional rather than textual and is
perfectly writable. The blocker is what the numbers mean, not how to read them.
**What would change this:** the site stating a year, and stating a real regional schedule rather than
an estimate over the CN one — plus a stable URL for the current version, or an index page naming it.
Any two of the three still leaves the other. No alternative HI3 source was looked for in this pass, so
the game is unsourced rather than declined outright.
**Superseded 2026-08-27.** The alternative this section did not look for exists — `arustats.com`,
§ 14 below — and it answers three of the four objections above. `marisaimpact.com` itself stays
declined on all four; nothing here has been re-tested and this verdict is not the live one for the
game.
## 14. Honkai Impact 3rd — BUILT 2026-08-27, on estimated week buckets, by decision
**Source:** `https://www.arustats.com/en-us/hi3/timeline` — AruStats' per-version timeline. Assessed
on request as an alternative to the `marisaimpact.com` page § 13 declined, and built with one of
§ 13's four objections still standing.
**Conduct is clean, and better than most things here.** `robots.txt` is `User-agent: *` / `Allow: /`
with `/hi3/*` and `/en-us/*` named explicitly, no `Disallow` anywhere, no `Content-Signal` header and
none of the named AI-crawler blocks; `Crawl-delay: 1`, far below our 6h floor. Verified through the
real `RobotsCache` and the runner's own User-Agent rather than `curl` — `robots.txt ok`,
`crawlDelayMs: 1000` — per AGENTS.md § Fandom on why the instrument matters. The page carries its own
"not affiliated with miHoYo" attribution line, and the host advertises a sitemap.
**Three of § 13's four objections are answered:**
| § 13 objection | Here |
|---|---|
| Dates are CN estimates redrawn for regions | The grid is headed `GLB/SEA` — our readers' server, not a CN schedule with an estimate over it |
| No year appears anywhere | `scheduleDates` states full dates with years — `2026-8-20`, `2026-10-22` |
| The URL is per-version with no stable route | **`/en-us/hi3/timeline` answers `307` to the live version.** The site names its own current version server-side; the runner follows it and no URL ever needs editing |
| The unit is a week column, not an event boundary | **Still true.** Unchanged, and it is the whole cost below |
**The objection that stands.** No event on the page states a date. Events are bars carrying
`startWeek` / `endWeek` *integers* into a nine-column week grid, and the header over that grid reads
`ESTIMATED WEEK`. So every boundary this source publishes is the edge of a bucket the site estimated.
That is weaker than the day-precision reading Game8 and Infinity Nikki get — there the page printed a
date per event and we declined only to invent a time of day for it — and § 13 declined
`marisaimpact.com` on exactly this ground.
**It was built anyway, and that is a decision rather than an oversight** (repository owner,
2026-08-27, with the cost stated before the adapter was written). The reasoning: Honkai Impact 3rd
had no source at all, this page is otherwise healthy and current, and a lane of approximate
six-week windows was judged better than no lane. Be exact about what that buys and what it costs:
- **What is honest about it.** The week grid's *outer* boundaries are real and independently
checkable: v8.9 reads `2026-6-25` → `2026-8-20` and v9.0 picks up at `2026-8-20` → `2026-10-22`,
which are the version's actual maintenance dates and chain exactly. Sixteen bars on the page yield
sixteen events, so nothing is silently dropped.
- **What is not.** Where a banner swaps *inside* a version is the estimated part, and those are most
of the boundaries. `startsAt` is also half of every event ID, so a week grid the site revises moves
IDs and orphans completion marks — a hazard no wiki source here carries.
- **How the fact is carried.** `ESTIMATE_CONFIDENCE` (0.4) on every event, against the 0.85–0.95 a
date-stating source earns. `mergeEvents` prefers the higher number, so a real HI3 source outranks
this one the moment one is added, with no cleanup step to forget.
- **The gap left open.** `confidence` is read nowhere in `src/client/`, so nothing on screen tells a
reader this lane is estimated while every other lane is announced. Closing that needs a schema
field or a client change — a design question, not adapter work — and it is the obvious next step
if this source stays.
**What would retire it:** any HI3 source that states a date per event. That is a strictly lower bar
than § 13's list, and this source outranks nothing — it is the floor, not the ceiling.
**The markup, so nobody re-derives it.** The site is Next.js and server-renders the whole schedule
into `__NEXT_DATA__` (`props.pageProps.timeline`), so `parsers/arustats.ts` reads JSON and ignores the
rendered grid entirely — the bars carry their geometry in Tailwind `col-span-N` classes and their
offsets in bare spacer `
`s, which a DOM reader would have to count positionally. `endWeek` is
exclusive and runs one past the grid to mean "to the end of the version". `scheduleBosses` is the
only exactly-dated material on the page (Abyss and Memorial Arena openings, three a week) and is
deliberately unread: a recurring rotation with no end is not a deadline, and `endsAt: null` on each
would render them live-with-unknown-end forever.
**As of 2026-08-27 the blocker above is addressable.** `src/shared/recurrence.ts`
gives an event a repeat rule, and a rule supplies the boundary the rotation was
missing: an occurrence with no stated end runs until the next one opens, so
`endsAt: null` no longer means live-with-unknown-end forever. Reading
`scheduleBosses` is now a parser change rather than a design question, and it is
Phase B's first target in
`docs/superpowers/specs/2026-08-27-recurring-events-design.md`. Nothing has been
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.
## What every one of these costs, beyond the source
Adding a game is never only a `SOURCES` entry:
- a `GameId` in `src/shared/schema.ts` — an enum value that becomes the first segment of every
completion key for that game, forever;
- a `GAMES` entry in `src/shared/games.ts` (name, short, hue, studio, `dailyTasks`), plus
`resetOffsets` / `resetHourLocal` **only where the source states the clock** — Nikke's is evidenced,
Stella Sora's is not stated on the page we would read;
- a fixture in `fixtures//` and a test asserting parsed output, one commit per game
(`docs/FEEDBACK.md` P1 step 4);
- the lane arriving switched off for existing readers via `adoptNewLanes`, which is automatic but
worth remembering when checking the app after a build.
## Recommended order
Steps 1 through 5 were done on 2026-08-19, and step 6's declines are now written into `AGENTS.md`
§ Scraping conduct. Every game assessed in the § P1 pass is now built or declined with its reasoning
recorded, and the two decisions it raised — Nikke's permission and Infinity Nikki's timezone — have
both been made. What is queued is the two live finds of the § 12 sweep:
1. **MementoMori** (§ 12a) — the cheapest adapter available today: a `GameId`, a `GAMES` entry, one
`SOURCES` entry, a fixture and a test, with no parser change. Seven events parse out of a
seven-row table and six of them are live.
2. **Fire Emblem Heroes** (§ 12b) — better data and a real cost: a ninth Game8 column shape, a
single-date `MM/DD/YYYY` reader, and a decision about the 180-day rule, which one legitimate
seven-month banner breaks. Two commits, parser then source, with the vocabulary widening diffed
across every fixture and snapshot as Umamusume's was.
Below that, in descending order of value: re-check `grayravens.com` if it ever puts its schedule in
a table, re-check Azur Lane if `koumakan` ever drops `ai-input=no`, and re-check `marisaimpact.com`
if it ever prints a year (§ 13). None is worth a pass until something changes upstream.
## Appendix — reproducing the checks
```bash
UA='gacha-event-tracker/1.0 (+https://github.com/StereotypicalCat/gacha-event-tracker)'
# conduct
curl -sS -A "$UA" https://iopwiki.com/robots.txt
curl -sS -A "$UA" -D - -o /dev/null https://stellasora.miraheze.org/wiki/Main_Page # Content-Signal?
# the pages
curl -sS -A "$UA" https://iopwiki.com/wiki/GFL2_Events > gfl2.html
curl -sS -A "$UA" https://stellasora.miraheze.org/wiki/Main_Page > stellasora.html
curl -sS -A "$UA" 'https://nikke-goddess-of-victory-international.fandom.com/api.php?action=parse&page=Event&prop=text&formatversion=2&format=json' > nikke.json
curl -sS -A "$UA" https://game8.co/games/Chaos-Zero-Nightmare/archives/559899 > czn.html
curl -sS -A "$UA" https://marisaimpact.com/robots.txt # comments only, no directive
curl -sS -A "$UA" https://marisaimpact.com/calendar90 > hi3-v90.html
# the § 12 sweep: hubs first, then the schedule page each one turned out to have.
# `--rate 30/m` keeps one request every 2s to a single host, per AGENTS.md § Scraping conduct.
for g in Black-Beacon Brawl-Stars Destiny-Rising Diablo-Immortal Epic-Seven \
fire-emblem-heroes Fire-Emblem-Shadows gundam-uce MementoMori \
Mongil-Star-Dive Pokemon-Champions Pokemon-UNITE Tower-of-Fantasy; do
args+=(-o "hub-$g.html" "https://game8.co/games/$g")
done
curl -sS --rate 30/m -A "$UA" --compressed -w '%{http_code} %{url_effective}\n' "${args[@]}"
# how stale is it? — the first question, and the cheapest
grep -o 'Last updated on:[^<]*' mmori.html
# does an existing parser read it? (offline, once a source id exists)
bun run parse czn-game8-events czn.html --json
```
Facts stated above that a future reader may want to re-check, with how they were established:
| Claim | How it was checked |
|---|---|
| game8 has no wiki hub for Nikke, GFL2, PGR, Azur Lane, Guardian Tales, Stella Sora, Aether Gazer | `HEAD https://game8.co/games/` → 404 for each, 200 for Umamusume-Pretty-Derby and Chaos-Zero-Nightmare |
| The `game8` parser already reads the CZN page | ran `game8Parser.parse` against the fetched bytes: 4 events, 2 correctly skipped for a missing start |
| `fandom.ts`, `akwiki`, `wikigg`, `bawiki`, `holodori` cannot read the new pages | `canParse` returned `false` for every combination tried |
| CI can fetch wiki.gg and Miraheze | `git log --name-only -- snapshots/` — `github-actions[bot]` commits carry `arknights-akwiki`, `endfield-wikigg`, `ba-bawiki` |
| A Fandom `robots.txt` 403 means the source is skipped, not failed | `src/ingest/robots.ts` `RobotsCache.load` — `status >= 400` other than 404/410 → `usable: false` |
| On a served address Fandom answers the runner's own client, and only `curl` was challenged | `bun -e` with `RobotsCache` + `DEFAULT_USER_AGENT` → `allowed`, `robots.txt ok`, all four sources; `curl` → `403` 8/8 with both a bare and a Chrome `User-Agent` |
| A served address is still a precondition, so that is not schedulability | run `a60eb66` (17:46 UTC, `ubuntu-latest`) → `skipped_robots` on all four Fandom sources, same Bun client, alongside nine game8 `202`s |
| All thirteen § 12 hubs exist | `GET https://game8.co/games/` → `200` for every one |
| The `game8` parser already reads the MementoMori events page | ran `game8Parser.parse` against the fetched bytes: 7 events from the 7 data rows of `Current Events \| Duration` |
| The FEH page is unreadable by shape, not by two header words | `columnLayout` finds no range column at all — `Availability` is a start and `End` is its own column, and `readStartEndTable` wants the rowspan `Start`/`End` pair |
| Game8's structural check rejects the Pokémon UNITE page | `game8Parser.canParse` → `false`; the page has 25 `a-table` matches and zero `a-header--3` |
| A hub's nav is not an index of its pages | the UNITE, Champions, Gundam and Black Beacon schedule pages exist and are linked from none of the hub navigation scanned; found by web search |
| `marisaimpact.com` states no year on any calendar | stripped tags from `/calendar89` and `/calendar90` and grepped the text for `20\d\d` — zero matches on either |
| Its calendar URL is per-version, with no stable alias | `/calendar90` is `200` and covers `Aug 20 - Oct 22`; `/valk/calendar90` — the scheme its own `og:url` uses — is `404`; the nav links only `/calendar88` and `/calendar89` |
| Its schedule is not behind an API | `GET /calendar90/__data.json` returns 144 bytes: an `ogImage` URL and nothing else |