Commit Graph

745 Commits

Author SHA1 Message Date
gamer147
98fb3c5fcd fix(svc): default MaxBattleCount=7 with warn-log on empty reward catalog 2026-05-31 11:41:57 -04:00
gamer147
2aa0bdefec test(tk2): routing smoke + end-to-end draft→retire
Adds 8 TestCase entries to Authenticated_route_resolves for all
arena_two_pick and arena_two_pick_battle endpoints, and a full
integration test exercising entry → class_choose → 15×card_choose →
retire, verifying seed reward grants (1 ticket + 100 rupies).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 11:37:47 -04:00
gamer147
65e0e0fb09 test(config): update section count from 10 → 11 (ArenaTwoPick) 2026-05-31 11:27:56 -04:00
gamer147
09b8c49743 feat(http): ArenaTwoPickBattleController (do_matching stub + finish)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 11:27:02 -04:00
gamer147
f272690a31 feat(http): ArenaTwoPickController (6 actions)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 11:15:53 -04:00
gamer147
e245d5b158 feat(svc): Retire + Finish + RecordBattleResult
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 11:10:41 -04:00
gamer147
cc40e2d2e8 feat(svc): ChooseClassAsync + ChooseCardAsync (draft state machine)
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>
2026-05-31 11:03:50 -04:00
gamer147
d550f66481 feat(svc): EntryAsync (ticket debit + run insert + candidate classes)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:59:05 -04:00
gamer147
ba49852c42 feat(svc): IArenaTwoPickService + response DTOs + GetTopAsync
6 response DTOs, IArenaTwoPickService interface + ArenaTwoPickException,
ArenaTwoPickService skeleton with GetTopAsync implemented and stubs for
Tasks 13-15. 3 NUnit tests for GetTopAsync all pass. DI: AddScoped.
2026-05-31 10:51:41 -04:00
gamer147
a98b60dd36 feat(svc): ArenaTwoPickCardPoolService (rarity-weighted, class+neutral)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:46:59 -04:00
gamer147
30a723322c feat(dto): TK2 common DTOs (entry/class/deck/candidate/results/reward)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:40:06 -04:00
gamer147
2df18425c4 feat(repo): IArenaTwoPickRunRepository + tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:37:42 -04:00
gamer147
721cd738d7 feat(repo): IArenaTwoPickRewardRepository + tests
Adds GetRewardsByWinCountAsync and GetMaxWinCountAsync (short-circuits
to 0 on empty table). Registers as AddTransient in Program.cs alongside
other global catalog repos. 3 NUnit tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:34:12 -04:00
gamer147
5a8ca8853f feat(bootstrap): ArenaTwoPickRewardImporter + tests
Idempotent upsert importer for arena-two-pick-rewards.json; 2 NUnit tests
(16-row load + idempotency). Wired into Program.cs globals pipeline after
ArenaSeasonImporter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:30:41 -04:00
gamer147
4f5b4c6a6b feat(bootstrap): add arena-two-pick-rewards seed file + POCO 2026-05-31 10:27:27 -04:00
gamer147
f535642109 feat(config): add ArenaTwoPickConfig section 2026-05-31 10:25:40 -04:00
gamer147
d49b435e53 fix(config): restore pre-existing two_pick_sleeve_id (3000011) 2026-05-31 10:24:55 -04:00
gamer147
6e7f0dc4c9 feat(config): extend ChallengeConfig with TK2 format_info + PoolCardSetIds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:23:08 -04:00
gamer147
5faa5e2445 feat(db): AddArenaTwoPick migration (rewards + viewer-run tables)
Register ArenaTwoPickRewards and ViewerArenaTwoPickRuns DbSets in SVSimDbContext and generate the AddArenaTwoPick migration with both CreateTable calls, all four jsonb columns on the run table, and the correct indexes (WinCount scalar + unique WinCount/RewardType/RewardId on rewards; unique ViewerId on runs).
2026-05-31 10:20:37 -04:00
gamer147
1dbc5fa831 feat(db): add ViewerArenaTwoPickRun entity + CandidatePair 2026-05-31 10:16:53 -04:00
gamer147
b32583ef48 feat(db): add ArenaTwoPickReward catalog entity 2026-05-31 10:12:16 -04:00
gamer147
50e4989b77 docs(importers): update data_dumps path references for reorg
Mirror of the outer-repo data_dumps/ reorganization (commit e1e595d in
the SVSim outer repo): updates all data_dumps/extract/ → data_dumps/scripts/,
data_dumps/client_master_csv → data_dumps/client-assets, data_dumps/traffic
→ data_dumps/captures/traffic in XML doc-comments and inline comments
across importers, controllers, middlewares, DTOs, and tests. Doc-only;
no logic changes; build green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 01:22:08 -04:00
gamer147
1470406e17 fix(gacha-points): include IsLeader cards regardless of draw tier
Prod's /pack/get_gacha_point_rewards offers leader cards from packs
where the leader sits in a non-Legendary tier — UCL pack 16015 has
Kyoka (711531010, Runecraft) and Miyako (711331010, Dragoncraft) as
Gold-tier rows with is_leader=1 in the drawrates. The old filter
(Tier == Legendary && !IsAltArt) excluded them, so the in-game
exchange UI was empty despite the banner advertising leader-card draw
rates.

Fix: filter on (Tier == Legendary || IsLeader) && !IsAltArt. Captures
every legendary plus any leader card regardless of page tier. Verified
against the captured 16015 response in
traffic_prod_all_gacha_exchange.ndjson (28 entries: 26 legendaries +
2 Gold-tier leaders).

Across the seeded data this surfaces 6 additional cards: 3 Bronze-tier
leaders + 3 Gold-tier leaders. The 68 Legendary-tier and 81 Special-
tier leaders were already included.

Renames legendaryCardIds -> exchangeableCardIds for clarity.

Regression test seeds a Gold-tier IsLeader=true card with a Skin row
and asserts the exchange catalog returns it with the Skin reward
entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 00:21:42 -04:00
gamer147
670e980dc2 data(cosmetics): regenerate CardCosmeticReward seed from refreshed CSVs
1068 -> 1098 rows. Net adds:
  emblems   870 -> 879  (+9)
  sleeves    92 -> 99   (+7)
  skins      74 -> 81   (+7, includes the 5 missing 719xxx LTL leaders)
  degrees    24 -> 31   (+7)
  mypagebg    8 -> 8

Generated by build-card-cosmetic-rewards.py from the per-type CSVs
refreshed in the previous outer-repo commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 00:02:28 -04:00
gamer147
61ae086332 fix(gacha-points): look up by odds_gacha_id, not parent_gacha_id
The two wire fields differ for seasonal packs (verified against
traffic_prod_all_gacha_exchange.ndjson — every captured request pairs
odds_gacha_id=16xxx with parent_gacha_id=10xxx). The OLD DTO docstring
assumed they were always equal; today's controller used
ParentGachaId, which lands on the base/family pack id (often a
synthesized disabled stub with no GachaPointConfig) and returns [].

Fix:
- GetGachaPointRewards and ExchangeGachaPoint now consume OddsGachaId.
- Update both DTO docstrings to document the seasonal-pack pattern.
- Regression test seeds (16015 enabled w/ GachaPointConfig, 10015
  disabled stub w/o config) and asserts the response uses 16015's
  catalog.

Symptom: opening pack 16015 (parent_gacha_id=16015 in /pack/open)
accrued gacha points correctly, but /pack/get_gacha_point_rewards with
{odds_gacha_id:16015, parent_gacha_id:10015} returned an empty list.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 23:30:18 -04:00
gamer147
9c9d0fc41f feat(packs): accept all currently-supported currencies on /pack/open
Extends /pack/open beyond the v1 CRYSTAL_MULTI=2 / DAILY=3 / RUPY_MULTI=7
trio to cover every type_detail whose payment primitive already exists:

  1 CRYSTAL              -> ICurrencySpendService crystal debit
  6 RUPY                 -> ICurrencySpendService rupee debit
  4 TICKET / 5 TICKET_MULTI -> debit child.ItemId from OwnedItemEntry
                            (ticketsNeeded = cost * packNumber), 400 on
                            missing/short balance; reward_list gets a
                            RewardType=4 post-state Item entry to mirror
                            project_wire_reward_list_post_state

Skin-overload type_details (8/9/13) and free-pack overlays (10/11/12)
stay 501 — they need selection / banner plumbing the current code
doesn't have.

Tutorial alias unchanged: it still consumes the gating ticket post-draw
and stamps tutorial_step=100. The two ticket flows diverge by intent
(tutorial = free server-grant; normal = paid by ticket inventory).

Removed Open_rejects_ticket_type_detail (asserted the old 501 path);
covered by Open_rejects_insufficient_tickets. Updated
NonTutorial_pack_open_does_not_emit_tutorial_step to assert the new
200-on-ticket-success behavior — same invariant under test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 23:17:11 -04:00
gamer147
d9d29fbfea Merge progression-import-export: progression import/export + pack system rewrite
Two unrelated feature sets landed on the same dev branch this session:

1. Progression import/export (7 prior commits): owned cards + items +
   decks + tolerant numeric my_rotation_id parsing + literal-client-JSON
   wire-shape coverage.

2. Pack system rewrite (7 new commits): full-fidelity per-pack draw
   tables seeded from the 279 archived drawrates CSVs, replacing the
   pack->CardSet pool assumption. New EF entities, importer, sampler,
   IsEnabled admin gate on PackConfig, statistical sampler test,
   PackRateConfig marked Obsolete.

Tests: 648/648 green.
Bootstrap end-to-end: 279 PackDrawConfigs / 1973 SlotRates /
90800 CardWeights / 35 enabled + 244 disabled stubs in Packs.
2026-05-30 22:51:23 -04:00
gamer147
d66d1d8c6e test(packs): statistical sampler + mark PackRateConfig [Obsolete]
200k-slot statistical test asserts observed tier rates within +/- 0.5pp
of the seeded SV Classic shape (Bronze=76.5 / Silver=16 / Gold=6 /
Legendary=1.5 on general slots). Marked [Category("Slow")].

PackRateConfig is marked [Obsolete] — no longer consulted by
PackOpenService. Internal callers (GameConfigService / DbContext config
seeding / its own tests) still reference it; they'll go when v1
stabilizes and PackRateConfig is fully retired.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 22:46:12 -04:00
gamer147
517f855112 feat(packs): wire PackDrawTableImporter; retire ICardPoolProvider
Bootstrap Program.cs now calls PackDrawTableImporter after PackImporter.
Delete DbCardPoolProvider, ICardPoolProvider, and the DbCardPoolProvider
tests — the new IPackDrawTableRepository covers what GachaPointService
needed (legendary-tier card_ids per pack) and PackOpenService takes the
draw table directly.

GachaPointService now resolves the legendary catalog from
PackDrawTable.CardWeights filtered by Tier==Legendary, instead of
ICardPoolProvider.GetPool then a rarity filter. Same end set, no DB pool
walk.

Test fallout: tests that fabricate custom card sets for gacha-point
tests now call factory.SeedPackDrawTableFromSetAsync(packId, setId)
to install a matching legendary-tier stub. Full suite: 647/647 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 22:45:02 -04:00
gamer147
1c386b5ed0 feat(packs): rewrite PackOpenService against per-pack draw table
Sampler is now driven by PackDrawTable: roll DrawTier per slot by
cumulative slot-rate weights, then pick a card within tier by per-card
weights renormalized within the tier. Rate-less Guaranteed-Leader-Card
rows draw uniform over (pool minus owned), falling back to the full
pool when all are owned. Bonus slot fires once at the end of a 10-pack
open when HasBonusSlot is set.

Slot 8 falls back to the general slot's per-card weights for the rolled
tier when slot-8 has only a rarity-level rate quoted (the common shape
on normal packs).

PackController.Open loads the draw table + viewer owned card ids and
passes them to the sampler; the category-based forced-Legendary slot-8
override is gone. ICardFoilLookup replaces ICardPoolProvider for the
foil-twin heuristic.

Drops the test-fixture pack-draw seed overlay so the production seed
flows through the importer tests; controller tests that fabricate their
own card sets now call factory.SeedPackDrawTableAsync(...) to install a
matching stub draw table.

WeightedPick helper handles the cumulative-band roll for both stages.
Five sampler tests + four WeightedPick tests + five importer/repo
tests; full suite is 653/653 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 22:26:45 -04:00
gamer147
0169ec57b4 feat(packs): split TryGetFoilTwin into ICardFoilLookup
Extracts the foil-twin lookup from ICardPoolProvider into a dedicated
ICardFoilLookup service. PackOpenService takes the lookup as a
parameter; the legacy DbCardPoolProvider stays registered until T12
removes it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 21:53:59 -04:00
gamer147
3c36124fa7 feat(packs): PackDrawTable aggregate + IPackDrawTableRepository
Aggregate (Config + SlotRates + CardWeights) and a single-pack getter
loaded as one unit per /pack/open. PackOpenService consumes the
aggregate; tests use the production seed (fixture overlay) to validate
shape.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 21:53:33 -04:00
gamer147
f7407fe382 feat(packs): PackImporter stubs pass + IsEnabled gate in active-packs
PackImporter now runs a second pass over pack-stubs.json, inserting
PackConfigEntry placeholders for any pack_id NOT already present from
the live-capture packs.json pass. Synthesized stubs default
IsEnabled=false; live-capture rows default IsEnabled=true.

PackRepository.GetActivePacks filters by IsEnabled in addition to the
date window, so synthesized stubs stay hidden until an operator opts
them in (UPDATE Packs SET IsEnabled=true WHERE Id=...).

Bundles Task 6 + Task 11 because adding pack-stubs.json to the
test-fixture set surfaces an extra row in PackControllerFullCatalogTests'
35-pack count assertion; the filter is what makes the test resilient.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 21:52:27 -04:00
gamer147
72c8fe627b feat(packs): PackDrawTableImporter with fixture tests
Idempotent upsert keyed on pack_id; slot rates and card weights are
wiped per pack and reinserted. String slot/tier in the seed translate
to enum at import time.

Tests:
- Imports_config_slot_rates_and_card_weights
- Is_idempotent_on_rerun

Fixtures live under SVSim.Bootstrap/Data/test-fixtures/seeds/ and overlay
the production seeds via the existing csproj rule (test-fixture file
beats production file at same Link path).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 21:45:06 -04:00
gamer147
f9f5b0dfa4 feat(packs): add PackDraw seed DTOs and IsEnabled on PackSeed
Three new seed DTOs (PackDrawConfigSeed, PackDrawSlotRateSeed,
PackDrawCardWeightSeed) — slot and tier carried as strings, importer
translates to enum.

PackSeed gains is_enabled (defaults true so existing seeds remain
enabled).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 21:43:27 -04:00
gamer147
8e98180951 feat(packs): add pack-draw and pack-stub seed files
Generated by data_dumps/extract/extract-pack-draw-rates.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 21:42:20 -04:00
gamer147
b78d7d6cbe feat(packs): add PackDraw* tables and IsEnabled column
Three new EF entities and a migration:
- PackDrawConfigEntry (per-pack: animation rate, has-bonus flag, special-kind label)
- PackDrawSlotRateEntry (pack/slot/tier -> rate, unique index)
- PackDrawCardWeightEntry (per-card-rate facts incl rate-less rows)

DrawSlot {General, Eighth, Bonus} and DrawTier {Bronze, Silver, Gold, Legendary, Special}.
Special collapses leader_card and limited_time_leader (verified mutually exclusive per pack).

IsEnabled column on PackConfigEntry — admin gate for synthesized stubs, distinct from
the wire-mirror IsHide.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 21:40:50 -04:00
gamer147
f754ef1ad3 fix(import): tolerate numeric my_rotation_id; skip empty deck slots
A real /load/index dump emits my_rotation_id as a bare number (0) for
unset MyRotation slots, which 400'd against the string? DTO field
(AllowReadingFromString only covers string->number). FlexibleStringConverter
accepts either form. Also skip empty deck slots (no cards) on import — a
dump carries every slot, mostly empty placeholders the client manages
itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 21:03:10 -04:00
gamer147
06108e4b6f test(import): literal-client-JSON wire-shape coverage for new fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:57:54 -04:00
gamer147
2e96001654 docs(import): update DefaultSleeveId comment after removing deck cloning 2026-05-29 18:54:00 -04:00
gamer147
4965851238 feat(import): import decks; remove obsolete default-deck cloning
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 18:42:07 -04:00
gamer147
d7e5557d61 feat(import): import consumable item inventory 2026-05-29 18:33:11 -04:00
gamer147
71b3c3e19f feat(import): import owned card collection with unknown-card skip
Extends POST /admin/import_viewer to accept owned_cards (snapshot full-replace).
Unknown card_ids are skipped, counted in skipped_card_count on the response, and
logged server-side. Count is clamped to OwnedCardEntry.MaxCopies (3). Also injects
ILogger into AdminController and adds Cards/Items/Decks to the viewer reload graph.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:22:44 -04:00
gamer147
ed5be80f08 Merge freeplay-mode: global freeplay toggle + centralized spend/entitlement primitives 2026-05-29 16:40:46 -04:00
gamer147
9b2696fac5 test(freeplay): assert DB-untouched invariant on freeplay pack open
Crystal-pack open under freeplay with 0 balance: verifies the request
succeeds (HTTP 200) and Currency.Crystals is unchanged in the DB afterward.
2026-05-29 14:42:10 -04:00
gamer147
302bf17c31 feat(cosmetics): route ownership checks + shop owned-flags through entitlements (freeplay)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 14:36:50 -04:00
gamer147
d68a85bbc5 refactor(battlepass): route premium-buy crystal spend through CurrencySpendService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 14:23:50 -04:00
gamer147
ee407befb5 refactor(spotcard): centralize spot-point spend via CurrencySpendService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 14:20:32 -04:00
gamer147
5c6b703276 refactor(itempurchase): route currency spend (not items) through CurrencySpendService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 14:18:57 -04:00
gamer147
fb257a544f refactor(leaderskin): route currency spend through CurrencySpendService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 14:17:01 -04:00