Aetheria/tests/Aetheria.Shared.Tests/SmokeRoundsCommandTests.cs
Meta Vens 93d5a0c69c AetherDb smoke-rounds: the five product-less smoke weapons, dry run unless apply
Smoke Proximity, Smoke Delayed and Smoke Refused are Spectra clones with the operator's fuse values; Smoke Refused Beam is a constant weapon and Smoke Refused Charge a charged one cloned from ChargeBlast SG, each fused with a Range below its BlastRadius. No products. SmokeRoundsCommandTests covers dry run, apply and the no-op second apply.

The catalog and the arenas that carry the designs follow in the next commit. This commit builds; its command test needs no catalog change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:33:22 +02:00

91 lines
5.4 KiB
C#

using System;
using System.IO;
using System.Linq;
using GameCult.Caching;
using GameCult.Caching.MessagePack;
using Xunit;
// AetherDb's smoke-rounds command (aetheria-release, cut scenarios-smoke-rounds), run on a scratch copy of the shipped catalog
// with the five smoke weapons removed from it.
public sealed class SmokeRoundsCommandTests : IDisposable
{
private static readonly string[] Names = { "Smoke Proximity", "Smoke Delayed", "Smoke Refused", "Smoke Refused Beam", "Smoke Refused Charge" };
private readonly string _root = Path.Combine(Path.GetTempPath(), "smoke-rounds-command-" + Guid.NewGuid().ToString("N"));
private string CatalogPath => Path.Combine(_root, "GameData", "Aetheria.cc");
public SmokeRoundsCommandTests()
{
Directory.CreateDirectory(Path.Combine(_root, "GameData"));
Directory.CreateDirectory(Path.Combine(_root, "Assets", "Resources"));
var shipped = AetherDb.FindRoot();
File.Copy(Path.Combine(shipped, "GameData", "Aetheria.cc"), CatalogPath);
File.Copy(Path.Combine(shipped, "Assets", "Resources", "Settings.asset"), Path.Combine(_root, "Assets", "Resources", "Settings.asset"));
using var cache = new CultCache(TestCatalog.Registry());
cache.AddBackingStore(new SingleFileMessagePackBackingStore(CatalogPath), AetheriaStores.CatalogTypes);
foreach (var weapon in cache.GetAll<WeaponItemData>().Where(w => Names.Contains(w.Name)).ToArray()) Assert.True(cache.Remove(cache.RefOf(weapon).Key));
cache.FlushAsync().Wait();
}
public void Dispose() => Directory.Delete(_root, true);
private (WeaponItemData[] Smoke, WeaponItemData Spectra, WeaponItemData ChargeBlast, int Products) Read()
{
var db = AetherDb.Open(root: _root);
try
{
var smoke = db.Cache.GetAll<WeaponItemData>().Where(w => Names.Contains(w.Name)).ToArray();
var keys = smoke.Select(w => db.Cache.RefOf(w).Key).ToArray();
var products = db.Cache.GetAll<FactionProductData>().Count(p => keys.Any(k => p.Design.Key.Equals(k)));
return (smoke, db.Cache.GetAll<WeaponItemData>().Single(w => w.Name == "Spectra"), db.Cache.GetAll<WeaponItemData>().Single(w => w.Name == "ChargeBlast SG"), products);
}
finally { db.Cache.Dispose(); }
}
[Fact]
public void A_dry_run_writes_nothing_and_apply_lands_five_productless_designs_once()
{
var before = File.ReadAllBytes(CatalogPath);
Assert.Empty(Read().Smoke);
Assert.Equal(0, Program.SmokeRoundsCatalog(apply: false, root: _root));
Assert.Equal(before, File.ReadAllBytes(CatalogPath));
Assert.Empty(Read().Smoke);
Assert.Equal(0, Program.SmokeRoundsCatalog(apply: true, root: _root));
var landed = File.ReadAllBytes(CatalogPath);
Assert.NotEqual(before, landed);
var (smoke, spectra, chargeBlast, products) = Read();
Assert.Equal(Names.OrderBy(n => n), smoke.Select(w => w.Name).OrderBy(n => n));
Assert.Equal(0, products);
WeaponItemData Design(string name) => smoke.Single(w => w.Name == name);
WeaponData Data(string name) => Design(name).Behaviors.OfType<WeaponData>().Single();
// The operator's own values: the fuse and blast of each, Smoke Delayed's two cells of penetration, Smoke Refused's range of 20.
Assert.Equal((WeaponFuse.Proximity, 8f), (Design("Smoke Proximity").Fuse.Value, Design("Smoke Proximity").BlastRadius.Value));
Assert.Equal((WeaponFuse.Delayed, 4f, 2f, 2f), (Design("Smoke Delayed").Fuse.Value, Design("Smoke Delayed").BlastRadius.Value, Data("Smoke Delayed").Penetration.Min, Data("Smoke Delayed").Penetration.Max));
Assert.Equal((WeaponFuse.Proximity, 30f, 20f, 20f), (Design("Smoke Refused").Fuse.Value, Design("Smoke Refused").BlastRadius.Value, Data("Smoke Refused").Range.Min, Data("Smoke Refused").Range.Max));
// Spectra clones are energy, 1x2, with the Spectra's own gear; the charged one is a ChargeBlast SG clone on its 2x2 energy mount.
foreach (var name in new[] { "Smoke Proximity", "Smoke Delayed", "Smoke Refused", "Smoke Refused Beam" })
Assert.Equal((spectra.Hardpoint, spectra.Shape.Width, spectra.Shape.Height, spectra.Mass), (Design(name).Hardpoint, Design(name).Shape.Width, Design(name).Shape.Height, Design(name).Mass));
Assert.Equal((chargeBlast.Hardpoint, chargeBlast.Shape.Width, chargeBlast.Shape.Height), (Design("Smoke Refused Charge").Hardpoint, 2, 2));
// The beam is a constant weapon and the charge a charged one, each fused with a Range below its BlastRadius.
Assert.IsType<ConstantWeaponData>(Data("Smoke Refused Beam"));
Assert.IsType<ChargedWeaponData>(Data("Smoke Refused Charge"));
foreach (var name in new[] { "Smoke Refused Beam", "Smoke Refused Charge" })
{
Assert.NotNull(Design(name).Fuse);
Assert.True(Data(name).Range.Max < Design(name).BlastRadius, $"{name}'s range is not below its blast radius");
}
// A second apply finds them authored and writes nothing; so does a dry run.
Assert.Equal(0, Program.SmokeRoundsCatalog(apply: true, root: _root));
Assert.Equal(landed, File.ReadAllBytes(CatalogPath));
Assert.Equal(0, Program.SmokeRoundsCatalog(apply: false, root: _root));
Assert.Equal(landed, File.ReadAllBytes(CatalogPath));
Assert.Equal(5, Read().Smoke.Length);
}
}