feat(library): use set total playtime in sort and stats

Sort, total playtime, most played, genres, moods and the public
profile use the set total of an entry, as the library card does.
New view user_game_entry_playtime (V45) is the one SQL rule.
This commit is contained in:
lobo committed 2026-10-09 14:35:07 +02:00
1 parent 1ff1e061c7
commit 4ed8a341af
11 files changed
+253 -62

No files matched your search

+16 -11
View File
@@ -118,8 +118,10 @@ flowchart LR
`listed_user_game`, V24). A linked set (see "Editions of one game") counts once: its most advanced edition (status, then
playtime, then the older `first_seen_at`; `counted_user_game`, V28), so the set is on the pile only when every owned edition is `BACKLOG`. Most played, most loyal,
genres, moods, perfect games and the longest day list one entry per set, with the playtime of all its editions
(`user_game_entry_member`, V43; its list shows that sum as `minutes`). Platforms and playtime sums (`sumPlaytime`) stay
per edition: a Steam + PlayStation set counts on both platforms.
(`user_game_entry_member`, V43; its list shows that sum as `minutes`). Every playtime number (most played, genres,
moods, the total playtime and moon trips, the drill-down lists) uses the set total, the view
`user_game_entry_playtime` (V45), the same as the library card, sort and public profile. Platforms stay per edition:
a Steam + PlayStation set counts on both platforms.
- **Drill-down**: every countable number opens the games behind it. A `StatKey` (library), `CommunityKey` (landing page)
or a Pile Points track/bonus row names a number; its SQL "game set" is the same text the number counts, so the
list and the number cannot drift. 40 games per page (at most page 250), a fixed order per key. A profile visitor
@@ -349,7 +351,7 @@ reads `deleted_user` and `blocked_user`, and for the picker `follow`, `public_us
The listed rule ("still owned, not hidden") is `UserGameRepository.LISTED` for JPQL and the view
`listed_user_game` for native SQL; V24 says what a migration of `user_game` must do with the view. The numbers that
count games once per linked set read `counted_user_game` (V28), built on `listed_user_game`: a migration that changes
a column of `user_game` drops the views built on it first: `user_game_entry_platform`, `user_game_entry_member`, `counted_user_game`, then `listed_user_game`.
a column of `user_game` drops the views built on it first: `user_game_entry_playtime`, `user_game_entry_platform`, `user_game_entry_member`, `counted_user_game`, then `listed_user_game`.
The two entry views (V43) map an entry (a counted row) to its editions and to their platforms: `user_game_entry_member`
is the one place that does it (stats and the library read it); `user_game_entry_platform` adds each edition's source
and platform and a PlayStation row for a `LINKED` or `STEAM_OWNED` title on that key.
@@ -562,19 +564,20 @@ IGDB id; remakes (8) keep their own id. `null` when IGDB does not know the game.
library and its total and badge, the dashboard, the recent games, the public profile lists, stats and their
drill-downs. There is no "Hide duplicates" filter any more; a hidden game is its own row under `hidden=true`. The
friends row of the picker matches a friend's game to the caller's entry through `game_link`. Kept per edition on
purpose: the hidden list, the platform stat, the average achievement completion (per edition; the tile's game counts are per entry), `sumPlaytime` and the
community stats of the landing page. The card playtime is the set total (`entryPlaytimes`, see "Detail per platform"). Pile Points pay a set once (see "Pile Points"), and the
purpose: the hidden list, the platform stat, the average achievement completion (per edition; the tile's game counts are per entry) and the
community stats of the landing page. The playtime of a card, the playtime sort and the stats is the set total (`user_game_entry_playtime`, V45, see "Detail per platform"). Pile Points pay a set once (see "Pile Points"), and the
`MONTH` bonus reads `backlog_snapshot`, which counts a set once.
The library query joins `user_game_entry_playtime` on every page (about +4 ms per page); upgrade path: join it only for the playtime sort or the PLAYING status, with a second query.
- **Detail per platform** (V44): the PSN step stores platform, minutes and last played per title in `user_psn_title`
(`LINKED` and `STEAM_OWNED`, every sync, whatever row the title is on); the view `psn_title_play` resolves a merged
key through `game_alias`. `PlatformPlay.lines` is the one rule for the detail lines: a Steam or manual row is one
line, a PlayStation row is its titles (or one line from the row before the first sync after V44), a manual row is
replaced by the titles of its own platform, and every other title linked to a Steam or manual row adds its
PlayStation line. The total is their sum; the card total
(`GameLinkRepository.entryPlaytimes`) follows the same rule in SQL, a test compares both. Each line carries its own
PlayStation line. The total is their sum; the view `user_game_entry_playtime` (V45) is the same rule in SQL, a test
compares both. The card total (`GameLinkRepository.entryPlaytimes`), the playtime sort of the library, `sumPlaytime`
and the stats read that view; it has no correlated subquery on purpose (it made a library page cost 2 s). Each line carries its own
achievement or trophy progress; the full list stays on the edition's own detail page. Still per row, not per
platform: the playtime history (PSN minutes on a Steam row are not in it), the stats sums (most played, total
playtime, moon trips), the playtime sort, and rewards (Steam only). Until a user's next sync after V44, PSN-on-Steam
platform: the playtime history (PSN minutes on a Steam row are not in it) and rewards (Steam only). Until a user's next sync after V44, PSN-on-Steam
lines are missing and Steam's last played is empty.
- The game detail has `linked` and `editions` (the user's other listed games of the set); a game row has `editions`
(size of the set in the user's library, `null` below 2), `platforms` (`source` + `platform` per edition, for
@@ -590,8 +593,10 @@ flowchart LR
Counted --> Member["user_game_entry_member"]
Member --> Platform["user_game_entry_platform"]
Platform -->|"platforms[]"| Card["library card, lists"]
Member -->|"totalPlaytimeMinutes"| Card
Title["user_psn_title play (V44)"] -->|"psn_title_play"| Member
EntryPlaytime -->|"totalPlaytimeMinutes"| Card
EntryPlaytime --> Stats["library sort, stats, public profile"]
Member --> EntryPlaytime["user_game_entry_playtime (V45)"]
Title["user_psn_title play (V44)"] -->|"psn_title_play"| EntryPlaytime
Title -->|"platforms[] of the detail"| Detail["game detail"]
Set -->|"join: align_user_game_set"| Row["user_game (status, rating, notes, priority)"]
Row -->|"update: user_game_set_share"| Row