From 6fc2aa4a91f66b0d53813ec8f9952ad513990eca Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Sun, 1 Jun 2025 11:51:37 -0400 Subject: [PATCH] Add basic test of station initial power supply (#29698) * Add basic test of station initial power supply * Add info about stored power vs needed amount * Update map list * Get it compiling again * Get it working again * Only run if explicitly requested * Fix merge * We call 'em batteries * Mark the test as dirty --- .../Tests/Power/StationPowerTests.cs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Content.IntegrationTests/Tests/Power/StationPowerTests.cs diff --git a/Content.IntegrationTests/Tests/Power/StationPowerTests.cs b/Content.IntegrationTests/Tests/Power/StationPowerTests.cs new file mode 100644 index 0000000000..c9f9498750 --- /dev/null +++ b/Content.IntegrationTests/Tests/Power/StationPowerTests.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using System.Linq; +using Content.Server.GameTicking; +using Content.Server.Maps; +using Content.Server.Power.Components; +using Content.Server.Power.NodeGroups; +using Content.Server.Power.Pow3r; +using Content.Shared.NodeContainer; +using Robust.Shared.EntitySerialization; + +namespace Content.IntegrationTests.Tests.Power; + +[Explicit] +public sealed class StationPowerTests +{ + /// + /// How long the station should be able to survive on stored power if nothing is changed from round start. + /// + private const float MinimumPowerDurationSeconds = 10 * 60; + + private static readonly string[] GameMaps = + [ + "Fland", + "Meta", + "Packed", + "Omega", + "Bagel", + "Box", + "Core", + "Marathon", + "Saltern", + "Reach", + "Train", + "Oasis", + "Gate", + "Amber", + "Loop", + "Plasma", + "Elkridge", + "Convex", + "Relic", + ]; + + [Test, TestCaseSource(nameof(GameMaps))] + public async Task TestStationStartingPowerWindow(string mapProtoId) + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + Dirty = true, + }); + var server = pair.Server; + + var entMan = server.EntMan; + var protoMan = server.ProtoMan; + var ticker = entMan.System(); + + // Load the map + await server.WaitAssertion(() => + { + Assert.That(protoMan.TryIndex(mapProtoId, out var mapProto)); + var opts = DeserializationOptions.Default with { InitializeMaps = true }; + ticker.LoadGameMap(mapProto, out var mapId, opts); + }); + + // Let powernet set up + await server.WaitRunTicks(1); + + // Find the power network with the greatest stored charge in its batteries. + // This keeps backup SMESes out of the calculation. + var networks = new Dictionary(); + var batteryQuery = entMan.EntityQueryEnumerator(); + while (batteryQuery.MoveNext(out var uid, out _, out var battery, out var nodeContainer)) + { + if (!nodeContainer.Nodes.TryGetValue("output", out var node)) + continue; + if (node.NodeGroup is not IBasePowerNet group) + continue; + networks.TryGetValue(group.NetworkNode, out var charge); + networks[group.NetworkNode] = charge + battery.CurrentCharge; + } + var totalStartingCharge = networks.MaxBy(n => n.Value).Value; + + // Find how much charge all the APC-connected devices would like to use per second. + var totalAPCLoad = 0f; + var receiverQuery = entMan.EntityQueryEnumerator(); + while (receiverQuery.MoveNext(out _, out var receiver)) + { + totalAPCLoad += receiver.Load; + } + + var estimatedDuration = totalStartingCharge / totalAPCLoad; + var requiredStoredPower = totalAPCLoad * MinimumPowerDurationSeconds; + Assert.Multiple(() => + { + Assert.That(estimatedDuration, Is.GreaterThanOrEqualTo(MinimumPowerDurationSeconds), + $"Initial power for {mapProtoId} does not last long enough! Needs at least {MinimumPowerDurationSeconds}s " + + $"but estimated to last only {estimatedDuration}s!"); + Assert.That(totalStartingCharge, Is.GreaterThanOrEqualTo(requiredStoredPower), + $"Needs at least {requiredStoredPower - totalStartingCharge} more stored power!"); + }); + + + await pair.CleanReturnAsync(); + } +} -- 2.51.2