diff --git a/SVSim.Database/Models/MyPageBgRotationEntry.cs b/SVSim.Database/Models/MyPageBgRotationEntry.cs
new file mode 100644
index 0000000..50cb7d5
--- /dev/null
+++ b/SVSim.Database/Models/MyPageBgRotationEntry.cs
@@ -0,0 +1,16 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace SVSim.Database.Models;
+
+///
+/// One row per (viewer, slot) in the viewer's saved MyPage BG rotation pool. The client posts
+/// the full pool on every /user_mypage/update regardless of mode, so the server overwrites
+/// it atomically each time. Slot is the 0-based position; order is preserved for the
+/// /mypage/index echo.
+///
+[Owned]
+public class MyPageBgRotationEntry
+{
+ public int Slot { get; set; }
+ public int BgId { get; set; }
+}
diff --git a/SVSim.Database/Models/Viewer.cs b/SVSim.Database/Models/Viewer.cs
index 4b50597..59861db 100644
--- a/SVSim.Database/Models/Viewer.cs
+++ b/SVSim.Database/Models/Viewer.cs
@@ -33,6 +33,12 @@ public class Viewer : BaseEntity
public DateTime LastLogin { get; set; }
+ /// BGType enum: 0=Deck, 1=CustomBG, 2=RandomBG. Default 0 = follow equipped deck's leader skin.
+ public int MyPageBgSelectType { get; set; }
+
+ /// The single chosen MyPageBG cosmetic id, used when SelectType=CustomBG. 0 = none.
+ public int MyPageBgId { get; set; }
+
#region Owned
public ViewerInfo Info { get; set; } = new ViewerInfo();
@@ -67,6 +73,8 @@ public class Viewer : BaseEntity
public List FreePackClaims { get; set; } = new List();
+ public List MyPageBgRotation { get; set; } = new List();
+
public List GachaPointBalances { get; set; } = new List();
public List GachaPointReceived { get; set; } = new List();
diff --git a/SVSim.Database/SVSimDbContext.cs b/SVSim.Database/SVSimDbContext.cs
index ee226b2..0df87cf 100644
--- a/SVSim.Database/SVSimDbContext.cs
+++ b/SVSim.Database/SVSimDbContext.cs
@@ -172,6 +172,12 @@ public class SVSimDbContext : DbContext
b.HasKey("ViewerId", nameof(ViewerFreePackClaim.FreeGachaCampaignId));
b.Property(x => x.FreeGachaCampaignId).ValueGeneratedNever();
});
+ modelBuilder.Entity().OwnsMany(v => v.MyPageBgRotation, b =>
+ {
+ b.WithOwner().HasForeignKey("ViewerId");
+ b.HasKey("ViewerId", nameof(MyPageBgRotationEntry.Slot));
+ b.Property(x => x.Slot).ValueGeneratedNever();
+ });
// OwnedCardEntry and OwnedItemEntry use composite PK (ViewerId, Id) where Id is auto-
// generated, which silently permits multiple rows per (Viewer, Card) or (Viewer, Item).
diff --git a/SVSim.UnitTests/Persistence/MyPageBgPersistenceTests.cs b/SVSim.UnitTests/Persistence/MyPageBgPersistenceTests.cs
new file mode 100644
index 0000000..3435fc8
--- /dev/null
+++ b/SVSim.UnitTests/Persistence/MyPageBgPersistenceTests.cs
@@ -0,0 +1,61 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using SVSim.Database;
+using SVSim.Database.Models;
+using SVSim.UnitTests.Infrastructure;
+
+namespace SVSim.UnitTests.Persistence;
+
+public class MyPageBgPersistenceTests
+{
+ [Test]
+ public async Task Viewer_round_trips_mypage_bg_scalars_and_rotation_pool()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync();
+
+ using (var seedScope = factory.Services.CreateScope())
+ {
+ var ctx = seedScope.ServiceProvider.GetRequiredService();
+ var viewer = await ctx.Viewers
+ .Include(v => v.MyPageBgRotation)
+ .FirstAsync(v => v.Id == viewerId);
+ viewer.MyPageBgSelectType = 2;
+ viewer.MyPageBgId = 1213410310;
+ viewer.MyPageBgRotation.Add(new MyPageBgRotationEntry { Slot = 0, BgId = 1211410310 });
+ viewer.MyPageBgRotation.Add(new MyPageBgRotationEntry { Slot = 1, BgId = 1212410310 });
+ viewer.MyPageBgRotation.Add(new MyPageBgRotationEntry { Slot = 2, BgId = 1213410310 });
+ await ctx.SaveChangesAsync();
+ }
+
+ using var verifyScope = factory.Services.CreateScope();
+ var ctx2 = verifyScope.ServiceProvider.GetRequiredService();
+ var roundtrip = await ctx2.Viewers
+ .Include(v => v.MyPageBgRotation)
+ .AsNoTracking()
+ .FirstAsync(v => v.Id == viewerId);
+
+ Assert.That(roundtrip.MyPageBgSelectType, Is.EqualTo(2));
+ Assert.That(roundtrip.MyPageBgId, Is.EqualTo(1213410310));
+ Assert.That(roundtrip.MyPageBgRotation.OrderBy(r => r.Slot).Select(r => r.BgId),
+ Is.EqualTo(new[] { 1211410310, 1212410310, 1213410310 }));
+ }
+
+ [Test]
+ public async Task Viewer_fresh_viewer_has_zero_defaults_and_empty_rotation()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync();
+
+ using var scope = factory.Services.CreateScope();
+ var ctx = scope.ServiceProvider.GetRequiredService();
+ var viewer = await ctx.Viewers
+ .Include(v => v.MyPageBgRotation)
+ .AsNoTracking()
+ .FirstAsync(v => v.Id == viewerId);
+
+ Assert.That(viewer.MyPageBgSelectType, Is.EqualTo(0));
+ Assert.That(viewer.MyPageBgId, Is.EqualTo(0));
+ Assert.That(viewer.MyPageBgRotation, Is.Empty);
+ }
+}