fix(game-tests): 清理生成配置消费者测试告警

- 修复 GeneratedConfigConsumerIntegrationTests 的 raw string 缩进和方法边界,恢复编译通过\n- 重构生成配置消费者集成测试的断言辅助方法,清理该文件剩余 warning\n- 更新 analyzer warning reduction 的 tracking 与 trace,记录 RP-056 验证结果和当前分支体积
This commit is contained in:
gewuyou 2026-04-24 19:31:37 +08:00
parent 5aefd77ad0
commit 56ed66976b
3 changed files with 182 additions and 92 deletions

View File

@ -50,65 +50,12 @@ public class GeneratedConfigConsumerIntegrationTests
var loader = new YamlConfigLoader(_rootPath)
.RegisterAllGeneratedConfigTables();
await loader.LoadAsync(registry);
await loader.LoadAsync(registry).ConfigureAwait(false);
var monsterTable = registry.GetMonsterTable();
var dungeonMonsters = monsterTable.FindByFaction("dungeon");
var itemTable = registry.GetItemTable();
Assert.Multiple(() =>
{
Assert.That(
GeneratedConfigCatalog.Tables.Select(static metadata => metadata.TableName),
Is.SupersetOf(new[] { "item", "monster" }));
Assert.That(GeneratedConfigCatalog.TryGetByTableName("item", out var itemCatalogEntry), Is.True);
Assert.That(itemCatalogEntry.ConfigDomain, Is.EqualTo("item"));
Assert.That(itemCatalogEntry.ConfigRelativePath, Is.EqualTo("item"));
Assert.That(itemCatalogEntry.SchemaRelativePath, Is.EqualTo("schemas/item.schema.json"));
Assert.That(GeneratedConfigCatalog.TryGetByTableName("monster", out var catalogEntry), Is.True);
Assert.That(catalogEntry.ConfigDomain, Is.EqualTo("monster"));
Assert.That(catalogEntry.ConfigRelativePath, Is.EqualTo("monster"));
Assert.That(catalogEntry.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
Assert.That(ItemConfigBindings.ConfigDomain, Is.EqualTo("item"));
Assert.That(ItemConfigBindings.Metadata.TableName, Is.EqualTo("item"));
Assert.That(MonsterConfigBindings.ConfigDomain, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.TableName, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.ConfigRelativePath, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
Assert.That(MonsterConfigBindings.Metadata.ConfigDomain, Is.EqualTo(MonsterConfigBindings.ConfigDomain));
Assert.That(MonsterConfigBindings.Metadata.TableName, Is.EqualTo(MonsterConfigBindings.TableName));
Assert.That(MonsterConfigBindings.Metadata.ConfigRelativePath,
Is.EqualTo(MonsterConfigBindings.ConfigRelativePath));
Assert.That(MonsterConfigBindings.Metadata.SchemaRelativePath,
Is.EqualTo(MonsterConfigBindings.SchemaRelativePath));
Assert.That(MonsterConfigBindings.References.All, Is.Empty);
Assert.That(MonsterConfigBindings.References.TryGetByDisplayPath("dropItems", out _), Is.False);
Assert.That(monsterTable.Count, Is.EqualTo(2));
Assert.That(monsterTable.Get(1).Name, Is.EqualTo("Slime"));
Assert.That(monsterTable.Get(2).Hp, Is.EqualTo(30));
Assert.That(monsterTable.FindByName("Slime").Select(static config => config.Id), Is.EqualTo(new[] { 1 }));
Assert.That(dungeonMonsters.Select(static config => config.Name),
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
Assert.That(monsterTable.TryFindFirstByName("Goblin", out var goblin), Is.True);
Assert.That(goblin, Is.Not.Null);
Assert.That(goblin!.Id, Is.EqualTo(2));
Assert.That(monsterTable.TryFindFirstByFaction("dungeon", out var firstDungeonMonster), Is.True);
Assert.That(firstDungeonMonster, Is.Not.Null);
Assert.That(firstDungeonMonster!.Name, Is.AnyOf("Slime", "Goblin"));
Assert.That(monsterTable.TryFindFirstByFaction("forest", out var missingMonster), Is.False);
Assert.That(missingMonster, Is.Null);
Assert.That(registry.TryGetMonsterTable(out var generatedTable), Is.True);
Assert.That(generatedTable, Is.Not.Null);
Assert.That(generatedTable!.All().Select(static config => config.Name),
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
Assert.That(itemTable.Count, Is.EqualTo(2));
Assert.That(itemTable.Get("potion").Name, Is.EqualTo("Potion"));
Assert.That(itemTable.FindByCategory("consumable").Select(static config => config.Id),
Is.EquivalentTo(new[] { "potion", "ether" }));
Assert.That(registry.TryGetItemTable(out var generatedItemTable), Is.True);
Assert.That(generatedItemTable, Is.Not.Null);
Assert.That(generatedItemTable!.Get("ether").Name, Is.EqualTo("Ether"));
});
AssertGeneratedBindingsLoadResults(registry, monsterTable, itemTable);
}
/// <summary>
@ -181,7 +128,7 @@ public class GeneratedConfigConsumerIntegrationTests
{
IncludedConfigDomains = new[] { MonsterConfigBindings.ConfigDomain }
});
await domainLoader.LoadAsync(domainRegistry);
await domainLoader.LoadAsync(domainRegistry).ConfigureAwait(false);
var tableNameRegistry = new ConfigRegistry();
var tableNameLoader = new YamlConfigLoader(_rootPath)
@ -190,7 +137,7 @@ public class GeneratedConfigConsumerIntegrationTests
{
IncludedTableNames = new[] { ItemConfigBindings.TableName }
});
await tableNameLoader.LoadAsync(tableNameRegistry);
await tableNameLoader.LoadAsync(tableNameRegistry).ConfigureAwait(false);
var emptyAllowListRegistry = new ConfigRegistry();
var emptyAllowListLoader = new YamlConfigLoader(_rootPath)
@ -200,7 +147,7 @@ public class GeneratedConfigConsumerIntegrationTests
IncludedConfigDomains = Array.Empty<string>(),
IncludedTableNames = Array.Empty<string>()
});
await emptyAllowListLoader.LoadAsync(emptyAllowListRegistry);
await emptyAllowListLoader.LoadAsync(emptyAllowListRegistry).ConfigureAwait(false);
var monsterDomain = MonsterConfigBindings.ConfigDomain;
var predicateRegistry = new ConfigRegistry();
@ -211,28 +158,13 @@ public class GeneratedConfigConsumerIntegrationTests
TableFilter = metadata =>
string.Equals(metadata.ConfigDomain, monsterDomain, StringComparison.Ordinal)
});
await predicateLoader.LoadAsync(predicateRegistry);
await predicateLoader.LoadAsync(predicateRegistry).ConfigureAwait(false);
Assert.Multiple(() =>
{
Assert.That(emptyAllowListRegistry.TryGetMonsterTable(out var emptyAllowListMonsterTable), Is.True);
Assert.That(emptyAllowListMonsterTable, Is.Not.Null);
Assert.That(emptyAllowListRegistry.TryGetItemTable(out var emptyAllowListItemTable), Is.True);
Assert.That(emptyAllowListItemTable, Is.Not.Null);
Assert.That(domainRegistry.TryGetMonsterTable(out var domainMonsterTable), Is.True);
Assert.That(domainMonsterTable, Is.Not.Null);
Assert.That(domainRegistry.TryGetItemTable(out _), Is.False);
Assert.That(tableNameRegistry.TryGetMonsterTable(out _), Is.False);
Assert.That(tableNameRegistry.TryGetItemTable(out var tableNameItemTable), Is.True);
Assert.That(tableNameItemTable, Is.Not.Null);
Assert.That(tableNameItemTable!.Get("potion").Name, Is.EqualTo("Potion"));
Assert.That(predicateRegistry.TryGetMonsterTable(out var predicateMonsterTable), Is.True);
Assert.That(predicateMonsterTable, Is.Not.Null);
Assert.That(predicateRegistry.TryGetItemTable(out _), Is.False);
});
AssertGeneratedRegistrationFilteringResults(
domainRegistry,
tableNameRegistry,
emptyAllowListRegistry,
predicateRegistry);
}
/// <summary>
@ -270,7 +202,7 @@ public class GeneratedConfigConsumerIntegrationTests
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", yaml));
Assert.DoesNotThrowAsync(async () =>
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml));
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml).ConfigureAwait(false));
var invalidYaml = """
id: 3
@ -282,7 +214,7 @@ public class GeneratedConfigConsumerIntegrationTests
var exception = Assert.Throws<ConfigLoadException>(() =>
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", invalidYaml));
var asyncException = Assert.ThrowsAsync<ConfigLoadException>(async () =>
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", invalidYaml));
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", invalidYaml).ConfigureAwait(false));
Assert.Multiple(() =>
{
@ -367,6 +299,138 @@ public class GeneratedConfigConsumerIntegrationTests
""");
}
/// <summary>
/// 统一断言生成绑定加载后的目录元数据、查询入口与强类型表包装结果,
/// 以便缩短端到端测试主体并降低分析器对方法长度的告警。
/// </summary>
private static void AssertGeneratedBindingsLoadResults(
ConfigRegistry registry,
MonsterTable monsterTable,
ItemTable itemTable)
{
AssertGeneratedCatalogMetadata();
AssertGeneratedMonsterTableResults(registry, monsterTable);
AssertGeneratedItemTableResults(registry, itemTable);
}
/// <summary>
/// 断言消费者项目的生成目录元数据与静态绑定常量保持一致。
/// </summary>
private static void AssertGeneratedCatalogMetadata()
{
Assert.Multiple(() =>
{
Assert.That(
GeneratedConfigCatalog.Tables.Select(static metadata => metadata.TableName),
Is.SupersetOf(new[] { "item", "monster" }));
Assert.That(GeneratedConfigCatalog.TryGetByTableName("item", out var itemCatalogEntry), Is.True);
Assert.That(itemCatalogEntry.ConfigDomain, Is.EqualTo("item"));
Assert.That(itemCatalogEntry.ConfigRelativePath, Is.EqualTo("item"));
Assert.That(itemCatalogEntry.SchemaRelativePath, Is.EqualTo("schemas/item.schema.json"));
Assert.That(GeneratedConfigCatalog.TryGetByTableName("monster", out var catalogEntry), Is.True);
Assert.That(catalogEntry.ConfigDomain, Is.EqualTo("monster"));
Assert.That(catalogEntry.ConfigRelativePath, Is.EqualTo("monster"));
Assert.That(catalogEntry.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
Assert.That(ItemConfigBindings.ConfigDomain, Is.EqualTo("item"));
Assert.That(ItemConfigBindings.Metadata.TableName, Is.EqualTo("item"));
Assert.That(MonsterConfigBindings.ConfigDomain, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.TableName, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.ConfigRelativePath, Is.EqualTo("monster"));
Assert.That(MonsterConfigBindings.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json"));
Assert.That(MonsterConfigBindings.Metadata.ConfigDomain, Is.EqualTo(MonsterConfigBindings.ConfigDomain));
Assert.That(MonsterConfigBindings.Metadata.TableName, Is.EqualTo(MonsterConfigBindings.TableName));
Assert.That(MonsterConfigBindings.Metadata.ConfigRelativePath,
Is.EqualTo(MonsterConfigBindings.ConfigRelativePath));
Assert.That(MonsterConfigBindings.Metadata.SchemaRelativePath,
Is.EqualTo(MonsterConfigBindings.SchemaRelativePath));
Assert.That(MonsterConfigBindings.References.All, Is.Empty);
Assert.That(MonsterConfigBindings.References.TryGetByDisplayPath("dropItems", out _), Is.False);
});
}
/// <summary>
/// 断言 monster 绑定在注册表中的查询辅助、索引查询与强类型访问入口都可用。
/// </summary>
private static void AssertGeneratedMonsterTableResults(
ConfigRegistry registry,
MonsterTable monsterTable)
{
var dungeonMonsters = monsterTable.FindByFaction("dungeon");
Assert.Multiple(() =>
{
Assert.That(monsterTable.Count, Is.EqualTo(2));
Assert.That(monsterTable.Get(1).Name, Is.EqualTo("Slime"));
Assert.That(monsterTable.Get(2).Hp, Is.EqualTo(30));
Assert.That(monsterTable.FindByName("Slime").Select(static config => config.Id), Is.EqualTo(new[] { 1 }));
Assert.That(dungeonMonsters.Select(static config => config.Name),
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
Assert.That(monsterTable.TryFindFirstByName("Goblin", out var goblin), Is.True);
Assert.That(goblin, Is.Not.Null);
Assert.That(goblin!.Id, Is.EqualTo(2));
Assert.That(monsterTable.TryFindFirstByFaction("dungeon", out var firstDungeonMonster), Is.True);
Assert.That(firstDungeonMonster, Is.Not.Null);
Assert.That(firstDungeonMonster!.Name, Is.AnyOf("Slime", "Goblin"));
Assert.That(monsterTable.TryFindFirstByFaction("forest", out var missingMonster), Is.False);
Assert.That(missingMonster, Is.Null);
Assert.That(registry.TryGetMonsterTable(out var generatedTable), Is.True);
Assert.That(generatedTable, Is.Not.Null);
Assert.That(generatedTable!.All().Select(static config => config.Name),
Is.EquivalentTo(new[] { "Slime", "Goblin" }));
});
}
/// <summary>
/// 断言 item 绑定的强类型表包装与按分类查询在聚合注册路径下可正常工作。
/// </summary>
private static void AssertGeneratedItemTableResults(
ConfigRegistry registry,
ItemTable itemTable)
{
Assert.Multiple(() =>
{
Assert.That(itemTable.Count, Is.EqualTo(2));
Assert.That(itemTable.Get("potion").Name, Is.EqualTo("Potion"));
Assert.That(itemTable.FindByCategory("consumable").Select(static config => config.Id),
Is.EquivalentTo(new[] { "potion", "ether" }));
Assert.That(registry.TryGetItemTable(out var generatedItemTable), Is.True);
Assert.That(generatedItemTable, Is.Not.Null);
Assert.That(generatedItemTable!.Get("ether").Name, Is.EqualTo("Ether"));
});
}
/// <summary>
/// 汇总断言不同聚合注册筛选条件下的装载结果,
/// 让测试主体聚焦于注册参数本身而不是展开大量重复断言。
/// </summary>
private static void AssertGeneratedRegistrationFilteringResults(
ConfigRegistry domainRegistry,
ConfigRegistry tableNameRegistry,
ConfigRegistry emptyAllowListRegistry,
ConfigRegistry predicateRegistry)
{
Assert.Multiple(() =>
{
Assert.That(emptyAllowListRegistry.TryGetMonsterTable(out var emptyAllowListMonsterTable), Is.True);
Assert.That(emptyAllowListMonsterTable, Is.Not.Null);
Assert.That(emptyAllowListRegistry.TryGetItemTable(out var emptyAllowListItemTable), Is.True);
Assert.That(emptyAllowListItemTable, Is.Not.Null);
Assert.That(domainRegistry.TryGetMonsterTable(out var domainMonsterTable), Is.True);
Assert.That(domainMonsterTable, Is.Not.Null);
Assert.That(domainRegistry.TryGetItemTable(out _), Is.False);
Assert.That(tableNameRegistry.TryGetMonsterTable(out _), Is.False);
Assert.That(tableNameRegistry.TryGetItemTable(out var tableNameItemTable), Is.True);
Assert.That(tableNameItemTable, Is.Not.Null);
Assert.That(tableNameItemTable!.Get("potion").Name, Is.EqualTo("Potion"));
Assert.That(predicateRegistry.TryGetMonsterTable(out var predicateMonsterTable), Is.True);
Assert.That(predicateMonsterTable, Is.Not.Null);
Assert.That(predicateRegistry.TryGetItemTable(out _), Is.False);
});
}
/// <summary>
/// 在临时消费者目录中创建 item schema 与 YAML 测试数据,用于验证多表聚合注册和筛选行为。
/// </summary>

View File

@ -6,22 +6,22 @@
## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-055`
- 当前阶段:`Phase 55`
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-056`
- 当前阶段:`Phase 56`
- 当前焦点:
- `2026-04-24` 本轮先纠正了 batch stop-condition 的计算口径:应使用 `origin/main``HEAD` 的 merge-base 分支 diff而不是工作树 diff
- 在该正确口径下,`RP-054` 提交后的真实 branch 体积是 `23` 个文件、`603` 行;当前这批提交后的投影体积是 `26` 个文件、`691` 行,仍低于 `$gframework-batch-boot 75`
- 本轮已完成 `ArchitectureConfigIntegrationTests``GameConfigBootstrapTests``JsonSerializerTests` 的小热点清理,并顺手补齐 `YamlConfigLoaderAllOfTests` / `PersistenceTests` 的残余 warning
- 当前仍在 `GFramework.Game.Tests` 内推进,但剩余热点已经越来越集中到 `YamlConfigLoaderTests.cs``GeneratedConfigConsumerIntegrationTests.cs` 这类高上下文文件
- `2026-04-24` 本轮延续 `RP-055``GFramework.Game.Tests` 小热点批次,修复了 `GeneratedConfigConsumerIntegrationTests.cs` 中 raw string 缩进导致的编译错误
- 进一步将 `GeneratedConfigConsumerIntegrationTests.cs` 的长断言逻辑拆分为多个辅助方法,并补齐异步等待的 `.ConfigureAwait(false)`,使该文件不再出现在项目构建 warning 输出中
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 已从上一轮收尾的 `63 Warning(s)` 进一步收敛到 `59 Warning(s)`
- 按当前工作树投影重新计算后,分支体积为 `27` 个文件、`943` 行,仍低于 `$gframework-batch-boot 75`
## 当前活跃事实
- 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值
- 仓库根目录 `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,项目级 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 也未能稳定提供 clean 基线
- 当前整仓最近一次直接观测值仍是 `dotnet build GFramework.sln -c Release``116 warning(s)`
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 已从上一批入口的 `116 warning(s)` 继续收敛到本轮收尾的 `63 warning(s)`
- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"`,结果为 `Passed: 19`
- `GFramework.Game.Tests` 当前剩余 warning 主要集中在未触碰的 `YamlConfigLoaderTests.cs``GeneratedConfigConsumerIntegrationTests.cs`,以及少量未处理的 `GameConfigBootstrapTests` 之外热点
- `RP-055` 后续补批已验证 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`,结果为 `59 Warning(s)``0 Error(s)`
- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"`,结果为 `Passed: 4`
- `GeneratedConfigConsumerIntegrationTests.cs` 当前已不再出现在项目 build warning 输出中;`GFramework.Game.Tests` 剩余热点进一步集中到未触碰的 `YamlConfigLoaderTests.cs` 等高上下文文件
## 当前风险
@ -55,11 +55,14 @@
- `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
- 结果失败clean 阶段在 MSBuild 清理路径结束前返回 `0 Warning(s)``0 Error(s)`,未输出额外错误文本
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
- 结果:成功;`63 Warning(s)``0 Error(s)`
- `RP-055` 收尾结果:成功;`63 Warning(s)``0 Error(s)`
- `RP-056` 当前结果:成功;`59 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"`
- 结果:成功;`Passed: 19``Failed: 0`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"`
- 结果:成功;`Passed: 4``Failed: 0`
## 下一步建议
1. 提交当前 `GFramework.Game.Tests` 小热点批次与 `RP-055` tracking 更新,继续保持只纳入本 topic 相关文件
1. 提交 `GeneratedConfigConsumerIntegrationTests.cs``RP-056` tracking/trace 更新,继续保持只纳入本 topic 相关文件
2. 下一轮若继续 warning reduction应优先决定是否接受进入 `YamlConfigLoaderTests.cs` 的高上下文批次

View File

@ -2,6 +2,29 @@
# Analyzer Warning Reduction 追踪
## 2026-04-24 — RP-056
### 阶段:修复 `GeneratedConfigConsumerIntegrationTests` 编译错误并清零该文件 warning
- 触发背景:
- `RP-055` 继续推进时,`GeneratedConfigConsumerIntegrationTests.cs` 在 raw string `invalidYaml` 段落附近出现 `CS8999`,导致 `GFramework.Game.Tests` 暂时无法编译
- 该文件同时仍是项目内少数残留 warning 热点之一,因此适合作为同一批次中的单文件收尾
- 主线程实施:
- 修复 `GeneratedConfigConsumerIntegrationTests.cs` 中损坏的 `CreateMonsterFiles` raw string 与方法边界,恢复文件可编译状态
- 保留并整理上一轮已开始的 `.ConfigureAwait(false)` 与断言 helper 抽取
- 继续将 `AssertGeneratedBindingsLoadResults` 再拆分为 catalog / monster / item 三个辅助方法,清除该文件剩余 `MA0051`
- 更新 active tracking / trace沿用 `merge-base(origin/main, HEAD)` 作为 `$gframework-batch-boot 75` 的唯一 stop-condition 口径
- 验证里程碑:
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
- 结果:成功;`59 Warning(s)``0 Error(s)`
- 结论:`GeneratedConfigConsumerIntegrationTests.cs` 不再出现在 warning 输出中
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"`
- 结果:成功;`Passed: 4``Failed: 0`
- 当前结论:
- `GFramework.Game.Tests` 已从 `RP-055` 收尾时的 `63 warning(s)` 进一步收敛到 `59 warning(s)`
- 当前工作树投影下,分支体积为 `27` 个文件、`943` 行,仍低于 `$gframework-batch-boot 75`
- 后续若继续自动推进,最自然的下一批将进入 `YamlConfigLoaderTests.cs` 这类高上下文大文件
## 2026-04-24 — RP-055
### 阶段:修正 stop-condition 口径并继续 `GFramework.Game.Tests` 小热点