Commit Graph

37 Commits

Author SHA1 Message Date
gamer147
2d9a6eea4b engine cleanup passes 4-7 + multi-instancing ambient rip
Squashes 146 commits from battle-engine-extraction. Net: 2,045 files changed,
+11,896 / -158,687 lines. Ships engine passes 4-7 (dead-code cull, view-layer
stub, receive-path shrink) plus the Phase-5 AsyncLocal ambient deletion that
turns concurrent battles into a type-system property rather than a scope
contract.

## What landed

**Passes 4-7 (chunks 1-34):** Extended the Phase-4 const-false collapse into a
cascading cull across the skill graph, view layer, and receive-path periphery.
Six mode flags (IsWatchBattle/IsReplayBattle/IsAdmin/IsAdminWatch/IsPuzzleQuest/
IsAINetwork) became `const false`, every guarded block deleted. Field*.cs
subclass ctors + BackGroundBase + ObjectChecker culled to no-ops. Mulligan
family reworked to take a mgr param through IMulliganMgr.InitMulligan.
Emotion/Recovery/Resource clusters null-stubbed. Prediction/OperationSimulator/
skill filters converted from static ambient reads to per-mgr reads via
SkillPrm.ownerCard.SelfBattlePlayer.BattleMgr / ins.BattleMgr / this.BattleMgr.

**Phase-5 ambient rip (chunks 35-47):** Deleted BattleAmbient / BattleAmbient-
Context / TestBattleScope in full. Every per-battle mutable slot now lives on
the mgr instance itself:
  mgr.InstanceIsForecast / InstanceIsRandomDraw / InstanceRecoveryInfo /
  InstanceViewerId / InstanceNetworkAgent / GameMgr
BattleManagerBase.GetIns() returns null unconditionally; the residual static
flags + 3 façades (Certification.ViewerId, Data.BattleRecoveryInfo,
ToolboxGame.RealTimeNetworkAgent) are null-tolerant defaults kept for the
handful of engine-internal readers that still reference their types. Zero
BattleAmbient references anywhere in engine + node + tests.

Added pre-seeded GameMgr ctor overload threaded through the mgr chain
(BattleManagerBase → SingleBattleMgr / NetworkBattleManagerBase → NetworkStandard-
BattleMgr → HeadlessBattleMgr / HeadlessNetworkBattleMgr). Fixtures build a
GameMgr, seed it via HeadlessEngineEnv.SeedCharaIds/SeedNetUser, and pass it
to the mgr's ctor — no ambient reach.

Node side (SVSim.BattleNode/SessionBattleEngine): _ctx replaced with a plain
GameMgr field; 34 `using var _ambient = BattleAmbient.Enter(_ctx)` scope wraps
ripped from every accessor and mutator; EngineGlobalInit.WirePerSessionGameMgr
takes GameMgr as a param and runs from SessionBattleEngine.SetupInternal
BEFORE mgr construction.

Test side: TestBattleScope deleted; 18 fixture [SetUp]s migrated to
`HeadlessEngineEnv.EnsureProcessGlobals()`; MultiInstanceEngineTests rewritten
around per-mgr construction (GetIns() → null is the pinned invariant).

## Regression fixes

- **chunk-48** (MulliganCtrl): chunk-35's `= null` stubs on card lookups broke
  the live receive-driven Deal path (BattlePlayerBase.DrawCard NRE'd downstream
  of NetworkPlayerMulliganCtrl.StartMulliganVfx). Restored the three lookups
  via `_battlePlayer.BattleMgr.GetBattleCardIdx`. Engine tests were satisfied
  by the WireMulliganPhase seam; unit tests exposed the live-path gap.

## Ship state

- SVSim.BattleEngine.Tests: 56/56 pass, 2 skip
- SVSim.UnitTests: 1554/1554 pass (was 1523/31-fail before chunk 48)
- Solution build: 0 source warnings (40 pre-existing NU1902 MessagePack CVEs
  in SVSim.EmulatedEntrypoint, unrelated)
- Sequential PVP smoke: verified live (two back-to-back battles, no regression
  on cleanup/spinup)
- Concurrent PVP smoke: verified live

Adds tools/engine-port/ClosureAnalyzer/ — the Roslyn transitive-type-closure
analyzer needed to make future cascade cleanup safe (per feedback memory
"Engine cleanup needs closure tool" from the 2026-06-28 pass-3 failure).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-03 19:18:54 -04:00
gamer147
45947053c8 fix(guild): single-pass bucket filter in GuildRepository.SearchAsync
Replace double-correlated COUNT subqueries in the member-bucket switch
with a project-filter-project pattern that computes the count once per
row.  Add SearchAsync_bucket_2_matches_guilds_with_11_to_25_members to
cover the previously untested medium-bucket range.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 11:21:32 -04:00
gamer147
3fdc20d21f feat(guild): repositories for Guild aggregate 2026-06-27 11:17:51 -04:00
gamer147
f707fb2ffb refactor: drop unused DailyLoginBonuses table + importer
The captured shape ({"1":[], "3":[], "4":[]}) is the empty-period
wire echo; the client parser (LoadDetail.cs:553) only reads normal/total/
campaign. Table, entity, seed, and importer were never wired to a code path.

Replaced by config-section catalog + service in the follow-up commits.
2026-06-13 15:32:33 -04:00
gamer147
43a3665775 refactor(arena-colosseum): dedup colosseum_info source-of-truth
/mypage/index data.colosseum_info and /arena_colosseum/{top,get_fee_info}
now both project through ColosseumLobbyInfoBuilder.Build(ColosseumSeasonConfig).
One source of truth for the home-tab gating and the lobby reads — admins
flip the season on by writing to GameConfigs (per the runbook), no second
table to keep in sync.

* New ColosseumLobbyInfoBuilder — static projection from ColosseumSeasonConfig
  to the wire ColosseumLobbyInfo. Replaces ArenaColosseumController's private
  BuildColosseumInfo and MyPageController's BuildColosseumInfo(ColosseumConfig).
* MyPageController reads ColosseumSeasonConfig via IGameConfigService; the
  IGlobalsRepository.GetCurrentColosseum() call goes away.
* MyPageIndexResponse.ColosseumInfo retyped to the new ColosseumLobbyInfo.
* ColosseumSalesPeriodInfo relocated into the ArenaColosseum namespace
  alongside the rest of the family DTOs.
* Drops: Colosseums DB table (migration DropColosseumsTable), ColosseumConfig
  entity, the captured-shape Models.Dtos.ColosseumInfo DTO, ColosseumSeed,
  MyPageGlobalsImporter.ImportColosseumAsync + seed file colosseum.json,
  IGlobalsRepository.GetCurrentColosseum + impl, the two related tests.

Net: home-screen Grand Prix tab now hidden by default because the season
ships with IsColosseumPeriod=false. Full suite: 1345/1345 (was 1347 — the
2 dropped tests covered the now-dead surface).
2026-06-13 13:31:03 -04:00
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