From ac2f31103d323f3abfff13b59919f869b5f580a6 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sun, 31 May 2026 13:21:44 -0400 Subject: [PATCH] fix(arena): match prod get_challenge_info wire shape; stub ranking_history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prod /arena/get_challenge_info capture (Season 26): - reward_step_info.reward_step_list is a Dict ({"5":"5","10":"10","15":"15"}), not the List I'd assumed - max_reward_step is stringified The previous stub would have parsed at the client (LitJson tolerates the shape via indexed iteration), but cleaning to match prod exactly. Also stubs /arena/get_challenge_ranking_history (new endpoint observed in the same capture). Prod ships {two_pick: [], sealed: []} with no history populated — empty lists match. Routing smoke added. Co-Authored-By: Claude Opus 4.7 --- .../Controllers/ArenaController.cs | 20 +++++++++++++++++-- .../Arena/GetChallengeInfoResponseDto.cs | 11 ++++++++-- .../GetChallengeRankingHistoryResponseDto.cs | 19 ++++++++++++++++++ SVSim.UnitTests/RoutingSmokeTests.cs | 1 + 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Arena/GetChallengeRankingHistoryResponseDto.cs diff --git a/SVSim.EmulatedEntrypoint/Controllers/ArenaController.cs b/SVSim.EmulatedEntrypoint/Controllers/ArenaController.cs index 0dcd36b..381965b 100644 --- a/SVSim.EmulatedEntrypoint/Controllers/ArenaController.cs +++ b/SVSim.EmulatedEntrypoint/Controllers/ArenaController.cs @@ -43,6 +43,14 @@ public class ArenaController : SVSimController catch { /* fall back to defaults */ } } + // Default Challenge Master reward steps from prod capture: 3 milestones at 5/10/15 wins. + var rewardSteps = new Dictionary + { + ["5"] = "5", + ["10"] = "10", + ["15"] = "15", + }; + return Ok(new GetChallengeInfoResponseDto { ChallengeName = name, @@ -51,9 +59,17 @@ public class ArenaController : SVSimController TwoPickAllWinCount = 0, RewardStepInfo = new RewardStepInfoDto { - MaxRewardStep = 0, - RewardStepList = new List(), + MaxRewardStep = 15, + RewardStepList = rewardSteps, }, }); } + + [HttpPost("get_challenge_ranking_history")] + public IActionResult GetChallengeRankingHistory([FromBody] GetChallengeInfoRequest req) + { + if (!TryGetViewerId(out _)) return Unauthorized(); + // Prod returns {two_pick: [], sealed: []}. Stub matches. + return Ok(new GetChallengeRankingHistoryResponseDto()); + } } diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Arena/GetChallengeInfoResponseDto.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Arena/GetChallengeInfoResponseDto.cs index 3c9f5fd..1547a10 100644 --- a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Arena/GetChallengeInfoResponseDto.cs +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Arena/GetChallengeInfoResponseDto.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using MessagePack; +using SVSim.EmulatedEntrypoint.Models.Dtos.Common; namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Arena; @@ -32,9 +33,15 @@ public class GetChallengeInfoResponseDto [MessagePackObject] public class RewardStepInfoDto { - [JsonPropertyName("max_reward_step")] [Key("max_reward_step")] + [JsonPropertyName("max_reward_step")] [JsonConverter(typeof(StringifiedIntConverter))] [Key("max_reward_step")] public int MaxRewardStep { get; set; } + /// + /// Wire shape: dict keyed by stringified-int win count → stringified-int reward step + /// (prod capture: {"5":"5","10":"10","15":"15"}). Client parser at + /// ChallangeHistoryInfoTask.cs:43 iterates by Count + indexed value access, which works + /// for both arrays and LitJson object-iteration order — but prod always ships the dict. + /// [JsonPropertyName("reward_step_list")] [Key("reward_step_list")] - public List RewardStepList { get; set; } = new(); + public Dictionary RewardStepList { get; set; } = new(); } diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Arena/GetChallengeRankingHistoryResponseDto.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Arena/GetChallengeRankingHistoryResponseDto.cs new file mode 100644 index 0000000..1022cbe --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Arena/GetChallengeRankingHistoryResponseDto.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; +using MessagePack; + +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Arena; + +/// +/// Wire shape for /arena/get_challenge_ranking_history. Prod returns two empty lists +/// (two_pick + sealed) per the season-26 capture. Populated history is per-viewer + per-season +/// ranking snapshots; not tracked locally yet. +/// +[MessagePackObject] +public class GetChallengeRankingHistoryResponseDto +{ + [JsonPropertyName("two_pick")] [Key("two_pick")] + public List TwoPick { get; set; } = new(); + + [JsonPropertyName("sealed")] [Key("sealed")] + public List Sealed { get; set; } = new(); +} diff --git a/SVSim.UnitTests/RoutingSmokeTests.cs b/SVSim.UnitTests/RoutingSmokeTests.cs index 143fad8..8342fd3 100644 --- a/SVSim.UnitTests/RoutingSmokeTests.cs +++ b/SVSim.UnitTests/RoutingSmokeTests.cs @@ -110,6 +110,7 @@ public class RoutingSmokeTests [TestCase("/arena_two_pick_battle/finish")] [TestCase("/arena_colosseum/get_fee_info")] [TestCase("/arena/get_challenge_info")] + [TestCase("/arena/get_challenge_ranking_history")] public async Task Authenticated_route_resolves(string path) { using var factory = new TestFactory();