Three new EF entities for /campaign/regist_serial_code: SerialCodeEntry (code, message,
window, enabled flag), SerialCodeRewardEntry (FK child, per-slot reward), and
ViewerSerialCodeRedemption (composite-PK redemption record). Registered in SVSimDbContext
with unique index on Code and cascade FK constraints. 3/3 persistence tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds AddViewerMyPageBgSelection migration: two int scalars on Viewers
(MyPageBgId, MyPageBgSelectType default 0) and ViewerMyPageBgRotation
owned table with composite PK (ViewerId, Slot), FK cascade to Viewers.
Also adds ToTable(ViewerMyPageBgRotation) to OwnsMany config so EF
uses the correct table name instead of defaulting to the entity class.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds BGType persistence (0=Deck/1=CustomBG/2=RandomBG) to Viewer via two scalar
columns and an owned collection keyed (ViewerId, Slot). Two persistence tests
confirm round-trip and zero-defaults on fresh viewers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the ViewerAcquireHistoryEntry model (8 fields: Id, ViewerId,
RewardType, RewardDetailId, RewardCount, AcquireType, Message,
AcquireTime), registers DbSet<ViewerAcquireHistoryEntry> on
SVSimDbContext, configures model (PK, FK cascade to Viewer, MaxLength
64 on Message, composite index on ViewerId/AcquireTime/Id), and adds a
DbSet round-trip integration test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The five tutorial gifts every fresh viewer is given at signup used to live as a
static C# array in SVSim.Database/SeedData/TutorialPresents.cs — outside the
seed-JSON pipeline used by all 40+ other globals tables. Editing a gift required
a code change + rebuild instead of a JSON edit + bootstrap re-run.
Now authored in SVSim.Bootstrap/Data/seeds/tutorial-presents.json and loaded into
a new TutorialPresentEntries table via TutorialPresentsImporter (clear-and-rewrite,
mirroring HomeDialogs). ViewerRepository.RegisterAnonymousViewer reads the table
at signup and projects each row into a ViewerPresent with Source="tutorial".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DDL-only per migrations-are-ddl-only convention. Seeded by
SVSim.Bootstrap MyPageGlobalsImporter (T5) — no HasData.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
Register ArenaTwoPickRewards and ViewerArenaTwoPickRuns DbSets in SVSimDbContext and generate the AddArenaTwoPick migration with both CreateTable calls, all four jsonb columns on the run table, and the correct indexes (WinCount scalar + unique WinCount/RewardType/RewardId on rewards; unique ViewerId on runs).
Three new EF entities and a migration:
- PackDrawConfigEntry (per-pack: animation rate, has-bonus flag, special-kind label)
- PackDrawSlotRateEntry (pack/slot/tier -> rate, unique index)
- PackDrawCardWeightEntry (per-card-rate facts incl rate-less rows)
DrawSlot {General, Eighth, Bonus} and DrawTier {Bronze, Silver, Gold, Legendary, Special}.
Special collapses leader_card and limited_time_leader (verified mutually exclusive per pack).
IsEnabled column on PackConfigEntry — admin gate for synthesized stubs, distinct from
the wire-mirror IsHide.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
(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>
Add ValueGeneratedOnAdd to ViewerBattlePassProgress.Id and
ViewerBattlePassClaims.Id so Postgres generates IDENTITY values at
runtime. Regenerate AddBattlePass migration in-place to include the
IdentityByDefaultColumn annotations. Add IBattlePassRepository /
BattlePassRepository (season lookup + level-curve cache) and
IViewerBattlePassRepository / ViewerBattlePassRepository
(get-or-create progress, claim reads/writes).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds DbSets and OnModelCreating config for BattlePassSeasonEntry,
BattlePassRewardEntry, ViewerBattlePassProgressEntry, and
ViewerBattlePassClaimEntry; generates migration 20260527021011_AddBattlePass
with DDL-only CreateTable + CreateIndex calls and no InsertData.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>