Forfeit = 2x coin loss + 0 XP (no kot); end-of-game roster + add friend
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 21s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m0s
CI/CD / Deploy - local stack (db + server + web) (push) Failing after 0s

Forfeit penalty reworked (client + server gamification, in sync):
- Surrendering team loses DOUBLE the entry coins; winner takes the stake.
- Forfeiter earns NO XP. No kot is applied or mentioned anymore.
- MatchSummary/Dto carry a `forfeit` flag; GameRoom.FinalizeForfeit →
  ApplyRewardsAsync(team) with Forfeit=true (dropped the kot path).
- Forfeit confirm dialogs now alert the real penalty (double coins, no XP).

End-of-game roster: SeatPlayerDto/ServerSeatPlayer + game-store SeatPlayer gain
userId/isBot. New <MatchPlayersList> lists everyone at the table on the final
screen (PostMatchRewardsModal + AI MatchOverlay) with a tactile "Add" button to
send a friend request to real (non-bot, non-self) players ("Sent" after).

Verified: tsc + sim + dotnet + next build clean; stack rebuilt :1500/:1505.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-05 10:40:14 +03:30
parent 6bbdbac23b
commit b739b503eb
13 changed files with 127 additions and 19 deletions
@@ -33,6 +33,8 @@ public static class Gamification
public static int CoinDelta(MatchSummaryDto s)
{
if (!s.Ranked) return 0;
// Forfeit: the surrendering team loses double the stake; the winner takes the stake.
if (s.Forfeit) return s.Won ? s.Stake : -2 * s.Stake;
// Kot bonus scales with the league stake (mirrors gamification.ts).
int kot = s.Won && s.KotFor ? (int)Math.Round(s.Stake * 0.4) : 0;
return (s.Won ? s.Stake : -s.Stake) + kot;
@@ -44,6 +46,8 @@ public static class Gamification
public const double PremiumXpMult = 1.5;
public static int MatchXp(MatchSummaryDto s)
{
// Forfeiting (surrendering) earns no XP.
if (s.Forfeit && !s.Won) return 0;
// Every game grants XP; the winner earns double.
int b = 40 + s.TricksWon * 5 + (s.KotFor ? 30 : 0);
return (int)Math.Round(b * (s.Won ? 2 : 1) * LeagueXpFactor(s.Stake));
@@ -65,6 +65,7 @@ public class MatchSummaryDto
public bool Shutout { get; set; }
public int HakemRounds { get; set; }
public int RoundsWon { get; set; }
public bool Forfeit { get; set; }
}
public class AchievementUnlockDto