From 7ad486f382c9797ea2ac8bfad2e189eb22526c56 Mon Sep 17 00:00:00 2001 From: MpakobecEx Date: Fri, 23 Oct 2026 17:37:34 +0500 Subject: [PATCH] Add detailed ride diagnostics to match logging --- .../src/Vanguard.Server/Matches/MatchGrain.cs | 105 +++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/server/src/Vanguard.Server/Matches/MatchGrain.cs b/server/src/Vanguard.Server/Matches/MatchGrain.cs index dcb6722..7860c37 100644 --- a/server/src/Vanguard.Server/Matches/MatchGrain.cs +++ b/server/src/Vanguard.Server/Matches/MatchGrain.cs @@ -109,6 +109,14 @@ public sealed class MatchGrain : IMatchGrain _engine.Start(setup.FirstPlayer); RefreshSummary(); + + _logger.LogInformation( + "Match {MatchId}: created {Seat0} vs {Seat1}, first={FirstPlayer}. {State}", + _matchId, + _seats[0].PlayerId, + _seats[1].PlayerId, + setup.FirstPlayer, + DescribeTurnState()); } public string MatchId => _matchId; @@ -172,8 +180,8 @@ public sealed class MatchGrain : IMatchGrain await SendClockAsync(seat, now); _logger.LogInformation( - "Match {MatchId}: seat {Seat} attached (resume from {Seq}, now {Now}).", - _matchId, seat, resumeFromSeq, to); + "Match {MatchId}: seat {Seat} attached (resume from {Seq}, now {Now}). {State}", + _matchId, seat, resumeFromSeq, to, DescribeTurnState()); await PersistHeaderAsync(); @@ -314,6 +322,10 @@ public sealed class MatchGrain : IMatchGrain var decisionBefore = _engine.State.ActivePlayer; + _logger.LogInformation( + "Match {MatchId}: seat {Seat} intent {IntentType} ({IntentId}) received. {State}", + _matchId, seat, DescribeIntent(intent.Intent), intentId, DescribeTurnState()); + CommandResult result; var commandText = string.Empty; @@ -349,6 +361,20 @@ public sealed class MatchGrain : IMatchGrain await SendResultAsync( seat, intentId, record.Accepted, record.Code, record.Reason, record.EventSeq, duplicate: false); + if (record.Accepted) + { + _logger.LogInformation( + "Match {MatchId}: seat {Seat} intent {IntentType} ({IntentId}) accepted. {State}", + _matchId, seat, DescribeIntent(intent.Intent), intentId, DescribeTurnState()); + } + else + { + _logger.LogWarning( + "Match {MatchId}: seat {Seat} intent {IntentType} ({IntentId}) rejected {Code}: {Reason}. {State}", + _matchId, seat, DescribeIntent(intent.Intent), intentId, record.Code, record.Reason, + DescribeTurnState()); + } + if (!record.Accepted) return; @@ -462,6 +488,10 @@ public sealed class MatchGrain : IMatchGrain IntentRejectCode code, string reason) { + _logger.LogWarning( + "Match {MatchId}: seat {Seat} intent {IntentId} rejected before execution {Code}: {Reason}. {State}", + _matchId, seat, intentId, code, reason, DescribeTurnState()); + _intents[intentId] = new IntentRecord(false, code, reason, _engine.Events.Count); await PersistIntentAsync(new PersistedIntent( @@ -510,6 +540,15 @@ public sealed class MatchGrain : IMatchGrain var from = Math.Max(1, fromSeq); var events = EventProjection.ProjectRange(_engine.Events, seat, from); + _logger.LogInformation( + "Match {MatchId}: events {FromSeq}..{ToSeq} to seat {Seat}: [{EventTypes}]. {State}", + _matchId, + from, + to, + seat, + string.Join(", ", events.Select(e => e.GetType().Name)), + DescribeTurnState()); + _seats[seat].SentSeq = to; await SendToSeatAsync(seat, new ServerEvents @@ -524,6 +563,22 @@ public sealed class MatchGrain : IMatchGrain { var to = _engine.Events.Count; var view = MatchProjection.ForViewer(_engine, seat, _matchId, to, BuildClock(now)); + var legalRides = string.Join( + ", ", + view.LegalIntents.OfType().Select(i => i.PrintingId)); + + _logger.LogInformation( + "Match {MatchId}: snapshot to seat {Seat} at seq {Seq}, you={You}, active={Active}, turn={Turn}, phase={Phase}, legal={LegalCount}, rides=[{Rides}]. {State}", + _matchId, + seat, + to, + view.You, + view.ActivePlayer, + view.TurnNumber, + view.Phase, + view.LegalIntents.Count, + legalRides, + DescribeTurnState()); _seats[seat].SentSeq = to; @@ -780,6 +835,52 @@ public sealed class MatchGrain : IMatchGrain } } + private static string DescribeIntent(IntentPayload? intent) => + intent is RideIntent ride + ? $"Ride({ride.PrintingId})" + : intent?.GetType().Name ?? "null"; + + /// + /// Diagnostic snapshot for ride issues: active player, phase, vanguard grade, + /// hand grades and cards that should be offered for a ride. + /// + private string DescribeTurnState() + { + var state = _engine.State; + var active = state.ActivePlayer; + var player = state.Players[active]; + var rideGrade = _engine.VanguardGrade(active); + + var vanguard = player.Vanguard is null + ? "none" + : $"{player.Vanguard.PrintingId}(g{rideGrade})"; + + var hand = string.Join( + ", ", + player.Hand.Select(card => + { + var grade = state.Catalog.CardOf(card.PrintingId).Grade; + return $"{card.PrintingId}(g{grade})"; + })); + + var rideReady = state.Phase == Phase.Ride + ? string.Join( + ", ", + player.Hand + .Where(card => + { + var grade = state.Catalog.CardOf(card.PrintingId).Grade; + return grade == rideGrade || grade == rideGrade + 1; + }) + .Select(card => card.PrintingId) + .Distinct()) + : string.Empty; + + return $"active={active} turn={state.TurnNumber} phase={state.Phase} " + + $"step={state.Step} vanguard={vanguard} hand=[{hand}] " + + $"rideReady=[{rideReady}]"; + } + private void RefreshSummary() { _summary = new MatchSummary(