From b17c80258197c590d235e5111aa71cd513d5165f Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 2 Jun 2026 00:54:44 -0400 Subject: [PATCH] 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 --- .../Models/Config/MatchingConfig.cs | 20 +++++++++++ .../Database/Config/MatchingConfigTests.cs | 34 +++++++++++++++++++ .../Models/GameConfigurationJsonbTests.cs | 6 ++-- 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 SVSim.Database/Models/Config/MatchingConfig.cs create mode 100644 SVSim.UnitTests/Database/Config/MatchingConfigTests.cs diff --git a/SVSim.Database/Models/Config/MatchingConfig.cs b/SVSim.Database/Models/Config/MatchingConfig.cs new file mode 100644 index 0000000..11229ad --- /dev/null +++ b/SVSim.Database/Models/Config/MatchingConfig.cs @@ -0,0 +1,20 @@ +namespace SVSim.Database.Models.Config; + +/// +/// 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. +/// +[ConfigSection("Matching")] +public class MatchingConfig +{ + /// + /// 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. + /// + public int RankBattleAiFallbackThresholdSeconds { get; set; } = 15; + + public static MatchingConfig ShippedDefaults() => new(); +} diff --git a/SVSim.UnitTests/Database/Config/MatchingConfigTests.cs b/SVSim.UnitTests/Database/Config/MatchingConfigTests.cs new file mode 100644 index 0000000..2a1a1d3 --- /dev/null +++ b/SVSim.UnitTests/Database/Config/MatchingConfigTests.cs @@ -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() + .FirstOrDefault(); + Assert.That(attr, Is.Not.Null); + Assert.That(attr!.Name, Is.EqualTo("Matching")); + } +} diff --git a/SVSim.UnitTests/Models/GameConfigurationJsonbTests.cs b/SVSim.UnitTests/Models/GameConfigurationJsonbTests.cs index c926b2a..7dd3e3b 100644 --- a/SVSim.UnitTests/Models/GameConfigurationJsonbTests.cs +++ b/SVSim.UnitTests/Models/GameConfigurationJsonbTests.cs @@ -25,13 +25,13 @@ public class GameConfigurationJsonbTests var rows = await db.GameConfigs.AsNoTracking().ToListAsync(); 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, - // Freeplay, ArenaTwoPick). + // Freeplay, ArenaTwoPick, Matching). Assert.That(byName.Keys, Is.EquivalentTo(new[] { "Player", "DefaultGrants", "DefaultLoadout", "Challenge", "Rotation", "PackRates", - "MyRotationSchedule", "Story", "ResourceConfig", "Freeplay", "ArenaTwoPick", + "MyRotationSchedule", "Story", "ResourceConfig", "Freeplay", "ArenaTwoPick", "Matching", })); var resources = JsonSerializer.Deserialize(byName["ResourceConfig"].ValueJson)!;