fix(sources): Mira Journey ends at Asia's reset after the version's last day
The Version table prints the version's last day as its End Date. Read as an ordinary day-precision end, the event left the calendar 9 to 24 hours before the pass did. The owner's timer on Europe put the end at about 20:00 UTC on 29 Oct: Asia's 04:00 reset opening the 30th, for every server. The source's entry now says so, with that evidence, and the event ends at exactly 2026-10-29T20:00Z. Found in the whole-branch review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cbcd8c594c
commit
1b116218e3
4 files changed
+41
-20
No files matched your search
+1
-1
@@ -883,7 +883,7 @@ passed. A `fetched` with more than zero events means the wiki is back.
|
||||
|
||||
**Source:** `https://infinity-nikki.fandom.com/api.php?action=parse&page=Version&prop=text&formatversion=2&format=json` (page `https://infinity-nikki.fandom.com/wiki/Version`), source id `nikki-fandom-mira-journey`, parser `fandomversion` (`src/ingest/parsers/versionTable.ts`).
|
||||
|
||||
**What it is:** Mira Journey has no dated page. The `Version` page dates *versions*: three tables headed `Version Name | Version Number | Release Date | End Date | Duration` (2.x, 1.x, prerelease), 25 rows in all (counted independently of the parser), e.g. `The Feather of Justice | 2.10 | September 23, 2026 | October 29, 2026 | 36 Days`. A version stands in for the pass only because the owner confirmed in game, 2026-10-16, that the pass ends with it (it read 13d 19h at 00:47 UTC, ending 2026-10-29 with version 2.10). That claim and its evidence are one entry in `VERSION_PASSES`, keyed by source id; the event's summary repeats it (`Follows version 2.10 (The Feather of Justice). That the pass ends with the version was confirmed in game, 2026-10-16.`). Rows already ended are skipped, a row without a readable end is skipped; at capture exactly one row survives: 2026-09-23 to 2026-10-29, day precision, titled `Mira Journey`. The page states no zone, so nothing is region-scoped.
|
||||
**What it is:** Mira Journey has no dated page. The `Version` page dates *versions*: three tables headed `Version Name | Version Number | Release Date | End Date | Duration` (2.x, 1.x, prerelease), 25 rows in all (counted independently of the parser), e.g. `The Feather of Justice | 2.10 | September 23, 2026 | October 29, 2026 | 36 Days`. A version stands in for the pass only because the owner confirmed in game, 2026-10-16, that the pass ends with it (it read 13d 19h at 00:47 UTC, ending 2026-10-29 with version 2.10). That claim and its evidence are one entry in `VERSION_PASSES`, keyed by source id; the event's summary repeats it (`Follows version 2.10 (The Feather of Justice). That the pass ends with the version was confirmed in game, 2026-10-16.`). Rows already ended are skipped, a row without a readable end is skipped; at capture exactly one row survives, titled `Mira Journey`, from 2026-09-23 (day precision). **Its End Date is the version's last day**, not the day it ends: the owner's timer on Europe put the end between 19:47 and 20:47 UTC on 2026-10-29, which is Asia's 04:00 reset opening the 30th. So the entry carries `endsAfterLastDay: { clock: "asia" }` and the event ends at exactly 2026-10-29T20:00Z for every server. Read as an ordinary day-precision end it would have left the calendar 9 to 24 hours early, before the pass did. Nothing is region-scoped.
|
||||
|
||||
**Not the history-table reader:** `fandomtable` refuses any table with a `Duration` heading and this reader requires one, so neither claims the other's page (tested across every fixture).
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"summary": "Follows version 2.10 (The Feather of Justice). That the pass ends with the version was confirmed in game, 2026-10-16.",
|
||||
"startsAt": "2026-09-23T00:00:00.000Z",
|
||||
"startPrecision": "day",
|
||||
"endsAt": "2026-10-29T00:00:00.000Z",
|
||||
"endPrecision": "day",
|
||||
"endsAt": "2026-10-29T20:00:00.000Z",
|
||||
"endPrecision": "exact",
|
||||
"regionScoped": false,
|
||||
"regionEnds": null,
|
||||
"sourceUrl": "https://infinity-nikki.fandom.com/wiki/Version",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { eventId, type GachaEvent } from "../../shared/schema.ts";
|
||||
import { latestBoundaryMs } from "../../shared/time.ts";
|
||||
import { eventId, type GachaEvent, type GameId, type Region } from "../../shared/schema.ts";
|
||||
import { DAY, dayStartMs, latestBoundaryMs } from "../../shared/time.ts";
|
||||
import { parseMonthDayYear, type ParsedInstant } from "../dates.ts";
|
||||
import { text } from "../html.ts";
|
||||
import type { ParseContext } from "../adapters/types.ts";
|
||||
@@ -53,6 +53,13 @@ export interface VersionPass {
|
||||
/** The sentence the summary closes with: the stated link, with its source. */
|
||||
readonly link: string;
|
||||
readonly evidence: VersionLinkEvidence;
|
||||
/**
|
||||
* When the table's End Date is the version's *last* day rather than the day
|
||||
* it ends on: the pass then ends at `clock`'s reset opening the next day, for
|
||||
* every server. Only where the evidence shows it; absent, the End Date is
|
||||
* read as other day-precision ends are, the reset opening that day.
|
||||
*/
|
||||
readonly endsAfterLastDay?: { readonly clock: Region };
|
||||
}
|
||||
|
||||
/** Keyed by source id, because the claim is a fact about the source. */
|
||||
@@ -64,8 +71,9 @@ export const VERSION_PASSES: Readonly<Record<string, VersionPass>> = {
|
||||
by: "owner",
|
||||
on: "2026-10-16",
|
||||
reading:
|
||||
"in game, the pass read 13d 19h at 00:47 UTC, ending 2026-10-29 with version 2.10",
|
||||
"in game on Europe, the pass read 13d 19h at 00:47 UTC: an end between 19:47 and 20:47 UTC on 2026-10-29, the table's End Date for version 2.10 — Asia's 04:00 reset opening 2026-10-30, not Europe's",
|
||||
},
|
||||
endsAfterLastDay: { clock: "asia" },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -146,6 +154,16 @@ function pageUrl(sourceUrl: string): string {
|
||||
: `${url.origin}/wiki/${encodeURIComponent(page).replace(/%2F/g, "/")}`;
|
||||
}
|
||||
|
||||
/** A row's end, moved to the stated reset when the table prints a last day. */
|
||||
function endOf(end: ParsedInstant, pass: VersionPass, game: GameId): ParsedInstant {
|
||||
if (pass.endsAfterLastDay === undefined || end.precision !== "day") return end;
|
||||
const next = new Date(Date.parse(end.iso) + DAY).toISOString().slice(0, 10);
|
||||
return {
|
||||
iso: new Date(dayStartMs(next, pass.endsAfterLastDay.clock, game)).toISOString(),
|
||||
precision: "exact",
|
||||
};
|
||||
}
|
||||
|
||||
export function parseFandomVersionTable(
|
||||
body: string,
|
||||
ctx: ParseContext,
|
||||
@@ -161,9 +179,10 @@ export function parseFandomVersionTable(
|
||||
|
||||
for (const row of versionRows(rendered).rows) {
|
||||
if (row.end.iso <= row.start.iso) continue;
|
||||
const end = endOf(row.end, pass, ctx.game);
|
||||
// Day-precision end: judged by the last moment any reset could put it at,
|
||||
// not the UTC-midnight placeholder (AGENTS.md § Domain rules).
|
||||
if (latestBoundaryMs(row.end.iso, row.end.precision, ctx.game) < nowMs) {
|
||||
if (latestBoundaryMs(end.iso, end.precision, ctx.game) < nowMs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -175,8 +194,8 @@ export function parseFandomVersionTable(
|
||||
summary: `Follows version ${row.number} (${row.name}). ${pass.link}`,
|
||||
startsAt: row.start.iso,
|
||||
startPrecision: row.start.precision,
|
||||
endsAt: row.end.iso,
|
||||
endPrecision: row.end.precision,
|
||||
endsAt: end.iso,
|
||||
endPrecision: end.precision,
|
||||
regionScoped: false,
|
||||
regionEnds: null,
|
||||
sourceUrl: pageUrl(ctx.sourceUrl),
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("fandomversion parser", () => {
|
||||
// Only 2.10 (to October 29) has not ended on 2026-10-16.
|
||||
const now = fandomVersionParser.parse(page, ctx("2026-10-16T00:00:00.000Z"));
|
||||
expect(now.map((e) => [e.title, e.startsAt, e.endsAt])).toEqual([
|
||||
["Mira Journey", "2026-09-23T00:00:00.000Z", "2026-10-29T00:00:00.000Z"],
|
||||
["Mira Journey", "2026-09-23T00:00:00.000Z", "2026-10-29T20:00:00.000Z"],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -52,18 +52,19 @@ describe("fandomversion parser", () => {
|
||||
"Follows version 2.10 (The Feather of Justice). That the pass ends with the version was confirmed in game, 2026-10-16.",
|
||||
);
|
||||
expect(e?.startPrecision).toBe("day");
|
||||
expect(e?.endPrecision).toBe("day");
|
||||
// The table's End Date is the version's last day. The owner's timer on
|
||||
// Europe put the pass's end at ~20:00 UTC on it: Asia's 04:00 reset that
|
||||
// opens the next day, for every server.
|
||||
expect(e?.endsAt).toBe("2026-10-29T20:00:00.000Z");
|
||||
expect(e?.endPrecision).toBe("exact");
|
||||
expect(e?.type).toBe("other");
|
||||
});
|
||||
|
||||
test("after 2026-10-29 the 2.10 row is gone", async () => {
|
||||
expect(fandomVersionParser.parse(await html, ctx("2026-10-30T00:00:00.000Z"))).toEqual([]);
|
||||
});
|
||||
|
||||
test("the row stays on its last day", async () => {
|
||||
expect(
|
||||
fandomVersionParser.parse(await html, ctx("2026-10-29T05:00:00.000Z")),
|
||||
).toHaveLength(1);
|
||||
test("the 2.10 row stays through its last day and is gone at Asia's next reset", async () => {
|
||||
const at = async (now: string) => fandomVersionParser.parse(await html, ctx(now)).length;
|
||||
expect(await at("2026-10-29T15:00:00.000Z")).toBe(1);
|
||||
expect(await at("2026-10-29T19:59:00.000Z")).toBe(1);
|
||||
expect(await at("2026-10-29T20:01:00.000Z")).toBe(0);
|
||||
});
|
||||
|
||||
test("a row without a readable end is skipped, not given one", () => {
|
||||
@@ -75,7 +76,8 @@ describe("fandomversion parser", () => {
|
||||
]),
|
||||
ctx("2026-09-10T00:00:00.000Z"),
|
||||
);
|
||||
expect(events.map((e) => e.endsAt)).toEqual(["2026-10-12T00:00:00.000Z"]);
|
||||
// End Date October 12 is the last day: Asia's reset opening the 13th.
|
||||
expect(events.map((e) => e.endsAt)).toEqual(["2026-10-12T20:00:00.000Z"]);
|
||||
});
|
||||
|
||||
test("a source id with no entry emits nothing", () => {
|
||||
|
||||
Reference in new issue
Block a user