feat(config): MatchingConfig section with AI-fallback threshold

Adds a [ConfigSection("Matching")] POCO carrying the
RankBattleAiFallbackThresholdSeconds tunable (default 15). Auto-picked
up by EnsureSeedDataAsync's reflection-based ConfigSection seeder.
Consumed by InProcessPairUp in a later task.

GameConfigurationJsonbTests.EnsureSeedData_writes_one_row_per_ConfigSection_with_ShippedDefaults_payload
updated to include the new Matching section in its expected keys.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-02 00:54:44 -04:00
parent 0095bdf0cf
commit b17c802581
3 changed files with 57 additions and 3 deletions

View File

@@ -0,0 +1,20 @@
namespace SVSim.Database.Models.Config;
/// <summary>
/// Tunables for the in-process pair-up matching service. Today: just the AI-fallback
/// threshold for rank-battle modes. The full matching-queue API is a separate spec;
/// this config section lives alongside the placeholder.
/// </summary>
[ConfigSection("Matching")]
public class MatchingConfig
{
/// <summary>
/// How long (seconds) a viewer must have been parked in a PvpFirstThenAiFallback
/// queue before their next /do_matching poll resolves to an AI battle.
/// Defaults to 15 — matches the prod 4s pre-AIBattleStart pause plus a comfortable
/// polling cycle.
/// </summary>
public int RankBattleAiFallbackThresholdSeconds { get; set; } = 15;
public static MatchingConfig ShippedDefaults() => new();
}

View File

@@ -0,0 +1,34 @@
using System.Linq;
using NUnit.Framework;
using SVSim.Database.Models.Config;
namespace SVSim.UnitTests.Database.Config;
[TestFixture]
public class MatchingConfigTests
{
[Test]
public void Default_threshold_is_15_seconds()
{
var cfg = new MatchingConfig();
Assert.That(cfg.RankBattleAiFallbackThresholdSeconds, Is.EqualTo(15));
}
[Test]
public void ShippedDefaults_returns_a_new_instance_with_default_threshold()
{
var cfg = MatchingConfig.ShippedDefaults();
Assert.That(cfg.RankBattleAiFallbackThresholdSeconds, Is.EqualTo(15));
}
[Test]
public void Has_ConfigSection_attribute_with_name_Matching()
{
var attr = typeof(MatchingConfig)
.GetCustomAttributes(typeof(ConfigSectionAttribute), false)
.Cast<ConfigSectionAttribute>()
.FirstOrDefault();
Assert.That(attr, Is.Not.Null);
Assert.That(attr!.Name, Is.EqualTo("Matching"));
}
}

View File

@@ -25,13 +25,13 @@ public class GameConfigurationJsonbTests
var rows = await db.GameConfigs.AsNoTracking().ToListAsync(); var rows = await db.GameConfigs.AsNoTracking().ToListAsync();
var byName = rows.ToDictionary(r => r.SectionName); var byName = rows.ToDictionary(r => r.SectionName);
// One row per [ConfigSection]-marked POCO (11 sections today: Player, DefaultGrants, // One row per [ConfigSection]-marked POCO (12 sections today: Player, DefaultGrants,
// DefaultLoadout, Challenge, Rotation, PackRates, MyRotationSchedule, Story, ResourceConfig, // DefaultLoadout, Challenge, Rotation, PackRates, MyRotationSchedule, Story, ResourceConfig,
// Freeplay, ArenaTwoPick). // Freeplay, ArenaTwoPick, Matching).
Assert.That(byName.Keys, Is.EquivalentTo(new[] Assert.That(byName.Keys, Is.EquivalentTo(new[]
{ {
"Player", "DefaultGrants", "DefaultLoadout", "Challenge", "Rotation", "PackRates", "Player", "DefaultGrants", "DefaultLoadout", "Challenge", "Rotation", "PackRates",
"MyRotationSchedule", "Story", "ResourceConfig", "Freeplay", "ArenaTwoPick", "MyRotationSchedule", "Story", "ResourceConfig", "Freeplay", "ArenaTwoPick", "Matching",
})); }));
var resources = JsonSerializer.Deserialize<ResourceConfig>(byName["ResourceConfig"].ValueJson)!; var resources = JsonSerializer.Deserialize<ResourceConfig>(byName["ResourceConfig"].ValueJson)!;