Commit Graph

49 Commits

Author SHA1 Message Date
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
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
2d675aa35d feat(practice): serve default/trial/leader-skin lists on practice/deck_list
practice/deck_list returns the same wire shape as /deck/info (the client parses
both via DeckGroupListData), but only ever sent user decks — so a fresh account
saw no default decks and couldn't start a practice match.

Extract the /deck/info hydration into a shared IDeckListBuilder used by
/deck/info, /deck/my_list, and /practice/deck_list. Practice passes
padEmptySlots:false (deck *select*, not builder) — matches the prod practice
capture, which returns real decks unpadded plus the 8 per-class default decks
and per-class leader-skin settings. Retire the near-duplicate
PracticeDeckListResponse DTO in favor of the shared DeckListResponse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:01:36 -04:00
gamer147
6f9976ebad style(story): blank line before StartAsync 2026-05-29 11:11:40 -04:00
gamer147
66dc0cc657 feat(story): populate build/trial/default deck lists on get_deck_list
Wire IBuildDeckRepository into StoryService; GetDeckListAsync now looks
up the chapter's CharaId, fetches class-specific prebuilt/trial decks via
GetStoryDecksByClass, and loads all DefaultDecks for default_deck_list.
Class guard (1-8) leaves build/trial empty for non-class chapters, matching
prod behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:47:20 -04:00
gamer147
7292c44082 fix(pack): include all pack legendaries in gacha-point catalog + correct class_id 2026-05-29 08:36:37 -04:00
gamer147
a8bbc39bfd fix(pack): emit one gacha-point entry per emblem cosmetic + clean stale docstring 2026-05-29 00:39:50 -04:00
gamer147
1eaf0d0bc4 refactor(pack): collapse null-forgive in gacha-point exchange balance guard 2026-05-28 23:42:32 -04:00
gamer147
e1f5b9b6c3 feat(pack): gacha-point exchange (debit + grant)
Implements GachaPointService.TryExchangeAsync: validates pack
exchangeability, balance >= threshold, card in catalog, not already
received; debits balance, marks received, grants the card through
RewardGrantService (cascade handles cosmetics). Re-adds the
RewardGrantService injection that was removed in the Task 3 fix-up
(matches the "inject when you call" convention).

Card grant produces the wire-shape reward_list directly via the
cosmetic cascade — the catalog's reward_list remains the display-only
shape for /pack/get_gacha_point_rewards.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 23:37:00 -04:00
gamer147
723a97e3af feat(pack): gacha-point accrual with per-child override 2026-05-28 23:23:33 -04:00
gamer147
e41ceff0be refactor(pack): clarify gacha-point leader detection and drop unused grants injection 2026-05-28 23:20:33 -04:00
gamer147
66c0b1c951 feat(pack): gacha-point catalog read (legendaries + leader cards) 2026-05-28 23:11:48 -04:00
gamer147
261ce67cee fix(story): tutorial section is_finished derives from viewer tutorial_step
Section 0 (prologue) has no chapter rows server-side — the prologue is
hardcoded client-side in Wizard/Prologue.cs — so the chapter-completion
rollup always emitted is_finished=false. The client uses that flag to
derive IsTutorialReplay; with it false, AreaSelectUI.OnTouchChapterListTutorial
blocks every chapter switch and the default focus (last visible chapter)
becomes the only confirmable one, matching the reported "all 3 greyed out,
only the 3rd playable" symptom on replay.

Override sectionFinished for id=0 with viewer.MissionData.TutorialState >= 100,
matching prod traffic_prod_626_story.ndjson btn_story_tutorial.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 22:01:50 -04:00
gamer147
177b4925a1 fix(session): bound the SID→UDID dict with FIFO eviction
The map used to grow unbounded over the process's lifetime — every fresh
signup added an entry that was never reclaimed. Long-running dev hosts
(or any future emulator deployment that doesn't restart often) would
gradually leak memory. Cap at 10k entries by default with a simple FIFO
eviction queue; re-stores of the same SID don't grow the queue.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:13:47 -04:00
gamer147
27ebb5114c fix(pack): tutorial flow display + open end-to-end
Four targeted fixes that together let /tutorial/pack_info display
the legendary starter at index 0, let /tutorial/pack_open succeed
on it, and let the pack drop out of the shop after.

1. /pack/info now loads viewer.Items into a Dictionary<long,int>
   and threads it through ToDto so child_gacha_info.item_number
   reflects the viewer's actual owned count of item_id. Previously
   defaulted to 0 for every pack, so the legendary pack 99047
   reported item_number=0 immediately after the gift granted 1×
   ticket id=90001. Verified against the prod tutorial capture.

2. PackRepository.GetActivePacks now orders parent_gacha_id DESC
   to match prod's /pack/info wire order (99047, 92001, 80047,
   16015...10001). The tutorial pack UI runs with controls locked
   and auto-selects index 0 via GachaUI.GetCurrentLegendPackId
   (FirstOrDefault on IsLegendPackId), so the legendary starter
   needs to be the first legend pack in the list.

3. DbCardPoolProvider.GetPool falls back to all in-rotation cards
   when a LegendCardPack's base set has no rows. Pack 99047's
   base_pack_id is 90001, a synthetic "Throwback Rotation" category
   that doesn't correspond to a real card_set in the prod card
   master — its real pool is curated across older rotation sets
   (Altersphere through Colosseum). We don't have that membership
   map captured yet; the rotation fallback is broader than prod
   but produces a valid 8-card draw, which is what the tutorial
   needs to advance to step 100. TODO in code points at the
   real fix.

4. PackController.Open's tutorial path now consumes the granted
   ticket (decrement viewer.Items by packNumber for child.ItemId)
   and emits the post-state count in reward_list as
   {reward_type:4, reward_id:item_id, reward_num:post_count}.
   Without this, the pack stayed at item_number=1 forever, the
   shop kept showing it post-tutorial, and the next click hit
   /pack/open (not /tutorial/pack_open) which 501s on type_detail=5.

Also: docstring on PackConfigDto.SalesPeriodInfo flags the deferred
wire-fidelity fix (prod emits {"sales_period_time": "<complete_date>"}
for limited windows, [] for evergreens; we always emit {}) and the
retype from Dictionary<string,string?> to a typed
PackSalesPeriodInfoDto. Doesn't affect tutorial flow, deferred for
the pack-system rework.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 18:04:13 -04:00
gamer147
6e6c8ee779 fix(signup): prestore client SID→UDID mapping so game_start can decrypt
After /tool/signup the client switches to SID-only headers (no UDID), so
the next request's body can't be decrypted unless the server already
knows the SID's UDID. ShadowverseSessionService now mirrors the client's
Cute/Cryptographer.MakeMd5(viewerId + udid) formula (salt
"r!I@ws8e5i="), and ToolController.Signup prestores the mapping at the
end. Verified against a live signup capture: viewerId=1 +
udid=62747917-93bc-454c-abb4-ef423b3c9317 produces the captured SID
dc4aac79d35fe15dfb6262e0071bb03c.

Note: this only fixes the fresh-signup path. Clients restarting with a
cached viewer_id (which skip /tool/signup entirely) still hit the same
issue — separate follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 13:34:05 -04:00
gamer147
5aac24d2b9 feat(deck-builder): /deck_code mint + /deck resolve with 3-min in-memory TTL
Adds the portal pair (shadowverse-portal.com deck-builder endpoints) as
anonymous routes on the app server. The translation middleware learns a new
[NoWireEncryption] attribute that skips both AES calls but keeps the rest of
the msgpack + base64 + envelope pipeline intact, matching prod's portal wire
profile observed in data_dumps/traffic_prod_deckcode.ndjson.

Storage is a 3-minute IMemoryCache — codes are anonymous-global, 4-char
lowercase alphanumeric (matches the shortest prod sample). Foil bit is
stripped on mint to match prod's normalize-on-encode behaviour.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:11:21 -04:00
gamer147
39b38e3c80 Battlepass fix 2026-05-28 00:54:46 -04:00
gamer147
5693ec0302 feat(missions): /load/index materializes viewer mission/achievement state
EnsureCurrentAsync now takes viewerId (was Viewer), so it works with
LoadController's AsNoTracking-loaded detached viewers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:45:31 -04:00
gamer147
b65a437102 feat(missions): /mission/info, /mission/retire, /mission/change_receive_setting
Three endpoints + 9 integration tests. Captured-data-is-catalog: viewer's
achievement Level starts at MIN(Level) per type from the catalog (not 1),
so the assembler always has a row to render against.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:35:40 -04:00
gamer147
574e9ca58b feat(missions): MissionAssembler — single DTO builder reused by all 4 endpoints
Tests intentionally deferred to controller integration tests (Tasks
18-21) which exercise the assembler end-to-end via the wire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:31:38 -04:00
gamer147
c9534d8fac feat(missions): ViewerMissionStateService — lazy materialize achievements + assign slots
Reads existing state from DB on each call (don't trust navigation
property — caller may pass it stale or double-tracked). Adds via DbSet
only, not via navigation property, to avoid EF double-tracking.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:29:30 -04:00
gamer147
aad604a589 feat(missions): MissionProgressService — counter upsert + achievement claimable on threshold
Also wires IMissionCatalogRepository + IViewerMissionRepository +
IMissionProgressService into DI. Task 17's separate DI step is now
subsumed by these registrations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:25:13 -04:00
gamer147
b38be1d953 feat(missions): JstPeriod helper — 02:00 JST anchored day/week/month keys
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:21:51 -04:00
gamer147
c7dfd43daa review(bp): doc fixes + sales_period_info non-nullable + drop checked cast + TODO
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 00:00:24 -04:00
gamer147
9147ab0ec7 feat(bp): AddPointsAsync plumbing + level-cross auto-grant + weekly cap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:47:30 -04:00
gamer147
4438e81e37 review(bp): post-state reward_list from RewardGrantService, not deltas
Crystal synthesis in BuyPremiumAsync is now unconditional: always remove
any crystal entry ApplyAsync may have added, then append the fresh
post-deduction total. Prevents stale on-screen balances when a retroactive
grant also touches crystal (or when no grants fire and the conditional
guard would have been the only crystal entry).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:42:06 -04:00
gamer147
2cb8c271a8 feat(bp): /battle_pass/buy — crystal-cost + retroactive premium grants
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:36:18 -04:00
gamer147
0ceab721e9 feat(bp): /battle_pass/item_list — derives product per active season
Adds BattlePassSalesPeriodInfoDto, BattlePassProductDto, BattlePassItemListResponse DTOs,
GetItemListAsync on BattlePassService (one product if not premium + CanPurchase, empty if
already premium or off-season), and the /battle_pass/item_list controller action.
2 new integration tests; all 408 pass.
2026-05-26 23:26:46 -04:00
gamer147
d877febcb8 review(bp): move SaveChanges into repo with race protection; JST constant
GetOrCreateProgressAsync now persists the new row itself and catches
DbUpdateException on unique-constraint violations — concurrent /info
calls no longer throw 500s. BattlePassService no longer calls
SaveChangesAsync after the get-or-create. FormatWireDate uses a named
JstOffset constant instead of an inline TimeSpan.FromHours(9).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:22:48 -04:00
gamer147
8a35f8c40b feat(bp): /battle_pass/info — service + controller + 3 tests
Also fixes BattlePassRepository.GetActiveSeasonAsync to use client-side
DateTimeOffset filtering (SQLite provider cannot translate DateTimeOffset
comparisons in LINQ WHERE/ORDER BY clauses).
2026-05-26 23:14:26 -04:00
gamer147
9043e20646 feat(bp): IBattlePassService skeleton + level-curve method + DI 2026-05-26 22:49:30 -04:00
gamer147
fa0901b776 More story fixes 2026-05-25 19:07:49 -04:00
gamer147
ce8d80559b Story leader fixes 2026-05-25 17:10:08 -04:00
gamer147
8e913578ff Consolidation 2026-05-25 16:34:24 -04:00
gamer147
9b051c444c Story fixes 2026-05-25 15:21:35 -04:00
gamer147
a5e39d71c1 Fix x/y complete story message 2026-05-25 14:42:24 -04:00
gamer147
5e7a65fe5a Story 2026-05-25 14:36:12 -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
34bcc579a5 Additional card content 2026-05-24 17:07:05 -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
bdff142d16 Practice/deck editing mostly there 2026-05-24 00:17:28 -04:00
gamer147
631e42289a Need to fix index load issues 2026-05-23 14:50:16 -04:00
gamer147
6b70850b7b More features 2026-05-23 14:18:01 -04:00
gamer147
ac3b002d74 Stuff works 2024-09-08 10:27:12 -04:00
gamer147
7e4bce9ac5 Testing more garbage encryption 2024-09-07 22:14:24 -04:00
gamer147
ee7e276036 Updates 2024-09-05 08:32:54 -04:00