feat(matching): ModePolicy registry for per-mode pair-up policy
Adds PolicyKind enum (PvpOnly, PvpFirstThenAiFallback), ModePolicy record, and ModePolicyRegistry singleton with last-wins dict + PvpOnly default for unknown modes. Wired into InProcessPairUp in a later task. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
36
SVSim.EmulatedEntrypoint/Matching/ModePolicy.cs
Normal file
36
SVSim.EmulatedEntrypoint/Matching/ModePolicy.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace SVSim.EmulatedEntrypoint.Matching;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public enum PolicyKind
|
||||
{
|
||||
/// <summary>Pair PvP; if no partner arrives, keep waiting indefinitely (modulo stale eviction).</summary>
|
||||
PvpOnly,
|
||||
/// <summary>Pair PvP if a partner arrives within the threshold; otherwise fall back to a Bot battle.</summary>
|
||||
PvpFirstThenAiFallback,
|
||||
}
|
||||
|
||||
public sealed record ModePolicy(string Mode, PolicyKind Kind);
|
||||
|
||||
/// <summary>
|
||||
/// DI singleton. Holds the per-mode policy lookup. Unknown modes default to
|
||||
/// <see cref="PolicyKind.PvpOnly"/> (safest — never accidentally fall through to AI
|
||||
/// for a mode whose policy hasn't been wired).
|
||||
/// </summary>
|
||||
public sealed class ModePolicyRegistry
|
||||
{
|
||||
private readonly Dictionary<string, ModePolicy> _byMode;
|
||||
|
||||
public ModePolicyRegistry(IEnumerable<ModePolicy> policies)
|
||||
{
|
||||
// Last-wins on duplicate keys — documented in tests.
|
||||
_byMode = new Dictionary<string, ModePolicy>();
|
||||
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);
|
||||
}
|
||||
46
SVSim.UnitTests/Matching/ModePolicyRegistryTests.cs
Normal file
46
SVSim.UnitTests/Matching/ModePolicyRegistryTests.cs
Normal file
@@ -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<ModePolicy>());
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user