diff --git a/SVSim.EmulatedEntrypoint/Matching/ModePolicy.cs b/SVSim.EmulatedEntrypoint/Matching/ModePolicy.cs
new file mode 100644
index 0000000..54a7585
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Matching/ModePolicy.cs
@@ -0,0 +1,36 @@
+namespace SVSim.EmulatedEntrypoint.Matching;
+
+///
+/// Per-mode pairing policy. TK2 is PvP-only; rotation/unlimited rank battles
+/// can fall back to an AI battle after a configurable threshold. Future modes
+/// add rows in DI registration.
+///
+public enum PolicyKind
+{
+ /// Pair PvP; if no partner arrives, keep waiting indefinitely (modulo stale eviction).
+ PvpOnly,
+ /// Pair PvP if a partner arrives within the threshold; otherwise fall back to a Bot battle.
+ PvpFirstThenAiFallback,
+}
+
+public sealed record ModePolicy(string Mode, PolicyKind Kind);
+
+///
+/// DI singleton. Holds the per-mode policy lookup. Unknown modes default to
+/// (safest — never accidentally fall through to AI
+/// for a mode whose policy hasn't been wired).
+///
+public sealed class ModePolicyRegistry
+{
+ private readonly Dictionary _byMode;
+
+ public ModePolicyRegistry(IEnumerable policies)
+ {
+ // Last-wins on duplicate keys — documented in tests.
+ _byMode = new Dictionary();
+ foreach (var p in policies) _byMode[p.Mode] = p;
+ }
+
+ public ModePolicy For(string mode) =>
+ _byMode.TryGetValue(mode, out var p) ? p : new ModePolicy(mode, PolicyKind.PvpOnly);
+}
diff --git a/SVSim.UnitTests/Matching/ModePolicyRegistryTests.cs b/SVSim.UnitTests/Matching/ModePolicyRegistryTests.cs
new file mode 100644
index 0000000..82572ea
--- /dev/null
+++ b/SVSim.UnitTests/Matching/ModePolicyRegistryTests.cs
@@ -0,0 +1,46 @@
+using NUnit.Framework;
+using SVSim.EmulatedEntrypoint.Matching;
+
+namespace SVSim.UnitTests.Matching;
+
+[TestFixture]
+public class ModePolicyRegistryTests
+{
+ [Test]
+ public void For_known_mode_returns_its_policy()
+ {
+ var reg = new ModePolicyRegistry(new[]
+ {
+ new ModePolicy("rotation_rank_battle", PolicyKind.PvpFirstThenAiFallback),
+ new ModePolicy("arena_two_pick_battle", PolicyKind.PvpOnly),
+ });
+
+ Assert.That(reg.For("rotation_rank_battle").Kind, Is.EqualTo(PolicyKind.PvpFirstThenAiFallback));
+ Assert.That(reg.For("arena_two_pick_battle").Kind, Is.EqualTo(PolicyKind.PvpOnly));
+ }
+
+ [Test]
+ public void For_unknown_mode_returns_PvpOnly_default()
+ {
+ var reg = new ModePolicyRegistry(Array.Empty());
+
+ var policy = reg.For("free_battle");
+
+ Assert.That(policy.Mode, Is.EqualTo("free_battle"));
+ Assert.That(policy.Kind, Is.EqualTo(PolicyKind.PvpOnly));
+ }
+
+ [Test]
+ public void Last_registration_for_same_mode_wins()
+ {
+ // Defensive: if someone double-registers a mode, the dict semantics
+ // give us the last one. Document the behavior in a test.
+ var reg = new ModePolicyRegistry(new[]
+ {
+ new ModePolicy("rotation_rank_battle", PolicyKind.PvpOnly),
+ new ModePolicy("rotation_rank_battle", PolicyKind.PvpFirstThenAiFallback),
+ });
+
+ Assert.That(reg.For("rotation_rank_battle").Kind, Is.EqualTo(PolicyKind.PvpFirstThenAiFallback));
+ }
+}