diff --git a/SVSim.Database/Repositories/Viewer/IViewerRepository.cs b/SVSim.Database/Repositories/Viewer/IViewerRepository.cs
index d032ebf..017e0b1 100644
--- a/SVSim.Database/Repositories/Viewer/IViewerRepository.cs
+++ b/SVSim.Database/Repositories/Viewer/IViewerRepository.cs
@@ -20,4 +20,11 @@ public interface IViewerRepository
/// viewer's UDID to the target, then deletes the anonymous viewer.
///
Task MergeAnonymousViewerInto(long anonymousViewerId, long targetViewerId);
+
+ ///
+ /// Focused load for building a battle-node MatchContext: viewer + Info + Info's
+ /// equipped Emblem/Degree nav refs. Read-only (AsNoTracking). Returns null if the viewer
+ /// doesn't exist.
+ ///
+ Task LoadForMatchContextAsync(long viewerId);
}
diff --git a/SVSim.Database/Repositories/Viewer/ViewerRepository.cs b/SVSim.Database/Repositories/Viewer/ViewerRepository.cs
index d60ce5e..8d80812 100644
--- a/SVSim.Database/Repositories/Viewer/ViewerRepository.cs
+++ b/SVSim.Database/Repositories/Viewer/ViewerRepository.cs
@@ -228,6 +228,15 @@ public class ViewerRepository : IViewerRepository
await _dbContext.SaveChangesAsync();
}
+ public async Task LoadForMatchContextAsync(long viewerId)
+ {
+ return await _dbContext.Set()
+ .AsNoTracking()
+ .Include(v => v.Info.SelectedEmblem)
+ .Include(v => v.Info.SelectedDegree)
+ .FirstOrDefaultAsync(v => v.Id == viewerId);
+ }
+
private async Task BuildDefaultViewer(string displayName, int initialTutorialState = 1)
{
Models.Viewer viewer = new Models.Viewer
diff --git a/SVSim.UnitTests/Repositories/ViewerRepositoryMatchContextTests.cs b/SVSim.UnitTests/Repositories/ViewerRepositoryMatchContextTests.cs
new file mode 100644
index 0000000..833d0ea
--- /dev/null
+++ b/SVSim.UnitTests/Repositories/ViewerRepositoryMatchContextTests.cs
@@ -0,0 +1,50 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using SVSim.Database;
+using SVSim.Database.Models;
+using SVSim.Database.Repositories.Viewer;
+using SVSim.UnitTests.Infrastructure;
+
+namespace SVSim.UnitTests.Repositories;
+
+[TestFixture]
+public class ViewerRepositoryMatchContextTests
+{
+ [Test]
+ public async Task LoadForMatchContext_includes_info_selected_emblem_and_degree()
+ {
+ await using var factory = new SVSimTestFactory();
+ var vid = await factory.SeedViewerAsync();
+
+ int emblemId, degreeId;
+ using (var seedScope = factory.Services.CreateScope())
+ {
+ var db = seedScope.ServiceProvider.GetRequiredService();
+ // Use existing rows from the reference-data seed — inserting EmblemEntry/DegreeEntry
+ // with a hardcoded Id collides with the seeded catalog (UNIQUE constraint).
+ var emblem = await db.Emblems.FirstAsync();
+ var degree = await db.Degrees.FirstAsync();
+ emblemId = emblem.Id;
+ degreeId = degree.Id;
+
+ var viewer = await db.Viewers.FindAsync(vid);
+ viewer!.Info.CountryCode = "KOR";
+ viewer.Info.IsOfficial = true;
+ viewer.Info.SelectedEmblem = emblem;
+ viewer.Info.SelectedDegree = degree;
+ viewer.DisplayName = "DraftedPlayer";
+ await db.SaveChangesAsync();
+ }
+
+ using var scope = factory.Services.CreateScope();
+ var repo = scope.ServiceProvider.GetRequiredService();
+ var loaded = await repo.LoadForMatchContextAsync(vid);
+
+ Assert.That(loaded, Is.Not.Null);
+ Assert.That(loaded!.DisplayName, Is.EqualTo("DraftedPlayer"));
+ Assert.That(loaded.Info.CountryCode, Is.EqualTo("KOR"));
+ Assert.That(loaded.Info.IsOfficial, Is.True);
+ Assert.That(loaded.Info.SelectedEmblem.Id, Is.EqualTo(emblemId));
+ Assert.That(loaded.Info.SelectedDegree.Id, Is.EqualTo(degreeId));
+ }
+}