From 776847e0af0bc0f92142859ab6f1b13cbe003e52 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Thu, 22 Oct 2026 17:06:32 +0200 Subject: [PATCH] docs(spec): routines that reset with the game's version Co-Authored-By: Claude Opus 5.5 --- .../2026-10-22-version-routines-design.md | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-22-version-routines-design.md diff --git a/docs/superpowers/specs/2026-10-22-version-routines-design.md b/docs/superpowers/specs/2026-10-22-version-routines-design.md new file mode 100644 index 0000000..2eab133 --- /dev/null +++ b/docs/superpowers/specs/2026-10-22-version-routines-design.md @@ -0,0 +1,201 @@ +# Routines that reset with the game's version — design + +Date: 2026-10-22. Status: **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: + +1. **Track version timers per game**, so that a routine can reset when a version ends. +2. **When the current version's end is unknown**, the routine says *Resets with next version + (<number>)* and gives an estimate. +3. **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. +4. **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. +5. **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): + +6. **Versions are a hand-kept table in code**: `src/shared/versions.ts`, held to the same bar as + `ROUTINES`. 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's + `Version` table 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. +7. **A version ends at an instant, not at a daily reset.** `end` is 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. +8. **The version period exists only for built-ins.** `Routine.period` accepts + `{ unit: "version" }`, but the stored `PeriodSchema` (and so `TaskSchema`, the + `gacha-tracker:v1:tasks` records 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). +9. **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. +10. **A version period shows with the cycles.** For grouping it is a `cycle`: the Dailies cadence, + the *Cycles* group on the routines page, the `GoalStatus.scope`, and the `n/7 · 19d` chip. + Only the caption differs (§ Display). A fifth cadence would touch every tally in `Dailies.tsx` + for three rows. +11. **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. +12. **Shop lines use the existing `pulls` flag.** Radiant Tide (limited character) and Forging + Tide (limited weapon) are `premium`. Lustrous Tide is `standard`, so it is offered only with + `prefs.standardPulls`, like Paimon's Bargains' Acquaint Fates. + +## Shape + +### `src/shared/versions.ts` (new) + +```ts +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>; +``` + +Plus one pure function, tested at fixed `now`: + +```ts +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 `now` falls inside a listed version with a recorded `end`, that version is the window. +- If the newest listed version has no `end` and `now` ≥ its `start`, the window is that version, + open-ended, with `estimate = start + VERSION_DAYS`. +- If `now` is past the newest recorded `end`, the window is the version that began at that + `end` (number = that entry's `next`, 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.period` becomes `PeriodSchema.or(z.object({ unit: z.literal("version") }))`. + `Routine`'s intersection with `TaskShape` has to allow it; the builder decides how (for + example, a `RoutinePeriod` type). +- Every consumer of a routine's period (`taskStatus`, `periodOf` callers in `Dailies.tsx`, + `TimelinePage.tsx`, `RoutineDrawer.tsx`, `taskCaption`, the cadence in + `state/reader/routines.ts`) handles `version` by going through `versionWindow` with `now`. The + type checker will list them. In `taskStatus`, an amount version routine reads + `amounts[window.key]`, and `daysLeft` / `open` are the whole days to `end`, or `null` when no end + is recorded. +- `routineToTask` / *Make it mine* is not offered for a version routine. + +### Display + +- **Routine caption** (`taskCaption` and 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` +- **Chip**: the cycle form, `n/7 · 19d` with a recorded end, and `n/7` alone 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 its `note` beneath 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's `Version` page into + `VERSIONS`. 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; `versionWindow` at 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 no `end` gives an + estimate from its own start. +- `task.test.ts`: `taskStatus` for a version amount routine with 3/7 entered under 3.7's key (it + appears; `daysLeft` 20 on 22 Oct), and at 11 Nov 10:30Z (back to 0/7, `daysLeft` null). +- `routines.test.ts`: the existing invariants pass for the three entries; Lustrous Tide is + `standard`; 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.