Commit Graph

939 Commits

Author SHA1 Message Date
gamer147
7ff8a7bd92 feat(rank): wire RankProgressService into RankBattleController.Finish
Replaces the phase-3 zero stub. Real progression math now lands in the
response body (rank / after_battle_point / after_master_point /
battle_point / master_point). Format is authored by URL (not derived from
BattleContext) so /finish still works when hit without a prior
/do_matching — mirrors the client's URL-per-format Wizard/RankBattleFinishTask
dispatch (decompile lines 12-35).

3 existing tests updated with real progression assertions; 3 new tests
cover D-tier floor loss, format separation, and AI-variant persistence.
Full solution suite: 1583/1583 pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 10:21:12 -04:00
gamer147
f18cfb58b8 feat(rank): viewer repo LoadForRankProgressAsync
Split-query load of Classes (for class XP) + RankProgress (for rank grant)
so RankBattleController.Finish uses a single viewer read. Test factory's
ReferenceDataImporter.ImportAllAsync already seeds RankInfo — no test
helper needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 10:16:10 -04:00
gamer147
4e87957306 feat(rank): RankProgressService — pure +100/-50 tier-floored math
Reads RankInfoEntry.LowerLimitPoint from ranks.csv (via IGlobalsRepository)
for main-tier floors — no hardcoded thresholds. Master (Point=50000) and
Grand Master (MP-based, floor 5000) branch handles the post-Master path.
GetAsync is a read-only snapshot used later by AI-start's SelfInfo.

13 unit tests cover first-win, loss floors (0, Beginner→D, D→D0), win
crossing 50000, MP awards at Master, GM promotion at MP≥5000, GM floor
protecting against Master demotion, format separation, Crossover throw.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 10:15:34 -04:00
gamer147
9f97837211 feat(rank): add ViewerRankProgress owned entity + migration
One row per (viewer, format) tracking accumulated Point + MasterPoint.
Unique index on (ViewerId, Format) enforces one row per format per viewer
(mirrors owned-collection precedent per project_owned_collection_unique_index).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 10:13:50 -04:00
gamer147
94ed6734c7 tune(battle-xp): raise defaults to XpPerWin=200 / XpPerLoss=50
Doubles the shipped defaults across all modes without touching per-mode
overrides. Curve interaction against classexp.csv (L1=50, L2=150):
- Win (200 XP): crosses both L1 + L2 → land at L3, Exp=0
- Loss (50 XP): exactly meets L1 → land at L2, Exp=0

Updates every affected test assertion (Practice, Rank, Free, TK2,
Colosseum, Story) for the new level/exp math.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 09:34:01 -04:00
gamer147
8199a95356 feat(battle-xp): wire story first-clear /finish through IBattleXpService
- StoryConfig.ClassXpPerClear retired; equivalent knob moves to
  BattleXpConfig.StoryXpPerClear (falls back to XpPerWin when null).
- StoryService.FinishAsync now grants class XP via IBattleXpService with
  BattleXpMode.Story on the firstClear && isPlayShape branch. Persists
  post-grant Level/Exp to Viewer.Classes.
- Wire fields (get_class_experience, class_level) stringified as before.
- StoryConfig kept as an empty [ConfigSection] for future story-specific
  knobs. StoryServiceTests updated to reflect the new default path
  (XpPerWin=100 crosses classexp.csv L1=50 → L2 with 50 carry).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 09:06:47 -04:00
gamer147
a03bff3ea7 feat(battle-xp): wire colosseum /finish through IBattleXpService, extend response DTO
- ColosseumBattleFinishResponseDto gains get_class_experience,
  class_experience, class_level. Client-safe: same BattleFinishResponsProcessing
  handles all three keys as ints (decomp verified).
- ArenaColosseumBattleController.FinishInternal grants win/loss XP via
  IBattleXpService with BattleXpMode.Colosseum for both /colosseum_battle/finish
  and /colosseum_rank_battle/finish URLs.
- Existing win test extended: XP wire assertions + persistence check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 09:02:25 -04:00
gamer147
91ec14fc7e feat(battle-xp): wire free-battle /finish through IBattleXpService
- FreeBattleController.Finish (rotation + unlimited URLs) now grants
  win/loss XP via IBattleXpService with BattleXpMode.Free.
- Preserves the strict field subset (no rank/master fields).
- Tests updated: win asserts XP+level-up; new loss test on rotation URL.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 09:00:29 -04:00
gamer147
1e9693e56d feat(battle-xp): wire rank + AI-rank /finish through IBattleXpService
- RankBattleController.Finish (covers all 4 URLs: rotation, unlimited,
  ai_rotation, ai_unlimited) now grants win/loss XP via IBattleXpService
  with BattleXpMode.Rank. Uses req.ClassId from wire.
- Tests updated: AI-variant win asserts new XP values; consistency-loss
  asserts loss XP. Added persistence test on /unlimited_rank_battle/finish.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 08:59:01 -04:00
gamer147
c3da6079b2 feat(battle-xp): wire /practice/finish through IBattleXpService
- Practice finish now grants win/loss XP via IBattleXpService.GrantAsync
  with BattleXpMode.Practice. Response fields (get_class_experience,
  class_experience, class_level) reflect post-grant totals.
- Uses req.ClassId from wire; no BattleContext lookup needed.
- Test asserts default XpPerWin=100 crosses classexp.csv L1=50 threshold
  → Level=2 + Exp=50 carry, persists to Viewer.Classes.
- Added loss-XP test asserting default XpPerLoss=25 stays under threshold.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 08:56:59 -04:00
gamer147
f85f221bfa refactor(battle-xp): retire ArenaTwoPickConfig.ClassXpPerBattle for shared BattleXpService
- ArenaTwoPickService now delegates class-XP grants to IBattleXpService
  with BattleXpMode.ArenaTwoPick. Inline GrantClassXp/ResolveClassLevel
  helpers deleted.
- ClassXpPerBattle field removed from ArenaTwoPickConfig; TK2 XP now
  configurable via ArenaTwoPickXpPerWin/XpPerLoss overrides or the global
  BattleXpConfig defaults (100/25).
- Updated 5 test files (ArenaTwoPickService callers) for the new
  constructor arg. Added loss-XP assertion to Finish tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 08:54:52 -04:00
gamer147
81771bb0a0 feat(battle-xp): IBattleXpService with per-mode overrides and auto level-up
- IBattleXpService.GrantAsync mutates viewer.Classes in place; caller SaveChanges
- Resolves win/loss amount from per-mode override or global XpPerWin/XpPerLoss
- Story mode ignores isWin, uses StoryXpPerClear ?? XpPerWin
- Level-up loop consults curve[row.Level].NecessaryExp; carries overflow
- Saturates at max curve level (excess piles in Exp)
- Unknown classId → no-op returning (0, 0, 1), logs Warning
- 10 unit tests (Moq-backed curve stub); DI registered next to InventoryService
- GameConfigurationJsonbTests baseline updated (19 sections)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 08:51:43 -04:00
gamer147
4541378885 feat(battle-xp): config section + viewer loader for class XP grants
Adds BattleXpConfig ([ConfigSection("BattleXp")]) with global
XpPerWin/XpPerLoss plus per-mode nullable overrides. Adds BattleXpMode
enum and IViewerRepository.LoadForBattleXpGrantAsync — focused tracked
load with viewer.Classes + Class nav ref included, for the service
about to be introduced.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 08:44:11 -04:00
gamer147
57df329923 content: wire Serilog into SVSim.ContentServer
Calls UseSvSimSerilog("svsim-content") right after CreateBuilder and
wraps Main in try/catch/finally. No environment gate needed —
ContentServer has no test-suite path. The existing manual request-log
middleware (uses ILogger<Program>) now flows through Serilog
automatically; swapping it for UseSerilogRequestLogging() is a
follow-up per the spec.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 02:08:44 -04:00
gamer147
9a47ac8d90 app: wire Serilog into SVSim.EmulatedEntrypoint
Registers Serilog via SVSim.Hosting.UseSvSimSerilog("svsim-api") right
after CreateBuilder and wraps Main in try/catch/finally so a fatal
startup exception logs before CloseAndFlush.

Also updates the SVSim.Hosting extension to gate the file sink on entry
assembly rather than the ASP.NET environment name. WebApplicationFactory
sets the environment via a hook that resolves after both CreateBuilder
and the UseSerilog callback see their environment view, so
IsEnvironment("Testing") is unreliable in tests. The entry assembly
under `dotnet test` is always "testhost", which is a reliable signal.

appsettings.json grows a Serilog section mirroring the existing
Logging:LogLevel overrides; the Logging block itself is left in place
per the spec's follow-up cleanup note.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 02:07:41 -04:00
gamer147
a7843adfe6 hosting: add SVSim.Hosting with Serilog extension
Adds new SVSim.Hosting project carrying Serilog + Serilog.AspNetCore +
Serilog.Settings.Configuration + enricher package refs, and a single
IHostBuilder extension method UseSvSimSerilog(appName) that installs a
colored console sink and a plain-text file sink rolling daily with a
100 MB per-file cap and 7-file retention. ReadFrom.Configuration is
called last so appsettings can override minimum levels per category.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 01:55:58 -04:00
gamer147
87891f9896 cull(engine-cleanup): pass-9 cascade round 2 after UILabel stub 2026-07-03 23:46:27 -04:00
gamer147
773844165b cull(engine-cleanup): pass-9 cascade round 1 after UILabel stub 2026-07-03 23:45:28 -04:00
gamer147
ed6f6f1d68 cull(engine-cleanup): pass-9 — stub UILabel to live surface 2026-07-03 23:44:22 -04:00
gamer147
737f375ea7 cull(engine-cleanup): pass-9 cascade round 3 after NGUIText stub 2026-07-03 23:39:49 -04:00
gamer147
39ae2b0c0a cull(engine-cleanup): pass-9 cascade round 2 after NGUIText stub 2026-07-03 23:38:51 -04:00
gamer147
08c6bca58c cull(engine-cleanup): pass-9 cascade round 1 after NGUIText stub 2026-07-03 23:37:54 -04:00
gamer147
9183625ac2 cull(engine-cleanup): pass-9 — stub NGUIText to live surface 2026-07-03 23:36:53 -04:00
gamer147
699e78755b cull(engine-cleanup): pass-9 cascade round 6 after UIPanel stub 2026-07-03 23:34:56 -04:00
gamer147
c6319696f3 cull(engine-cleanup): pass-9 cascade round 5 after UIPanel stub 2026-07-03 23:33:54 -04:00
gamer147
01f9635d50 cull(engine-cleanup): pass-9 cascade round 4 after UIPanel stub 2026-07-03 23:32:57 -04:00
gamer147
0103050fe0 cull(engine-cleanup): pass-9 cascade round 3 after UIPanel stub 2026-07-03 23:31:57 -04:00
gamer147
18844b2233 cull(engine-cleanup): pass-9 cascade round 2 after UIPanel stub 2026-07-03 23:30:59 -04:00
gamer147
36bd90c2b3 cull(engine-cleanup): pass-9 cascade round 1 after UIPanel stub 2026-07-03 23:29:52 -04:00
gamer147
853b060f02 cull(engine-cleanup): pass-9 — stub UIPanel to live surface 2026-07-03 23:28:50 -04:00
gamer147
b54e79f1fb cull(engine-cleanup): pass-9 cascade round 3 after DialogBase stub 2026-07-03 23:25:54 -04:00
gamer147
6eb7d51d53 cull(engine-cleanup): pass-9 cascade round 2 after DialogBase stub 2026-07-03 23:24:59 -04:00
gamer147
4e3bb6f88f cull(engine-cleanup): pass-9 cascade round 1 after DialogBase stub 2026-07-03 23:23:59 -04:00
gamer147
6de5f03956 cull(engine-cleanup): pass-9 — stub DialogBase to live surface 2026-07-03 23:22:43 -04:00
gamer147
840ac27027 chore(engine-cleanup): pass-8 phase-2 — sync stub headers with post-cascade state 2026-07-03 22:38:17 -04:00
gamer147
0ee97cdd93 cull(engine-cleanup): pass-8 phase-2 cascade round 5 after CardDetailUI stub 2026-07-03 22:37:13 -04:00
gamer147
4950a57b11 cull(engine-cleanup): pass-8 phase-2 cascade round 4 after CardDetailUI stub 2026-07-03 22:36:16 -04:00
gamer147
0bf981ce4d cull(engine-cleanup): pass-8 phase-2 cascade round 3 after CardDetailUI stub 2026-07-03 22:35:21 -04:00
gamer147
99120ec858 cull(engine-cleanup): pass-8 phase-2 cascade round 2 after CardDetailUI stub 2026-07-03 22:34:23 -04:00
gamer147
3d641e9e35 cull(engine-cleanup): pass-8 phase-2 cascade round 1 after CardDetailUI stub 2026-07-03 22:33:26 -04:00
gamer147
8e6dbad56b cull(engine-cleanup): pass-8 phase-2 — stub CardDetailUI to live surface 2026-07-03 22:32:22 -04:00
gamer147
f26e8ceaec cull(engine-cleanup): pass-8 phase-2 cascade round 4 after GachaUI stub 2026-07-03 22:28:42 -04:00
gamer147
4e49a1236d cull(engine-cleanup): pass-8 phase-2 cascade round 3 after GachaUI stub 2026-07-03 22:27:45 -04:00
gamer147
78971adc46 cull(engine-cleanup): pass-8 phase-2 cascade round 2 after GachaUI stub 2026-07-03 22:26:45 -04:00
gamer147
e3b8b8dc0c cull(engine-cleanup): pass-8 phase-2 cascade round 1 after GachaUI stub 2026-07-03 22:25:44 -04:00
gamer147
a0533bf2e3 cull(engine-cleanup): pass-8 phase-2 — stub GachaUI to live surface 2026-07-03 22:24:38 -04:00
gamer147
59de7a8b86 cull(engine-cleanup): pass-8 phase-2 cascade round 5 after AreaSelectUI stub 2026-07-03 22:22:31 -04:00
gamer147
80abda90bf cull(engine-cleanup): pass-8 phase-2 cascade round 4 after AreaSelectUI stub 2026-07-03 22:21:26 -04:00
gamer147
9d4d5ff1ec cull(engine-cleanup): pass-8 phase-2 cascade round 3 after AreaSelectUI stub 2026-07-03 22:20:18 -04:00
gamer147
b148eb2968 cull(engine-cleanup): pass-8 phase-2 cascade round 2 after AreaSelectUI stub 2026-07-03 22:19:20 -04:00