feat(endfield): track AIC Quota Exchange permits by banner rotation
The shop's five permits restock when the character and weapon banners change together, which no week, month or fixed cycle can express: rotations ran 21 days and then 19. A rotation is not a game version, so it gets its own table and caption, and follows the version rule that only a recorded end resets the count. The dates and reset rule are the owner's in-game reading, as no page states them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
be80e785ca
commit
327f6389cc
16 files changed
+242
-46
No files matched your search
@@ -12,7 +12,7 @@ import { isCustomGameId, type DisplayEvent, type LaneId } from "../../shared/cus
|
||||
import type { GameMeta } from "../../shared/games.ts";
|
||||
import type { GameId, Region } from "../../shared/schema.ts";
|
||||
import { goalCount, goalLabel, type GoalStatus } from "../../shared/goal.ts";
|
||||
import { addDays, cadenceOf, periodOf, type Period } from "../../shared/period.ts";
|
||||
import { addDays, cadenceOf, isTimerPeriod, periodOf, type Period } from "../../shared/period.ts";
|
||||
import type { TaskShape } from "../../shared/task.ts";
|
||||
import { asOfLabel } from "../../shared/routines.ts";
|
||||
import { AmountEditor } from "./AmountEditor.tsx";
|
||||
@@ -207,8 +207,8 @@ export function dailyGroups(
|
||||
notBefore: null,
|
||||
goal: taskStatusFor(task),
|
||||
cadence: cadenceOf(task.period),
|
||||
// A version has no period before it to catch up on (spec 2026-10-22).
|
||||
period: task.period.unit === "version" ? undefined : task.period,
|
||||
// A timer has no period before it to catch up on (spec 2026-10-22).
|
||||
period: isTimerPeriod(task.period) ? undefined : task.period,
|
||||
unit: task.measure.kind === "amount" ? task.measure.unit : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import {
|
||||
type Routine,
|
||||
} from "../../../shared/routines.ts";
|
||||
import { asOfLabel, TaskSchema, type Task } from "../../../shared/task.ts";
|
||||
import { periodOf, type Period } from "../../../shared/period.ts";
|
||||
import { versionCaption, versionWindow } from "../../../shared/versions.ts";
|
||||
import { isTimerPeriod, periodOf, type Period } from "../../../shared/period.ts";
|
||||
import { timerWindow, versionCaption } from "../../../shared/versions.ts";
|
||||
|
||||
const capitalise = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
import { AmountTarget } from "../../../shared/goal.ts";
|
||||
@@ -379,16 +379,16 @@ function Library({
|
||||
const rows = rowsOf(id),
|
||||
first = rows[0]!,
|
||||
def = builtins.find((b) => b.id === id);
|
||||
const version =
|
||||
def?.period.unit === "version"
|
||||
? versionWindow(def.game, now)
|
||||
const timer =
|
||||
def && isTimerPeriod(def.period)
|
||||
? timerWindow(def.period, def.game, now)
|
||||
: undefined;
|
||||
const line = first.event ? ["Battle pass", "Uses its sourced window", "Mark complete when finished"] : def
|
||||
? [
|
||||
def.period.unit === "version"
|
||||
? version
|
||||
? capitalise(versionCaption(version, now))
|
||||
: "Resets with the version"
|
||||
isTimerPeriod(def.period)
|
||||
? timer
|
||||
? capitalise(versionCaption(timer, now))
|
||||
: `Resets with the ${def.period.unit}`
|
||||
: scheduleWords(
|
||||
def.period,
|
||||
first.clock.resetHour,
|
||||
|
||||
@@ -21,8 +21,8 @@ import { routineRows, type RoutineRow } from "../../state/reader/routines.ts";
|
||||
import { actionMeta } from "../../state/reader/actions.ts";
|
||||
import { useGameMeta } from "../../state/gameMeta.tsx";
|
||||
import { DAY, formatRemaining, resetShiftMs } from "../../../shared/time.ts";
|
||||
import { periodOf } from "../../../shared/period.ts";
|
||||
import { versionCaption, versionWindow } from "../../../shared/versions.ts";
|
||||
import { isTimerPeriod, periodOf } from "../../../shared/period.ts";
|
||||
import { timerWindow, versionCaption } from "../../../shared/versions.ts";
|
||||
import { routinesFor } from "../../../shared/routines.ts";
|
||||
import { canStep, snapDayWidth, stepDayWidth } from "../../state/zoom.ts";
|
||||
import { orderGames } from "../../state/gameOrder.ts";
|
||||
@@ -57,12 +57,12 @@ export function routinePeriod(
|
||||
if (row.window) return { ...row.window, estimated: false };
|
||||
const key = readerDayKey(now, row.clock),
|
||||
task = row.task ?? routinesFor(row.game).find((r) => r.id === row.id);
|
||||
if (task?.period.unit === "version") {
|
||||
const w = versionWindow(row.game, now);
|
||||
if (task && isTimerPeriod(task.period)) {
|
||||
const w = timerWindow(task.period, row.game, now);
|
||||
if (w?.end !== undefined) return { start: w.start, end: w.end, estimated: false };
|
||||
if (w) return { start: w.start, end: Math.max(w.estimate ?? 0, now + DAY), estimated: true };
|
||||
}
|
||||
const period = task && task.period.unit !== "version"
|
||||
const period = task && !isTimerPeriod(task.period)
|
||||
? periodOf(task.period, key)
|
||||
: { start: key, days: [key] };
|
||||
const shift = resetShiftMs(row.clock.region, row.game, row.clock);
|
||||
@@ -72,8 +72,10 @@ export function routinePeriod(
|
||||
|
||||
/** What to print instead of a countdown when `routinePeriod` is estimated. */
|
||||
export const estimatedReset = (row: RoutineRow, now: number): string => {
|
||||
const w = versionWindow(row.game, now);
|
||||
return w ? versionCaption(w, now) : "resets with next version";
|
||||
const task = routinesFor(row.game).find((r) => r.id === row.id),
|
||||
period = task && isTimerPeriod(task.period) ? task.period : ({ unit: "version" } as const),
|
||||
w = timerWindow(period, row.game, now);
|
||||
return w ? versionCaption(w, now) : `resets with next ${period.unit}`;
|
||||
};
|
||||
export function TimelinePage({
|
||||
state,
|
||||
|
||||
Reference in new issue
Block a user