将 openspec skills 迁移到全局 + 定义后续 proto 类型

This commit is contained in:
SepComet
2026-03-28 09:48:53 +08:00
parent 156d72bf4a
commit 371ab30eea
37 changed files with 1560 additions and 2342 deletions
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
@@ -55,7 +55,7 @@ namespace Tests.EditMode.Network
}
[Test]
public void SendMessage_PlayerInput_UsesSyncLanePolicy()
public void SendMessage_MoveInput_UsesSyncLanePolicy()
{
var reliableTransport = new FakeTransport();
var syncTransport = new FakeTransport();
@@ -64,20 +64,80 @@ namespace Tests.EditMode.Network
new MainThreadNetworkDispatcher(),
new DefaultMessageDeliveryPolicyResolver(),
syncTransport);
var message = new PlayerInput
var message = new MoveInput
{
PlayerId = "player-1",
Tick = 12
Tick = 12,
MoveX = 1,
MoveY = -1
};
manager.SendMessage(message, MessageType.PlayerInput);
manager.SendMessage(message, MessageType.MoveInput);
Assert.That(reliableTransport.SendCallCount, Is.EqualTo(0));
Assert.That(syncTransport.SendCallCount, Is.EqualTo(1));
var envelope = Envelope.Parser.ParseFrom(syncTransport.LastSentData);
Assert.That(envelope.Type, Is.EqualTo((int)MessageType.PlayerInput));
Assert.That(PlayerInput.Parser.ParseFrom(envelope.Payload).Tick, Is.EqualTo(12));
Assert.That(envelope.Type, Is.EqualTo((int)MessageType.MoveInput));
Assert.That(MoveInput.Parser.ParseFrom(envelope.Payload).Tick, Is.EqualTo(12));
}
[Test]
public void SendMessage_ShootInput_UsesReliableLanePolicy()
{
var reliableTransport = new FakeTransport();
var syncTransport = new FakeTransport();
var manager = new MessageManager(
reliableTransport,
new MainThreadNetworkDispatcher(),
new DefaultMessageDeliveryPolicyResolver(),
syncTransport);
var message = new ShootInput
{
PlayerId = "player-1",
Tick = 15,
DirX = 0.5f,
DirY = 1f,
TargetId = "enemy-1"
};
manager.SendMessage(message, MessageType.ShootInput);
Assert.That(reliableTransport.SendCallCount, Is.EqualTo(1));
Assert.That(syncTransport.SendCallCount, Is.EqualTo(0));
var envelope = Envelope.Parser.ParseFrom(reliableTransport.LastSentData);
Assert.That(envelope.Type, Is.EqualTo((int)MessageType.ShootInput));
Assert.That(ShootInput.Parser.ParseFrom(envelope.Payload).TargetId, Is.EqualTo("enemy-1"));
}
[Test]
public void SendMessage_CombatEvent_UsesReliableLanePolicy()
{
var reliableTransport = new FakeTransport();
var syncTransport = new FakeTransport();
var manager = new MessageManager(
reliableTransport,
new MainThreadNetworkDispatcher(),
new DefaultMessageDeliveryPolicyResolver(),
syncTransport);
var message = new CombatEvent
{
Tick = 20,
EventType = CombatEventType.DamageApplied,
AttackerId = "player-1",
TargetId = "enemy-1",
Damage = 7
};
manager.SendMessage(message, MessageType.CombatEvent);
Assert.That(reliableTransport.SendCallCount, Is.EqualTo(1));
Assert.That(syncTransport.SendCallCount, Is.EqualTo(0));
var envelope = Envelope.Parser.ParseFrom(reliableTransport.LastSentData);
Assert.That(envelope.Type, Is.EqualTo((int)MessageType.CombatEvent));
Assert.That(CombatEvent.Parser.ParseFrom(envelope.Payload).Damage, Is.EqualTo(7));
}
[Test]
@@ -184,6 +244,31 @@ namespace Tests.EditMode.Network
Assert.That(handledSpeeds, Is.EqualTo(new[] { 1, 2 }));
}
[Test]
public void Receive_StaleMoveInput_IsDropped()
{
var transport = new FakeTransport();
var manager = new MessageManager(transport, new MainThreadNetworkDispatcher());
var handledTicks = new List<long>();
manager.RegisterHandler(MessageType.MoveInput, (payload, sender) =>
{
handledTicks.Add(MoveInput.Parser.ParseFrom(payload).Tick);
});
transport.EmitReceive(
BuildEnvelope(MessageType.MoveInput, new MoveInput { PlayerId = "player-1", Tick = 8, MoveX = 1 }),
Sender);
manager.DrainPendingMessagesAsync().GetAwaiter().GetResult();
transport.EmitReceive(
BuildEnvelope(MessageType.MoveInput, new MoveInput { PlayerId = "player-1", Tick = 6, MoveX = -1 }),
Sender);
manager.DrainPendingMessagesAsync().GetAwaiter().GetResult();
Assert.That(handledTicks, Is.EqualTo(new long[] { 8 }));
}
[Test]
public void Receive_StalePlayerState_IsDropped()
{
@@ -209,6 +294,31 @@ namespace Tests.EditMode.Network
Assert.That(handledTicks, Is.EqualTo(new long[] { 8 }));
}
[Test]
public void Receive_ShootInput_IsNotDroppedBySequenceTracker()
{
var transport = new FakeTransport();
var manager = new MessageManager(transport, new MainThreadNetworkDispatcher());
var handledTicks = new List<long>();
manager.RegisterHandler(MessageType.ShootInput, (payload, sender) =>
{
handledTicks.Add(ShootInput.Parser.ParseFrom(payload).Tick);
});
transport.EmitReceive(
BuildEnvelope(MessageType.ShootInput, new ShootInput { PlayerId = "player-1", Tick = 8, DirX = 1f }),
Sender);
manager.DrainPendingMessagesAsync().GetAwaiter().GetResult();
transport.EmitReceive(
BuildEnvelope(MessageType.ShootInput, new ShootInput { PlayerId = "player-1", Tick = 6, DirY = 1f }),
Sender);
manager.DrainPendingMessagesAsync().GetAwaiter().GetResult();
Assert.That(handledTicks, Is.EqualTo(new long[] { 8, 6 }));
}
private static byte[] BuildEnvelope(MessageType type, IMessage payload)
{
return new Envelope
@@ -282,4 +392,4 @@ namespace Tests.EditMode.Network
}
}
}
}
}
@@ -76,7 +76,7 @@ namespace Tests.EditMode.Network
}
[Test]
public void SharedNetworkRuntime_RoutesSyncMessagesThroughConfiguredSyncLane()
public void SharedNetworkRuntime_RoutesMoveInputThroughConfiguredSyncLane()
{
var reliableTransport = new FakeTransport();
var syncTransport = new FakeTransport();
@@ -84,21 +84,23 @@ namespace Tests.EditMode.Network
reliableTransport,
new ImmediateNetworkMessageDispatcher(),
syncTransport: syncTransport);
var message = new PlayerInput
var message = new MoveInput
{
PlayerId = "shared-player",
Tick = 33
Tick = 33,
MoveX = 1f,
MoveY = -1f
};
runtime.MessageManager.SendMessage(message, MessageType.PlayerInput);
runtime.MessageManager.SendMessage(message, MessageType.MoveInput);
Assert.That(reliableTransport.SendCallCount, Is.EqualTo(0));
Assert.That(syncTransport.SendCallCount, Is.EqualTo(1));
Assert.That(syncTransport.LastSentData, Is.Not.Null);
var envelope = Envelope.Parser.ParseFrom(syncTransport.LastSentData);
Assert.That(envelope.Type, Is.EqualTo((int)MessageType.PlayerInput));
Assert.That(PlayerInput.Parser.ParseFrom(envelope.Payload).Tick, Is.EqualTo(33));
Assert.That(envelope.Type, Is.EqualTo((int)MessageType.MoveInput));
Assert.That(MoveInput.Parser.ParseFrom(envelope.Payload).Tick, Is.EqualTo(33));
}
private static byte[] BuildEnvelope(MessageType type, IMessage payload)
@@ -165,4 +167,4 @@ namespace Tests.EditMode.Network
}
}
}
}
}
@@ -12,12 +12,12 @@ namespace Tests.EditMode.Network
public class SyncStrategyTests
{
[Test]
public void ClientPredictionBuffer_AuthoritativeState_PrunesAcknowledgedInputs()
public void ClientPredictionBuffer_AuthoritativeState_PrunesAcknowledgedMoveInputs()
{
var buffer = new ClientPredictionBuffer();
buffer.Record(new PlayerInput { PlayerId = "player-1", Tick = 10 });
buffer.Record(new PlayerInput { PlayerId = "player-1", Tick = 11 });
buffer.Record(new PlayerInput { PlayerId = "player-1", Tick = 12 });
buffer.Record(new MoveInput { PlayerId = "player-1", Tick = 10, MoveX = 1f });
buffer.Record(new MoveInput { PlayerId = "player-1", Tick = 11, MoveX = 1f });
buffer.Record(new MoveInput { PlayerId = "player-1", Tick = 12, MoveX = 1f });
var accepted = buffer.TryApplyAuthoritativeState(
new PlayerState { PlayerId = "player-1", Tick = 11 },
@@ -34,7 +34,7 @@ namespace Tests.EditMode.Network
public void ClientPredictionBuffer_StaleAuthoritativeState_IsIgnored()
{
var buffer = new ClientPredictionBuffer();
buffer.Record(new PlayerInput { PlayerId = "player-1", Tick = 10 });
buffer.Record(new MoveInput { PlayerId = "player-1", Tick = 10, MoveX = 1f });
buffer.TryApplyAuthoritativeState(new PlayerState { PlayerId = "player-1", Tick = 10 }, out _);
var accepted = buffer.TryApplyAuthoritativeState(
@@ -75,7 +75,7 @@ namespace Tests.EditMode.Network
}
[Test]
public void ServerNetworkHost_RejectsStaleInputPerPeerWithoutCrossPeerInterference()
public void ServerNetworkHost_RejectsStaleMoveInputPerPeerWithoutCrossPeerInterference()
{
var transport = new FakeTransport();
var host = new ServerNetworkHost(transport);
@@ -83,7 +83,7 @@ namespace Tests.EditMode.Network
var peerB = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5002);
var handledTicksByPeer = new Dictionary<string, List<long>>();
host.MessageManager.RegisterHandler(MessageType.PlayerInput, (payload, sender) =>
host.MessageManager.RegisterHandler(MessageType.MoveInput, (payload, sender) =>
{
var key = sender.ToString();
if (!handledTicksByPeer.TryGetValue(key, out var ticks))
@@ -92,17 +92,17 @@ namespace Tests.EditMode.Network
handledTicksByPeer.Add(key, ticks);
}
ticks.Add(PlayerInput.Parser.ParseFrom(payload).Tick);
ticks.Add(MoveInput.Parser.ParseFrom(payload).Tick);
});
transport.EmitReceive(
BuildEnvelope(MessageType.PlayerInput, new PlayerInput { PlayerId = "player-a", Tick = 5 }),
BuildEnvelope(MessageType.MoveInput, new MoveInput { PlayerId = "player-a", Tick = 5, MoveX = 1f }),
peerA);
transport.EmitReceive(
BuildEnvelope(MessageType.PlayerInput, new PlayerInput { PlayerId = "player-a", Tick = 4 }),
BuildEnvelope(MessageType.MoveInput, new MoveInput { PlayerId = "player-a", Tick = 4, MoveX = -1f }),
peerA);
transport.EmitReceive(
BuildEnvelope(MessageType.PlayerInput, new PlayerInput { PlayerId = "player-b", Tick = 4 }),
BuildEnvelope(MessageType.MoveInput, new MoveInput { PlayerId = "player-b", Tick = 4, MoveY = 1f }),
peerB);
Assert.That(handledTicksByPeer[peerA.ToString()], Is.EqualTo(new long[] { 5 }));
@@ -149,4 +149,4 @@ namespace Tests.EditMode.Network
}
}
}
}
}