fix(tk2): per-viewer is_join in arena_info + stub /arena/get_challenge_info

Bug 1 ("pay to enter again after restart"):
arena_info[0].is_join shipped from the static ArenaSeasonConfig seed,
so /load/index and /mypage/index always emitted false regardless of
viewer state. The client uses is_join to choose between the "Pay to
enter" and "Resume run" dialogs (Wizard/ChallengeEntry.cs:165 + the
ArenaEntryBase._isJoinFunc pivot). Without a per-viewer override every
cold start after a partial run looked like "no run" and the player got
charged again.

LoadController + MyPageController now compute is_join from
ViewerArenaTwoPickRuns presence. MyPageController grew an
IArenaTwoPickRunRepository dep (LoadController already had _db).

Bug 2: /arena/get_challenge_info 404. Stubbed via a new
ArenaController + DTO pair. Returns the season seed's begin/end_time
+ name where available; placeholder zeros for win history. All 6 keys
required by ChallangeHistoryInfoTask.Parse are present (unconditional
JsonData lookups).

Routing smoke added for /arena/get_challenge_info.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-05-31 13:13:11 -04:00
parent 1e2e18e828
commit 1af56b4ec4
6 changed files with 135 additions and 8 deletions

View File

@@ -1,5 +1,6 @@
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SVSim.Database;
using SVSim.Database.Enums;
using SVSim.Database.Models;
@@ -244,7 +245,7 @@ public class LoadController : SVSimController
UseChallengePickTwoPremiumCard = challenge.UseTwoPickPremiumCard ? 1 : 0,
ChallengePickTwoCardSleeve = (int)challenge.TwoPickSleeveId,
},
ArenaInfos = await BuildArenaInfosAsync(),
ArenaInfos = await BuildArenaInfosAsync(viewer.Id),
RotationSets = rotationSets,
UserConfig = new UserConfig(),
OpenBattlefieldIds = (await _globalsRepository.GetBattlefields(true))
@@ -263,7 +264,7 @@ public class LoadController : SVSimController
/// field is omitted on the wire, which the client's <c>Keys.Contains("arena_info")</c> guard
/// (LoadDetail.cs:261) handles cleanly.
/// </summary>
private async Task<List<ArenaInfo>?> BuildArenaInfosAsync()
private async Task<List<ArenaInfo>?> BuildArenaInfosAsync(long viewerId)
{
var season = await _globalsRepository.GetCurrentArenaSeason();
if (season is null) return null;
@@ -274,6 +275,15 @@ public class LoadController : SVSimController
format = JsonSerializer.Deserialize<ArenaFormatInfo>(season.FormatInfo, JsonbReadOptions.Instance);
}
// is_join must reflect the viewer's actual TK2 state — true if they have an
// active ViewerArenaTwoPickRun row. The client uses this to decide between the
// "Pay to enter" and "Resume run" dialogs (Wizard/ChallengeEntry.cs:165 + ArenaEntryBase).
// Without a per-viewer override here, every cold start after a partial run shows
// "Pay to enter" — losing the in-progress draft from the player's perspective.
bool hasActiveRun = await _db.ViewerArenaTwoPickRuns
.AsNoTracking()
.AnyAsync(r => r.ViewerId == viewerId);
return new List<ArenaInfo>
{
new ArenaInfo
@@ -283,7 +293,7 @@ public class LoadController : SVSimController
Cost = season.Cost,
RupeeCost = season.RupyCost,
TicketCost = season.TicketCost,
IsJoin = season.IsJoin,
IsJoin = hasActiveRun,
FormatInfo = format,
}
};