diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 97b73c4..29a0171 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -5,7 +5,8 @@
"Bash(openspec status:*)",
"Bash(openspec instructions:*)",
"Bash(dotnet build:*)",
- "Bash(dotnet test:*)"
+ "Bash(dotnet test:*)",
+ "Bash(dotnet Temp/Bin/Debug/Network.EditMode.Tests/Network.EditMode.Tests.dll)"
]
},
"outputStyle": "default"
diff --git a/Assets/Scripts/Network/NetworkApplication/DefaultMessageDeliveryPolicyResolver.cs b/Assets/Scripts/Network/NetworkApplication/DefaultMessageDeliveryPolicyResolver.cs
index b4d215c..18907b0 100644
--- a/Assets/Scripts/Network/NetworkApplication/DefaultMessageDeliveryPolicyResolver.cs
+++ b/Assets/Scripts/Network/NetworkApplication/DefaultMessageDeliveryPolicyResolver.cs
@@ -1,22 +1,60 @@
using System.Collections.Generic;
+using System.Linq;
using Network.Defines;
namespace Network.NetworkApplication
{
+ ///
+ /// Resolves the delivery policy for each .
+ /// Policies are intentionally explicit here so that adding a new
+ /// requires an intentional decision rather than silently falling through to a default.
+ ///
public sealed class DefaultMessageDeliveryPolicyResolver : IMessageDeliveryPolicyResolver
{
- private static readonly IReadOnlyDictionary DefaultPolicies =
+ private static readonly IReadOnlyDictionary Policies =
new Dictionary
{
+ // High-frequency sync lane: latest-wins, stale-drop.
+ // These messages are sent every frame and a stale value is never useful.
{ MessageType.MoveInput, DeliveryPolicy.HighFrequencySync },
- { MessageType.PlayerState, DeliveryPolicy.HighFrequencySync }
+ { MessageType.PlayerState, DeliveryPolicy.HighFrequencySync },
+
+ // Reliable ordered lane: guaranteed delivery, ordered.
+ // ShootInput carries player intent; losing or reordering it changes gameplay outcomes.
+ { MessageType.ShootInput, DeliveryPolicy.ReliableOrdered },
+
+ // CombatEvent is a server-authoritative result; clients must receive it reliably
+ // and in order to maintain consistent HP/death state.
+ { MessageType.CombatEvent, DeliveryPolicy.ReliableOrdered },
+
+ // PlayerJoin carries spawn data that the client needs to instantiate a player.
+ // Reliable ordered ensures the client receives it before gameplay begins.
+ { MessageType.PlayerJoin, DeliveryPolicy.ReliableOrdered },
+
+ // Login/logout are session-control messages that must not be lost or reordered.
+ { MessageType.LoginRequest, DeliveryPolicy.ReliableOrdered },
+ { MessageType.LoginResponse, DeliveryPolicy.ReliableOrdered },
+ { MessageType.LogoutRequest, DeliveryPolicy.ReliableOrdered },
+
+ // Heartbeat carries server tick used for clock sync; a missing sample is
+ // simply a lost sample — no value in stale delivery.
+ { MessageType.Heartbeat, DeliveryPolicy.ReliableOrdered },
+ { MessageType.HeartbeatResponse, DeliveryPolicy.ReliableOrdered },
};
public DeliveryPolicy Resolve(MessageType messageType)
{
- return DefaultPolicies.TryGetValue(messageType, out var policy)
- ? policy
- : DeliveryPolicy.ReliableOrdered;
+ if (Policies.TryGetValue(messageType, out var policy))
+ {
+ return policy;
+ }
+
+ // A new MessageType was added without an explicit policy decision.
+ // Fail fast so the omission is noticed rather than silently defaulting.
+ throw new System.ArgumentException(
+ $"MessageType '{messageType}' has no assigned {nameof(DeliveryPolicy)}. " +
+ "Add an explicit entry in the policy dictionary.",
+ nameof(messageType));
}
}
-}
\ No newline at end of file
+}
diff --git a/Assets/Scripts/Network/NetworkApplication/NetworkIntegrationFactory.cs b/Assets/Scripts/Network/NetworkApplication/NetworkIntegrationFactory.cs
index b2f0f6f..a7ea839 100644
--- a/Assets/Scripts/Network/NetworkApplication/NetworkIntegrationFactory.cs
+++ b/Assets/Scripts/Network/NetworkApplication/NetworkIntegrationFactory.cs
@@ -1,5 +1,4 @@
using System;
-using System.Threading.Tasks;
using Network.NetworkHost;
using Network.NetworkTransport;
@@ -63,8 +62,6 @@ namespace Network.NetworkApplication
ServerAuthoritativeCombatConfiguration authoritativeCombat = null,
IAuthoritativeMovementWorldValidator authoritativeMovementWorldValidator = null)
{
- ValidateDualPortConfiguration(reliablePort, syncPort);
-
transportFactory ??= static port => new KcpTransport(port);
var reliableTransport = transportFactory(reliablePort)
@@ -91,11 +88,6 @@ namespace Network.NetworkApplication
authoritativeMovementWorldValidator ?? PermissiveAuthoritativeMovementWorldValidator.Instance);
}
- public static Task StartServerRuntimeAsync(ServerRuntimeConfiguration configuration)
- {
- return ServerRuntimeEntryPoint.StartAsync(configuration);
- }
-
private static void ValidateDualPortConfiguration(int reliablePort, int? syncPort)
{
if (reliablePort <= 0)
diff --git a/Assets/Tests/EditMode/Network/ServerRuntimeEntryPointTests.cs b/Assets/Tests/EditMode/Network/ServerRuntimeEntryPointTests.cs
index 33a0689..0d13c3b 100644
--- a/Assets/Tests/EditMode/Network/ServerRuntimeEntryPointTests.cs
+++ b/Assets/Tests/EditMode/Network/ServerRuntimeEntryPointTests.cs
@@ -24,7 +24,7 @@ namespace Tests.EditMode.Network
TransportFactory = port => CreateTransport(createdTransports, port)
};
- using var runtime = NetworkIntegrationFactory.StartServerRuntimeAsync(configuration).GetAwaiter().GetResult();
+ using var runtime = ServerRuntimeEntryPoint.StartAsync(configuration).GetAwaiter().GetResult();
Assert.That(createdTransports.Keys, Is.EquivalentTo(new[] { 9000 }));
Assert.That(runtime.IsRunning, Is.True);