Adds IMissionProgressService to RankBattle, FreeBattle, ArenaTwoPickBattle, and
ArenaTwoPick controllers. Each finish handler emits the appropriate
MissionEventKeys builder result:
- RankBattle.Finish (win) → Ranked.WinAll(classId)
Includes AI-rank routes; on a private server AI wins count the same.
- FreeBattle.Finish (win) → Free.WinAll() (aggregates only)
- ArenaTwoPickBattle.Finish → Challenge.MatchPlayAll or MatchWinAll
Always fires challenge_play; wins additionally fire challenge_win and
the ranked/arena/daily aggregates.
- ArenaTwoPick.Finish (5-win full clear) → Challenge.FullClearAll
Gated on RunFinishOutcome.WasFullClear from the previous commit.
This closes six of the eight remaining catalog wire-up gaps; class_level_up
and rank_achieved follow in Tier 2 (need pre-state detection).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduces RunFinishOutcome — a semantic record that wraps the wire
FinishResponseDto with the controller-side WasFullClear signal used to fire
the challenge_full_clear mission event. The wire response isn't affected
(controller unwraps the .Response field before returning to the client);
RetireAsync stays returning FinishResponseDto because retire cannot full-clear.
Full-clear detection compares run.WinCount against ResolveMaxBattleCountAsync
rather than hard-coding 5, so a future TK2 rules-config change tracks
automatically. New test covers the full-clear signal explicitly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>