12 KiB
Routines that reset with the game's version — design
Date: 2026-10-22. Status: shipped on branch feat/version-routines (34ebc40..5896819), approved by the owner 2026-10-22.
Problem
Wuthering Waves' shop sells gacha pulls with a limit of 7 Radiant Tide, 7 Forging Tide and 7 Lustrous Tide per version, and the stock refills when the next version arrives (owner, 2026-10-22). The in-game timer read 19d 20h at about 14:30 UTC on 2026-10-22, which puts the restock at about 10:30 UTC on 11 November 2026. That matches the owner's reading of 16 October ("resets each version, 11 Nov ~10–11 UTC", built-in routines spec § Scope item 3) and the community estimates for 3.8's launch (Game8, u7buy: 11–12 November).
No built-in period can express "until the next version". Weeks and months are fixed, and a
cycle assumes every version has the same length. This change adds version timers as a concept,
so routines can follow them. The owner asked for exactly that: "We should start tracking version
timers to allow routines to follow after them."
Decisions
The owner's, from the conversation on 2026-10-22:
- Track version timers per game, so that a routine can reset when a version ends.
- When the current version's end is unknown, the routine says Resets with next version (<number>) and gives an estimate.
- The estimate is shown only. A routine resets only at an end that is recorded in the table. Until someone records the current version's end, the count stays where it is, however far past the estimate. This follows AGENTS.md's rule against acting on an invented date.
- A version timer is shown in two places: through the routines that follow it, and on the game's page under Settings → Your games. It gets no row in the list, no headline, and no timeline marker.
- The first three routines are WuWa's shop pulls: 7 Radiant Tide, 7 Forging Tide and 7 Lustrous Tide each version.
The builder's, marked as such (the owner may overrule them while reading):
- Versions are a hand-kept table in code:
src/shared/versions.ts, held to the same bar asROUTINES. Each entry needs a cited page, and figures the owner read in game say so ("Confirmed in game, YYYY-MM-DD."). No feed source dates WuWa's versions today: Fandom'sVersiontable stops at 3.5, and Game8 gives both 11 and 12 November (pass windows spec § Not shipped). A scraped source can replace entries later; this change does not build one. - A version ends at an instant, not at a daily reset.
endis a UTC timestamp: the moment the new version's stock appears, the same on every server. WuWa's daily reset (04:00 server time) would roll 11 Nov's game-day at 20:00 UTC on 10 Nov on Asia, about 14 hours before the restock. A reader there would see 0/7 when they can't buy yet, and pulls bought that evening would be filed under 3.8. The instant comes from the owner's in-game timer, accurate to about an hour. - The version period exists only for built-ins.
Routine.periodaccepts{ unit: "version" }, but the storedPeriodSchema(and soTaskSchema, thegacha-tracker:v1:tasksrecords and the export format) stays unchanged. An older cached bundle never sees an unknown period. Make it mine is hidden on a version routine; a reader's own version task is a follow-up (§ Not in this change). - Version routines count amounts only. A "days ticked" measure needs the list of game-days in the period, and an open-ended version has no last day. A test enforces this.
- A version period shows with the cycles. For grouping it is a
cycle: the Dailies cadence, the Cycles group on the routines page, theGoalStatus.scope, and then/7 · 19dchip. Only the caption differs (§ Display). A fifth cadence would touch every tally inDailies.tsxfor three rows. - The estimate is the version's start plus the game's usual version length. For WuWa that is 42 days: 3.7 ran 30 Sep → 11 Nov, and AGENTS.md puts patch cycles at about six weeks. The estimate is computed when shown and never stored.
- Shop lines use the existing
pullsflag. Radiant Tide (limited character) and Forging Tide (limited weapon) arepremium. Lustrous Tide isstandard, so it is offered only withprefs.standardPulls, like Paimon's Bargains' Acquaint Fates.
Shape
src/shared/versions.ts (new)
export interface GameVersion {
game: GameId;
number: string; // "3.7": a label, never parsed as a number (3.8 may be followed by 4.0)
start: string; // ISO UTC instant the version's stock appeared
end?: string; // ISO UTC instant the next version's stock appears; absent when unknown
next?: string; // the next version's number, when a page or the owner names it
source: string; // https page
checked: string; // YYYY-MM-DD
note?: string; // "Confirmed in game, 2026-10-22." and similar
}
export const VERSIONS: readonly GameVersion[];
/** Usual length in days, per game, with where the figure comes from in a comment. */
export const VERSION_DAYS: Partial<Record<GameId, number>>;
Plus one pure function, tested at fixed now:
versionWindow(game, now: number) → {
number: string | undefined; // the version holding now; undefined if not named
key: string; // the period key amounts are stored under
start: number; // ms
end: number | undefined; // ms, only when recorded
estimate: number | undefined; // ms, start + VERSION_DAYS, when end is not recorded
nextNumber: string | undefined;
} | undefined // the game has no versions at all
Rules, with the newest entry's ends resolved in order:
- If
nowfalls inside a listed version with a recordedend, that version is the window. - If the newest listed version has no
endandnow≥ itsstart, the window is that version, open-ended, withestimate = start + VERSION_DAYS. - If
nowis past the newest recordedend, the window is the version that began at thatend(number = that entry'snext, possibly undefined). It is open-ended, with an estimate from that start. Its start is real data, so amounts stored under its key stay where they are when the owner adds its entry. - The window never rolls on an estimate (decision 3). Past the estimate, it still shows that version, and the caption says the end is overdue (§ Display).
- No
VERSION_DAYS→ no estimate; the caption omits it.
The key is `v${start date in UTC, YYYY-MM-DD}`, or the bare YYYY-MM-DD if the amounts
store's key schema requires a date. The builder checks AmountLog's key rule and records which
one. It depends only on the start instant, so adding the next entry later never moves it.
VERSIONS is validated by a test: entries per game do not overlap, end > start, each recorded
end equals the next entry's start where both exist, every source is https, and both instants
parse.
First entry
| game | number | start | end | next | source | note |
|---|---|---|---|---|---|---|
| wuwa | 3.7 | 2026-09-30 (instant per page or owner) | 2026-11-11T10:30:00Z | 3.8 | game8.co/games/Wuthering-Waves/archives/629904 (Version 3.8 Banners and Release Date) | The page estimates 11–12 Nov; the end is the owner's in-game timer, 19d 20h on 22 Oct. Confirmed in game, 2026-10-22. |
The builder checks 3.7's start against a page before committing. If no page states it, the builder
uses 30 Sep (42 days before the end, matching the six-week cycle) and says so in note. The start
affects only the period key.
Routine and the period
RoutineSchema.periodbecomesPeriodSchema.or(z.object({ unit: z.literal("version") })).Routine's intersection withTaskShapehas to allow it; the builder decides how (for example, aRoutinePeriodtype).- Every consumer of a routine's period (
taskStatus,periodOfcallers inDailies.tsx,TimelinePage.tsx,RoutineDrawer.tsx,taskCaption, the cadence instate/reader/routines.ts) handlesversionby going throughversionWindowwithnow. The type checker will list them. IntaskStatus, an amount version routine readsamounts[window.key], anddaysLeft/openare the whole days toend, ornullwhen no end is recorded. routineToTask/ Make it mine is not offered for a version routine.
Display
- Routine caption (
taskCaptionand the drawer's period line):- recorded end:
resets with 3.8 · 11 Nov - no recorded end:
resets with next version (3.9) · expected ~23 Dec, without the number when unknown:resets with next version · expected ~23 Dec - past the estimate:
resets with next version (3.9) · expected ~23 Dec, not yet confirmed
- recorded end:
- Chip: the cycle form,
n/7 · 19dwith a recorded end, andn/7alone without one. - Your games → game → About: one row under the reset rows, shown only for a game with versions:
Version 3.7 · ends 11 Nov 2026, 11:30 (in 19d)in the reader's display timezone, with the source linked and itsnotebeneath it.- No recorded end:
Version 3.8 · next version expected ~23 Dec 2026 (estimate).
Entries
| id | title | period | measure | pulls |
|---|---|---|---|---|
routine:wuwa:shop-radiant-tide |
Shop: Radiant Tide | version | amount 7 Radiant Tide | premium |
routine:wuwa:shop-forging-tide |
Shop: Forging Tide | version | amount 7 Forging Tide | premium |
routine:wuwa:shop-lustrous-tide |
Shop: Lustrous Tide | version | amount 7 Lustrous Tide | standard |
Each has source set to a page that describes the shop's pull stock, preferring one that names the
shop and the currency (the builder finds it). The title names the shop if that page does. Each
note ends "Confirmed in game, 2026-10-22." The limit of 7 per version is the owner's reading.
Not in this change
- A scraped version source for WuWa, or wiring
versionTable.ts/ Nikki'sVersionpage intoVERSIONS. The table is shaped so that work can fill it later. - Version periods on a reader's own tasks, and Make it mine for version routines.
- Version markers on the timeline, or a version row in the list or Next up.
- Moving existing routines (Pioneer Podcast and others) onto the version period.
Upkeep
When a version's end is announced or read in game, add it to VERSIONS with the next version's
entry. Until then, version routines hold their count (decision 3). AGENTS.md gets a bullet saying
so, and TODO.md gets a standing item for 3.8's end.
Testing
versions.test.ts: the table invariants;versionWindowat 3.7's start, inside it, one minute before 2026-11-11T10:30Z, at that instant (3.8, open-ended, estimate 23 Dec), well past the estimate (still 3.8, same key), and for a game with no versions. An entry with noendgives an estimate from its own start.task.test.ts:taskStatusfor a version amount routine with 3/7 entered under 3.7's key (it appears;daysLeft20 on 22 Oct), and at 11 Nov 10:30Z (back to 0/7,daysLeftnull).routines.test.ts: the existing invariants pass for the three entries; Lustrous Tide isstandard; every version routine is an amount measure.- Caption and chip strings for a recorded end, no end, and past the estimate.
- A browser check of the routine rows and the About row, at the real date.
Docs
docs/DATA-MODEL.md (no stored change; note that the version period exists only for built-ins and
the key it stores amounts under), docs/PRD.md F17 (routines can follow a version), AGENTS.md (one
bullet: versions are hand-kept and cited, estimates are shown and never acted on), TODO.md (the
Oscillated Coral item is partly addressed; the follow-ups above; 3.8's end), and a changelog entry
in a follow-up commit, following the repo's habit.