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
+ }
+}