Skip to content

fix(tdp-control): persist user-set TDP across shutdown (#204)#205

Merged
srsholmes merged 3 commits into
mainfrom
claude/issue-204-jwa9it
Jul 8, 2026
Merged

fix(tdp-control): persist user-set TDP across shutdown (#204)#205
srsholmes merged 3 commits into
mainfrom
claude/issue-204-jwa9it

Conversation

@srsholmes

@srsholmes srsholmes commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Problem

The manually-set TDP lived only in memory, so the user's choice was lost on
every reboot — hardware power limits reset to firmware defaults on shutdown.

Fix

Persist the last user-requested wattage to the plugin config file
(~/.config/loadout/plugins/tdp-control.json, manualTdp key) and re-apply it
on startup. Only user-initiated setTdp() / applyProfile() calls persist —
automatic applies (per-game profiles, AC transitions, resume, device changes)
route through a private applyTdp() and leave the saved value untouched.

The "no game" TDP model

The saved manual TDP is the authoritative "no game running" value, and is
restored regardless of whether per-game profiles are enabled. Precedence:

  • A recognized per-game profile's game launches → that profile's TDP wins.
  • No game running (boot, game exit, or a game without a profile) → the
    saved manual TDP is applied.
  • The engine's stored defaultTdp remains only as a fallback for users who
    never set a manual value.

Implemented via a getNoGameTdp callback injected into the profile engine, so
handleGameExit, the no-profile branch of handleGameLaunch, removeProfile,
and the per-game toggle-on path all resolve the same no-game value.

Note: this supersedes the earlier "manual restore only runs when per-game is
off" behavior — with per-game on, boot previously fell back to the engine
defaultTdp and silently dropped the user's manual value (e.g. set 35W, got
12W after reboot).

Guard against in-game drift

setTdp() does not overwrite the saved no-game value while a per-game
profile is actively governing TDP (per-game enabled + a game running). In that
state the slider tunes the running game's profile (the frontend routes it to
setGameProfile), so persisting there would let an in-game tweak silently
redefine the value restored on game exit / reboot.

Tests

Added regression coverage for the getNoGameTdp paths (exit, no-profile launch,
profile precedence, removeProfile, toggle-on, null/omitted-callback fallback)
and the setTdp in-game persistence guard. 90/90 tdp-control tests pass;
typecheck clean.

Verification

Built and installed on hardware (OneXPlayer APEX / Ryzen AI MAX+ 395). Service
logs confirm Restoring saved manual TDP …W on startup where it previously
applied the 12W per-game default.

Co-Authored-By: Claude Opus 4.8 [email protected]
Claude-Session: https://claude.ai/code/session_01XNKJxF3QuZjhsjn5CqKyJM

claude and others added 3 commits July 8, 2026 07:17
The manually-set TDP lived only in memory, so the user's choice was
lost on every reboot (hardware power limits reset to firmware defaults).

Persist the last user-requested wattage to the plugin config file
(~/.config/loadout/plugins/tdp-control.json, `manualTdp` key) and
re-apply it on startup. Only user-initiated setTdp()/applyProfile()
calls persist — automatic applies (per-game profiles, AC transitions,
resume, device changes) route through a new private applyTdp() and
leave the saved value untouched. When per-game profiles is enabled the
engine still owns TDP; the manual restore only runs when it's off.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XNKJxF3QuZjhsjn5CqKyJM
…game on

The startup restore only re-applied the saved manual TDP when per-game
profiles were OFF. With per-game ON, boot (and game exit) fell back to the
engine's separate `defaultTdp`, so a user's manually-set TDP was lost on
reboot even though it was persisted — it went to the per-game default
instead (e.g. 35W set, 12W after reboot).

Make the saved manual TDP authoritative for the "no game" state:
- Engine gains a `getNoGameTdp` injection; game-exit, no-profile launch,
  and the toggle-on-while-running path use it instead of `defaultTdp`.
- Backend boot restore prefers the saved manual TDP regardless of the
  per-game toggle; the engine `defaultTdp` remains a fallback only when no
  manual value was ever saved.

A recognized per-game profile still takes precedence when its game launches.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Follow-up to the manual-TDP restore fix, from code review:

- setTdp() no longer overwrites the saved manual "no game" TDP while a
  per-game profile is actively governing (per-game enabled + game running).
  In that state the slider tunes the running game's profile (the frontend
  routes it to setGameProfile), so persisting here let an in-game tweak
  silently redefine the value restored on game exit / reboot.
- removeProfile() now falls back to the manual no-game TDP (noGameTdp()),
  matching handleGameExit — previously deleting the running game's profile
  applied the raw engine default, an inconsistency with the exit path.
- Add regression tests for the getNoGameTdp paths (exit, no-profile launch,
  profile precedence, removeProfile, toggle-on, null/omitted fallback) and
  the setTdp in-game persistence guard.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@srsholmes srsholmes merged commit 987732e into main Jul 8, 2026
2 checks passed
@srsholmes srsholmes deleted the claude/issue-204-jwa9it branch July 8, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants