diff --git a/SVSim.BattleNode/Reliability/Gungnir.cs b/SVSim.BattleNode/Reliability/Gungnir.cs new file mode 100644 index 0000000..c1644e7 --- /dev/null +++ b/SVSim.BattleNode/Reliability/Gungnir.cs @@ -0,0 +1,23 @@ +namespace SVSim.BattleNode.Reliability; + +/// +/// Body builders for the alive channel. The timer/loop that drives 5s emits lives on +/// BattleSession; this class is just the pure body-shape factory. +/// v1 always reports scs/ocs=ONLINE — real disconnect detection is deferred. +/// +public static class Gungnir +{ + public static readonly TimeSpan EmitInterval = TimeSpan.FromSeconds(5); + + public static Dictionary BuildAliveEmitBody(InboundTracker tracker) => new() + { + ["currentSeq"] = tracker.HighWaterMark, + // actionSeq omitted in v1 — no turn-transition flag yet. + }; + + public static Dictionary BuildAlivePushBody() => new() + { + ["scs"] = "ONLINE", + ["ocs"] = "ONLINE", + }; +} diff --git a/SVSim.UnitTests/BattleNode/Reliability/GungnirTests.cs b/SVSim.UnitTests/BattleNode/Reliability/GungnirTests.cs new file mode 100644 index 0000000..40ea1b5 --- /dev/null +++ b/SVSim.UnitTests/BattleNode/Reliability/GungnirTests.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; +using SVSim.BattleNode.Reliability; + +namespace SVSim.UnitTests.BattleNode.Reliability; + +[TestFixture] +public class GungnirTests +{ + [Test] + public void BuildAlivePush_AlwaysReturnsScsOnlineOcsOnline() + { + var body = Gungnir.BuildAlivePushBody(); + Assert.That(body["scs"], Is.EqualTo("ONLINE")); + Assert.That(body["ocs"], Is.EqualTo("ONLINE")); + } + + [Test] + public void BuildAliveEmit_CarriesCurrentSeqFromTracker() + { + var tracker = new InboundTracker(); + tracker.Observe(7); + + var body = Gungnir.BuildAliveEmitBody(tracker); + + Assert.That(body["currentSeq"], Is.EqualTo(7L)); + Assert.That(body.ContainsKey("actionSeq"), Is.False); // omitted in v1 + } +}