diff --git a/AGENTS.md b/AGENTS.md index 64b02a0..6598e87 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,86 +1,76 @@ -# AGENTS.md — maintaining GameHours +# AGENTS.md — GameHours -## Goal +Before planning, reviewing or modifying GameHours, read this file and `docs/CONSTITUTION.md`. -GameHours measures and reconstructs Windows game playtime independently of launchers. It is local-first and is intended to become the tracking subsystem of the Gestor de Juegos desktop application without coupling the tracking core to that backend. +## Project -## Planning sources +GameHours is a local-first Windows desktop application that measures and reconstructs videogame activity independently of launchers. -- `docs/ROADMAP.md` is the canonical **forward product roadmap** after the merge of `desktop-foundation`. -- `docs/REFERENCE-PROJECTS.md` records mature external projects/source files worth studying and the license/attribution boundary for using them. -- `docs/EXECUTION-PLAN.md` preserves detailed operational plans, completed foundation evidence and any explicitly opened implementation batch. Pre-merge "next"/candidate sections in that historical document are **not automatically authorized post-foundation work** unless they are deliberately reopened and aligned with `docs/ROADMAP.md`. -- A roadmap item is direction, not blanket implementation authorization. Open one small vertical slice at a time with explicit scope, tests, validation and exclusions. +Stack: .NET 8, C#, WPF and SQLite. -## Non-negotiable design rules +GameHours must remain useful without an account, backend or Internet connection. External integrations are optional. -1. **Exact and reconstructed time stay distinguishable.** Never label SRUM/UserAssist evidence as exact process runtime. -2. **No double counting.** Baseline evidence ends at the tracking cutover. Gap recovery must not overlap measured sessions. -3. **Path outranks filename.** Two executables with the same filename may belong to different roles or games. -4. **Helpers are not game time by default.** Launchers, crash reporters and helper processes need explicit resolution/grouping rules. -5. **Local-first.** Tracking and persistence work without network access. -6. **Privacy-minimal sync.** Raw SRUM, registry values, PIDs, Windows usernames and full paths are not uploaded by default. -7. **Idempotent persistence/sync.** Client-generated UUIDs identify sessions/evidence so retries cannot duplicate time. -8. **Events are not enough.** The production monitor keeps periodic reconciliation as a fallback for missed process events. -9. **No silent data repair.** Never repair or mutate the live SRUM database. Read from safe copies/imports only. -10. **Tests accompany timeline changes.** Any change to cutover, overlap or duration rules requires focused tests. +Before significant work, also read: -## Projects +- `docs/ROADMAP.md` +- the active spec/plan for the task, if one exists. -- `GameHours.Core`: domain models, timeline policy and interfaces. No Windows/SQLite/backend dependencies. -- `GameHours.Windows`: Windows-specific discovery and monitoring. -- `GameHours.Storage`: SQLite schema and repositories. -- `GameHours.Sync`: normalized sync contracts/client boundary. -- `GameHours.App`: development host now; future desktop shell. -- `tests/GameHours.Tests`: unit/integration tests using temporary SQLite databases. +Before repeating prior investigation, check `docs/VERIFIED-FINDINGS.md` and `docs/REFERENCE-PROJECTS.md`. + +## Architecture + +- `GameHours.Core`: neutral domain models and interfaces. +- `GameHours.Windows`: Windows discovery, monitoring and platform integration. +- `GameHours.Storage`: SQLite schema, migrations and repositories. +- `GameHours.Desktop`: WPF desktop product and composition. +- `GameHours.Portability`: backup, restore and import/export. +- `GameHours.AchievementProbe`: isolated achievement probing. +- `GameHours.Update`: update/package boundaries. +- `GameHours.Sync`: optional normalized integration contracts. + +Do not introduce WPF, Windows, SQLite or backend dependencies into `GameHours.Core`. ## Commands -```powershell -dotnet restore GameHours.sln -dotnet build GameHours.sln -c Release -dotnet test GameHours.sln -c Release -``` +Restore: + +`dotnet restore GameHours.sln --locked-mode` + +Build: + +`dotnet build GameHours.sln -c Release --no-restore` -## Efficient subagent policy +Tests: -Use the project-scoped agents in `.codex/agents/` automatically when their role matches the work. The goal is to reduce primary-context pollution and total cost, not to maximize agent count. +`dotnet test GameHours.sln -c Release --no-build` -- Keep the primary agent on `gpt-5.6-sol` with `medium` reasoning for requirements, architecture, integration, external actions and the final decision. -- Use `gamehours_mapper` for bounded read-only codebase questions before expensive exploration in the primary thread. -- Use `gamehours_worker` for a clearly owned implementation slice and `gamehours_storage_worker` for SQLite, migrations, restore or portability. Never assign overlapping file ownership to concurrent workers. -- Use `gamehours_test_runner` for lengthy or independent local validation and failure reproduction. -- Use `gamehours_supervisor` after non-trivial or high-risk implementation involving architecture, persistent data, concurrency, security or broad diffs. It reviews; it does not reimplement the worker's task. -- Always delegate GitHub Actions, PR checks and CI observation to `ci_monitor`. The primary agent retains rerun, merge, cancellation, deployment and rollback decisions. -- Choose the cheapest capable role. Do not spawn every agent mechanically, do not delegate trivial one- or two-step work, and do not duplicate the same investigation in multiple agents. -- Run independent read-heavy tasks in parallel when useful. Serialize write-heavy tasks that touch related files. -- Give every worker a concrete objective, explicit file ownership, constraints, expected evidence and a reminder that other agents may be editing the shared worktree. -- Spawn custom project agents with `fork_turns="none"` and pass a compact, self-contained briefing. Do not copy the full parent history unless a task demonstrably requires it. -- The primary agent reviews and integrates all worker output, runs proportionate final validation, and remains responsible for the final diff. +Publish smoke: -## Verified design state +`dotnet publish src/GameHours.Desktop/GameHours.Desktop.csproj -c Release -r win-x64 --self-contained true --no-restore -o artifacts/desktop-smoke` -As of 2026-08-20: +## Conventions -- SRUM `AppResourceUseInfo.FaceTime` was successfully extracted from a copied SRUDB and matched the user's recalled playtime much better than UserAssist for the test game. -- UserAssist v5 focus fields parsed structurally, but the last-run value became stale and therefore it is secondary evidence. -- A live process session was detected entirely through one-second reconciliation when WMI events were missed; measured duration was 65.180 seconds. -- The tested game exposed two executable paths with the same filename (helper/root executable and the real game binary), proving that filename-only identity is insufficient. +Code and identifiers are in English. User-facing UI/messages are in Spanish unless a feature explicitly requires localization. -See `docs/VERIFIED-FINDINGS.md` for details. +Reuse existing GameHours components, styles and abstractions before creating new ones. -## Do not assume +## Rules -- SRUM foreground time equals process lifetime. -- `FocusCount` in UserAssist equals launch count. -- a process event will always arrive. -- one executable filename uniquely identifies a game. -- a Steam counter and GameHours counter can safely be added. -- backend availability during play. +- Research before relevant technical, architectural, performance or UX decisions. +- Prefer the simplest correct solution; avoid speculative abstractions and dependencies. +- Fix root causes using evidence rather than layering patches. +- Never mix exact measured time with reconstructed historical estimates or double-count evidence. +- Never mutate the live SRUM database. +- Do not invent timestamps, achievements, metadata or identity. +- Keep GameHours functional offline; optional integrations must remain decoupled from authoritative tracking. +- Meaningful UI changes must respect the GameHours design and be visually verified on Windows when automation cannot prove the result. +- Do not merge, release, deploy or perform other irreversible external actions without explicit human authorization. -## Pull-request checklist +## When finishing a task -- `dotnet build GameHours.sln -c Release` -- `dotnet test GameHours.sln -c Release` -- no machine-specific paths, usernames or secrets committed; -- timeline rules unchanged or explicitly tested/documented; -- SQLite migrations remain forward-only and additive where practical. +- Review the final diff for dead code, duplication, debug output, temporary logs and stale comments. +- Run validation proportional to the change; code PRs should pass build and relevant tests before being considered ready. +- Add regression tests where behavior could recur. +- Never weaken valid tests merely to obtain green CI. +- State clearly what is only implemented, what compiled, what passed automated tests/CI and what was manually or real-machine verified. +- If something could not be verified, say so. diff --git a/docs/CONSTITUTION.md b/docs/CONSTITUTION.md new file mode 100644 index 0000000..e25e940 --- /dev/null +++ b/docs/CONSTITUTION.md @@ -0,0 +1,71 @@ +# GameHours Constitution + +These principles are non-negotiable. Every specification, plan, implementation and review must respect them. + +## 1. Research before deciding + +Understand the existing behavior and code before changing it. For relevant technical, architectural, performance or UX decisions, check whether GameHours, .NET/WPF/Windows or an established solution already solves the problem. Prefer official documentation and reliable primary sources; inspect mature open-source implementations when they add useful evidence. + +Do not implement the first plausible solution merely because it works. + +## 2. Simplicity and reuse first + +Prefer the smallest clear solution that correctly solves the real problem. Reuse existing GameHours components and platform capabilities before adding code, abstractions or dependencies. + +Avoid duplicate logic, speculative frameworks, unnecessary state, hidden side effects and broad refactors unrelated to the task. Fewer lines are only better when clarity and maintainability are preserved. + +## 3. Evidence and root cause over assumptions + +For bugs or unexpected behavior: characterize the failure, gather evidence, identify the owning layer and fix the root cause there. Logs, tests, metrics, runtime behavior and the actual code outrank hypotheses or stale documentation. + +Do not accumulate patches around a structural problem. If evidence disproves an earlier assumption, discard the assumption. + +## 4. Preserve data truth and provenance + +GameHours must never fabricate precision. + +Measured runtime, reconstructed historical evidence, focused/active telemetry, achievements, timestamps and external metadata must preserve their source and confidence. Never double-count time, present estimated evidence as exact, invent unlock times or silently overwrite authoritative local history with external snapshots. + +The live SRUM database is read-only evidence: never repair or mutate it. Use safe copies/imports. + +## 5. Local-first and optional integrations + +Core tracking, persistence and the useful desktop experience must work without an account, backend or Internet connection. + +External systems such as Gestor de Juegos, online metadata providers or save engines are optional adapters behind GameHours-owned boundaries. They may enrich the product but must not become runtime dependencies of authoritative tracking or replace GameHours identities and evidence. + +Privacy follows the same rule: collect, persist and transmit only what the feature actually needs. + +## 6. Measure performance before optimizing + +For meaningful performance work follow: + +`measure -> locate -> optimize -> measure again` + +Prefer avoiding unnecessary work over making unnecessary work slightly faster. Pay particular attention to startup, the WPF UI thread, periodic polling, process enumeration, filesystem/database access, image work and repeated scans. + +Do not add caches, workers, timers or concurrency without a demonstrated reason. + +## 7. Product quality includes UX + +A feature is not complete merely because the code is correct. GameHours should feel coherent, modern, responsive, clear and deliberate. + +Reuse the existing visual language and components. Consider hierarchy, spacing, density, focus/keyboard behavior, hover/disabled states, loading, errors, empty states and destructive actions. Do not leave default WPF/Windows styling visible when it conflicts with the GameHours design. + +Meaningful visual or interaction changes require real Windows verification when automated tests cannot establish the result. + +## 8. Validate before claiming completion + +Compilation is not verification. Use tests and validation proportional to the risk of the change, including focused regression tests, the full suite when reasonable, CI, packaging/persistence checks and real-machine verification where appropriate. + +Never remove, skip or weaken a valid test merely to obtain a green result. Always distinguish between implemented, compiled, automated-tests-passed, CI-passed and manually/real-machine verified. If something could not be verified, say so explicitly. + +## Working interpretation + +For substantial product or architectural work, use a lightweight Spec-Driven flow appropriate to the change: + +`specification -> clarification -> technical plan -> small tasks -> implementation -> validation` + +Do not create documentation for its own sake. Small, obvious fixes do not need heavyweight specs. The code, tests, current specification and observed behavior should remain aligned; when implementation reveals a durable decision that changes the specification or plan, update the relevant document. + +Human review remains part of the loop. The agent accelerates engineering; it does not replace engineering judgment. diff --git a/docs/ROADMAP-DETAILS.md b/docs/ROADMAP-DETAILS.md index b03889a..5661953 100644 --- a/docs/ROADMAP-DETAILS.md +++ b/docs/ROADMAP-DETAILS.md @@ -1,79 +1,54 @@ -# GameHours roadmap — detailed product design +# GameHours roadmap details -**Status:** active companion to [`ROADMAP.md`](ROADMAP.md). +This document expands [`ROADMAP.md`](ROADMAP.md) into implementation guidance. `ROADMAP.md` decides product priority; this file records the problem, intended UX, architecture, delivery sequence, risks and definition of done so future work does not have to reconstruct these decisions from chat history. -`ROADMAP.md` defines priority and direction. This document explains the intended product outcome, architectural boundaries, staged delivery, risks and definition of done for each roadmap area. - -This is a design guide, not a promise that every implementation detail is frozen. Before each implementation slice, re-check the current GameHours code, official platform documentation and the external references in [`REFERENCE-PROJECTS.md`](REFERENCE-PROJECTS.md). Evidence from real Windows installations can change a proposed implementation. +The roadmap remains intentionally local-first and avoids turning GameHours into a launcher, storefront or social network. --- -# 1. Product thesis +# 1. Cross-cutting product principles -GameHours should become the **reliable personal history of the user's videogames**. +## 1.1 Local truth stays authoritative -The product should answer, clearly and with provenance: +Measured GameHours sessions, local achievement evidence, historical-recovery provenance and future save-backup records remain authoritative within their own domains. Optional providers may enrich presentation, but they do not silently replace local truth. -- what did I play?; -- when did I play it?; -- how long was it really running?; -- how much of that time was focused/active when that coverage is available?; -- what achievements did I unlock and what is known versus uncertain about their history?; -- are my saves protected?; -- is this game's tracking healthy?; -- what patterns exist in my own gaming history? +## 1.2 Optional network features must fail soft -GameHours is deliberately **not** trying to become a storefront, social network or universal launcher. +Metadata, rarity, external catalogue data and future sync must sit behind provider boundaries with local caches. GameHours must remain useful offline and should never block tracking startup on a remote service. -That distinction matters architecturally. Tracking identity and measured history are authoritative local data. Covers, rarity, descriptions and other enrichments are replaceable metadata. Optional integrations must never become prerequisites for recording playtime. +## 1.3 Keep identities separate from presentation ---- +Do not grow `TrackedGame` into a giant object. Stable tracking identity, user preferences, metadata, health state, save state and presentation read models should remain distinct so failures and migrations stay local to their domains. -# 2. Cross-cutting design rules +## 1.4 Product sophistication should reduce user complexity -## 2.1 Keep authoritative data separate from presentation/enrichment +A technically sophisticated detector should produce simple user-facing states. Prefer one understandable status plus relevant actions over exposing internal resolver/provider terminology by default. -A game identity used by tracking should stay small and stable. User organization and optional metadata belong in separate models. +## 1.5 Preserve uncertainty -Conceptually: +GameHours must continue distinguishing measured, reconstructed, estimated, observed and externally sourced information. A richer UI must not make weak evidence look exact. -```text -TrackedGame identity - | - +--> measured sessions / evidence / achievements - | - +--> LibraryPreferences user-owned, durable - | - +--> MetadataSnapshot replaceable/cacheable - | - +--> GameHealthSnapshot derived - | - +--> SaveSafetyState derived + operation history -``` +--- -A metadata-provider outage must not affect playtime tracking. Changing a tag must not change executable identity. A failed save backup must not mutate a measured session. +# 2. Engineering rules for every roadmap phase -## 2.2 Local-first means useful offline, not "never use the network" +## 2.1 Research before implementation -Network enrichment is allowed when it materially improves the product, but it must be: +Before a significant slice: -- optional; -- explicit in Settings; -- cached locally; -- non-blocking for the core UX; -- privacy-minimal; -- replaceable behind provider boundaries. +- check what GameHours already has; +- review official platform/framework documentation; +- inspect the concrete reference files recorded in [`REFERENCE-PROJECTS.md`](REFERENCE-PROJECTS.md); +- compare the smallest reasonable alternatives; +- document substantial third-party adaptation and licensing in the PR that actually introduces it. -## 2.3 Provenance beats false certainty +## 2.2 Prefer framework/platform primitives -GameHours already distinguishes measured playtime from reconstructed evidence and complete achievement catalogues from partial state. New features must preserve that principle. +Use WPF collection views, Windows APIs, SQLite, existing GameHours providers and lifecycle events before adding dependencies or parallel frameworks. -Examples: +## 2.3 Keep third-party instability behind our contracts -- rarity from an online provider is enrichment, not authoritative unlock state; -- a save backup can be `verified`, `failed`, `unknown` or `not configured`; do not present "protected" merely because a folder exists; -- focus ratios are shown only over intervals where focus coverage is known; -- historical achievement timestamps remain explicitly uncertain when the source does not preserve them. +Permissively licensed source may be reused when that is genuinely better than recreating it, but unstable upstream APIs must terminate at a small GameHours-owned boundary. ## 2.4 Small vertical slices @@ -101,16 +76,29 @@ The user needs to answer quickly: ## 3.2 Intended UX -The default view remains simple: +Library 2.0 deliberately separates **browsing** from **organization** so the main playtime table does not become a dense settings form. -```text -[ Buscar juegos... ] [Todos] [Favoritos] [Jugando] [Completados] [Más] +The normal browsing view remains compact and tracking-oriented: -AHORA - current games... +```text +[ Buscar juegos... ] [ Mostrar: Todos v ] [ Organizar biblioteca ] BIBLIOTECA active first -> recently played -> older + game | last activity | achievements | total | measured | historical +``` + +`Organizar biblioteca` switches the content of the same Biblioteca section into an explicit management view rather than opening a separate settings window: + +```text +Organizar biblioteca [ <- Volver ] +Elige tu estado, marca favoritos y oculta juegos sin tocar su historial. + +[ Buscar... ] + +JUEGO ESTADO FAVORITO RESUMEN VISIBILIDAD +Gothic 1 Remake [Jugando v] ★ 53,8 h [Ocultar] +Another Game [Pendiente v] ☆ 12,1 h [Ocultar] ``` Important behavior: @@ -120,7 +108,13 @@ Important behavior: - filters combine predictably; - hidden/archive does not delete the game or its history; - favorites influence filtering/presentation but do not silently reorder every view unless the chosen sort asks for it; -- user state is editable from the game detail and, where useful, a compact context action. +- user state is editable from the dedicated organizer and may also remain available as a compact right-click shortcut; +- the organizer shows hidden games too, so hiding something can never make it impossible to recover; +- organizer changes persist immediately and returning to browse reuses the same existing view/filter state rather than constructing a second library; +- search behavior should be shared between browse and organizer instead of having two independent matching implementations; +- long-running tracking refreshes should coalesce organizer read-model refreshes instead of rebuilding the full organizer once per collection change. + +The dedicated organizer is the primary UX for setting status. Context menus are convenience shortcuts, not discoverability-critical functionality. ## 3.3 Data boundary @@ -141,6 +135,17 @@ Tags should be normalized separately so they remain queryable and do not become Completion status is a user preference, not inferred truth. GameHours may suggest a status later, but it should not silently mark a game `Completado` simply because achievements reach 100%: some games have no achievements and achievement completion is not equivalent to finishing a game. +For compatibility with the optional Gestor de Juegos adapter, the shared status subset is currently: + +- `Pendiente`; +- `Jugando`; +- `Pausado`; +- `Completado`; +- `Abandonado`; +- plus local `Sin estado` when the user has not classified a game. + +Do not conflate Gestor `completado_100` or GameHours achievement 100% with the personal completion status. + ## 3.4 Search strategy Start with the simplest implementation that fits the expected library scale: @@ -195,13 +200,15 @@ Do not let metadata providers write executable mappings or measured-history iden ## 3.6 Delivery slices -**Library 2.0A — preferences + search** +**Library 2.0A — preferences + search + explicit organizer** - favorite; - hidden/archive; - completion status; - search; - quick filters; +- dedicated `Organizar biblioteca` UX; +- right-click actions retained as shortcuts; - persistence/migrations/tests. **Library 2.0B — tags + filter polish** @@ -227,7 +234,7 @@ Do not let metadata providers write executable mappings or measured-history iden ## 3.7 Definition of done -Library 2.0 is successful when a user with a large local history can find and organize a game quickly without changing or endangering the tracking identity underneath it. +Library 2.0 is successful when a user with a large local history can find and organize a game quickly without changing or endangering the tracking identity underneath it. In particular, setting a status/favorite/visibility must be discoverable without knowing that a context menu exists. --- @@ -239,105 +246,92 @@ GameHours already knows a great deal about why a game is or is not being tracked The product should answer a simpler question: -> "Is GameHours tracking this game correctly, and if not, what exactly needs attention?" - -## 4.2 Health model +> Is this game working correctly in GameHours, and if not, what should I do? -A per-game snapshot should be derived from existing services, not from a new scanner. +## 4.2 Intended UX -Conceptually: +Per-game health should collapse technical detail into one status: ```text -GameHealthSnapshot -- OverallState: Ready | NeedsAttention | NotTracking -- Summary -- Checks[] -- AvailableActions[] -- TechnicalDetails -- ObservedAtUtc +Correcto +Necesita atención +No se está siguiendo ``` -Each check should have its own state and explanation, for example: - -- game identity; -- executable mapping; -- currently observed/tracked process; -- last measured session; -- historical recovery availability; -- achievement catalogue/state source; -- notification transport; -- Save Safety state when enabled. - -The overall state should be a deterministic reduction of checks, not a pile of UI-specific conditions. +Example healthy state: -## 4.3 Simple versus advanced information +```text +Estado de Gothic 1 Remake: Correcto +Tracking correcto +Ejecutable reconocido +Historial disponible +Logros fuente detectada +Última medida hace 2 min + +[ Ver detalles técnicos ] +``` -Default presentation should say things such as: +Problem state: ```text -CORRECTO -GameHours reconoce el ejecutable y está siguiendo el juego. +Necesita atención +GameHours ha observado el proceso pero no puede asociarlo con suficiente confianza. -✓ Ejecutable reconocido -✓ Seguimiento activo -✓ Logros locales disponibles -✓ Guardados protegidos hace 12 min +[ Resolver ] ``` -Advanced details can expose exact executable path, resolver source, AppID/source identity, timestamps and diagnostic codes. - -This distinction is inspired by Achievement Watcher Next's Game Health UX, but GameHours should implement its own health model from its own services. - -## 4.4 Diagnosis and repair must be separate +## 4.3 Architecture -The first Game Health PR should be **read-only**. +Do not create another scanner. Build a projection over existing authoritative services/repositories, conceptually: -Actions come afterwards and should invoke existing authoritative workflows: +```text +GameHealthSnapshot +- OverallStatus +- IdentityStatus +- TrackingStatus +- LastObservation +- HistoricalRecoveryStatus +- AchievementStatus +- NotificationStatus +- SaveSafetyStatus (later) +- Issues[] +``` -- resolve/associate executable; -- open Pendientes; -- open install folder; -- rescan achievement sources; -- run existing confirmed GSE catalogue preparation; -- send a test notification; -- copy/export technical diagnostics. +Each issue should have: -A repair should never duplicate the business logic already owned by another service. +- stable code; +- user-facing explanation; +- severity; +- optional existing action identifier; +- optional technical detail. -## 4.5 Diagnostic bundle +The read model must not mutate anything. -Before a public beta, GameHours should create a support ZIP with centrally enforced redaction. +## 4.4 Guided actions -Candidate contents: +The first slice is read-only. After the health model proves useful, guided repair actions may invoke existing workflows such as candidate confirmation, executable-role override, source refresh or future save mapping. -- GameHours version/build/channel; -- Windows/.NET/runtime summary; -- schema version; -- provider/source health summary; -- selected logs; -- safe configuration; -- optional user problem description. +Do not put destructive "fix automatically" logic into diagnostic checks. -Explicitly exclude or redact: +## 4.5 Diagnostic bundle -- usernames; -- tokens/secrets; -- raw SRUM or unrelated registry content; -- unrelated personal files; -- full machine paths unless a path has been transformed into a safe diagnostic representation. +Before beta, add one-click export that can include: -Playnite's MIT `Diagnostic.CreateDiagPackage` is a useful implementation reference for packaging flow. If substantial code is adapted, preserve attribution as required by its license. +- GameHours version/build; +- Windows/.NET summary; +- sanitized preferences; +- relevant diagnostics/logs; +- provider/source summaries; +- schema/application ID; +- optionally game-specific health details. -## 4.6 Delivery slices +Centralize redaction. Paths containing Windows user names, tokens, secrets or unnecessarily identifying information should be removed or normalized before the archive is created. -**Game Health 1 — read-only model + panel** -**Game Health 2 — contextual safe actions** -**Diagnostics 1 — privacy-minimal support bundle** -**Diagnostics 2 — direct linking from errors/empty states to the relevant health check** +Playnite's MIT `Diagnostic.CreateDiagPackage` is a useful implementation reference, but GameHours should apply stricter privacy defaults. -## 4.7 Definition of done +## 4.6 Definition of done -A non-technical user should be able to tell whether tracking is healthy and what to do next without reading logs; an advanced user should still be able to inspect precise technical evidence. +A non-technical user should be able to tell whether a game is tracked correctly and obtain actionable information without reading raw logs. --- @@ -345,190 +339,99 @@ A non-technical user should be able to tell whether tracking is healthy and what ## 5.1 User problem -The underlying achievement architecture already handles local sources, catalogue completeness, unlock state, confidence and session-scoped notifications. The next value is mainly product quality and broader source/enrichment coverage, not another rewrite of the core model. - -## 5.2 Modern Windows notifications - -The unlock event should remain transport-neutral. Replace the legacy tray balloon with a modern Windows notification transport behind the existing boundary. - -Desired result: - -```text -🏆 Logro desbloqueado -Big Walk -Click the Button -``` - -with artwork where already available, without coupling achievement detection to WPF notification APIs. - -Before implementation, verify the current recommended Microsoft path for unpackaged/Velopack WPF apps and test activation/identity behavior on the installed build. +The achievement engine already distinguishes complete catalogues, incomplete state, historical uncertainty and supplemental positive evidence. The UI should make that sophistication useful rather than exposing only compact counts. -## 5.3 Achievement browsing - -Improve the detail experience with: +## 5.2 Near-term UX +- modern Windows toast when a genuinely new unlock survives existing baseline/session gates; - locked/unlocked/hidden/progress filters; -- progress values only when the source provides them; -- clear completion milestones; -- stable ordering; -- good empty/loading/source-incomplete states; -- no punctuation tricks that imply extra unlocks or false totals. - -## 5.4 Optional rarity enrichment - -Rarity belongs behind an optional provider boundary. - -It may provide: - -- global unlock percentage; -- rarity tier; -- richer official metadata/artwork. - -It must not change the authoritative local unlock state. If the provider is offline or unavailable, achievement tracking remains fully functional. - -SuccessStory's MIT models are useful references for rarity/presentation data, but GameHours already has stronger evidence/provenance semantics and should preserve them. - -## 5.5 Screenshot souvenirs - -A screenshot captured around an unlock could become a distinctive feature, but it stays deferred until a prototype proves: +- progress presentation only when the source supplies real progress; +- stronger 100% completion moment and game-detail hierarchy; +- keep "hora histórica no disponible" or equivalent when timestamps are not trustworthy. -- reliable capture for common windowed/borderless/fullscreen cases; -- acceptable performance; -- safe behavior with protected content/anti-cheat; -- predictable storage/privacy controls. +Notification transport must remain behind the existing neutral achievement event so detection/persistence do not depend on Windows toast APIs. -No hooking/overlay dependency should be introduced merely to enable souvenirs. +## 5.3 Rarity -## 5.6 Delivery slices +Rarity is optional enrichment, not local truth. -**Achievements 2.0A — modern Windows notification transport** -**Achievements 2.0B — filters/progress/completion UX** -**Achievements 2.0C — optional rarity provider** -**Achievements 2.0D — screenshot prototype only if justified** - -## 5.7 Definition of done - -Achievements should feel native and polished while preserving the current local-first, evidence-aware behavior when no online enrichment is available. - ---- - -# 6. Save Safety — integrated Ludusavi engine - -## 6.1 User outcome - -GameHours should be able to protect a user's game saves without asking the user to discover save paths manually and without requiring a separate save-manager installation. - -The intended experience is: +Desired cached model: ```text -Gothic 1 Remake -Guardados -✓ Protegidos -Última copia: hoy, 23:14 -3 versiones conservadas - -[Crear copia ahora] [Ver copias] -``` - -Later, after restore safety is proven: - -```text -[Restaurar...] +AchievementRarity +- GameId +- AchievementApiName +- UnlockPercent +- Tier +- Provider +- ObservedAtUtc ``` -Automatic backup after a measured session is opt-in and should feel like a native GameHours capability. - -## 6.2 Why reuse Ludusavi instead of writing a save engine from zero +If the network/provider is unavailable, achievements still function normally. -Ludusavi already solves the difficult, maintenance-heavy part: +Do not infer unlock state from rarity data. -- game-to-save-layout knowledge; -- file path expansion; -- Windows Registry save locations; -- store identifiers; -- scanning; -- backup/restore logic; -- retention/version handling; -- a community-maintained manifest. - -The upstream project is MIT licensed. Its current Rust package also separates the GUI/CLI `app` feature from the library crate, and `src/lib.rs` explicitly exposes internal modules such as `scan`, `path`, `resource`, `api`, `serialization` and `report`. - -That makes source-level reuse technically realistic. - -There is an important caveat: Ludusavi's own `lib.rs` warns that the library API is currently unstable and many internals were not originally designed as a stable public library. GameHours must therefore isolate that instability behind a small boundary it owns. - -## 6.3 Explicitly rejected default approaches +## 5.4 Screenshot souvenir -### Require the user to install Ludusavi separately +Treat this as an experiment only after notifications are solid. If implemented: -Rejected as the preferred product architecture. +- opt-in; +- no anti-cheat-sensitive hooking; +- capture using supported Windows mechanisms; +- bounded storage/retention; +- clear indication if capture failed; +- unlock detection must not wait for the screenshot. -It would be easy to implement, but it creates avoidable UX and support burden: +## 5.5 Definition of done -- a second application to install/update/configure; -- path/version detection; -- mismatched configurations; -- unclear responsibility when backup fails; -- GameHours appears incomplete without another app. +Achievements feel integrated and polished while evidence uncertainty remains accurate and local operation remains independent of online rarity/metadata. -A separately installed Ludusavi may remain a useful development/debug compatibility path, but it is not the intended end-user requirement. - -### Port Ludusavi's Rust engine to C# - -Rejected. +--- -A port would immediately fork years of path/scanning/backup behavior. Every upstream fix would need to be reinterpreted and manually reimplemented. The apparent convenience of "all C#" would create much greater long-term maintenance cost. +# 6. Save Safety — integrated Ludusavi engine -### Vendor the whole Ludusavi application +## 6.1 User problem -Rejected. +Save protection is highly valuable but game-save discovery is a large solved problem with thousands of game-specific layouts, registry entries, path variables and store IDs. Recreating that manifest and scanner in GameHours would add enormous maintenance cost for little differentiation. -GameHours does not need Ludusavi's GUI, CLI presentation, themes or unrelated application lifecycle. Pulling the full app into the product would add dependency/build/update surface without adding user value. +At the same time, requiring users to separately install/configure another application weakens GameHours' product experience. -### Expose Ludusavi Rust types directly throughout .NET via FFI +## 6.2 Upstream facts and constraints -Not preferred for the first implementation. +Ludusavi is MIT licensed. Its current Rust package separates the user-facing application behind the Cargo `app` feature and exposes library modules such as scanning/path/resource/API/serialization. Its `lib.rs` also warns that the library API is unstable and many internals were not originally designed as a stable public API. -Direct FFI can work, but it creates native ABI, ownership, error-marshalling and unsafe-boundary complexity. It would also expose an explicitly unstable upstream API too widely. +Therefore: -## 6.4 Preferred architecture: bundled GameHours SaveEngine +- source-level reuse is legally and technically possible; +- direct coupling throughout .NET would be a maintenance mistake; +- copying/porting the engine into C# would create a fork we must maintain; +- the correct value to reuse is the engine/manifests, not Ludusavi's GUI. -Create a small GameHours-owned native helper, conceptually: +## 6.3 Preferred architecture ```text -GameHours.Desktop / Core (.NET) - | - | versioned JSON request/response - v -GameHours.SaveEngine (small Rust helper, shipped with GameHours) - | - | pinned source/library dependency - v -Ludusavi core (MIT, default app feature disabled) - | - +--> scan/path/registry/backup/restore - +--> Ludusavi manifest data +GameHours.Desktop / .NET + | + | stable, versioned GameHours JSON protocol + v +GameHours.SaveEngine +small Rust executable shipped in the GameHours package + | + | exact pinned Ludusavi revision + v +Ludusavi core + ludusavi-manifest ``` -The helper is **part of GameHours distribution**. The user installs one product and does not manage `ludusavi.exe` separately. - -Why prefer a small helper process over direct FFI initially: - -- process isolation contains crashes/panics; -- JSON gives a language-neutral, testable contract; -- the unstable Rust API is confined to one small component; -- no native pointer/object lifetime crosses into .NET; -- it can be versioned and smoke-tested independently; -- a helper failure cannot corrupt the WPF process state; -- future replacement of the underlying engine is possible without changing the Desktop contract. +`GameHours.SaveEngine` is part of GameHours from the user's perspective. It is not a separately installed application and does not have its own tray icon/settings UX. -Start with one-shot operations rather than a permanent daemon. A backup does not require another always-running process. +Prefer a one-shot helper process at first rather than a resident daemon: -## 6.5 Proposed GameHours-owned contract +- no permanent extra process; +- clean cancellation/timeout boundary; +- process crash cannot corrupt the desktop host; +- JSON protocol is easier to version/test than exposing unstable Rust structs over FFI/ABI. -The exact schema is an implementation decision, but the boundary should stay small and versioned. - -Candidate operations: +Potential GameHours protocol operations: ```text GetCapabilities @@ -540,482 +443,468 @@ PreviewRestore RestoreBackup ``` -Every request/response should include a protocol version and machine-readable error category. GameHours should distinguish at least: +Each response should carry a protocol version and structured result/error codes rather than human CLI strings. -- unsupported game; -- ambiguous game mapping; -- no save data found; -- permission/access failure; -- backup storage failure; -- incompatible engine/protocol version; -- cancelled/timeout; -- internal engine failure. +Candidate errors: -Do not parse human-readable console output. +```text +UnsupportedGame +AmbiguousMapping +NoSaveData +AccessDenied +Busy +BackupFailed +InvalidBackup +RestoreConflict +ProtocolMismatch +EngineFailure +``` -## 6.6 Game identity mapping +## 6.4 Responsibility split -Prefer stable IDs already known by GameHours: +Ludusavi-derived engine is responsible for: -1. Steam AppID when present; -2. GOG/store identity where available; -3. other stable platform IDs supported by the engine; -4. normalized title only when unambiguous; -5. explicit user mapping when ambiguity remains. +- manifest interpretation; +- save-file/registry path expansion; +- scanning; +- backup mechanics; +- restore mechanics; +- backup format where reused. -A title guess must never silently back up another game's data. +GameHours is responsible for: -## 6.7 Responsibility split +- mapping GameHours identity to the appropriate upstream identity; +- lifecycle/when operations happen; +- user consent/settings; +- background scheduling and cancellation; +- presenting support/health/history; +- automatic backup policy; +- backup retention UX/policy; +- restore confirmation/conflict policy; +- making sure Save Safety failures never damage tracking/achievement state. -**Ludusavi-derived engine owns:** +## 6.5 Identity mapping -- understanding save locations; -- resolving manifest paths/registry locations; -- scanning save files; -- creating/restoring backup content; -- backup-format details that belong to the engine. +Use the general GameHours external-identity boundary rather than title-only matching where possible: -**GameHours owns:** +```text +GameHours UUID + -> Steam AppID / GOG ID / Epic identity / etc. + -> Ludusavi manifest identity +``` -- when to request a backup; -- mapping GameHours game identity to the engine request; -- UI and user consent; -- operation scheduling/cancellation; -- displaying backup health/history; -- policy such as automatic-after-session on/off; -- persistence of GameHours-side operation summaries; -- protecting playtime tracking from backup failures. +Title matching may be a fallback that requires confirmation when ambiguous. -This boundary avoids duplicating Ludusavi while keeping product behavior under GameHours control. +Store a verified mapping so every backup does not repeat expensive or ambiguous discovery. -## 6.8 Session integration +## 6.6 Automatic backup lifecycle -GameHours already knows when a measured session completes. Reuse that lifecycle: +GameHours already knows when a measured session completes. Reuse it: ```text -Measured SessionCompleted - | - v +SessionCompleted + | + v SaveBackupCoordinator - | - +--> disabled? -> no work - +--> backup already running? -> coalesce/skip safely - | - v + | + +-- auto backup disabled -> stop + | + +-- unsupported/ambiguous -> record health state, do not block session finalization + | + v GameHours.SaveEngine CreateBackup - | - v -record operation result + refresh UI ``` -No new process scanner and no second game-running detector. +Do **not** add another process watcher just for saves. + +Automatic work should happen after authoritative session persistence. A backup failure can be surfaced/retried but cannot roll back or invalidate the measured session. + +If multiple rapid session boundaries occur for one game, coalesce/serialize appropriately rather than creating simultaneous backups of the same save set. -The save operation runs outside the UI-critical tracking path. Failure must not modify session duration, game identity or achievement state. +## 6.7 Restore safety -## 6.9 Restore safety +Restore is intentionally later than backup. It can destroy newer save data and therefore requires stronger UX and validation. -Restore is more dangerous than backup and should ship later. +Before any destructive restore: -Requirements before enabling restore: +1. preview affected files/registry values; +2. verify backup integrity/version; +3. create a safety backup of current state where possible; +4. present explicit confirmation; +5. handle game-running/busy state; +6. execute restore; +7. verify result; +8. retain enough audit data to understand what happened. -- preview exactly what would change; -- explicit user confirmation; -- create a pre-restore safety backup where possible; -- never overwrite while the game is known to be running unless the engine/game-specific evidence says it is safe; -- surface conflicts/downgrades rather than guessing; -- report partial failure precisely; -- keep recovery information if a restore does not complete. +Avoid a one-click destructive restore in early slices. -## 6.10 Manifest/update strategy +## 6.8 Delivery slices -Do not casually fork the Ludusavi manifest into a GameHours-specific format. +### Save Safety 1 — bridge feasibility -During the first implementation spike, prove the cleanest way for the integrated engine to consume/upkeep upstream manifest data while retaining offline usefulness. The chosen mechanism must have: +Goal: prove the hardest boundary before building UX. -- a known upstream revision/source; -- reproducible builds; -- a local cached/pinned fallback; -- an update path that cannot silently replace authoritative GameHours data; -- clear attribution. +- add smallest Rust helper project; +- pin an exact Ludusavi revision; +- compile only required core/library surface where feasible; +- define versioned JSON envelope; +- implement `GetCapabilities` plus one read-only save-data/preview operation; +- .NET process wrapper with cancellation, timeout, stdout size bound and structured errors; +- Rust + .NET contract tests; +- CI build for win-x64; +- include helper in publish/package smoke; +- establish `THIRD-PARTY-NOTICES.md`, upstream revision record and license verification. -If the manifest is redistributed with GameHours, its MIT license/copyright notice must be included. +Do not enable automatic backup yet. -## 6.11 Licensing and provenance +### Save Safety 2 — manual backup -Once Ludusavi code is actually incorporated into the build/distribution: +- map a real GameHours game to manifest identity; +- show detected save locations/count/size in a preview; +- `Crear copia ahora`; +- clear unsupported/ambiguous/permission failure UX; +- list latest successful backup in game detail/health. -- pin the exact upstream revision/version; -- preserve Matthew T. Kennerly's MIT copyright/license notice; -- add/update `THIRD-PARTY-NOTICES.md`; -- keep an upstream/revision record close to the native component; -- document local changes if any source is vendored or patched; -- include the same discipline for `ludusavi-manifest` if redistributed. +### Save Safety 3 — automatic after session -The roadmap/reference documents alone do not require a third-party notice because they do not distribute upstream code. +- opt-in globally and/or per game; +- trigger from existing `SessionCompleted`; +- background execution; +- coalesce per game; +- don't block session persistence/application shutdown indefinitely; +- visible result/health state. -## 6.12 Build, security and performance requirements +### Save Safety 4 — history and retention -The native component must not become an opaque exception to GameHours quality rules. +- backup history; +- storage usage; +- retention policy; +- delete/cleanup UX with safe defaults. -Before shipping: +### Save Safety 5 — restore -- reproducible/pinned Cargo dependencies; -- Release build in CI; -- tests for the JSON protocol; -- smoke tests using temporary save trees; -- cancellation/timeout behavior; -- path traversal/unsafe destination review; -- no shell command construction from untrusted strings; -- package contents and license notices verified in CI; -- Windows artifact signing strategy includes the helper binary; -- backup work never runs on the WPF UI thread; -- no persistent helper process unless measurement proves it is needed. +Only after the backup path is mature: -## 6.13 Delivery slices +- preview restore; +- current-state safety backup; +- confirmation; +- running-game protection; +- restore verification; +- conflict/version handling. -**Save Safety 1 — engine feasibility/bridge** +## 6.9 Upstream update strategy -- add the smallest Rust helper project; -- pin a Ludusavi revision with application feature disabled where viable; -- `GetCapabilities` + one read-only save-data resolution/preview path; -- protocol tests; -- packaging/licensing proof; -- no automatic backups yet. +Do not track Ludusavi `master` implicitly. -This slice answers the highest-risk architectural question before building UI around it. +Maintain a record containing: -**Save Safety 2 — manual preview + backup** +- repository; +- exact commit/tag; +- Ludusavi version if applicable; +- manifest revision; +- local integration patch list if any; +- date reviewed; +- licenses. -- GameHours game mapping; -- preview detected save data; -- `Crear copia ahora`; -- operation result persisted/displayed; -- clear unsupported/ambiguous/error states. +Upstream update PRs should run the SaveEngine contract suite and representative manifest fixtures before changing the pinned revision. -**Save Safety 3 — automatic after measured session** +## 6.10 Licensing and attribution -- opt-in setting; -- hook existing `SessionCompleted` lifecycle; -- serialization/coalescing per game; -- non-blocking background execution; -- visible last-success/last-failure state. +The first PR that actually distributes Ludusavi source/binary-derived content or `ludusavi-manifest` must add the required MIT notices. -**Save Safety 4 — backup history + retention UX** +Expected repository-level artifact: -- list versions; -- storage usage; -- retention controls that map cleanly onto the engine; -- no duplicated backup index if the engine already owns that data. +```text +THIRD-PARTY-NOTICES.md +- Ludusavi + copyright + MIT text / pointer according to packaging arrangement + upstream URL + exact revision +- ludusavi-manifest + corresponding notice/revision +``` -**Save Safety 5 — restore** +Package verification must ensure the notices ship with the product where required. -- preview; -- safety backup; -- explicit confirmation; -- conflict/downgrade handling; -- robust failure/recovery UX. +Do not create a misleading notice before any third-party code/data is actually distributed. -## 6.14 Definition of done +## 6.11 Alternatives rejected -Save Safety is complete when GameHours can natively protect supported saves with no separate application installation, while clearly attributing/reusing Ludusavi's MIT engine and keeping all unstable/native details behind a small GameHours-owned boundary. +### Require separately installed Ludusavi ---- +Simple technically, but worse product UX and creates version/path/configuration dependency on another application. Keep it only as a possible developer/debug fallback, not the intended product path. -# 7. Platform/source expansion +### Port the engine to C# -## 7.1 Separate three different capabilities +Rejected: high ongoing maintenance and loss of upstream improvements. -For every platform, distinguish: +### Vendor the whole Ludusavi application -1. **Library discovery** — know that a game is installed and its identity/path; -2. **Playtime tracking** — resolve its real processes through the existing tracker; -3. **Achievements** — read an achievement catalogue/state if a reliable local/optional source exists. +Rejected: unnecessary GUI/CLI/cloud/translations/dependencies and larger attack/maintenance surface. -Do not block discovery/tracking on achievement support. +### Broad direct FFI -## 7.2 Proposed order +Rejected initially: unstable Rust types/ABI would leak throughout GameHours and make upstream updates expensive. A small process/JSON boundary is easier to reason about and recover from. -1. Xbox / Microsoft Store / Game Pass; -2. Ubisoft Connect; -3. EA Desktop; -4. Amazon Games / Battle.net when reliable local evidence is characterized; -5. selected emulators only when a real use case justifies them. +## 6.12 Definition of done -## 7.3 Per-platform research template +Save Safety should feel native to GameHours: the user should not have to know Ludusavi exists to protect saves. Internally, upstream reuse must remain obvious, pinned, licensed and isolated enough that a Ludusavi update does not require rewriting GameHours UI/domain code. -Before writing production code: +--- -- install/inspect the real Windows client where possible; -- identify official APIs/docs first; -- characterize manifests/databases/packages on disk; -- record exact evidence in a short `docs/` format note; -- inspect mature OSS adapters for leads; -- review license before adapting code; -- define what can be supported offline; -- identify launcher/helper processes that must never count as game time; -- add fixtures/tests from sanitized representative layouts; -- validate on a real installed game before claiming support. +# 7. Platform expansion -## 7.4 Architecture rule +## 7.1 Distinguish capability layers -A new platform adapter feeds existing identity/discovery layers. It must not create a separate platform-specific process tracker. +Never describe a platform as simply "supported" without saying what works. -Conceptually: +For every platform track separately: ```text -Xbox/Ubisoft/EA local metadata - | - v -DiscoveredGame / store identity - | - v -existing Windows resolver + tracker +Discovery +Tracking +Achievements +Metadata +Historical recovery (if applicable) ``` -## 7.5 Definition of done +Example: -A platform is supported only for the capabilities actually verified. Documentation/UI should be able to say, for example, "installed-game discovery + playtime tracking supported; achievements not yet supported" rather than presenting one vague support flag. +```text +Xbox / Microsoft Store +Discovery: yes +Tracking: yes +Achievements: not yet +Metadata: partial +``` ---- +This avoids coupling platform discovery work to a much harder achievement integration. -# 8. Insights 2.0 +## 7.2 Priority order -## 8.1 User problem +Current candidate order: -GameHours already collects data that normal launchers often collapse into a single total. The value now is to make that history explorable without inventing precision. +1. Xbox / Microsoft Store / PC Game Pass; +2. Ubisoft Connect; +3. EA Desktop; +4. Amazon Games / Battle.net if stable local evidence is available; +5. emulators based on real user cases, not a speculative universal emulator framework. -## 8.2 Candidate insights +For each platform first investigate official/local manifests/package APIs and what Playnite/Heroic/Achievement Watcher currently use. Prefer documented/local stable identity over executable-name heuristics. -Per game and globally: +## 7.3 Performance rule -- sessions per day/week/month; -- average and median session duration; -- longest sessions; -- day-of-week distribution; -- time-of-day distribution; -- calendar heatmap; -- executed/focused/estimated-active time; -- focus/active ratio where coverage is known; -- recent versus lifetime trends; -- achievement unlock activity; -- streaks only if defined carefully and not gamified misleadingly. +Platform discovery should remain event/startup/manual-refresh oriented where possible. Do not add high-frequency filesystem/registry scans to the tracking loop. -## 8.3 Coverage-aware metrics +## 7.4 Definition of done -A ratio such as active/executed time is meaningful only over periods where both measurements exist. +Each shipped platform states exactly which capabilities work and does not reduce existing tracking precision/reliability for other games. -Do not compute: +--- -```text -all-time active / all-time executed -``` +# 8. Insights 2.0 -if active telemetry only started halfway through the history. Instead compute over the intersection of known coverage and display that scope. +## 8.1 Opportunity -Historical SRUM evidence should stay separate from measured daily timelines because it may not preserve exact per-session/day structure. +GameHours has a differentiator that traditional launcher playtime often lacks: it can distinguish executed time, foreground time and active-estimated time for measured sessions while preserving historical evidence separately. -## 8.4 Query architecture +Use that data to answer useful personal-history questions rather than creating dashboards for their own sake. -Prefer SQLite/bulk read models for larger aggregates rather than loading every session into WPF repeatedly. +Candidate insights: -Add only indexes/summary tables shown necessary by query plans/measurements. Keep raw authoritative sessions; derived aggregates should be reproducible. +- average/median session length; +- longest session; +- playtime by day of week/hour; +- monthly/weekly trends; +- heatmap/calendar summaries; +- executed vs focused vs active-estimated; +- focus ratio; +- streaks; +- achievement activity; +- per-game drill-down. -## 8.5 UX +## 8.2 Coverage-aware ratios -Insights should answer a question, not become a wall of charts. +Do not compute misleading lifetime ratios when attention telemetry only exists for recent measured sessions. -A good hierarchy: +For example: ```text -Esta semana -12 h 40 min · 6 sesiones - -Tus hábitos -Sábado es tu día más jugado -Sesión mediana: 1 h 18 min -Horario habitual: 21:00–00:00 - -Actividad real -Ejecutado 10 h 20 min -En primer plano 9 h 04 min -Activo estimado 8 h 31 min -Cobertura: últimos 30 días +focus_ratio = focused_time / executed_time ``` -Drill-down should lead to the sessions behind the aggregate. +must use only intervals/sessions where both signals have valid coverage. -## 8.6 Delivery slices +Presentation should state the coverage denominator, for example: -**Insights 2.0A — session distribution + median/longest** -**Insights 2.0B — day/time heatmaps** -**Insights 2.0C — focus/active coverage-aware metrics** -**Insights 2.0D — achievement activity + drill-down polish** +> Foco 83 % · basado en 24 sesiones con telemetría -## 8.7 Definition of done +Do not blend reconstructed historical playtime into active/focus denominators unless the source genuinely provides equivalent information. -The statistics screen should reveal useful patterns that cannot be obtained from a single launcher playtime counter while remaining faithful to measurement coverage/confidence. +## 8.3 Performance ---- +Prefer SQLite aggregation for genuinely large historical queries rather than materializing all sessions repeatedly in WPF. -# 9. First-run, help and beta UX +Before adding indexes, measure representative query plans/timing and add only those justified by actual queries. -## 9.1 User problem +## 8.4 Definition of done -A technically sophisticated tracker can still feel broken if the user does not understand what it detected, what it is waiting for or why a game is absent. +Insights reveal patterns a user could not easily get from Steam/launcher totals while remaining statistically honest about coverage. -The beta experience must explain itself without requiring knowledge of SRUM, resolver confidence or launcher manifests. +--- + +# 9. First-run, help and beta UX -## 9.2 First-run principles +## 9.1 First-run goal -Do not create a long mandatory wizard. +A new user should understand what GameHours does without reading the repository. -The default path should be roughly: +Possible first-run flow: ```text Bienvenido a GameHours -Tu historial se guarda localmente. +Tu historial local de juego, independiente del launcher. -Detectado en este PC +Detectando fuentes... ✓ Steam ✓ Epic ✓ GOG -GameHours empezará a medir automáticamente los juegos reconocidos. -[Empezar] +GameHours funciona en segundo plano y empieza a medir desde ahora. +[ Empezar ] ``` -Advanced choices belong in Settings. +Do not force optional network providers/accounts during onboarding. + +## 9.2 Contextual help -## 9.3 Contextual help +Prefer explanations based on actual state: -Prefer help at the point of failure: +- why a game is missing; +- what "histórico estimado" means; +- why achievement time is unavailable; +- why active time differs from executed time; +- why a game needs attention. -- no games -> explain discovery and offer rescan/manual add; -- unresolved executable -> link to Pendientes; -- achievement source incomplete -> explain exactly what is known; -- Save Safety unsupported -> explain unsupported/ambiguous rather than a generic error; -- updater failure -> provide recovery action/documentation. +Link directly to the relevant action/section where possible. -Game Health should become the common destination for per-game troubleshooting. +## 9.3 Empty/loading/error states -## 9.4 Accessibility and localization +Every major view/provider should define: -Before multiplying UI strings further: +- initial loading; +- empty but healthy; +- offline/unavailable optional provider; +- recoverable error; +- permanent unsupported state. -- establish localization resource structure; -- audit keyboard navigation/focus order; -- ensure status is not communicated only by color; -- verify scaling/DPI and text clipping; -- test screen-reader names for primary controls where practical; -- respect reduced-motion expectations for nonessential animation. +Avoid presenting blank tables that look broken. -## 9.5 Diagnostic support +## 9.4 Accessibility -The privacy-minimal diagnostic bundle belongs in this beta track even though its implementation is grouped with Game Health. Supportability is part of UX. +Before beta review: + +- keyboard navigation/focus order; +- focus visuals; +- high-DPI scaling; +- color contrast; +- screen-reader labels for icon-only controls; +- reduced-motion consideration if animations are added; +- avoid using color as the only status signal. + +## 9.5 Localization foundation + +Do not hardwire future provider/error logic to Spanish display text. Stabilize user-facing message identifiers/models first, then move strings toward resources when the beta UX is sufficiently settled. ## 9.6 Definition of done -A new user can install GameHours, understand what it will do, see what was detected and recover from common problems without reading repository documentation. +A first-time user can install, understand, diagnose common issues and find their data/privacy controls without external instructions. --- # 10. Distribution and trust -## 10.1 Objective - -Convert the already-implemented packaging/update foundation into a trustworthy public release path. +## 10.1 Signing -## 10.2 Required gates +Move from internal unsigned/dev validation to a repeatable Azure Artifact Signing release path. -- operational Azure Artifact Signing/OIDC configuration; -- real signed release from `main`; -- Authenticode verification of every executable binary shipped, including future native helpers such as `GameHours.SaveEngine`; -- clean installation test; -- signed previous-version -> current-version update test; -- rollback/recovery validation; -- package-content verification; -- published checksums/attestation as already designed; -- SmartScreen observation with the real signed installer; -- clear install/update/uninstall/data-location documentation. +Requirements: -## 10.3 Dependency and third-party visibility +- OIDC identity; +- minimum required signer role; +- no long-lived signing secret in GitHub; +- sign every executable/DLL that requires Authenticode trust, including future native helpers such as `GameHours.SaveEngine.exe`; +- verify signatures after packaging. -As GameHours begins to ship third-party/native components, release packaging must verify: +## 10.2 Release gates -- required license/notice files are present; -- no source/build secrets are packaged; -- helper binaries correspond to the pinned reviewed source revision; -- SBOM/third-party inventory can be generated or audited reproducibly. +For a beta candidate verify: -## 10.4 Definition of done +- clean install; +- launch; +- single instance; +- update from previous signed version; +- recovery behavior; +- uninstall preserving external data by design; +- reinstall finds preserved data; +- package hashes/signatures; +- updater source remains HTTPS/read-only/trusted according to existing policy. -A public build can be downloaded, its publisher/signature verified, installed and updated predictably, and the user knows where local data lives and how to recover it. +## 10.3 SmartScreen ---- +Evaluate reputation with the actual signed release binary rather than extrapolating from unsigned development builds. -# 11. Cross-cutting performance track +## 10.4 Public documentation -Performance is continuous and evidence-driven, not a separate rewrite phase. +At minimum: -Measure when relevant: +- what GameHours measures; +- where local data lives; +- backup/restore/export/import; +- install/update/uninstall; +- privacy/network behavior; +- limitations of historical recovery; +- troubleshooting/diagnostic export. -- startup/time-to-interactive; -- idle/tray CPU; -- active tracking CPU; -- UI frame/interaction responsiveness; -- Private Memory / Working Set; -- managed allocation rate/GC when investigating memory; -- SQLite query counts/durations; -- image-cache size/hit behavior; -- native SaveEngine startup/operation overhead once it exists; -- network work and cache misses for metadata providers. +## 10.5 Definition of done -Preferred optimization order: +A beta can be installed and updated by another Windows user with understandable trust/privacy behavior and a recovery path if something goes wrong. -1. eliminate unnecessary work; -2. avoid repeated I/O/network calls; -3. batch database reads; -4. lazy-load expensive views/artwork; -5. virtualize long UI lists; -6. bound caches; -7. only then consider lower-level tuning. +--- -Never use forced GC/working-set trimming as cosmetic optimization. +# 11. Later architectural opportunities ---- +## 11.1 Public plugin/provider SDK -# 12. Deliberately deferred capabilities +Do not freeze one yet. Exercise internal provider boundaries through multiple real platform/metadata/save integrations first. Once the contracts stop changing frequently, evaluate exposing a deliberately small public SDK. -Reconsider later only after internal contracts have matured: +## 11.2 Local API -- public plugin/provider SDK; -- local automation/query API; -- optional cross-device/cloud sync on top of the neutral sync boundary; -- richer theming; -- screenshot souvenirs after a safe capture prototype. +ActivityWatch demonstrates the flexibility of an API/event model. GameHours should only add a local API if concrete automation/integration use cases justify the security/lifecycle surface. -Still excluded unless product direction explicitly changes: +## 11.3 Cloud/sync -- social network / friends / feeds / public profiles; -- game purchasing/storefront; -- game installation/uninstallation management; -- replacing Steam/Playnite/Heroic as a general launcher. +Optional only. Never make account/cloud availability a prerequisite for local tracking or local history access. --- -# 13. Recommended implementation order +# 12. Roadmap-wide definition of quality -The roadmap priority remains product-driven, but implementation should keep risk contained. +A roadmap item is not complete simply because it compiles. -Recommended near-term sequence: +Depending on the slice, completion requires: -1. **Library 2.0A** — preferences + lightweight search/filtering; -2. **Game Health 1** — read-only health snapshot/panel; -3. **Diagnostics 1** — privacy-minimal support bundle; -4. **Achievements 2.0A** — modern Windows notification transport; -5. **Save Safety 1** — integrated `GameHours.SaveEngine` feasibility/bridge using pinned Ludusavi core; -6. **Library 2.0C** — metadata boundary after basic organization is stable; -7. **Save Safety 2** — manual preview/backup after the native bridge is proven; -8. then select Insights/platform work from measured user/product value rather than executing every phase mechanically. +- root cause/problem clearly characterized; +- architecture consistent with existing boundaries; +- no unnecessary dependency/parallel mechanism; +- migrations and failure states considered; +- focused automated tests; +- full applicable suite; +- Release build; +- CI/CodeQL where applicable; +- real-Windows functional/visual verification where CI cannot prove behavior; +- updated docs/attribution when required; +- no claim stronger than the evidence actually collected. -Each slice should update this detailed document only when evidence changes the intended architecture or product outcome. Avoid turning the roadmap into a changelog; completed implementation evidence belongs in PRs and validation documents. +The goal is not to maximize feature count. The goal is for every addition to make GameHours more useful without degrading its reliability, clarity or maintainability. diff --git a/integration/gestor-juegos/API-CONTRACT-DRAFT.md b/integration/gestor-juegos/API-CONTRACT-DRAFT.md index e67daf2..955251f 100644 --- a/integration/gestor-juegos/API-CONTRACT-DRAFT.md +++ b/integration/gestor-juegos/API-CONTRACT-DRAFT.md @@ -1,10 +1,79 @@ -# Gestor de Juegos adapter contract — deferred draft +# Gestor de Juegos adapter contract — compatibility draft -This document belongs to the optional Gestor de Juegos integration, not to the backend-neutral GameHours sync contract. +This document belongs to the optional Gestor de Juegos integration, not to the backend-neutral GameHours sync contract. It was reviewed against the current `Ayerdi/gestor-juegos` schema/API documentation and `main` implementation on 2026-08-31. -GameHours emits its own UUID-based model described in `../../docs/SYNC-BOUNDARY.md`. A future Gestor adapter will be responsible for resolving a GameHours `game_id` to a Gestor `catalogo_juego_id`, authenticating the native device and translating the neutral model into the Gestor API shape. +GameHours remains the tracking authority and emits its UUID-based model described in `../../docs/SYNC-BOUNDARY.md`. A future adapter may enrich or synchronize selected library fields, but the adapter must be removable without changing local tracking behaviour. -Possible Gestor-side payload shape: +## Identity mapping + +Never use a Gestor database primary key as the canonical GameHours identity. + +Preferred matching order: + +1. `steam:` from GameHours `game_external_identities` -> Gestor `catalogo_juegos.steam_id`; +2. `igdb:` -> Gestor `catalogo_juegos.igdb_id` when GameHours has a verified IGDB identity in the future; +3. title matching only as an explicit user-assisted fallback, never as silent authoritative identity. + +After a verified match, an adapter may cache `catalogo_juego_id` as a Gestor-specific link. That cached link is replaceable integration state; the GameHours UUID and measured history remain valid if the Gestor is unavailable or rebuilt. + +Provider IDs are namespaced. `steam:123` and `gog:123` are different identities. Provider names are normalized by GameHours, while external identity values remain exact so each provider adapter owns any provider-specific normalization. One provider identity must not silently move between two GameHours games. + +## Library state mapping + +The current common personal-state subset is: + +| GameHours | Gestor `mis_juegos.estado` | +| --- | --- | +| `Backlog` | `Pendiente` | +| `Playing` | `Jugando` | +| `Paused` | `Pausado` | +| `Completed` | `Completado` | +| `Abandoned` | `Abandonado` | +| `Unspecified` | no imported state | + +Gestor also supports states such as `Deseado`, `En Espera` and `Wishlist`. GameHours must not coerce those into a different completion state. Until GameHours intentionally adds an equivalent concept, an adapter should preserve them as source-specific information or leave local completion status unchanged. + +`mis_juegos.favorito` maps to GameHours `IsFavorite`. + +Gestor `completado_100` is a separate flag and must **not** be translated into `LibraryCompletionStatus.Completed`; GameHours completion status maps only from `mis_juegos.estado`. Achievement completion and “finished the game” are deliberately different concepts in GameHours as well. + +GameHours `IsHidden` is local-only presentation state. There is no equivalent field in the reviewed Gestor schema, so remote data must never clear or set it. + +## Field authority + +| Information | Authority / rule | +| --- | --- | +| GameHours UUID | GameHours only | +| measured sessions and focused/active telemetry | GameHours only; never overwritten by Gestor | +| reconstructed SRUM history | GameHours evidence; never converted into exact Gestor/Steam truth | +| `favorito` / completion status | optional library sync; conflict policy must be explicit before bidirectional writes are enabled | +| `tiempo_jugado` | external/personal Gestor information; may be displayed or imported as separately labelled evidence, not written over measured sessions | +| `horas_steam_snapshot`, Steam achievement snapshots | external snapshots only | +| cover, developer, publisher, release date, genres and similar catalogue metadata | optional enrichment with provider provenance/cache | +| hidden/archive | GameHours local only | + +A first integration should therefore be read-only enrichment/import. Bidirectional preference writes should be a later opt-in feature with a visible conflict policy rather than last-write-wins by accident. + +## Current Gestor surfaces relevant to a future adapter + +The reviewed Gestor exposes a global `catalogo_juegos` model and a separate `mis_juegos` user relationship. This separation matches GameHours' decision to keep external catalogue identity separate from local user preferences. + +Useful current endpoints include: + +- `GET /mis-juegos` for the authenticated user's personal library; +- `GET /mis-juegos/detalle/?fast=1` for DB-only detail; +- `GET /mis-juegos/detalle//enrich` for slower external enrichment; +- `GET /catalogo/buscar-o-importar?nombre=...` for catalogue lookup/import. + +The exact response shape remains owned by the Gestor repository and must be translated inside the adapter rather than copied into `GameHours.Core`. + +## Authentication + +The desktop client must use a dedicated native device/account credential flow. It must not spoof browser-oriented Authentik identity headers. No credential or Gestor URL is required for GameHours startup or tracking. + +## Deferred playtime upload shape + +If session upload is resumed later, the adapter may translate a neutral GameHours session after resolving the local UUID to a Gestor catalogue entry, for example: ```json { @@ -18,11 +87,8 @@ Possible Gestor-side payload shape: "capture_method": "reconciliation", "confidence": "high" } - ], - "historical": [] + ] } ``` -This shape is intentionally deferred and may change when the Gestor integration is resumed. It must not leak back into `GameHours.Core` or the neutral `GameHours.Sync` contracts. - -The native client must use a dedicated device/account credential flow rather than spoofable browser-oriented Authentik headers. +This wire shape is still deferred and may change. It must not leak into the backend-neutral GameHours sync contracts. diff --git a/integration/gestor-juegos/README.md b/integration/gestor-juegos/README.md index fadf170..9ac1c30 100644 --- a/integration/gestor-juegos/README.md +++ b/integration/gestor-juegos/README.md @@ -1,7 +1,19 @@ # Gestor de Juegos integration -This directory is an optional adapter boundary. GameHours itself remains backend-neutral and does not import or depend on the Gestor backend. +This directory is the optional adapter boundary between GameHours and `Ayerdi/gestor-juegos`. GameHours remains fully local-first and must continue working without an account, network connection or Gestor deployment. -The canonical GameHours sync model lives in [`../../docs/SYNC-BOUNDARY.md`](../../docs/SYNC-BOUNDARY.md) and uses GameHours-owned UUIDs. Any Gestor-specific catalogue mapping, field translation, authentication or endpoint behaviour belongs here or in the `gestor-juegos` repository, not in `GameHours.Core` or the neutral sync contracts. +The canonical GameHours sync model lives in [`../../docs/SYNC-BOUNDARY.md`](../../docs/SYNC-BOUNDARY.md) and uses GameHours-owned UUIDs. Gestor catalogue IDs, authentication and endpoint behaviour must not leak into `GameHours.Core` or the neutral sync contracts. -The deferred Gestor wire draft is documented in [`API-CONTRACT-DRAFT.md`](API-CONTRACT-DRAFT.md). Integration work is intentionally paused while GameHours continues maturing as a standalone application. +## Compatibility foundation + +Library 2.0 keeps the two products compatible without coupling them: + +- GameHours keeps its UUID as the authoritative tracking identity; +- provider-scoped identities such as `steam:3946950` can be persisted in `game_external_identities` and are the preferred correlation key for optional catalogue providers; +- a future Gestor adapter may resolve `steam:` against `catalogo_juegos.steam_id` and cache the resulting `catalogo_juego_id`, but that Gestor-local ID never replaces the GameHours UUID; +- `favorito` maps naturally to GameHours `IsFavorite`; +- the shared personal states are `Pendiente`, `Jugando`, `Pausado`, `Completado` and `Abandonado`; +- GameHours `IsHidden` is local presentation state and has no Gestor equivalent, so an external provider must not overwrite it; +- GameHours measured playtime remains authoritative local evidence. Gestor `tiempo_jugado` and Steam snapshots are optional external information and must not rewrite measured sessions. + +The reviewed Gestor field/API mapping and conflict rules live in [`API-CONTRACT-DRAFT.md`](API-CONTRACT-DRAFT.md). The actual network adapter remains deferred: this foundation deliberately adds no remote request, credential or startup dependency. diff --git a/src/GameHours.Core/Domain/GameExternalIdentity.cs b/src/GameHours.Core/Domain/GameExternalIdentity.cs new file mode 100644 index 0000000..d070b22 --- /dev/null +++ b/src/GameHours.Core/Domain/GameExternalIdentity.cs @@ -0,0 +1,49 @@ +namespace GameHours.Core.Domain; + +/// +/// Stable identity assigned by an external catalogue/platform. GameHours keeps its own UUID as +/// the tracking identity; these values exist only to correlate that UUID with optional sources. +/// +public sealed record GameExternalIdentity +{ + public string Provider { get; } + public string ExternalId { get; } + + public GameExternalIdentity(string provider, string externalId) + { + if (string.IsNullOrWhiteSpace(provider)) + { + throw new ArgumentException("External identity provider cannot be empty.", nameof(provider)); + } + + if (string.IsNullOrWhiteSpace(externalId)) + { + throw new ArgumentException("External identity value cannot be empty.", nameof(externalId)); + } + + Provider = provider.Trim().ToLowerInvariant(); + ExternalId = externalId.Trim(); + } +} + +public static class GameExternalIdentityProviders +{ + public const string Steam = "steam"; + public const string Epic = "epic"; + public const string Gog = "gog"; + public const string Igdb = "igdb"; + + public static GameExternalIdentity? FromDiscoveredGame(DiscoveredGame game) + { + ArgumentNullException.ThrowIfNull(game); + var provider = game.Source switch + { + GameDiscoverySource.Steam => Steam, + GameDiscoverySource.Epic => Epic, + GameDiscoverySource.Gog => Gog, + _ => null + }; + + return provider is null ? null : new GameExternalIdentity(provider, game.ExternalId); + } +} diff --git a/src/GameHours.Core/Domain/LibraryCompletionStatus.cs b/src/GameHours.Core/Domain/LibraryCompletionStatus.cs new file mode 100644 index 0000000..aee2e21 --- /dev/null +++ b/src/GameHours.Core/Domain/LibraryCompletionStatus.cs @@ -0,0 +1,13 @@ +namespace GameHours.Core.Domain; + +public enum LibraryCompletionStatus +{ + Unspecified = 0, + Backlog = 1, + Playing = 2, + Completed = 3, + Abandoned = 4, + // Keep this appended so v8 development databases that already stored 3/4 retain their + // Completed/Abandoned meaning after Gestor de Juegos compatibility is added. + Paused = 5 +} diff --git a/src/GameHours.Core/Domain/LibraryGamePreferences.cs b/src/GameHours.Core/Domain/LibraryGamePreferences.cs new file mode 100644 index 0000000..a2fe283 --- /dev/null +++ b/src/GameHours.Core/Domain/LibraryGamePreferences.cs @@ -0,0 +1,13 @@ +namespace GameHours.Core.Domain; + +public sealed record LibraryGamePreferences( + Guid GameId, + bool IsFavorite = false, + bool IsHidden = false, + LibraryCompletionStatus CompletionStatus = LibraryCompletionStatus.Unspecified) +{ + public bool IsDefault => + !IsFavorite && + !IsHidden && + CompletionStatus == LibraryCompletionStatus.Unspecified; +} diff --git a/src/GameHours.Desktop/App.xaml b/src/GameHours.Desktop/App.xaml index 796d13e..2bf138a 100644 --- a/src/GameHours.Desktop/App.xaml +++ b/src/GameHours.Desktop/App.xaml @@ -45,6 +45,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +