Replace bare `int RewardType` on 12 catalog/reward entities and GrantedReward
with the existing UserGoodsType enum. Verified against the decompiled client:
every wire reward_type decodes through the single Wizard.UserGoods.Type enum, so
one enum is correct across all endpoint families (item_type is a separate
Item.Type axis, left untouched). EF stores the enum as the same int column, so
there is no migration.
- Importers cast seed int -> UserGoodsType at the ingest boundary.
- New GrantedReward.ToRewardList() extension replaces 8 copy-pasted
GrantedReward -> RewardListEntry projections.
- Fix 3 .ToString() sites that would otherwise emit enum names ("Crystal")
instead of the int wire value ("2").
- Wire DTOs keep int; the enum is widened to int at the wire boundary only.
Build green; 962/962 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace RewardGrantService + ICurrencySpendService + IViewerEntitlements with
IInventoryService. tx.IsFreeplay replaces FakeEntitlements.IsFreeplay; debit
helpers take IInventoryTransaction. ComputePostStateRewardList deleted (replaced
by result.RewardList from CommitAsync). Update 5 test files to new 8-arg ctor.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the all-rows-granted reward model with per-group weighted
pick. Each ArenaTwoPickReward row now belongs to a RewardGroup with a
Weight; finish/retire groups the WinCount's rows by RewardGroup and
picks exactly one row per group, weighted by Weight (excluding
Weight==0). A RewardNum==0 outcome skips both the grant and the
rewards[] emission. Empty WinCount catalogs emit empty arrays.
Existing seed entries preserve deterministic behavior by living in
single-option groups (each with weight 1). Future seasons can expand
groups to multi-option for true randomized rewards (e.g. 200-280
rupies).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The /retire and /finish responses carry two reward arrays with DIFFERENT
key schemas:
rewards[] → ReceivedReward(JsonData) parser
{reward_type, reward_detail_id, item_type, reward_count?, is_usable}
reward_list[] → PlayerStaticData.UpdateHaveUserGoodsNumByJsonData
{reward_type, reward_id, reward_num}
We were emitting both with reward_list's schema, so the client threw
KeyNotFoundException on `data["reward_detail_id"]` while parsing each
delta entry — observed live as the retire-screen failure.
- New TwoPickRewardReceivedDto mirrors the existing Achievement/
TotalReceiveCountDto shape.
- FinishResponseDto.Rewards switched from List<RewardEntryDto>
to List<TwoPickRewardReceivedDto>.
- GrantRunRewardsAndDeleteAsync pre-loads ItemEntry.Type for any
Item-typed reward so item_type ships correctly (0 for currencies).
- Existing tests renamed RewardNum→RewardCount on the deltas list.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User clarified: the 0..7 win reward tiers came from Shadowverse Worlds
Beyond (sequel), not the original game we're emulating. Original SV's
Take Two caps at 5 total battles played and has no loss limit (verified
on prod: queueing continues with 2+ losses).
- arena-two-pick-rewards.json: drop 6w + 7w tiers (12 rows remain).
- ArenaTwoPickConfig: remove MaxLosses property.
- ArenaTwoPickService: termination is now battlesPlayed >= maxBattles
(5 from MAX(reward.WinCount)). RecordBattleResult no longer flips
IsSelectCompleted on the 2nd loss.
- ResolveMaxBattleCountAsync empty-catalog default 7 → 5.
- Tests updated for the new counts (16 → 12 rows, max 7 → 5).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements the class-selection and card-pick turns for the Take Two arena draft:
- ChooseClassAsync validates class is offered, locks ClassId, generates first pick set via pool
- ChooseCardAsync appends the two picked cards, advances SelectTurn 1–15, completes draft at turn 15
- 6 new tests covering happy paths and all error codes (class_not_offered, invalid_state, invalid_selection)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>