Commit Graph

467 Commits

Author SHA1 Message Date
gamer147
cbee0f9a50 feat(arena-colosseum): playable bracket + rank-match promotion (phase 2)
Closes 4 more arena-colosseum spec shapes (6/16 → 10/16): the dual-prefix
battle URLs (colosseum_battle/* and colosseum_rank_battle/*) for both
do_matching and per-match finish, plus the bracket-end /finish + /retire
pair on /arena_colosseum.

* ColosseumProgressionService: pure-logic decisions for win-threshold
  advancement, 3008 promotion trigger, and per-round reward bundles.
  Extends ColosseumRoundsConfig with FinishRewards/RetireRewards per
  round + a top-level ChampionRewards list.
* MatchContextBuilder.BuildForColosseumAsync: lifts the registered deck
  via IDeckRepository (NOT viewer-graph) per project_ef_nav_include_pitfall.
* ArenaColosseumBattleController: dual route prefixes off one controller
  via explicit absolute [HttpPost("/...")] routes — does NOT extend
  SVSimController's [Route("[controller]")]. Same pattern as FreeBattle.
  URL-vs-IsRankMatching mismatch returns 400 with a phase-mismatch error;
  no-deck triggers the standard 3001 matchmaking-illegal state. 3008
  flips run.IsRankMatching exactly once via the progression service.
* /arena_colosseum/finish + /retire share FinishResponse (rewards +
  reward_list + colosseum_status). Champion finish appends ChampionRewards
  and surfaces colosseum_status.is_champion = true. Both delete the run
  row after granting via IInventoryTransaction.GrantAsync.
* DI: ModePolicy entries for "colosseum_battle" and "colosseum_rank_battle".
* Tests: 8 progression-service unit tests (pure logic), 5 battle-controller
  HTTP tests, 5 bracket-terminate HTTP tests, 2 full-bracket E2E walks
  (champion path + retire mid-round). Full suite: 1331/1331.
2026-06-13 12:32:56 -04:00
gamer147
110867358c feat(arena-colosseum): lobby + constructed entry (phase 1)
Closes 5 of arena-colosseum's 16 spec shapes (1/16 → 6/16). Lobby reads
(/top, /get_fee_info, /event_info) render an empty "no event scheduled"
payload by default; /entry + /register_deck activate via admin-flipped
ColosseumSeason + ColosseumRounds config sections.

* Schema: ViewerArenaColosseumRun standalone table (unique on ViewerId)
  with jsonb run-state columns mirroring ViewerArenaTwoPickRun.
* Config sections: ColosseumSeasonConfig (event-level), ColosseumRoundsConfig
  (the 3-round bracket). Empty defaults — IsColosseumPeriod=false.
* Migration AddArenaColosseumRun (DDL only; seed rows come from the section
  ShippedDefaults via EnsureSeedDataAsync).
* DTOs: ColosseumLobbyInfo (round-level, /top + /get_fee_info), ColosseumEventInfo
  (event-level, /event_info), ColosseumOwnStatus (shared status block),
  ColosseumEntryRef, ColosseumFeeList, ColosseumUserDeck, ColosseumBattleResults,
  ColosseumRoundDetail + ColosseumGroupRow. EventInfoResponse uses explicit
  [JsonPropertyName("1"|"2"|"3")] for the string-keyed rounds shape per spec.
* Controller: ArenaColosseumController replaces the 1-action stub with
  the five lifecycle endpoints. /top emits leader_skin_id even when 0
  (project_wire_null_policy override).
* Tests: 11 controller-level tests + 6 config round-trip tests covering
  empty-season payloads, run-seeded /top round-trip, crystal/rupy entry
  debits, now_round_id mismatch rejection, deck_no_list round-trip + reject.
2026-06-13 12:16:22 -04:00
gamer147
a5fe484775 feat(account): /account/update_birth persists viewer birth date
Strict yyyy-MM-dd parse (matches /load/index round-trip format) — rejects
malformed input. data_headers.servertime is emitted by the standard
envelope, which is what the client reads into BirthDayUpdateServerTime.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 11:35:41 -04:00
gamer147
715dd4dea8 feat(config): wire /config/update + /config/update_challenge_config (closes family 4/4)
is_skip_gacha_effect, use_challenge_two_pick_premium_card, and
challenge_two_pick_sleeve_id all persist on ViewerInfo and round-trip
through /load/index.

The two challenge fields move from the global ChallengeConfig section to
ViewerInfo — they're viewer preferences, not server-wide policy. Premium
card defaults to 0; sleeve falls back to DefaultLoadoutConfig.SleeveId
(3000011) when unset. MatchContextBuilder's TK2 sleeve read switches to
the same viewer-scoped path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 11:25:40 -04:00
gamer147
43dee9d00b feat(card): /card/create_foil_card Seer's Globe conversion (1 orb + 1 base → 1 foil) 2026-06-13 09:58:55 -04:00
gamer147
13ca7d118e feat(config): persist is_foil_preferred + is_prize_preferred (stubs → real) 2026-06-13 09:54:51 -04:00
gamer147
911374106f feat(emblem): /emblem/update_emblem sets viewer.SelectedEmblem (ownership-validated) 2026-06-13 08:16:19 -04:00
gamer147
bc110e2274 feat(emblem): /emblem/favorite accept-only (persistence deferred) 2026-06-13 08:15:29 -04:00
gamer147
921297227e feat(emblem): /emblem/emblem_list returns owned ids 2026-06-13 08:14:36 -04:00
gamer147
c5c61b6e9d feat(degree): /degree/update_degree sets viewer.SelectedDegree (ownership-validated) 2026-06-13 08:13:49 -04:00
gamer147
356e58ba25 feat(degree): /degree/favorite accept-only (persistence deferred) 2026-06-13 08:12:02 -04:00
gamer147
68741b2980 feat(degree): /degree/degree_list returns owned ids 2026-06-13 08:11:13 -04:00
gamer147
025c2a8c56 feat(mission): /mission/receive_reward stub (refresh-only, no decomp)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 23:33:50 -04:00
gamer147
e54e63a762 feat(mission): /mission/buy_additional_right stub (refresh-only, no decomp)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 23:29:55 -04:00
gamer147
30b8af2d16 feat(pack): /pack/get_leader_skin_owned_status (empty per-class buckets) 2026-06-12 23:26:29 -04:00
gamer147
c870b95be9 feat(sleeve): /sleeve/favorite accept-only (persistence deferred) 2026-06-12 23:22:25 -04:00
gamer147
f879c05dca feat(sleeve): /sleeve/sleeve_list returns owned ids 2026-06-12 23:17:10 -04:00
gamer147
e2f5786b60 feat(mypage): /mypage/get_special_crystal_info (empty — no offers) 2026-06-12 23:12:49 -04:00
gamer147
43e4c06a2c feat(mypage): /mypage/finish_battle (returns check=0 — no in-flight state yet) 2026-06-12 23:08:42 -04:00
gamer147
b4a87792dc feat(account): /account/update_region_code (accept-only) 2026-06-12 23:01:53 -04:00
gamer147
56ef994275 style(profile): drop file-path header comments from quick-wins task 1 2026-06-12 22:59:35 -04:00
gamer147
398764a4cc feat(profile): /profile/update_official_mark_display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 22:54:52 -04:00
gamer147
37cb8b5eeb test(free-battle): literal-prod-JSON parity for /do_matching 2026-06-12 21:30:19 -04:00
gamer147
55ec279f73 test(free-battle): force_finish no-op returns empty object 2026-06-12 21:29:52 -04:00
gamer147
31ecda95f7 test(free-battle): consistency battle_result echoes on rotation URL 2026-06-12 21:29:26 -04:00
gamer147
523ac605a1 test(free-battle): /finish echoes battle_result with no rank fields 2026-06-12 21:29:02 -04:00
gamer147
487a86fbbb test(free-battle): 3001 when viewer has no deck for format 2026-06-12 21:28:35 -04:00
gamer147
db298a0652 test(free-battle): two-viewer PvP pairing flow 2026-06-12 21:28:00 -04:00
gamer147
4a09b03820 test(free-battle): DoMatching first poll returns 3002 2026-06-12 21:27:33 -04:00
gamer147
3e58fbb650 feat(free-battle): FreeBattleController + routing smoke tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 21:24:02 -04:00
gamer147
d0d6a6916c fix(card-master): wire EnableServing kill switch in controller + add to config-section test 2026-06-12 12:52:06 -04:00
gamer147
b4a279ef0c feat(load-index): emit data.card_master_hash on mismatch (tier-1 freshness gate) 2026-06-12 12:27:40 -04:00
gamer147
92a21e7a7a test(routing): cover /immutable_data/card_master routing 2026-06-12 12:20:46 -04:00
gamer147
6eb4547866 fix(card-master): 500 not 503 when blob missing; explicit test-project copy link 2026-06-12 12:19:37 -04:00
gamer147
d74bae9ba0 feat(card-master): implement POST /immutable_data/card_master serving captured blob 2026-06-12 12:11:15 -04:00
gamer147
1960e28298 refactor(auth): decouple Steam handler from request DTO shape
Translation middleware now extracts viewer_id/steam_id/steam_session_ticket
from the decrypted msgpack dict into HttpContext.Items before the typed
DTO deserialize. The Steam handler reads from there instead of re-parsing
Request.Body — so authed action DTOs no longer need to inherit BaseRequest
to keep the auth fields alive through the msgpack→DTO→JSON pivot.

Retires the recurring footgun documented in
docs/superpowers/specs/2026-06-02-baseRequest-auth-footgun-improvement.md
(2026-05-25 basic-puzzle, 2026-05-28 deck-code, 2026-06-02 Phase 3 Bot,
2026-06-10 profile/index + item_acquire_history/info + user_mypage/update).

Pinned by AuthDecouplingTests — posts an encrypted msgpack body to
/profile/index (DTO does not inherit BaseRequest) through the real
translation middleware + auth handler and asserts 200. Adds an
EncryptedMsgpackHelper + useRealAuthHandler factory flag, reusable for
future wire-shape tests.

ProfileIndexRequest, ItemAcquireHistoryInfoRequest, and
UserMyPageUpdateRequest revert to the naked shape — the per-DTO
workarounds become vestigial under the new architecture.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 12:29:10 -04:00
gamer147
b18b24b085 test(ranking): literal-prod-JSON wire-shape parity
Per feedback_wire_shape_tests: controller round-trip tests using the
same DTO can't catch wire-key/wire-type drift. Asserts parsing of and
snake_case emission for the period list + monthly ranking shapes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 10:48:51 -04:00
gamer147
f743b27696 feat(ranking): stub /ranking/* (6 endpoints)
Rankings menu opens. Period picker shows deterministic monthly schedule.
Every leaderboard returns { period, ranking: [] }.

Endpoints:
- /ranking/get_viewable_ranking_period_list
- /ranking/master_point_{rotation,unlimited}_info
- /ranking/rank_match_class_win_{rotation,unlimited}_info
- /ranking/two_pick_win_info

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 10:46:00 -04:00
gamer147
80f249f8a2 feat(ranking): add RankingPeriodSchedule helper
Pure deterministic monthly period generator for the four ranking
families. Anchor dates derived from prod capture (2026-06-09): id=1 is
each family's launch month in JST; id=N is anchor + N-1 months. Used
by /ranking/get_viewable_ranking_period_list to render the period
picker and by per-family leaderboard endpoints to echo the requested
period back.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 10:36:51 -04:00
gamer147
b4aa07577f fix(friend): add BaseRequest body param to 6 body-less actions
ShadowverseTranslationMiddleware throws InvalidOperationException when
a Unity client posts an encrypted msgpack body to an action with zero
[FromBody] parameters — it has no target type for the deserializer.
Tests pass because they post JSON directly with no UnityPlayer UA and
the middleware short-circuits. Same defect already fixed on /replay/info
in 216dcab; this catches up the friend system shipped 2026-06-09.

Fixed actions: info, receive_apply_info, send_apply_info,
played_together_info, reject_apply_all, cancel_apply_all.

Tests updated to post the BaseRequest auth fields so [ApiController]
model validation passes (BaseRequest.ViewerId is non-nullable string).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 08:54:50 -04:00
gamer147
b54a47f333 test(replay): literal-prod-JSON wire-shape parity for /replay/info
Per feedback_wire_shape_tests (2026-05-28): controller round-trip tests
using the same DTO can't catch wire-key/wire-type drift. Asserts
parsing of, snake_case emission for, and MessagePack round-trip
through the captured prod frame.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 08:24:24 -04:00
gamer147
0996074287 feat(replay): wire arena two-pick finish hook
Same pattern as rank-battle: DoMatching stashes context; Finish takes
it and records history + played-together. Opponent identity is left
as placeholder fields until the resolver carries it through.

Test seeds an active ViewerArenaTwoPickRun so RecordBattleResultAsync
does not throw no_active_run during the e2e flow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 08:22:19 -04:00
gamer147
81aac701f4 feat(replay): wire finish hook for rank-battle family
Finish now consumes the stashed BattleContext, records a
ViewerBattleHistory row (idempotent + retention-capped), and
calls IPlayedTogetherWriter for human PvP (skipped for AI).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 08:15:12 -04:00
gamer147
b44354315a fix(replay-tests): supply BaseRequest auth fields in PostAsJsonAsync bodies
Following the 216dcab fix that added [FromBody] BaseRequest _ to the
Info action, the existing tests' empty new {} payloads no longer satisfy
[ApiController] model validation (BaseRequest.ViewerId is non-nullable
string). Use the same EmptyBody() shape as RankBattleControllerTests
to mirror the production wire.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 08:06:36 -04:00
gamer147
c6259e5a14 feat(replay): add ReplayController for /replay/{info,detail}
/replay/info reads from ReplayHistoryReader, newest-first, capped at 50.
/replay/detail returns 400 with result_code=99 - local cache is the
canonical playback source so this endpoint is cache-miss fallback only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 07:59:51 -04:00
gamer147
2b6c7bd6a4 feat(replay): add BattleHistoryWriter with 50-row per-viewer retention
Idempotent on (ViewerId, BattleId); evicts oldest CreateTime row when
at cap. No-op when ctx is null (server-restart safety).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 07:43:50 -04:00
gamer147
869f9ce13d feat(replay): add BattleContextStore for start->finish handoff
Bridges the start-time -> finish-time gap. /finish carries neither
battle_id nor opponent identity; this store holds both for the finish
handler to compose a ViewerBattleHistory row.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 07:38:37 -04:00
gamer147
0bb0f46abc feat(replay): add ViewerBattleHistory entity + migration
New table backs /replay/info; composite PK (ViewerId, BattleId), index on
(ViewerId, CreateTime) for the newest-first list query. 50-row per-viewer
retention enforced by BattleHistoryWriter (next commit).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 07:32:05 -04:00
gamer147
2d65fcd91c test(friend): end-to-end multi-viewer apply→approve→friendship flow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:14:45 -04:00
gamer147
d848f4a03f test(friend): literal wire-shape round-trip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:13:16 -04:00