Commit Graph

48 Commits

Author SHA1 Message Date
gamer147
86d86f6ead feat(replay): add ReplayHistoryReader for newest-first list query
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 07:48:18 -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
d078f275f8 feat(friend): implement 5 read methods on FriendService + register DI + read test suite
GetFriendsAsync, GetReceiveAppliesAsync, GetSendAppliesAsync, GetPlayedTogetherAsync,
SearchAsync all implemented. LoadViewerProjectionAsync materialises the full Viewer
entity (with Include/ThenInclude for SelectedEmblem/Degree) then projects in-memory —
avoids the EF Core limitation where Include is silently ignored under Select.
FriendService + IPlayedTogetherWriter registered as Scoped in Program.cs.
12 read tests, all green; full suite 1171/1171 still passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 21:53:10 -04:00
gamer147
7e757ebcd2 feat(home-dialog): per-session suppression tracker
Singleton keyed by ShortUdid; lock on per-viewer set to avoid
cross-viewer contention. Process lifetime — restart re-fires.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 18:53:33 -04:00
gamer147
f21ab7a38c refactor(battle-node): remove ScriptedBotParticipant and dev-affordance wiring
Deletes the scripted opponent and every entry point that created a
BattleType.Scripted session (the ?scripted=1 query opt-in, the
SoloDefaultsToScripted toggle, the resolver short-circuit, the WS handler case,
the bridge validation arm). Real two-client PvP and the Bot matchmaking-timeout
fallback are untouched. ResolveAsync drops its scriptedOptIn parameter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:15:48 -04:00
gamer147
feaa149f04 feat(auth): select ISteamServer impl by Auth:BypassSteamTicket config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 09:07:51 -04:00
gamer147
672a89ed46 refactor(matching): IMatchingResolver shared by every do_matching family
SoloDefaultsToScripted was only consulted by ArenaTwoPickBattleController;
RankBattleController did its own inline pair-up + state-code mapping and
ignored the flag entirely. Result: turning on the flag globally only
short-circuited TK2 polls, while rank-battle polls still parked for the
PvpFirstThenAiFallback threshold (15s) before resolving — surfaced today
when the user set the flag and saw rank-battle still queue, then bot-
battle via the client-side AI (not the server-side Scripted lifecycle we
need to test WS traffic against).

New IMatchingResolver owns the cross-cutting decisions:
- honor scriptedOptIn (per-request) OR options.SoloDefaultsToScripted
  (process-wide) — bypass pair-up, register Scripted, return 3004
- otherwise call IMatchingPairUpService.TryPairAsync and translate the
  PairUpResult to the 3002/3004/3007/3011 vocabulary

Family controllers shed the duplicated logic:
- ArenaTwoPickBattleController: ~50 LOC → ~25; preserves ?scripted=1
  query opt-in (parsed permissively for "1"/"true") and the
  ArenaTwoPickException catch
- RankBattleController: ~30 LOC → ~12; preserves the 3001 mapping for
  InvalidOperationException (no deck for format) and card_master_id
  emission

DoMatchingContractTests is the durable enforcement: parametrized over
TK2 + rotation + unlimited rank, asserts SoloDefaultsToScripted=true
makes every family's first poll skip 3002 and return SUCCEEDED with a
battle_id + node_server_url. Adding a fourth family that forgets to
route through IMatchingResolver fails this test — that's the point.

MatchingResolverTests covers the six resolver paths in isolation with
mocks; per-test Harness locals (not fixture-level fields) because the
assembly is [Parallelizable(ParallelScope.All)] and shared mocks race.

957 tests passing (was 948; +9: 6 resolver + 3 contract parametrizations).
No regressions in the existing TK2 / rank-battle controller suites.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 15:18:48 -04:00
gamer147
24f9b2240e feat(matching): move BotRoster from hardcoded fixture to DB-backed seed
Phase 3 shipped the AI rank battle bot pool as a hardcoded 8-entry list
inlined in SVSim.EmulatedEntrypoint/Matching/BotRoster.cs — editing meant
recompiling. Per PLAN.md 2026-06-02 item (d), move it to a Bootstrap
importer so the roster lives in seeds/bot-roster.json and the DB.

Shape mirrors PracticeOpponent end-to-end:
- BotRosterEntry (SVSim.Database/Models) — PK = AiId via the Id passthrough
  pattern. DbSet<BotRosterEntry> BotRoster on SVSimDbContext.
- AddBotRoster migration (DDL only, per migrations-are-DDL-only rule).
- seeds/bot-roster.json — 8 rows preserving the current prod-verified
  cosmetic ids (sleeve 704141010 / emblem 400001100 / degree 120027 /
  field 5) and series-1 ai_ids from rm_ai_setting.csv (1111..1181).
- BotRosterSeed POCO + BotRosterImporter (idempotent upsert keyed by AiId,
  leaves seed-missing rows intact). Wired into SVSim.Bootstrap/Program.cs
  next to PracticeOpponentImporter.
- IGlobalsRepository.GetBotRoster() + impl.

IBotRoster.Pick → PickAsync because BotRoster now depends on the transient
IGlobalsRepository. RankBattleController awaits the new signature. The
deterministic hash-on-ctx invariant (same ctx → same bot, so /ai_<fmt>/start
retries pick the same opponent) is preserved.

DI: AddSingleton<IBotRoster> → AddTransient (matches IGlobalsRepository's
lifetime). Test fixture's SeedGlobalsAsync also runs the importer so
RankBattleControllerTests + the rewritten BotRosterTests both see seeded
rows.

Tests: 931 → 936 passing. Existing 3 BotRosterTests reshaped for the DB
backing + 1 new "throws on empty roster" guard; 4 new
BotRosterImporterTests mirror PracticeOpponentImporterTests
(round-trip / idempotent / seed-missing-row-intact / ai_id=0 skip).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 11:58:19 -04:00
gamer147
a55187e10e feat(matching): IBotRoster + hardcoded BotRoster fixture (8 bots, one per class)
AIBotProfile carries the cosmetic metadata the AI rank-battle start
endpoint composes into oppo_info. BotRoster.Pick is deterministic per
MatchContext so mid-flight retries get the same opponent. ai_id values
4001..4008 are placeholders per the existing ai-start.md TODO — we have
no live capture of the prod catalog.

Future improvement: migrate Roster to a bot-roster.json seed under
SVSim.Bootstrap/Data/seeds/ for editability without rebuilds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 01:15:41 -04:00
gamer147
b65cf81977 feat(matching): per-mode policy + AI-fallback branch in InProcessPairUp
InProcessPairUp now consults ModePolicyRegistry per call and reads the
fallback threshold from MatchingConfig via IServiceScopeFactory (singleton
service consuming a scoped IGameConfigService). New behavior for
PvpFirstThenAiFallback modes: when the calling viewer IS the slot's
waiter and Now - WaitingSince >= threshold, the waiter unparks and the
bridge resolves a Bot match. PvpOnly modes (TK2) keep parking forever
(modulo a 5-minute stale-waiter eviction backstop).

TimeProvider is injected so tests can drive time forward with
FakeTimeProvider — 7 new tests cover the four key transitions
(stay-parked / pair-pvp / fall-back / stale-evict) plus per-mode
isolation. Fixture uses [FixtureLifeCycle(InstancePerTestCase)] because
the assembly is Parallelizable(ParallelScope.All).

Program.cs registers ModePolicyRegistry with three rows: TK2 PvpOnly,
rotation/unlimited rank PvpFirstThenAiFallback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 01:09:42 -04:00
gamer147
0095bdf0cf feat(arena-tk2): SoloDefaultsToScripted config flag for dev convenience
Adds BattleNodeOptions.SoloDefaultsToScripted (default false). When true,
the TK2 do_matching controller treats every solo poll as if ?scripted=1
were passed and returns a Scripted 3004 match immediately — useful for
the live client (which can't append query params) to drive the scripted
bot without needing a second player.

Toggle via "BattleNode:SoloDefaultsToScripted" in appsettings*.json
(Program.cs now binds the BattleNode section over the AddBattleNode
defaults). Turn off to test real PvP with two clients.

Trade-off documented on the option: while on, two simultaneous pollers
each get their own Scripted match instead of pairing, so PvP is
effectively disabled until the flag is flipped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 23:48:14 -04:00
gamer147
28b1d7531a feat(emulated-entrypoint): InProcessPairUp service for TK2 PvP matching
Tiny per-mode FCFS slot. First poller parks; second pairs and triggers
bridge.RegisterBattle(p1, p2, Pvp). Match cached for first poller's
next poll (consume-on-read). No MMR, no cross-mode, no timeouts --
the proper queue API is a separate spec; this is the smallest thing
that lets TK2 PvP work end-to-end.
2026-06-01 22:06:49 -04:00
gamer147
a0fdb0f3c5 feat(match-context): add IMatchContextBuilder TK2 implementation
Assembles MatchContext from ArenaTwoPickRun + viewer cosmetics + config.
Per-mode interface — future modes (rank/free/open-room/...) add one method
each. DI scoped registration. Four tests cover happy path, no-run, incomplete
draft, default-loadout fallback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 12:40:26 -04:00
gamer147
5525dbee24 fix(battle-node): node_server_url matches prod wire format (no scheme, with path)
Prod do_matching captures (data_dumps/captures/traffic_prod_tk2_*) send
the node URL as host:port/socket.io/ with no scheme prefix —
e.g. "node06.shadowverse.jp:13560/socket.io/". BestHTTP's SocketManager
expects this exact shape; the leading ws:// we were sending plus the
missing /socket.io/ path was preventing the client from completing the
post-do_matching connect (eventually times out with "connection timed
out").

Update BattleNodeOptions default, Program.cs override, and both
controller and bridge tests to use "localhost:5148/socket.io/".

Discovered during v1 smoke walkthrough.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 01:06:40 -04:00
gamer147
7914bab84e test(unit-tests): silence captured stdout in Testing env
The unit-test suite was spending most of its wall clock writing logs.
NUnit captures stdout per test and embeds it in the trx; with HttpLogging
emitting full request/response per controller call, EF Core SQL at
Information level, and ReferenceDataImporter banners running ~500x
(once per factory construction), the trx grew to 3.2 GB and the NUnit
result-XML serializer OOMed in StringBuilder.ToString() — which the
runner reported as one mysteriously failed test, masking a real
date-dependent failure underneath.

Three sources silenced under environment "Testing":
- appsettings.Testing.json drops Default + Microsoft.AspNetCore +
  HttpLoggingMiddleware + EntityFrameworkCore to Warning.
- Program.cs skips app.UseHttpLogging() entirely (avoids the
  middleware overhead, not just the log emission).
- ReferenceDataImporter takes optional TextWriters; the test factory
  passes TextWriter.Null. Per-importer helpers become instance methods
  so they can use the injected writer.

Result on a fresh run with ParallelScope.Fixtures already in place:
- Test duration: 1m46s -> 59s
- Wall clock: 2m23s -> 1m00s
- trx size: 3.2 GB -> 1.7 MB

The previously-masked date-dependent failure (PackControllerFullCatalog
.Info_returns_full_35_pack_catalog_from_production_seed asserting 35
active packs as of 2026-05-23 against a live clock) is now visible and
can be addressed separately.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 00:13:42 -04:00
gamer147
88ed8254af feat(emulated-entrypoint): wire AddBattleNode + UseBattleNode into the web host 2026-05-31 22:49:31 -04:00
gamer147
2c62a7be80 refactor(inventory): delete old primitives after InventoryService cutover
Removed RewardGrantService, CurrencySpendService, ICurrencySpendService,
ViewerEntitlements, IViewerEntitlements, CardAcquisitionService,
ICardAcquisitionService, CardGrantResult and their tests
(RewardGrantServiceTests, CurrencySpendServiceTests,
CardAcquisitionServiceTests, ViewerEntitlementsTests). Removed four DI
registrations from Program.cs. No caller references any deleted type;
GrantedReward and EffectiveCosmetics were pre-moved to InventoryGrantTypes.cs
in the prior commit. Build clean, 712/712 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 17:07:30 -04:00
gamer147
02e86cf16c feat(inventory): BeginAsync loads viewer with canonical graph
Includes Cards/Sleeves/Emblems/LeaderSkins/Degrees/MyPageBackgrounds/Items
under AsSplitQuery, plus caller-supplied extras via InventoryLoadConfig.
Opens a DB transaction and returns an InventoryTransaction shell. All
mutation methods throw NotImplementedException until subsequent tasks
land them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 15:46:20 -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
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
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
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
d560f9ade4 chore(di): register entitlements + spend services; add test freeplay helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 13:55:46 -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
66c0b1c951 feat(pack): gacha-point catalog read (legendaries + leader cards) 2026-05-28 23:11:48 -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
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
9043e20646 feat(bp): IBattlePassService skeleton + level-curve method + DI 2026-05-26 22:49:30 -04:00
gamer147
b6966ece6e Prebuilt deck purchasing and fixes 2026-05-26 09:16:21 -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
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
bdff142d16 Practice/deck editing mostly there 2026-05-24 00:17:28 -04:00
gamer147
66184b3685 Things were working, suddenly regressed 2026-05-23 18:14:42 -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
b2024af852 Lots of data and model setup 2025-05-18 02:27:17 -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