Skip to content

Commit 5e7ee84

Browse files
committed
release: CA-O 2.2.17 con correctness de overclock (turbo, AMD, NVML, sonda GPU)
1 parent c740fa2 commit 5e7ee84

28 files changed

Lines changed: 1579 additions & 537 deletions

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
Formato basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/).
44

55
## [Unreleased]
6+
## [2.2.17] - 2026-10-14
7+
### Corregido
8+
- **Perfil Rendimiento ya no apaga el turbo.** `KnobValue.FromIndex` dejaba `Scalar` a 0 y el preset agresivo (indice 2) escribia `PERFBOOSTMODE=0`: turbo apagado reportado como exito. El indice se refleja en `Scalar` y Conservador pasa a indice 1 (fabrica), nunca 0.
9+
- **Conservador AMD ya no pone la tarjeta al maximo.** ADLX habla en offset (-10..+15) y el catalogo en % de fabrica (100 = fabrica): escribir 100 directo lo recortaba al maximo. Nueva conversion `ToCatalog/ToDriverPercent` con stock del driver o inferido del rango.
10+
- **Offsets de reloj NVIDIA por fin disponibles.** Las versiones `16|1<<24` y `24|2<<24` no existen en `nvml.h` (una sola `v1` de 24 bytes): el driver devolvia `VERSION_MISMATCH` y los offsets nunca tenian bounds. Unificada a `24|1<<24`.
11+
- **Tuning sin veredicto ya no cuenta como aprobado.** Nueva sonda `NvmlGpuStabilityProbe` (reloj/utilizacion/temperatura/slowdown, 1 Hz): TDR/device-lost es fallo, ventana sin 70 % de carga media es `Pending` -> estado `Unverified` con restore al ultimo bueno. Si el driver recorta al mismo valor, el recorrido corta en `CeilingReached` con el valor real.
12+
- **Registry ya no mezcla vendors.** `AllCatalogKnobs` incluye AMD, una vez que hay GPU se ocultan los knobs de la contraria, `CompositeCeilingSource` concatena techos por vendor, y el rank de CPU deja de usar el orden del enum (PolicyOnly pisaba XTU).
13+
- **Textos honestos.** CPU deja de decir "sin MSR no hay OC": dice que ajusta turbo/frecuencia maxima del plan. Perfiles advierten que son valores fijos sin prueba; el auto avisa que necesita juego/benchmark con carga. Tour de Overclock actualizado.
14+
### Probado
15+
- Suite 1892 passed, 0 failed (Security 274, Benchmark 17, Infrastructure 215, Core 947, UI 379, Integration 60). 19 tests nuevos de correctness (turbo, Pending-no-pass, clamp, NVML version, AMD % stock, sonda GPU, rank CPU).
616
## [2.2.16] - 2026-10-13
717
### Corregido
818
- **El aviso de ajustes pendientes ya dice cuales y como resolverlos.** Nombra cada ajuste pendiente, cuantos son y manda a la seccion 05 ESTABILIDAD con Revertir/Mantener/Descartar, en vez del generico "Resuelvelos primero".

‎README.md‎

Lines changed: 23 additions & 23 deletions
Large diffs are not rendered by default.

‎Version.props‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<Project>
22
<!-- Single source of truth for versioning (FASE 28). All other files derive from here. -->
33
<PropertyGroup>
4-
<Version>2.2.16</Version>
5-
<AssemblyVersion>2.2.16.0</AssemblyVersion>
6-
<FileVersion>2.2.16.0</FileVersion>
7-
<PackageVersion>2.2.16</PackageVersion>
8-
<ProductVersion>2.2.16</ProductVersion>
9-
<InformationalVersion>2.2.16+$(GHTag)</InformationalVersion>
4+
<Version>2.2.17</Version>
5+
<AssemblyVersion>2.2.17.0</AssemblyVersion>
6+
<FileVersion>2.2.17.0</FileVersion>
7+
<PackageVersion>2.2.17</PackageVersion>
8+
<ProductVersion>2.2.17</ProductVersion>
9+
<InformationalVersion>2.2.17+$(GHTag)</InformationalVersion>
1010
</PropertyGroup>
1111
</Project>
1212

‎docs/ARCHITECTURE.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,14 @@ Nunca se registran secretos, credenciales ni contenido de entrada del usuario (s
114114
- **i18n**: diccionario tipado es-ES/en-US (`Localizer`); migración a `.resw` planificada sin cambiar llamadas (`Localizer.Get(key)`).
115115
- **HAGS/VBS/MPO/etc.**: nunca tratados como ganancias universales; HAGS es WorkloadDependent+Conditional+RequiresReboot, VBS es Critical+SecurityTradeoff+ExpertOnly.
116116

117-
## Pruebas (1873 passed, 0 failed — Release)
117+
## Pruebas (1892 passed, 0 failed — Release)
118118

119119
| Suite | Cubre | Count |
120120
|---|---|---|
121-
| `CA-O.Core.Tests` | Contratos catálogo 93, `AnalysisStateStore` (save/load/corrupt), `GameCompatibility` (VBS bloqueado), transacciones, scoring, health, ledger one-shot, temporales a 10 min | 943 |
121+
| `CA-O.Core.Tests` | Contratos catálogo 93, `AnalysisStateStore` (save/load/corrupt), `GameCompatibility` (VBS bloqueado), transacciones, scoring, health, ledger one-shot, temporales a 10 min | 947 |
122122
| `CA-O.Security.Tests` | IPC validator (`Ping`/`Apply` cross-check), `IpcPingTests`, inyección | 274 |
123123
| `CA-O.Integration.Tests` | `E2EFlowsTests` 9 pruebas (8 casos numerados + benchmark: Abrir→Analizar→Persistir→Vanguard→Restore→Benchmark) + `TransactionJournalRecovery` + contrato docs + `PrivilegedServicePackagingTests` (payload self-contained) | 60 |
124-
| `CA-O.Infrastructure.Tests` | `HistoryRobustness` (malformed), `SnapshotRepository` (TX identity), `SystemContextCache` dual-TTL | 202 |
124+
| `CA-O.Infrastructure.Tests` | `HistoryRobustness` (malformed), `SnapshotRepository` (TX identity), `SystemContextCache` dual-TTL | 215 |
125125
| `CA-O.Benchmark.Tests` | `SystemBenchmarkRunner` (suelo 3%, mediana) | 17 |
126-
| `CA-O.UI.Tests` | `ViewModelTests` (Analyze/Dashboard con `SystemAnalysisService` + `Correlation`), Overclock (filas visuales + mood de tuning), tours por pagina (83 pasos, destinos verificados), auto-tuning con propuesta, puerta de restore point, aviso recovery con nombres | 377 |
127-
| **Total** | **Gates 1-5 `verify.ps1` + `build-release` con `gui-installer`** | **1873** |
126+
| `CA-O.UI.Tests` | `ViewModelTests` (Analyze/Dashboard con `SystemAnalysisService` + `Correlation`), Overclock (filas visuales + mood de tuning), tours por pagina (83 pasos, destinos verificados), auto-tuning con propuesta, puerta de restore point, aviso recovery con nombres | 379 |
127+
| **Total** | **Gates 1-5 `verify.ps1` + `build-release` con `gui-installer`** | **1892** |

‎installer/CA-O.iss‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
; (lee la versión de BuildConstants.cs y firma con CAO_SIGN_THUMBPRINT si existe)
44

55
#ifndef AppVersion
6-
#define AppVersion "2.2.16"
6+
#define AppVersion "2.2.17"
77
#endif
88
#ifndef RepoRoot
99
#define RepoRoot ".."

‎src/CA-O.Core/Overclock/Catalog/OverclockCatalog.cs‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,8 @@ public static class OverclockCatalog
8989
presets: []),
9090
];
9191
/// <summary>
92-
/// AMD knobs, served by amdadlx64.dll. Power limit only: ADLX reports it in
93-
/// percent of TDP, which is exactly the catalog unit, so there is no anchor
94-
/// to resolve and no conversion to get wrong. AMD clocks stay out on
92+
/// AMD knobs, served by amdadlx64.dll. Power limit only, in % of stock like
93+
/// the NVIDIA one; the backend converts from ADLX's offset-around-0 range. AMD clocks stay out on
9594
/// purpose: from Navi4 up SetGPUMaxFrequency takes an offset, before that
9695
/// an absolute frequency, and guessing which one a card expects is how a
9796
/// number the hardware never meant gets written.
@@ -123,7 +122,10 @@ public static class OverclockCatalog
123122
"knob_cpu_boost_mode", "knob_cpu_boost_mode_desc", requiresReboot: false,
124123
presets:
125124
[
126-
(OverclockProfile.Conservative, KnobValue.FromIndex(0)),
125+
// 0 = turbo off, 1 = turbo normal, 2 = turbo agresivo. Ningun perfil
126+
// apaga el turbo: "Conservador" en una pestana de overclock es el
127+
// comportamiento de fabrica, no un procesador mas lento.
128+
(OverclockProfile.Conservative, KnobValue.FromIndex(1)),
127129
(OverclockProfile.Balanced, KnobValue.FromIndex(2)),
128130
(OverclockProfile.Performance, KnobValue.FromIndex(2)),
129131
]),

‎src/CA-O.Core/Overclock/OverclockModel.cs‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ private KnobValue(double scalar, int index)
3030

3131
public static KnobValue FromScalar(double value) => new(value, 0);
3232

33-
public static KnobValue FromIndex(int index) => new(0d, index);
33+
/// <summary>
34+
/// A discrete value. The index is mirrored into <see cref="Scalar"/> on
35+
/// purpose: backends write the setting as a number (powercfg takes the raw
36+
/// index), and a discrete preset whose Scalar stayed 0 made the Performance
37+
/// profile write PERFBOOSTMODE=0, turning turbo OFF while reporting success.
38+
/// </summary>
39+
public static KnobValue FromIndex(int index) => new(index, index);
3440

3541
/// <summary>Scalar values are the common case in the catalog, so literals convert implicitly.</summary>
3642
public static implicit operator KnobValue(double value) => FromScalar(value);

‎src/CA-O.Core/Overclock/Stability/StabilityContracts.cs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ public enum ProbeMetric
1010
KernelBugcheck,
1111
EffectiveClockStall,
1212
GpuThermalLimit,
13+
14+
/// <summary>The GPU stopped answering mid-window (driver reset / TDR).</summary>
15+
GpuDeviceLost,
1316
Composite,
1417
}
1518

@@ -71,6 +74,17 @@ public sealed record ThermalLimitReached(string SensorName, double LimitCelsius,
7174
public bool IsFault => true;
7275
}
7376

77+
/// <summary>
78+
/// La GPU dejó de responder durante la ventana: el driver se reinició (TDR) o el
79+
/// dispositivo se perdió. Es la firma clásica de un offset de reloj inestable.
80+
/// </summary>
81+
public sealed record DeviceLost(string DeviceName, string ReasonKey) : ProbeEvidence
82+
{
83+
public override ProbeMetric Metric => ProbeMetric.GpuDeviceLost;
84+
85+
public bool IsFault => true;
86+
}
87+
7488
/// <summary>
7589
/// Un sensor que no se pudo leer. Se registra para que la UI pueda decir "sin datos"
7690
/// en lugar de dar a entender que todo va bien.
@@ -109,6 +123,7 @@ public sealed record ProbeSample(DateTimeOffset TakenAt, IReadOnlyList<ProbeEvid
109123
ProbeEvidence.KernelBugcheck b => b.IsFault,
110124
ProbeEvidence.ClockStall c => c.IsFault,
111125
ProbeEvidence.ThermalLimitReached t => t.IsFault,
126+
ProbeEvidence.DeviceLost d => d.IsFault,
112127
_ => false,
113128
};
114129
}

‎src/CA-O.Core/Overclock/Stability/StabilityEvaluator.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public static IReadOnlyList<ProbeEvidence> Faults(ProbeRun run, StabilityThresho
6262
ProbeEvidence.KernelBugcheck => true,
6363
ProbeEvidence.ClockStall c => c.StallRatio > thresholds.MaxClockStallRatio,
6464
ProbeEvidence.ThermalLimitReached t => thresholds.ThermalLimitIsFault,
65+
ProbeEvidence.DeviceLost => true,
6566
ProbeEvidence.SensorUnavailable => thresholds.SensorUnavailableIsFault,
6667
_ => false,
6768
};

‎src/CA-O.Core/Overclock/Stability/TuningEngine.cs‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ public enum TuningStatus
5353

5454
/// <summary>El backend no pudo ni aplicar el primer valor. No se abrió nada.</summary>
5555
Rejected,
56+
57+
/// <summary>
58+
/// La sonda no pudo dar veredicto (sin carga, sin telemetría). Un punto sin
59+
/// veredicto NO es un aprobado: se vuelve al último valor bueno y se cierra.
60+
/// </summary>
61+
Unverified,
5662
}
5763

5864
public sealed record TuningRequest(
@@ -125,6 +131,7 @@ public async Task<TuningRunResult> RunAsync(
125131
cancellationToken.ThrowIfCancellationRequested();
126132

127133
var proposed = KnobValue.FromScalar(value);
134+
128135
var applied = await request.Target
129136
.ApplyAsync(knob.Id, proposed, cancellationToken)
130137
.ConfigureAwait(false);
@@ -135,6 +142,17 @@ public async Task<TuningRunResult> RunAsync(
135142
return Build(request, TuningStatus.Rejected, lastGoodValue, null, points, applied.FailureReasonKey, null);
136143
}
137144

145+
// El driver recorta contra su propio techo. Si lo que de verdad recibió no
146+
// pasa del último valor bueno, seguir subiendo solo repetiría el mismo punto:
147+
// el techo del hardware está alcanzado y se dice con el valor real.
148+
var actual = applied.Applied?.Scalar ?? value;
149+
if (points.Count > 0 && actual <= lastGoodValue.Scalar + (request.Step * 1e-6))
150+
{
151+
return Build(request, TuningStatus.CeilingReached, lastGoodValue, lastGoodValue.Scalar, points, null, null);
152+
}
153+
154+
proposed = KnobValue.FromScalar(actual);
155+
138156
await WriteAsync(TransactionPhase.OverclockStepApplied, knob.Id.Value, cancellationToken).ConfigureAwait(false);
139157
await WriteAsync(TransactionPhase.OverclockCheckpointTaken, $"{knob.Id}={Format(proposed)}", cancellationToken).ConfigureAwait(false);
140158

@@ -170,6 +188,16 @@ public async Task<TuningRunResult> RunAsync(
170188
return Build(request, TuningStatus.StabilityCeilingFound, lastGoodValue, ceiling, points, null, null);
171189
}
172190

191+
if (verdict == StabilityVerdict.Pending)
192+
{
193+
// Sin veredicto no hay aprobado: una ventana sin carga o sin telemetría
194+
// no demuestra nada. Se vuelve al último valor bueno y se cierra.
195+
await RestoreAsync(request, lastGoodSnapshot, cancellationToken).ConfigureAwait(false);
196+
var reason = run.AbortReasonKey ?? "overclock-tuning-no-verdict";
197+
await WriteAsync(TransactionPhase.OverclockStepRejected, reason, cancellationToken).ConfigureAwait(false);
198+
return Build(request, TuningStatus.Unverified, lastGoodValue, null, points, reason, null);
199+
}
200+
173201
await WriteAsync(TransactionPhase.OverclockTestPassed, knob.Id.Value, cancellationToken).ConfigureAwait(false);
174202

175203
lastGoodValue = proposed;

0 commit comments

Comments
 (0)