@@ -379,44 +379,102 @@ public void ImplausibleMemory_Throws(string mem)
379379 ( ) => Parse ( "tune" , "--mv" , "960" , "--mem" , mem ) . ResolveMemory ( baseMemMhz : 14001 ) ) ;
380380 }
381381
382- // --- ToAbsoluteArgs (what the logon task re-applies) ---
382+ // --- ToPersistedArgs (what the logon task re-applies) ---
383383
384384 [ Fact ]
385- public void ToAbsoluteArgs_BakesTheResolvedCapAndClock ( )
385+ public void ToPersistedArgs_EmitsTheClockCapRelative ( )
386386 {
387- // The relative form and peak reference collapse into the plain absolute values that were applied,
388- // so the persisted run doesn't re-read the curve or need the peak.
387+ // The clock persists as an offset from the cap anchor's own stock clock (--peak-mv = the cap
388+ // voltage), so the logon run re-resolves it against the boot-time curve read: the reported
389+ // curve shifts with temperature, and the offset form re-applies the validated frequency
390+ // offset at any temperature where a baked absolute clock would drift.
389391 var args = Parse ( "tune" , "--mv-offset" , "-100" , "--peak-mv" , "1060" )
390- . ToAbsoluteArgs ( capMv : 960 , targetMhz : 2880 , memMhz : null ) ;
391- Assert . Equal ( new [ ] { "--mv" , "960" , "--mhz" , "2880 " } , args ) ;
392+ . ToPersistedArgs ( capMv : 960 , capDeltaMhz : 190 , memMhz : null ) ;
393+ Assert . Equal ( new [ ] { "--mv" , "960" , "--mhz-offset " , "190" , "--peak-mv" , "960 " } , args ) ;
392394 }
393395
394396 [ Fact ]
395- public void ToAbsoluteArgs_OmitsClockWhenNoneWasSet ( )
397+ public void ToPersistedArgs_OmitsClockWhenNoneWasSet ( )
396398 {
397- var args = Parse ( "tune" , "--mv" , "960" ) . ToAbsoluteArgs ( capMv : 960 , targetMhz : null , memMhz : null ) ;
399+ var args = Parse ( "tune" , "--mv" , "960" ) . ToPersistedArgs ( capMv : 960 , capDeltaMhz : null , memMhz : null ) ;
398400 Assert . Equal ( new [ ] { "--mv" , "960" } , args ) ;
399401 }
400402
401403 [ Fact ]
402- public void ToAbsoluteArgs_IncludesMemoryAndNonDefaultCapPoints ( )
404+ public void ToPersistedArgs_IncludesMemoryAndNonDefaultCapPoints ( )
403405 {
404406 var args = Parse ( "tune" , "--mv" , "960" , "--mem-offset" , "1000" , "--cap-points" , "4" )
405- . ToAbsoluteArgs ( capMv : 960 , targetMhz : null , memMhz : 15001 ) ;
407+ . ToPersistedArgs ( capMv : 960 , capDeltaMhz : null , memMhz : 15001 ) ;
406408 Assert . Equal ( new [ ] { "--mv" , "960" , "--mem" , "15001" , "--cap-points" , "4" } , args ) ;
407409 }
408410
409411 [ Fact ]
410- public void ToAbsoluteArgs_OmitsDefaultCapPoints ( )
412+ public void ToPersistedArgs_OmitsDefaultCapPoints ( )
411413 {
412- var args = Parse ( "tune" , "--mv" , "960" ) . ToAbsoluteArgs ( capMv : 960 , targetMhz : null , memMhz : null ) ;
414+ var args = Parse ( "tune" , "--mv" , "960" ) . ToPersistedArgs ( capMv : 960 , capDeltaMhz : null , memMhz : null ) ;
413415 Assert . DoesNotContain ( "--cap-points" , args ) ;
414416 }
415417
416418 [ Fact ]
417- public void ToAbsoluteArgs_OmitsTheCapOnAMemoryOnlyRun ( )
419+ public void ToPersistedArgs_OmitsTheCapOnAMemoryOnlyRun ( )
418420 {
419- var args = Parse ( "tune" , "--mem-pct" , "5" ) . ToAbsoluteArgs ( capMv : null , targetMhz : null , memMhz : 14700 ) ;
421+ var args = Parse ( "tune" , "--mem-pct" , "5" ) . ToPersistedArgs ( capMv : null , capDeltaMhz : null , memMhz : 14700 ) ;
420422 Assert . Equal ( new [ ] { "--mem" , "14700" } , args ) ;
421423 }
424+
425+ // --- The persisted line's round trip: the logon run plans from a curve read at a different
426+ // temperature than the original run, and must re-apply the same tuning ---
427+
428+ /// <summary>Resolves and plans <paramref name="request"/> against <paramref name="stock"/>,
429+ /// exactly as a tune run does.</summary>
430+ private static GpuTuning . CurvePlan PlanOn ( TuneRequest request , List < ( int Mv , int Mhz ) > stock )
431+ {
432+ var ( capMv , targetMhz ) = request . Resolve ( stock ) ;
433+ return GpuTuning . BuildCurvePlan ( stock , capMv , targetMhz , request . CapPoints ) ;
434+ }
435+
436+ /// <summary>The re-apply a persisted run performs: parse the persisted line (with the implied
437+ /// command word) and plan it against the boot-time curve.</summary>
438+ private static GpuTuning . CurvePlan ReapplyOn ( string [ ] persisted , List < ( int Mv , int Mhz ) > curve )
439+ => PlanOn ( Parse ( persisted . Prepend ( "tune" ) . ToArray ( ) ) , curve ) ;
440+
441+ [ Fact ]
442+ public void PersistedArgs_ReapplyTheSameDeltas_OnATemperatureShiftedCurve ( )
443+ {
444+ // GPU Boost shifts the whole reported stock curve with core temperature, and the logon
445+ // re-apply reads at boot temperature, not at the original run's. The cap-relative persisted
446+ // form resolves every curve-dependent term from the boot-time read alone, so a uniform shift
447+ // cancels out of all of it - the cap delta, the flatten above and the band - and the written
448+ // vector is identical.
449+ var stock = TestCurves . Realistic ( ) ; // anchor 10 = (1000 mV, 2500 MHz)
450+ var request = Parse ( "tune" , "--mv" , "1000" , "--mhz" , "2600" ) ;
451+ var plan = PlanOn ( request , stock ) ;
452+ string [ ] persisted = request . ToPersistedArgs ( plan . CapMv , plan . CapDeltaMhz , memMhz : null ) ;
453+
454+ var colder = stock . Select ( p => ( p . Mv , p . Mhz + 45 ) ) . ToList ( ) ;
455+ var replan = ReapplyOn ( persisted , colder ) ;
456+
457+ Assert . Equal ( plan . DeltasKhz , replan . DeltasKhz ) ;
458+ }
459+
460+ [ Fact ]
461+ public void PersistedArgs_ReapplyTheExactCapOffset_EvenWhenTheShiftIsNotUniform ( )
462+ {
463+ // If the thermal shift bends across the curve instead of moving it as one block, the deltas
464+ // in the flatten/band region can wobble - but the cap anchor's offset is resolved against
465+ // that same anchor's own read, so it reproduces exactly no matter the shift's shape. The cap
466+ // point is where the boost pins under load, so this is the part that must hold.
467+ var stock = TestCurves . Realistic ( ) ; // anchor 10 = (1000 mV, 2500 MHz)
468+ var request = Parse ( "tune" , "--mv" , "1000" , "--mhz" , "2600" ) ;
469+ var plan = PlanOn ( request , stock ) ;
470+ string [ ] persisted = request . ToPersistedArgs ( plan . CapMv , plan . CapDeltaMhz , memMhz : null ) ;
471+
472+ // A colder read where the shift tapers toward the top of the curve (+45 at the bottom anchor
473+ // down to +26 at the top) - the compensation compressing as clocks near their ceiling.
474+ var colder = stock . Select ( ( p , i ) => ( p . Mv , p . Mhz + 45 - i ) ) . ToList ( ) ;
475+ var replan = ReapplyOn ( persisted , colder ) ;
476+
477+ Assert . Equal ( plan . CapDeltaMhz , replan . CapDeltaMhz ) ;
478+ Assert . Equal ( plan . DeltasKhz [ 10 ] , replan . DeltasKhz [ 10 ] ) ; // the cap anchor's written delta
479+ }
422480}
0 commit comments