@@ -1708,13 +1708,7 @@ export class ReforgeOptimizer {
17081708 }
17091709
17101710 if ( stat == Stat . StatHasteRating || stat == Stat . StatMasteryRating || stat == Stat . StatSpirit ) {
1711- this . player . getAmplificationTrinkets ( ) . forEach ( trinket => {
1712- const randPropPoints = this . sim . db . getItemEffectRandPropPoints ( trinket . ilvl ) ?. randPropPoints ;
1713- if ( ! randPropPoints ) return ;
1714- const statScalingCoeff = 0.00176999997 ;
1715- const buffValue = 1 + ( statScalingCoeff * randPropPoints ) / 100 ;
1716- amount *= buffValue ;
1717- } ) ;
1711+ amount *= this . player . getTotalAmplificationTrinketStatModifier ( ) ;
17181712 }
17191713
17201714 // Handle Spirit to Spell Hit conversion for hybrid casters separately from standard dependencies
@@ -1939,6 +1933,29 @@ export class ReforgeOptimizer {
19391933 return updatedGear ;
19401934 }
19411935
1936+ private getOptimisticUnitStatUpperBound ( unitStat : UnitStat , variables : YalpsVariables ) : number {
1937+ const statKey = unitStat . getKey ( ) ;
1938+ const maxByGroup = new Map < string , number > ( ) ;
1939+
1940+ for ( const [ variableKey , coefficients ] of variables . entries ( ) ) {
1941+ const splitKey = variableKey . split ( '_' ) ;
1942+ const groupKey = splitKey . length > 2 ? `${ splitKey [ 0 ] } _${ splitKey [ 1 ] } ` : ( splitKey [ 0 ] ?? variableKey ) ;
1943+ const statContribution = coefficients . get ( statKey ) || 0 ;
1944+ const currentMax = maxByGroup . get ( groupKey ) ?? Number . NEGATIVE_INFINITY ;
1945+
1946+ if ( statContribution > currentMax ) {
1947+ maxByGroup . set ( groupKey , statContribution ) ;
1948+ }
1949+ }
1950+
1951+ let upperBound = 0 ;
1952+ for ( const contribution of maxByGroup . values ( ) ) {
1953+ upperBound += Math . max ( 0 , contribution ) ;
1954+ }
1955+
1956+ return upperBound ;
1957+ }
1958+
19421959 checkCaps (
19431960 solution : LPSolution ,
19441961 reforgeCaps : Stats ,
@@ -1997,6 +2014,25 @@ export class ReforgeOptimizer {
19972014 const statName = unitStat . getKey ( ) ;
19982015 const currentValue = reforgeStatContribution . getUnitStat ( unitStat ) ;
19992016
2017+ const firstBreakpoint = nextSoftCap . breakpoints [ 0 ] ;
2018+ if ( firstBreakpoint !== undefined && currentValue < firstBreakpoint && ! updatedConstraints . has ( statName ) ) {
2019+ const optimisticUpperBound = this . getOptimisticUnitStatUpperBound ( unitStat , variables ) ;
2020+
2021+ if ( optimisticUpperBound >= firstBreakpoint ) {
2022+ updatedConstraints . set ( statName , greaterEq ( firstBreakpoint ) ) ;
2023+ anyCapsExceeded = true ;
2024+ if ( isDevMode ( ) ) console . log ( 'Soft cap target not met, enforcing floor for: %s' , statName ) ;
2025+ break ;
2026+ } else if ( isDevMode ( ) ) {
2027+ console . log (
2028+ 'Soft cap target is unreachable for %s (needed: %s, optimistic max: %s); skipping floor constraint.' ,
2029+ statName ,
2030+ firstBreakpoint ,
2031+ optimisticUpperBound ,
2032+ ) ;
2033+ }
2034+ }
2035+
20002036 let idx = 0 ;
20012037 for ( const breakpoint of nextSoftCap . breakpoints ) {
20022038 if ( currentValue > breakpoint ) {
0 commit comments