diff --git a/SVSim.EmulatedEntrypoint/Controllers/UserMyPageController.cs b/SVSim.EmulatedEntrypoint/Controllers/UserMyPageController.cs
new file mode 100644
index 0000000..4e942c0
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Controllers/UserMyPageController.cs
@@ -0,0 +1,51 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using SVSim.Database;
+using SVSim.Database.Models;
+using SVSim.EmulatedEntrypoint.Models.Dtos.UserMyPage;
+
+namespace SVSim.EmulatedEntrypoint.Controllers;
+
+///
+/// /user_mypage/* — viewer-scoped MyPage configuration writes. Separate from the
+/// /mypage/* family because the wire URL family is distinct.
+///
+[Route("user_mypage")]
+public sealed class UserMyPageController : SVSimController
+{
+ private readonly SVSimDbContext _db;
+
+ public UserMyPageController(SVSimDbContext db) => _db = db;
+
+ [HttpPost("update")]
+ public async Task> Update(
+ [FromBody] UserMyPageUpdateRequest request,
+ CancellationToken ct)
+ {
+ if (!TryGetViewerId(out var viewerId)) return Unauthorized();
+
+ var viewer = await _db.Viewers
+ .Include(v => v.MyPageBgRotation)
+ .FirstOrDefaultAsync(v => v.Id == viewerId, ct);
+ if (viewer is null) return NotFound();
+
+ viewer.MyPageBgSelectType = request.SelectType;
+ viewer.MyPageBgId = ParseIdOrZero(request.MyPageId);
+
+ viewer.MyPageBgRotation.Clear();
+ for (int slot = 0; slot < request.MyPageIdList.Count; slot++)
+ {
+ viewer.MyPageBgRotation.Add(new MyPageBgRotationEntry
+ {
+ Slot = slot,
+ BgId = ParseIdOrZero(request.MyPageIdList[slot]),
+ });
+ }
+
+ await _db.SaveChangesAsync(ct);
+ return new UserMyPageUpdateResponse();
+ }
+
+ private static int ParseIdOrZero(string s) =>
+ int.TryParse(s, out var n) ? n : 0;
+}
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/UserMyPage/UserMyPageUpdateRequest.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/UserMyPage/UserMyPageUpdateRequest.cs
new file mode 100644
index 0000000..6fbeec7
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/UserMyPage/UserMyPageUpdateRequest.cs
@@ -0,0 +1,28 @@
+using System.Text.Json.Serialization;
+using MessagePack;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos.UserMyPage;
+
+///
+/// Body of POST /user_mypage/update. Client task: MyPageSettingUpdateTask
+/// (Shadowverse_Code_2026-05-23/Wizard/MyPageSettingUpdateTask.cs). Note that
+/// select_type is the only int on the wire — id fields are strings.
+///
+[MessagePackObject]
+public sealed class UserMyPageUpdateRequest
+{
+ /// BGType enum: 0=Deck, 1=CustomBG, 2=RandomBG. Client sends as an int.
+ [JsonPropertyName("select_type")]
+ [Key("select_type")]
+ public int SelectType { get; set; }
+
+ /// Chosen BG id when SelectType=CustomBG; empty or "0" otherwise.
+ [JsonPropertyName("mypage_id")]
+ [Key("mypage_id")]
+ public string MyPageId { get; set; } = "0";
+
+ /// Saved rotation pool, in slot order; client sends the full list on every call.
+ [JsonPropertyName("mypage_id_list")]
+ [Key("mypage_id_list")]
+ public List MyPageIdList { get; set; } = new();
+}
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/UserMyPage/UserMyPageUpdateResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/UserMyPage/UserMyPageUpdateResponse.cs
new file mode 100644
index 0000000..481bc63
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/UserMyPage/UserMyPageUpdateResponse.cs
@@ -0,0 +1,12 @@
+using MessagePack;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos.UserMyPage;
+
+///
+/// Empty response payload. The client's MyPageSettingUpdateTask.Parse() is the default
+/// pass-through; server just acknowledges.
+///
+[MessagePackObject(true)]
+public sealed class UserMyPageUpdateResponse
+{
+}
diff --git a/SVSim.UnitTests/Controllers/UserMyPageControllerTests.cs b/SVSim.UnitTests/Controllers/UserMyPageControllerTests.cs
new file mode 100644
index 0000000..fb20901
--- /dev/null
+++ b/SVSim.UnitTests/Controllers/UserMyPageControllerTests.cs
@@ -0,0 +1,149 @@
+using System.Net;
+using System.Net.Http.Json;
+using System.Text;
+using System.Text.Json;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using SVSim.Database;
+using SVSim.Database.Models;
+using SVSim.UnitTests.Infrastructure;
+
+namespace SVSim.UnitTests.Controllers;
+
+public class UserMyPageControllerTests
+{
+ private static StringContent JsonBody(string json) => new(json, Encoding.UTF8, "application/json");
+
+ private static async Task LoadViewerWithRotation(SVSimTestFactory factory, long viewerId)
+ {
+ using var scope = factory.Services.CreateScope();
+ var ctx = scope.ServiceProvider.GetRequiredService();
+ return await ctx.Viewers
+ .Include(v => v.MyPageBgRotation)
+ .AsNoTracking()
+ .FirstAsync(v => v.Id == viewerId);
+ }
+
+ [Test]
+ public async Task Update_persists_select_type_and_single_bg()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync();
+ using var client = factory.CreateAuthenticatedClient(viewerId);
+
+ var body = JsonBody("""
+ {"select_type":1,"mypage_id":"1213410310","mypage_id_list":[]}
+ """);
+ var response = await client.PostAsync("/user_mypage/update", body);
+ var raw = await response.Content.ReadAsStringAsync();
+ Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), raw);
+
+ var viewer = await LoadViewerWithRotation(factory, viewerId);
+ Assert.That(viewer.MyPageBgSelectType, Is.EqualTo(1));
+ Assert.That(viewer.MyPageBgId, Is.EqualTo(1213410310));
+ Assert.That(viewer.MyPageBgRotation, Is.Empty);
+ }
+
+ [Test]
+ public async Task Update_persists_rotation_pool_in_slot_order()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync();
+ using var client = factory.CreateAuthenticatedClient(viewerId);
+
+ var body = JsonBody("""
+ {"select_type":2,"mypage_id":"0","mypage_id_list":["1211410310","1212410310","1213410310"]}
+ """);
+ var response = await client.PostAsync("/user_mypage/update", body);
+ Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
+
+ var viewer = await LoadViewerWithRotation(factory, viewerId);
+ var pool = viewer.MyPageBgRotation.OrderBy(r => r.Slot).Select(r => r.BgId).ToList();
+ Assert.That(pool, Is.EqualTo(new[] { 1211410310, 1212410310, 1213410310 }));
+ }
+
+ [Test]
+ public async Task Update_overwrites_previous_rotation_atomically()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync();
+
+ // Seed a 5-entry rotation directly.
+ 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);
+ for (int slot = 0; slot < 5; slot++)
+ {
+ viewer.MyPageBgRotation.Add(new MyPageBgRotationEntry { Slot = slot, BgId = 9000 + slot });
+ }
+ await ctx.SaveChangesAsync();
+ }
+
+ using var client = factory.CreateAuthenticatedClient(viewerId);
+ var body = JsonBody("""
+ {"select_type":2,"mypage_id":"0","mypage_id_list":["1001","1002","1003"]}
+ """);
+ var response = await client.PostAsync("/user_mypage/update", body);
+ Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
+
+ var viewer2 = await LoadViewerWithRotation(factory, viewerId);
+ var pool = viewer2.MyPageBgRotation.OrderBy(r => r.Slot).Select(r => r.BgId).ToList();
+ Assert.That(pool, Is.EqualTo(new[] { 1001, 1002, 1003 }),
+ "old slots 3-4 should have been deleted, not orphaned");
+ }
+
+ [Test]
+ public async Task Update_with_empty_mypage_id_falls_back_to_zero()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync();
+ using var client = factory.CreateAuthenticatedClient(viewerId);
+
+ var body = JsonBody("""
+ {"select_type":0,"mypage_id":"","mypage_id_list":[]}
+ """);
+ var response = await client.PostAsync("/user_mypage/update", body);
+ Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
+
+ var viewer = await LoadViewerWithRotation(factory, viewerId);
+ Assert.That(viewer.MyPageBgSelectType, Is.EqualTo(0));
+ Assert.That(viewer.MyPageBgId, Is.EqualTo(0));
+ }
+
+ [Test]
+ public async Task Update_with_unparseable_mypage_id_falls_back_to_zero()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync();
+ using var client = factory.CreateAuthenticatedClient(viewerId);
+
+ var body = JsonBody("""
+ {"select_type":0,"mypage_id":"garbage","mypage_id_list":[]}
+ """);
+ var response = await client.PostAsync("/user_mypage/update", body);
+ Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
+
+ var viewer = await LoadViewerWithRotation(factory, viewerId);
+ Assert.That(viewer.MyPageBgId, Is.EqualTo(0));
+ }
+
+ [Test]
+ public async Task Update_returns_envelope_with_empty_data_payload()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync();
+ using var client = factory.CreateAuthenticatedClient(viewerId);
+
+ var body = JsonBody("""
+ {"select_type":0,"mypage_id":"0","mypage_id_list":[]}
+ """);
+ var response = await client.PostAsync("/user_mypage/update", body);
+ var raw = await response.Content.ReadAsStringAsync();
+ Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), raw);
+
+ // Test path bypasses the translation middleware (gated on UnityPlayer UA), so the
+ // controller's literal return value is what comes back. An empty class serializes to "{}".
+ Assert.That(raw, Is.EqualTo("{}"));
+ }
+}