Commit Graph

32 Commits

Author SHA1 Message Date
gamer147
7118b92522 refactor(pack): type PackChildGachaEntry.TypeDetail as CardPackType enum 2026-06-09 08:48:16 -04:00
gamer147
6d60edaa2a feat(home-dialog): IGlobalsRepository.GetActiveHomeDialogsAsync
Window is [begin, end) — exclusive upper bound. Ordered priority-DESC
then Id-ASC so the controller can break on the first match.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 18:52:32 -04:00
gamer147
05d8169012 refactor: type reward_type columns as UserGoodsType enum
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>
2026-06-03 07:50:49 -04:00
gamer147
89b3d23bde feat(viewer-repo): add LoadForMatchContextAsync for battle-node ctx build
Focused AsNoTracking load with Info.SelectedEmblem/SelectedDegree includes
for the new MatchContextBuilder. Single test locks the include graph.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 12:37:44 -04:00
gamer147
6381e4da51 fix(tk2): match original SV (5-battle cap, no loss limit)
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>
2026-05-31 12:47:43 -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
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
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
be19c0ad8d feat(repo): cosmetic catalog id enumerations on ICollectionRepository 2026-05-29 13:29:19 -04:00
gamer147
68d783192d feat(repo): GetStoryDecksByClass joins story-deck presentation to product card lists
Adds StoryDeckView projection, IBuildDeckRepository.GetStoryDecksByClass interface method,
and BuildDeckRepository implementation that loads StoryDeckEntry rows for a class, fetches
matching BuildDeckProductEntry card lists, and expands each card by Number into a flat
CardIdArray. TDD: 2 tests in StoryDeckRepositoryTests (expand + empty-class).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:36:14 -04:00
gamer147
22c01ed11a fix(viewer): fresh signups start with empty DisplayName
Verified against Wizard.Title/UserNameInput.cs:30 in the 2026-05-23
decompile:

    IsFinished = !string.IsNullOrEmpty(PlayerStaticData.UserName);

Any non-empty seeded value — including the prior " - " placeholder
this method was passing — sets IsFinished=true on the first frame and
silently skips both the input dialog and the /tutorial/update_action #1
+ /account/update_name calls that travel with it. The in-source comment
described the opposite behavior; empty string is what actually triggers
the dialog.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:19:03 -04:00
gamer147
b50a884af9 fix(viewer): RegisterViewer defaults to post-tutorial TutorialState=100
BuildDefaultViewer hardcoded TutorialState=1 — correct for fresh anonymous
signups (RegisterAnonymousViewer) but wrong for AdminController.ImportViewer
and Steam-social signups, which both go through RegisterViewer and expect a
prod-replica viewer that boots to the home screen. Add an initialTutorialState
parameter (default 1 preserves RegisterAnonymousViewer behavior); RegisterViewer
passes 100.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 20:07:05 -04:00
gamer147
1af0e03eeb fix(viewer): fresh signup defaults match prod tutorial capture
Three corrections to BuildDefaultViewer + RegisterAnonymousViewer
verified against data_dumps/traffic_prod_tutorial.ndjson:

- TutorialState now 1 (TUTORIAL_STEP0), not 0 (PRE_TUTORIAL_STEP).
  Wizard.Title.NextSceneSwitcher routes step==1 to the Prologue scene;
  any other non-{31,41,100} step routes to AreaSelect at section 0,
  which has no chapter data and crashes the client with a LINQ
  Single() "Sequence contains no matching element" from
  AreaSelectUI.SelectChapter. Prod's first /check/game_start returns
  now_tutorial_step="1"; step 0 is a pre-existence state we never
  want to expose on the wire.

- DisplayName " - " (literal space-dash-space), not "Player".
  Wizard.Title.UserNameInput.Start short-circuits with
  IsFinished=true on !string.IsNullOrEmpty(PlayerStaticData.UserName),
  silently skipping the name dialog AND the tutorial sub-step it
  drives (/tutorial/update_action #1 + /account/update_name). Prod
  uses " - " as the unset placeholder.

- viewer.Info.SelectedEmblem/SelectedDegree assigned from the default
  emblem/degree the loadout grants. Without this, /load/index emits
  selected_emblem_id=0 and selected_degree_id=0 for a fresh viewer
  that owns those cosmetics — prod sends the real granted IDs.

Also surfaces the actual Postgres constraint name in the unique-
violation re-raise (ExtractConstraintName), instead of always
saying "UDID" — the original message was misleading whenever the
real constraint was on owned-collection rows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 18:02:52 -04:00
gamer147
f233a8c8d6 fix(viewer): fresh signups start at tutorial_state=0, not 100
Previously RegisterAnonymousViewer auto-completed the tutorial, which
prevented the client from ever entering the tutorial flow. SeedViewerAsync
gains a tutorialState parameter (default 100) so existing tests keep
their pre-completed-tutorial assumption.
2026-05-28 11:27:37 -04:00
gamer147
ecf819ca61 repo(card): SetProtected with zero-count-row preservation
Implements ICardInventoryRepository.SetProtected — loads only the
owned-cards collection (no decks/currency), guards against the EF
owned-nav Card.Id==0 default-init quirk, and accepts Count=0 rows
(destruct→re-protect round-trip). Covered by 4 new NUnit tests
(flip, unset, zero-count-row, unknown-card error). Full suite: 533/533.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 01:48:29 -04:00
gamer147
1eb34c7830 test(card): CreateCards validation matrix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 01:22:03 -04:00
gamer147
e1becca659 repo(card): CreateCards happy path 2026-05-28 01:09:56 -04:00
gamer147
529fd13668 signup: close two concurrency holes from final review
(1) RegisterAnonymousViewer now catches the unique-violation
    race (SQLSTATE 23505 on Postgres / code 19 on SQLite) and
    re-reads by UDID, returning the existing row instead of
    surfacing 500 to the second concurrent /tool/signup caller.
    New repo test exercises the back-to-back register path.

(2) Add unique index on SocialAccountConnection (AccountType,
    AccountId). The auth handler's find-or-link path claimed
    this index existed as the dedup backstop; the claim was
    accurate as design intent but the schema was missing. Now
    matched. Comment in handler updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:46:19 -04:00
gamer147
f85589d208 repo(viewer): restore dropped rationale comment, add link-idempotency assertion
Review polish on the prior commit (30874c6):
- BuildDefaultViewer extract dropped the "filter out Id=0 placeholders
  and dedupe" comment from the leader-skin grant block — restored.
- LinkSteamToViewer test now calls link twice and asserts count stays
  at 1, exercising the alreadyLinked short-circuit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:06:35 -04:00
gamer147
30874c681f repo(viewer): add UDID lookup, anonymous register, Steam link helpers
Extracts the default-loadout body into a private BuildDefaultViewer
helper shared by the existing Steam-import path and a new
RegisterAnonymousViewer for /tool/signup. LinkSteamToViewer is the
seam SteamSessionAuthenticationHandler will call on first-Steam-touch
of a UDID-keyed viewer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:01:02 -04:00
gamer147
141f34f817 chore(bootstrap): refresh stale GlobalsImporter references in docs/test names 2026-05-26 16:44:54 -04:00
gamer147
9090086a47 Class leader fixes 2026-05-26 10:01:37 -04:00
gamer147
8e913578ff Consolidation 2026-05-25 16:34:24 -04:00
gamer147
558e8288eb Puzzles 2026-05-25 12:03:47 -04:00
gamer147
c14408ba06 Seeding reorg 2026-05-24 21:13:15 -04:00
gamer147
12fb2f4801 Card liquefication 2026-05-24 14:42:44 -04:00
gamer147
d9ef9fe1fc Pack logic cleanup 2026-05-24 09:27:10 -04:00
gamer147
79209bd70b Pack opening 2026-05-24 02:03:13 -04:00
gamer147
56d3cf0ec8 Seeding updated 2026-05-23 16:25:49 -04:00
gamer147
bf6ddf5428 Forgot unversioned xd 2026-05-23 14:18:18 -04:00