Skip to content

Commit f50ad8d

Browse files
carterscodeclaude
andcommitted
fix(cpu): the dual-CCD panel misread a working AMD install, and recommend CPPC=Cache
Three problems in the dual-CCD dependency panel, reported from a 9950X3D with AMD chipset drivers already installed. Service detection probed four guessed names, two of which do not exist. The list was "AMD3DVCacheSvc", "Amd3DVCacheSvc", "AmdV3DCacheSvc" and "AMDProvisioningPackagesSvc"; on a real install the services are actually named amd3dvcacheSvc and AmdPpkgSvc, so half the list could never match anything and the fourth entry was probing the provisioning service as if it were the optimizer. It now enumerates the service list and matches on the distinctive part of the name or display name, which survives casing and any rename, and it reports the name it found so the row is diagnosable instead of a dead end. "Stopped" was reported as a fault. The optimizer installs with StartMode=Auto and sits stopped until it has routing work, so stopped is its normal idle state -- confirmed on this machine, where it reads Stopped/Auto. The old panel told the user to go and start it. The state is now split into Idle and Disabled, and only Disabled is treated as unmet. "Xbox Game Bar: unknown" came from a second, worse copy of a reader that already existed. CpuPlanStatus read only AutoGameModeEnabled and returned null when the value was absent, while GameModeMonitor.ReadCurrent already falls back to AllowAutoGameMode. It now calls that one, and an absent value resolves to the Windows default (Game Mode on) rather than "unknown". The row is also relabelled: it reads Game Mode, not Game Bar, and said so incorrectly. CPPC guidance now recommends Cache rather than Driver on asymmetric dual-CCD X3D. Driver is AMD's official setting and routes dynamically, but that routing depends on Xbox Game Bar recognising the title, which it often does not -- non-Steam launchers, emulators and older games all miss. Cache pins preferred cores to the V-Cache CCD so routing does not depend on detection at all. It is a tradeoff rather than a correction, so BiosRecommendation gained an Alternative field and the panel renders Driver alongside with the reason to prefer each. The prose in CpuPlanDetails and SettingDocsCatalog that asserted CPPC=Driver as a hard dependency was reworded to match, and SETTINGS-REFERENCE.md regenerated. Verified against this machine's real service list: the detector resolves amd3dvcacheSvc / "AMD 3D V-Cache Performance Optimizer Service" as Idle, where the old code reported Stopped and the panel said "start it". The dual-CCD panel itself cannot be rendered here -- this is a single-CCD 9850X3D -- so the catalog output the panel consumes is pinned by tests instead. 762 stable / 744 beta tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 5242939 commit f50ad8d

9 files changed

Lines changed: 367 additions & 49 deletions

File tree

docs/SETTINGS-REFERENCE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,19 +170,19 @@ powercfg /setactive SCHEME_BALANCED # restore the Balanced plan
170170

171171
| Choice | Pro | Con |
172172
|---|---|---|
173-
| Build the optimized plan (recommended) | A Balanced clone tuned to your CPU -- aggressive boost and the right core-parking -- without High Performance's heat/boost cost. | For asymmetric dual-CCD X3D it isn't enough alone; it relies on BIOS CPPC=Driver + the AMD V-Cache service the app can't set. |
173+
| Build the optimized plan (recommended) | A Balanced clone tuned to your CPU -- aggressive boost and the right core-parking -- without High Performance's heat/boost cost. | For asymmetric dual-CCD X3D it isn't enough alone; it relies on the BIOS "CPPC Dynamic Preferred Cores" setting the app cannot set -- Cache pins games to the V-Cache CCD, Driver routes dynamically via the AMD V-Cache service. |
174174
| Keep Balanced / a stock plan | Zero setup, and fine on most modern CPUs whose own boost is already good. | Misses the per-CPU parking/boost tuning (notably for X3D chips). |
175175

176176
**Per-scenario recommendation:**
177177

178178
| Scenario | Setting |
179179
|---|---|
180180
| Single-CCD X3D (9850X3D / 9800X3D / 7800X3D) | Build optimized -- no parking, aggressive boost |
181-
| Asymmetric dual-CCD X3D (9950X3D / 7950X3D) | Build optimized -- parks frequency CCD; also verify BIOS CPPC=Driver + AMD V-Cache service + Game Bar |
181+
| Asymmetric dual-CCD X3D (9950X3D / 7950X3D) | Build optimized -- parks frequency CCD; also set BIOS CPPC=Cache (or Driver + the AMD V-Cache service + Game Mode) |
182182
| Non-X3D / Intel hybrid | Build optimized (no parking / leave Thread Director) or suggest Balanced |
183183
| Unknown CPU | Build optimized uses a labeled generic tune, or suggest the best prebuilt plan |
184184

185-
**Risks.** Low. The plan is additive -- your existing Windows plans are never modified or deleted, and you can switch back at any time. For asymmetric dual-CCD X3D the power plan alone is not sufficient: it depends on the AMD CCD-routing stack (BIOS CPPC=Driver, the 3D V-Cache Optimizer service, and Xbox Game Bar), which the app surfaces but cannot set.
185+
**Risks.** Low. The plan is additive -- your existing Windows plans are never modified or deleted, and you can switch back at any time. For asymmetric dual-CCD X3D the power plan alone is not sufficient: which CCD a game lands on is decided by the BIOS "CPPC Dynamic Preferred Cores" setting. Cache pins games to the V-Cache CCD outright; Driver routes them dynamically and then also depends on the 3D V-Cache Optimizer service and Xbox Game Bar game-detection. The app surfaces this but cannot set it.
186186

187187
**Command line (PowerShell):**
188188

src/GamerGuardian/Models/CpuTuneDefinition.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ public enum ParkingStrategy { Default, NoParking, ParkFrequencyCcd }
2020
public sealed record PowerOverride(Guid Subgroup, Guid Setting, uint Value, string Label);
2121

2222
/// <summary>An advisory BIOS recommendation (never read or applied by the app).</summary>
23-
public sealed record BiosRecommendation(string Name, string RecommendedValue, string Rationale);
23+
/// <param name="Alternative">A second defensible value and when to prefer it, for
24+
/// settings where there is a real tradeoff rather than one right answer. Null when
25+
/// the setting has a single sensible value.</param>
26+
public sealed record BiosRecommendation(
27+
string Name, string RecommendedValue, string Rationale, string? Alternative = null);
2428

2529
/// <summary>
2630
/// One catalog entry: how to recognize a CPU class and the gaming-optimized

src/GamerGuardian/Services/CpuPlanDetails.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,11 @@ public static string Rationale(CpuTuneResult r)
106106
return "Your CPU has two core clusters (CCDs) but only one carries the extra 3D V-Cache "
107107
+ "that games benefit from. This plan parks the other, higher-frequency cluster during "
108108
+ "light loads so Windows keeps game threads on the cache cluster, while still unparking "
109-
+ "every core under heavy multi-threaded work. It depends on the AMD 3D V-Cache Optimizer "
110-
+ "service, Xbox Game Bar game-detection, and BIOS \"CPPC = Driver\" to route correctly "
111-
+ "(see the dependency checklist above).";
109+
+ "every core under heavy multi-threaded work. The power plan alone does not decide which "
110+
+ "cluster a game lands on: that comes from the BIOS setting \"CPPC Dynamic Preferred "
111+
+ $"Cores\". Setting it to {CpuTuneCatalog.PreferredCppcValue} pins games to the cache cluster outright; leaving it on "
112+
+ "Driver instead routes them dynamically and depends on the AMD 3D V-Cache Optimizer "
113+
+ "service plus Xbox Game Bar recognising the game (see the dependency checklist above).";
112114

113115
if (r.IsGeneric)
114116
return "A conservative, safe tune: it only raises boost aggressiveness and makes no "

src/GamerGuardian/Services/CpuPlanStatus.cs

Lines changed: 106 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,34 @@
1+
using GamerGuardian.Monitors;
12
using Microsoft.Win32;
23

34
namespace GamerGuardian.Services;
45

5-
public enum CcdServiceState { Running, Stopped, NotFoundOrUnknown }
6+
/// <summary>
7+
/// What the app could learn about the AMD 3D V-Cache Optimizer service.
8+
///
9+
/// <para><see cref="Stopped"/> is split from <see cref="Disabled"/> deliberately.
10+
/// The service is installed with StartMode=Auto and sits Stopped until it has
11+
/// routing work to do, so "stopped" is its normal idle state and is not something
12+
/// the user needs to fix. Only a Disabled start mode is actually broken.</para>
13+
/// </summary>
14+
public enum CcdServiceState
15+
{
16+
/// <summary>No service matching the optimizer was found at all.</summary>
17+
NotInstalled,
18+
/// <summary>Present and currently running.</summary>
19+
Running,
20+
/// <summary>Present, start mode is Automatic or Manual, not running right now.
21+
/// This is the expected idle state, not a fault.</summary>
22+
Idle,
23+
/// <summary>Present but its start mode is Disabled, so it can never run.</summary>
24+
Disabled,
25+
}
26+
27+
/// <summary>What was found, so the UI can name it instead of guessing.</summary>
28+
/// <param name="State">Installed/running/idle/disabled.</param>
29+
/// <param name="ServiceName">The service's real name, or null when nothing matched.</param>
30+
/// <param name="DisplayName">Its display name, or null.</param>
31+
public sealed record CcdServiceInfo(CcdServiceState State, string? ServiceName, string? DisplayName);
632

733
/// <summary>Status of the asymmetric dual-CCD X3D routing dependencies.</summary>
834
public enum CcdDependencyStatus
@@ -18,18 +44,22 @@ public enum CcdDependencyStatus
1844

1945
/// <summary>
2046
/// Pure status logic for the dual-CCD X3D dependency stack, plus best-effort
21-
/// readers. The app can never claim full "optimized" because BIOS CPPC=Driver is
22-
/// not readable from user mode — it surfaces what it can and stays honest.
47+
/// readers. The app can never claim full "optimized" because the BIOS CPPC setting
48+
/// is not readable from user mode — it surfaces what it can and stays honest.
2349
/// </summary>
2450
public static class CpuPlanStatus
2551
{
2652
/// <summary>Pure: combine the checkable signals into a status. Unit-tested.</summary>
2753
public static CcdDependencyStatus DependencyStatus(
2854
bool planActive, CcdServiceState service, bool? gameBarEnabled)
2955
{
30-
if (service == CcdServiceState.NotFoundOrUnknown)
56+
if (service == CcdServiceState.NotInstalled)
3157
return CcdDependencyStatus.Unknown;
32-
if (!planActive || service == CcdServiceState.Stopped)
58+
// A Disabled start mode is the only service state the user must act on.
59+
// Idle is normal: the optimizer is demand-driven and sits stopped until a
60+
// game gives it something to route. Reporting that as an unmet dependency
61+
// sent people off to reinstall drivers that were already installed.
62+
if (!planActive || service == CcdServiceState.Disabled)
3363
return CcdDependencyStatus.PartlyUnmet;
3464
if (gameBarEnabled == false)
3565
return CcdDependencyStatus.PartlyUnmet;
@@ -38,33 +68,88 @@ public static CcdDependencyStatus DependencyStatus(
3868

3969
// ---- Best-effort readers (not unit-tested) ----
4070

41-
private static readonly string[] AmdServiceNames =
42-
{ "AMD3DVCacheSvc", "Amd3DVCacheSvc", "AmdV3DCacheSvc", "AMDProvisioningPackagesSvc" };
43-
44-
public static CcdServiceState ReadAmdVCacheService()
71+
/// <summary>
72+
/// Finds the AMD 3D V-Cache Optimizer service by enumerating the service list
73+
/// and matching, rather than probing a list of guessed names.
74+
///
75+
/// <para>The previous version tried four hardcoded names, two of which
76+
/// ("AmdV3DCacheSvc", "AMDProvisioningPackagesSvc") do not exist on a real
77+
/// install — the actual names are <c>amd3dvcacheSvc</c> and <c>AmdPpkgSvc</c>.
78+
/// Any name AMD ships that a future driver renames would break it again, and the
79+
/// failure mode was the worst possible one: "not detected (install AMD chipset
80+
/// drivers)" on a machine where they were already installed.</para>
81+
/// </summary>
82+
public static CcdServiceInfo ReadAmdVCacheService()
4583
{
46-
foreach (var name in AmdServiceNames)
84+
try
4785
{
48-
try
86+
foreach (var sc in System.ServiceProcess.ServiceController.GetServices())
4987
{
50-
using var sc = new System.ServiceProcess.ServiceController(name);
51-
var status = sc.Status; // throws if the service does not exist
52-
return status == System.ServiceProcess.ServiceControllerStatus.Running
53-
? CcdServiceState.Running
54-
: CcdServiceState.Stopped;
88+
using (sc)
89+
{
90+
if (!LooksLikeVCacheOptimizer(sc.ServiceName, sc.DisplayName)) continue;
91+
92+
var state = sc.Status == System.ServiceProcess.ServiceControllerStatus.Running
93+
? CcdServiceState.Running
94+
: IsDisabled(sc.ServiceName) ? CcdServiceState.Disabled : CcdServiceState.Idle;
95+
96+
return new CcdServiceInfo(state, sc.ServiceName, sc.DisplayName);
97+
}
5598
}
56-
catch { /* not this name; try the next */ }
5799
}
58-
return CcdServiceState.NotFoundOrUnknown;
100+
catch { /* enumeration denied or unavailable -- fall through */ }
101+
102+
return new CcdServiceInfo(CcdServiceState.NotInstalled, null, null);
103+
}
104+
105+
/// <summary>Matches the optimizer by service name or display name. Pure, so the
106+
/// matching rule is unit-tested without touching the service manager.</summary>
107+
public static bool LooksLikeVCacheOptimizer(string? serviceName, string? displayName)
108+
{
109+
// Match on the distinctive part rather than a full name: the shipped service
110+
// is "amd3dvcacheSvc" / "AMD 3D V-Cache Performance Optimizer Service", and
111+
// matching the middle survives casing and Svc/Service suffix changes.
112+
return Contains(serviceName, "3dvcache")
113+
|| Contains(displayName, "3d v-cache")
114+
|| Contains(displayName, "3d vcache");
115+
116+
static bool Contains(string? haystack, string needle) =>
117+
haystack is not null &&
118+
haystack.Contains(needle, StringComparison.OrdinalIgnoreCase);
119+
}
120+
121+
/// <summary>Start mode from the registry. ServiceController exposes StartType
122+
/// only on .NET Core 3.0+ for some platforms, and reading the key is the same
123+
/// registry-first approach the rest of the app uses.</summary>
124+
private static bool IsDisabled(string serviceName)
125+
{
126+
try
127+
{
128+
using var k = Registry.LocalMachine.OpenSubKey(
129+
$@"SYSTEM\CurrentControlSet\Services\{serviceName}", writable: false);
130+
// 4 == SERVICE_DISABLED
131+
return k?.GetValue("Start") is int start && start == 4;
132+
}
133+
catch { return false; }
59134
}
60135

136+
/// <summary>
137+
/// Whether Windows Game Mode is on — the signal the AMD optimizer's game
138+
/// detection rides on.
139+
///
140+
/// <para>Reuses <see cref="GameModeMonitor.ReadCurrent"/> rather than reading the
141+
/// registry again. The duplicate reader here checked only
142+
/// <c>AutoGameModeEnabled</c> and returned null when it was absent, which is why
143+
/// the panel said "unknown" on machines that had simply never had the value
144+
/// written. Absent now resolves to the Windows default instead.</para>
145+
/// </summary>
61146
public static bool? ReadGameBarEnabled()
62147
{
63148
try
64149
{
65-
using var k = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\GameBar");
66-
var v = k?.GetValue("AutoGameModeEnabled");
67-
return v is int i ? i != 0 : null;
150+
// Neither value present means the user has never changed it, so the
151+
// effective state is the Windows 11 default: Game Mode on.
152+
return GameModeMonitor.ReadCurrent() ?? true;
68153
}
69154
catch { return null; }
70155
}

src/GamerGuardian/Services/CpuTuneCatalog.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,32 @@ private static IReadOnlyList<PowerOverride> BoostOnlyRecipe() => new[]
7171

7272
// ---- BIOS guidance (advisory only) ----
7373

74+
/// <summary>
75+
/// What to set "CPPC Dynamic Preferred Cores" to on an asymmetric dual-CCD X3D.
76+
/// Referenced by the dependency panel so the advice reads the same everywhere.
77+
/// </summary>
78+
public const string PreferredCppcValue = "Cache";
79+
7480
private static IReadOnlyList<BiosRecommendation> BiosAmdDualCcd() => new[]
7581
{
76-
new BiosRecommendation("CPPC Dynamic Preferred Cores", "Driver",
77-
"Lets the AMD 3D V-Cache Optimizer route game threads to the cache CCD; 'Auto' makes the kernel ignore the optimizer."),
82+
// Cache rather than Driver, for a gaming-first machine.
83+
//
84+
// Driver is AMD's official configuration: it hands preferred-core ranking to
85+
// the chipset driver's 3D V-Cache Optimizer, which switches CCDs depending on
86+
// whether Xbox Game Bar recognises the foreground app as a game. That
87+
// detection is the weak link -- it misses non-Steam launchers, emulators and
88+
// plenty of older or indie titles, and when it misses, the game runs on the
89+
// frequency CCD and loses most of the V-Cache benefit.
90+
//
91+
// Cache pins preferred cores to the V-Cache CCD outright, so routing does not
92+
// depend on detection working. The cost is that heavily multi-threaded
93+
// non-gaming work no longer gets first claim on the faster CCD.
94+
new BiosRecommendation("CPPC Dynamic Preferred Cores", PreferredCppcValue,
95+
"Pins preferred cores to the CCD carrying the 3D V-Cache, so games land there without depending on Xbox Game Bar recognising them. "
96+
+ "Option names vary by board vendor and AGESA version; some boards label the choices Auto / Driver / Frequency / Cache.",
97+
Alternative: "Driver -- AMD's official setting. It switches CCDs dynamically via the 3D V-Cache Optimizer service, "
98+
+ "so productivity work can still use the faster CCD, but game routing then depends on Xbox Game Bar detecting the title. "
99+
+ "Prefer it if you run heavy multi-threaded work on the same machine; prefer Cache if this is mainly a gaming PC."),
78100
new BiosRecommendation("EXPO / Memory profile", "Enabled", "Runs RAM at its rated speed/timings."),
79101
new BiosRecommendation("Global C-States", "Auto / Enabled", "Required for proper boost behavior; do not disable."),
80102
new BiosRecommendation("Resizable BAR (Smart Access Memory)", "Enabled", "Improves GPU memory access in many games."),

src/GamerGuardian/Services/SettingDocsCatalog.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,11 +416,11 @@ public static IReadOnlyList<ChoiceTradeoff> ProsConsFor(string settingId)
416416
HowItHelps: "Aggressive boost lets the CPU reach and hold its gaming clocks; correct parking keeps game threads on the right cores; faster ramp thresholds reduce clock-up latency. All without the heat/boost-headroom cost of High Performance.",
417417
Scenarios: Scenarios(
418418
("Single-CCD X3D (9850X3D / 9800X3D / 7800X3D)", "Build optimized -- no parking, aggressive boost"),
419-
("Asymmetric dual-CCD X3D (9950X3D / 7950X3D)", "Build optimized -- parks frequency CCD; also verify BIOS CPPC=Driver + AMD V-Cache service + Game Bar"),
419+
("Asymmetric dual-CCD X3D (9950X3D / 7950X3D)", "Build optimized -- parks frequency CCD; also set BIOS CPPC=Cache (or Driver + the AMD V-Cache service + Game Mode)"),
420420
("Non-X3D / Intel hybrid", "Build optimized (no parking / leave Thread Director) or suggest Balanced"),
421421
("Unknown CPU", "Build optimized uses a labeled generic tune, or suggest the best prebuilt plan")),
422422
Recommended: "Build optimized for your CPU (or suggest Balanced)",
423-
Risks: "Low. The plan is additive -- your existing Windows plans are never modified or deleted, and you can switch back at any time. For asymmetric dual-CCD X3D the power plan alone is not sufficient: it depends on the AMD CCD-routing stack (BIOS CPPC=Driver, the 3D V-Cache Optimizer service, and Xbox Game Bar), which the app surfaces but cannot set.",
423+
Risks: "Low. The plan is additive -- your existing Windows plans are never modified or deleted, and you can switch back at any time. For asymmetric dual-CCD X3D the power plan alone is not sufficient: which CCD a game lands on is decided by the BIOS \"CPPC Dynamic Preferred Cores\" setting. Cache pins games to the V-Cache CCD outright; Driver routes them dynamically and then also depends on the 3D V-Cache Optimizer service and Xbox Game Bar game-detection. The app surfaces this but cannot set it.",
424424
ReversibleVia: "Switch the active plan back via Settings > System > Power, or 'powercfg /setactive SCHEME_BALANCED'. The GamerGuardian plan can be deleted from the legacy Power control panel if you no longer want it."),
425425

426426
// ---- System toggles -----------------------------------------------
@@ -1235,7 +1235,7 @@ public static IReadOnlyList<ChoiceTradeoff> ProsConsFor(string settingId)
12351235
("Balanced (default)", "Lets modern CPUs' boost algorithms run (often better than a pegged plan) and saves power when idle.", "A few ms of clock-ramp latency at the start of bursts on older CPUs.")),
12361236

12371237
["cpuplan"] = Pc(
1238-
("Build the optimized plan (recommended)", "A Balanced clone tuned to your CPU -- aggressive boost and the right core-parking -- without High Performance's heat/boost cost.", "For asymmetric dual-CCD X3D it isn't enough alone; it relies on BIOS CPPC=Driver + the AMD V-Cache service the app can't set."),
1238+
("Build the optimized plan (recommended)", "A Balanced clone tuned to your CPU -- aggressive boost and the right core-parking -- without High Performance's heat/boost cost.", "For asymmetric dual-CCD X3D it isn't enough alone; it relies on the BIOS \"CPPC Dynamic Preferred Cores\" setting the app cannot set -- Cache pins games to the V-Cache CCD, Driver routes dynamically via the AMD V-Cache service."),
12391239
("Keep Balanced / a stock plan", "Zero setup, and fine on most modern CPUs whose own boost is already good.", "Misses the per-CPU parking/boost tuning (notably for X3D chips).")),
12401240

12411241
["powerthrottling"] = Pc(

0 commit comments

Comments
 (0)