From 4438e81e37aa74dd5db4cabd32abccb03f4456a2 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 26 May 2026 23:42:06 -0400 Subject: [PATCH] review(bp): post-state reward_list from RewardGrantService, not deltas Crystal synthesis in BuyPremiumAsync is now unconditional: always remove any crystal entry ApplyAsync may have added, then append the fresh post-deduction total. Prevents stale on-screen balances when a retroactive grant also touches crystal (or when no grants fire and the conditional guard would have been the only crystal entry). Co-Authored-By: Claude Sonnet 4.6 --- .../Services/BattlePassService.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SVSim.EmulatedEntrypoint/Services/BattlePassService.cs b/SVSim.EmulatedEntrypoint/Services/BattlePassService.cs index db097cd..0707e94 100644 --- a/SVSim.EmulatedEntrypoint/Services/BattlePassService.cs +++ b/SVSim.EmulatedEntrypoint/Services/BattlePassService.cs @@ -198,12 +198,12 @@ public sealed class BattlePassService : IBattlePassService await _db.SaveChangesAsync(ct); await tx.CommitAsync(ct); - // Post-state reward_list must also include the crystal balance after the deduction. - if (!postState.Any(r => r.RewardType == (int)UserGoodsType.Crystal)) - { - postState.Add(new GrantedReward( - (int)UserGoodsType.Crystal, 0, checked((int)viewer.Currency.Crystals))); - } + // Post-state reward_list must always include the crystal balance after the deduction. + // Unconditionally overwrite: remove any crystal entry ApplyAsync may have added, then + // append the post-deduction total so the client gets the correct final balance. + postState.RemoveAll(r => r.RewardType == (int)UserGoodsType.Crystal); + postState.Add(new GrantedReward( + (int)UserGoodsType.Crystal, 0, checked((int)viewer.Currency.Crystals))); return new BattlePassBuyOutcome(1, achieved, postState); }