9 Commits

Author SHA1 Message Date
gamer147
1007cf24d2 refactor(battlenode): type MatchContext.ClassId as CardClass enum (§C)
Behavior-preserving; full solution builds, 1013 tests green.

ClassId is the one genuinely-closed set of the three flagged stringly fields, so it
becomes a CardClass enum (1..8). Wire stays "1".."8": producer casts
(CardClass)run.ClassId, ServerBattleFrames renders via CardClassWire.ToWireValue().
RankBattleController's AI-start path drops a fragile int.TryParse(...)?:-1 for (int)cast.

CharaId (free-form leader/skin id, e.g. "5000123") and CountryCode (open-ended account
data) stay string with proper XML docs; CountryCodes.Korea/Japan name the captured values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 08:04:49 -04:00
gamer147
578d0a75ef refactor(battlenode): rename mode-id field off BattleType, add BattleModes (§D)
Behavior-preserving; 271 BattleNode/Matching/Services tests green, full solution builds.

"BattleType" meant two things: the Sessions.BattleType enum (Pvp/Bot) and an int
"mode id" field. Renamed the int field on MatchContext AND the BattleStartBody wire
DTO to BattleModeId (wire key stays "battleType" via JsonPropertyName), so BattleType
now means only the enum project-wide.

New Bridge/BattleModes.cs (TakeTwo = 11) replaces every 11 literal — both prod
MatchContextBuilder sites and the test fixtures/assertions. The arbitrary-passthrough
42 and bot 0 stay literal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 07:44:02 -04:00
gamer147
3e8901eec3 refactor(battlenode): split BattleSessionPhase into HandshakePhase + SessionLifecycle
Behavior-preserving; 231 BattleNode tests green.

One enum conflated two axes. Split:
- HandshakePhase (per participant): AwaitingInitNetwork..AfterReady. On
  IHasHandshakePhase.Phase, FrameDispatchContext.SenderPhase, the handler gates.
- SessionLifecycle (per battle): Active | Terminal. On the renamed
  BattleSessionState.Lifecycle (was SessionPhase, defaulting to a handshake value)
  and BattleSession.Lifecycle (was Phase). Reads are only != Terminal, so the
  Active default is behavior-identical.

OpponentTurn was dead (never assigned) -> dropped. BattleSessionPhase deleted; the
two axes can no longer be cross-assigned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 07:21:59 -04:00
gamer147
7d4da69f22 refactor(battlenode): low-churn §B/§D/§E/§F quality cleanups
Behavior-preserving; 231 BattleNode tests green.

- §D: MsgEnvelope.Try -> RetryAttempt (drops keyword-escape; wire key stays "try");
  SocketIoFrame.AckResponse arg -> pubSeqEcho.
- §B: Gungnir.EmitInterval -> BattleNodeOptions.AliveEmitInterval (unused literal
  moved to its config home); deck-idx 4L -> InitialHand.Length + 1.
- §E: shared Wire.WireJsonOptions.CamelCase replaces the duplicated camelCase
  JsonSerializerOptions in EngineIoHandshake and MsgEnvelope.
- §F: do-NOT-consistency-fix polarity notes on TurnEndFinalHandler (From wins)
  and RetireKillHandler (From loses).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 23:06:44 -04:00
gamer147
e70f32db79 refactor(battlenode): close §A boolean-blindness items (MinedToken, Stock, KeyActionType)
Behavior-preserving; 231 BattleNode tests green.

- MinedToken record struct replaces the transpose-prone (int Idx, long CardId,
  CardOwner IsSelf) tuple returned by KnownListBuilder.Mine*. Positional deconstruct
  keeps the Record*From call sites unchanged.
- enum Stock { Normal, Bypass } replaces the negative `bool noStock` on
  IBattleParticipant.PushAsync and DispatchRoute, threaded through both participants,
  BattleSession, and all handler construction sites.
- enum KeyActionType mirrors the client's SendKeyActionDataManager.KeyActionType;
  the StripKeyActionForOpponent guard compares named values, KeyActionEntry.Type is
  the enum (wire-identical via JsonNumberEnumConverter).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:53:32 -04:00
gamer147
ca5a1e926d feat(battle-node): RealParticipant session-finished signal + Pvp cascade
RealParticipant gains _sessionFinished TCS + MarkSessionFinished /
AwaitSessionFinishedAsync. PvP first-arriver's handler awaits the
signal instead of calling self.RunAsync (which the session does
internally on the same instance — double-call would race the WS read).

BattleSession.RunAsync branches on Type: Pvp uses WhenAny + synthesize
BattleFinish(Win) to survivor + WhenAll(drain); Scripted/Bot keep
Phase 1's WhenAll-everything semantics. Disconnect cascade now drives
end-of-battle when a WS drops without a graceful Retire.
2026-06-01 21:58:47 -04:00
gamer147
ac78473a3e feat(battle-node): add RealParticipant.Phase for per-side handshake state
Internal setter; defaults to AwaitingInitNetwork. PvP needs A and B to
progress through the handshake states independently, which the
session-level BattleSession.Phase can't model. Session migration to read
realFrom.Phase is the next task.
2026-06-01 21:25:11 -04:00
gamer147
2d7cee38d3 refactor(battle-node): drop old BattleSession; rename V2 -> BattleSession
Old single-WS BattleSession + its dispatch/pump/ClipAckArg tests are
obsolete after the Task 9 handler cutover. ClipAckArg overflow + boundary
coverage moved into RealParticipantTests. BattleSessionV2 renamed back
to BattleSession; the V2 suffix was a placeholder during the parallel
-build refactor.
2026-06-01 20:10:14 -04:00
gamer147
acd0997cfb feat(battle-node): add RealParticipant wrapping WS + sequencers
Lifts the WS read loop, SIO encode/decode, per-WS OutboundSequencer +
InboundTracker, and SIO ack out of BattleSession into a participant.
PushAsync(noStock=false) assigns playSeq via the sequencer; noStock=true
bypasses it. FrameEmitted fires on each deduplicated inbound envelope.
The existing BattleSession keeps its own copy of the WS code for now;
Task 9 cuts the handler over to use BattleSessionV2 + RealParticipant
and Task 10 deletes the old BattleSession + duplicate code.
2026-06-01 19:57:45 -04:00