process TODO.md

This commit is contained in:
SepComet 2026-03-28 11:35:00 +08:00
parent 371ab30eea
commit fc8675f081
15 changed files with 323 additions and 26 deletions

View File

@ -140,6 +140,28 @@ namespace Tests.EditMode.Network
Assert.That(CombatEvent.Parser.ParseFrom(envelope.Payload).Damage, Is.EqualTo(7)); Assert.That(CombatEvent.Parser.ParseFrom(envelope.Payload).Damage, Is.EqualTo(7));
} }
[Test]
public void SendMessage_Heartbeat_UsesReliableLanePolicy()
{
var reliableTransport = new FakeTransport();
var syncTransport = new FakeTransport();
var manager = new MessageManager(
reliableTransport,
new MainThreadNetworkDispatcher(),
new DefaultMessageDeliveryPolicyResolver(),
syncTransport);
var message = new Heartbeat();
manager.SendMessage(message, MessageType.Heartbeat);
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.Heartbeat));
Assert.That(envelope.Payload.ToByteArray(), Is.EqualTo(message.ToByteArray()));
}
[Test] [Test]
public void BroadcastMessage_UsesBroadcastSend() public void BroadcastMessage_UsesBroadcastSend()
{ {

View File

@ -75,6 +75,27 @@ namespace Tests.EditMode.Network
Assert.That(transport.LastSendTarget, Is.EqualTo(Sender)); Assert.That(transport.LastSendTarget, Is.EqualTo(Sender));
} }
[Test]
public void SharedNetworkRuntime_StartStop_WithDistinctSyncTransport_ControlsBothLanes()
{
var reliableTransport = new FakeTransport();
var syncTransport = new FakeTransport();
var runtime = new SharedNetworkRuntime(
reliableTransport,
new ImmediateNetworkMessageDispatcher(),
syncTransport: syncTransport);
runtime.StartAsync().GetAwaiter().GetResult();
Assert.That(reliableTransport.StartCallCount, Is.EqualTo(1));
Assert.That(syncTransport.StartCallCount, Is.EqualTo(1));
runtime.Stop();
Assert.That(reliableTransport.StopCallCount, Is.EqualTo(1));
Assert.That(syncTransport.StopCallCount, Is.EqualTo(1));
}
[Test] [Test]
public void SharedNetworkRuntime_RoutesMoveInputThroughConfiguredSyncLane() public void SharedNetworkRuntime_RoutesMoveInputThroughConfiguredSyncLane()
{ {
@ -103,6 +124,23 @@ namespace Tests.EditMode.Network
Assert.That(MoveInput.Parser.ParseFrom(envelope.Payload).Tick, Is.EqualTo(33)); Assert.That(MoveInput.Parser.ParseFrom(envelope.Payload).Tick, Is.EqualTo(33));
} }
[Test]
public void ServerNetworkHost_StartsDistinctSyncTransport_AndTracksInboundActivityFromSyncLane()
{
var reliableTransport = new FakeTransport();
var syncTransport = new FakeTransport();
var host = new ServerNetworkHost(reliableTransport, syncTransport: syncTransport);
host.StartAsync().GetAwaiter().GetResult();
syncTransport.EmitReceive(BuildEnvelope(MessageType.Heartbeat, new Heartbeat()), Sender);
Assert.That(reliableTransport.StartCallCount, Is.EqualTo(1));
Assert.That(syncTransport.StartCallCount, Is.EqualTo(1));
Assert.That(host.ManagedSessions.Count, Is.EqualTo(1));
Assert.That(host.TryGetSession(Sender, out var session), Is.True);
Assert.That(session.SessionManager.State, Is.EqualTo(ConnectionState.TransportConnected));
}
private static byte[] BuildEnvelope(MessageType type, IMessage payload) private static byte[] BuildEnvelope(MessageType type, IMessage payload)
{ {
return new Envelope return new Envelope
@ -122,15 +160,21 @@ namespace Tests.EditMode.Network
public int SendCallCount { get; private set; } public int SendCallCount { get; private set; }
public int StartCallCount { get; private set; }
public int StopCallCount { get; private set; }
public event Action<byte[], IPEndPoint> OnReceive; public event Action<byte[], IPEndPoint> OnReceive;
public Task StartAsync() public Task StartAsync()
{ {
StartCallCount++;
return Task.CompletedTask; return Task.CompletedTask;
} }
public void Stop() public void Stop()
{ {
StopCallCount++;
} }
public void Send(byte[] data) public void Send(byte[] data)

16
TODO.md
View File

@ -65,17 +65,17 @@ Acceptance:
### 5. Preserve And Use Dual-Transport Runtime Wiring ### 5. Preserve And Use Dual-Transport Runtime Wiring
- [ ] Verify [`Assets/Scripts/Network/NetworkApplication/SharedNetworkRuntime.cs`](D:/Learn/GameLearn/UnityProjects/NetworkFW/Assets/Scripts/Network/NetworkApplication/SharedNetworkRuntime.cs) is used with both reliable and sync transports - [x] Verify [`Assets/Scripts/Network/NetworkApplication/SharedNetworkRuntime.cs`](D:/Learn/GameLearn/UnityProjects/NetworkFW/Assets/Scripts/Network/NetworkApplication/SharedNetworkRuntime.cs) is used with both reliable and sync transports
- [ ] Verify [`Assets/Scripts/Network/NetworkHost/ServerNetworkHost.cs`](D:/Learn/GameLearn/UnityProjects/NetworkFW/Assets/Scripts/Network/NetworkHost/ServerNetworkHost.cs) is used with both reliable and sync transports - [x] Verify [`Assets/Scripts/Network/NetworkHost/ServerNetworkHost.cs`](D:/Learn/GameLearn/UnityProjects/NetworkFW/Assets/Scripts/Network/NetworkHost/ServerNetworkHost.cs) is used with both reliable and sync transports
- [ ] Keep the current dual-transport constructor shape for MVP - [x] Keep the current dual-transport constructor shape for MVP
- [ ] Do not expand `ITransport` yet unless MVP proves it is necessary - [x] Do not expand `ITransport` yet unless MVP proves it is necessary
Acceptance: Acceptance:
- [ ] Client runtime can start with two distinct transport instances - [x] Client runtime can start with two distinct transport instances
- [ ] Server host can start with two distinct transport instances - [x] Server host can start with two distinct transport instances
- [ ] `MoveInput` / `PlayerState` can flow through the sync transport - [x] `MoveInput` / `PlayerState` can flow through the sync transport
- [ ] `ShootInput` / `CombatEvent` can flow through the reliable transport - [x] `ShootInput` / `CombatEvent` can flow through the reliable transport
### 6. Finalize MVP Message Fields ### 6. Finalize MVP Message Fields

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-28

View File

@ -0,0 +1,41 @@
## Context
`SharedNetworkRuntime` and `ServerNetworkHost` already expose an MVP-friendly constructor shape with one reliable `ITransport` and an optional sync `ITransport`. That behavior is important because the message-routing layer now distinguishes reliable gameplay/control traffic from high-frequency sync traffic, but the current spec only says sync traffic is composable in principle. TODO step 5 is about preserving the current dual-transport runtime boundary before the Unity integration layer is updated in a later step.
The main constraint is to keep the shared networking core host-agnostic. We need explicit runtime and host requirements for dual-lane startup, shutdown, and receive composition without introducing Unity types or widening `ITransport` into a multi-lane abstraction too early.
## Goals / Non-Goals
**Goals:**
- Preserve a shared-runtime contract where client and server hosts can provide distinct reliable and sync transport instances.
- Make startup, shutdown, and inbound handling expectations explicit for both `SharedNetworkRuntime` and `ServerNetworkHost`.
- Keep lane selection in `MessageManager` and delivery-policy abstractions rather than redesigning `ITransport`.
- Drive minimal implementation work, ideally verification plus any missing regression tests.
**Non-Goals:**
- Updating `NetworkManager` or the server bootstrap integration layer to instantiate two transports.
- Redesigning `ITransport`, introducing a transport multiplexer abstraction, or changing the envelope protocol.
- Expanding the sync strategy beyond the message-lane split already captured in `network-sync-strategy`.
## Decisions
### Preserve constructor-based dual-transport composition
The shared runtime contract will continue to accept one primary reliable transport plus an optional sync transport. This matches the existing code, keeps host wiring simple, and avoids forcing every transport implementation to understand multiple delivery lanes.
Alternative considered: extend `ITransport` with multiple send lanes or channel APIs. Rejected for MVP because it would ripple through every transport implementation and blur the boundary between transport concerns and message-routing policy.
### Specify dual-lane lifecycle behavior at the host/runtime level
The spec will explicitly require `SharedNetworkRuntime` and `ServerNetworkHost` to start and stop both transport instances when distinct transports are supplied, and to observe inbound activity from both lanes on the server side. That turns the current code shape into a protected contract instead of an incidental implementation detail.
Alternative considered: leave lifecycle behavior implicit and only test lane selection in `MessageManager`. Rejected because lane selection is not sufficient if host/runtime wiring later collapses back to one started transport.
### Keep message and delivery policy contracts unchanged
The updated requirement will keep the existing shared envelope format and `MessageManager` delivery-policy resolution, with dual-lane composition remaining outside `ITransport`. This isolates TODO step 5 from later integration changes while still making `MoveInput`/`PlayerState` vs. `ShootInput`/`CombatEvent` lane expectations usable at runtime.
Alternative considered: introduce a new capability just for dual transport wiring. Rejected because this is a refinement of the existing shared foundation contract, not a separate product capability.
## Risks / Trade-offs
- [Risk] Shared specs may become too implementation-shaped by naming concrete runtime classes. → Mitigation: keep the requirement centered on host-visible behavior while using `SharedNetworkRuntime` and `ServerNetworkHost` only where the current shared entry points are the contract.
- [Risk] Future transport redesign could invalidate the constructor shape. → Mitigation: scope this explicitly to the MVP and note that broader `ITransport` changes remain out of scope.
- [Risk] Existing tests may not cover startup or shutdown of both lanes. → Mitigation: include regression tasks for dual-transport lifecycle behavior, not only message routing.

View File

@ -0,0 +1,21 @@
## Why
The shared runtime already has an MVP-oriented dual-transport shape, but TODO step 5 is still undocumented at the spec level. We need to lock that contract before changing integration wiring so client and server hosts keep a clear, host-agnostic way to run reliable control traffic and high-frequency sync traffic on separate transport instances without prematurely redesigning `ITransport`.
## What Changes
- Document that `SharedNetworkRuntime` preserves separate reliable and sync transport inputs for the MVP and starts or stops both lanes when distinct instances are supplied.
- Document that `ServerNetworkHost` preserves the same dual-transport constructor shape and attaches receive handling for both lanes without forking message contracts.
- Require the shared runtime foundation to keep delivery-lane composition outside `ITransport` so hosts can use two transport instances instead of expanding the transport abstraction early.
- Add implementation tasks to verify or tighten regression coverage around dual-transport startup and message-lane usage in the shared runtime and server host.
## Capabilities
### New Capabilities
### Modified Capabilities
- `shared-network-foundation`: Narrow the shared runtime contract so MVP hosts explicitly preserve dual-transport composition through `SharedNetworkRuntime` and `ServerNetworkHost` without widening `ITransport`.
## Impact
Affected code is expected in `Assets/Scripts/Network/NetworkApplication/SharedNetworkRuntime.cs`, `Assets/Scripts/Network/NetworkHost/ServerNetworkHost.cs`, and related edit-mode regression tests. This change should not introduce new transport interfaces or Unity-specific dependencies into shared networking code.

View File

@ -0,0 +1,24 @@
## MODIFIED Requirements
### Requirement: Shared core preserves current transport and message contracts
The shared client/server foundation SHALL preserve the envelope-based business-message contract across client and server hosts while allowing delivery-policy selection behind the shared message-routing layer. Reliable control traffic MUST continue to use the existing `ITransport` contract, and high-frequency sync traffic MUST remain composable by supplying a second host-agnostic sync transport instance to `SharedNetworkRuntime` or `ServerNetworkHost` rather than by expanding `ITransport` for MVP-specific lane semantics. The shared message-type contract MUST allow hosts to distinguish `MoveInput`, `ShootInput`, `CombatEvent`, and `PlayerState` as separate business messages across both delivery lanes.
#### Scenario: Shared runtime starts distinct reliable and sync transports
- **WHEN** a client host constructs `SharedNetworkRuntime` with one reliable transport and a different sync transport instance
- **THEN** starting the runtime starts both transport instances
- **THEN** stopping the runtime stops both transport instances while keeping the same shared message-routing contract
#### Scenario: Server host composes both transport lanes without protocol forks
- **WHEN** a server host constructs `ServerNetworkHost` with one reliable transport and a different sync transport instance
- **THEN** it observes inbound activity from both transport lanes through shared host logic
- **THEN** it routes messages with the same envelope and message-type contract instead of defining a lane-specific protocol fork
#### Scenario: Shared hosts exchange the same envelope format across delivery lanes
- **WHEN** a client host sends a business message through either the reliable control path or the high-frequency sync path
- **THEN** the payload is encoded with the same shared envelope and message-type contract
- **THEN** the server host decodes and routes it through shared networking logic without a host-specific protocol fork
#### Scenario: Hosts preserve dual-transport composition outside ITransport
- **WHEN** a host needs separate reliable and sync lanes for MVP gameplay traffic
- **THEN** it provides separate transport instances plus delivery-policy configuration to shared runtime or host entry points
- **THEN** the shared networking core does not require `ITransport` itself to grow MVP-specific multi-lane APIs

View File

@ -0,0 +1,14 @@
## 1. Verify shared dual-transport runtime boundaries
- [x] 1.1 Verify `SharedNetworkRuntime` still preserves the MVP constructor shape with a primary reliable `ITransport`, an optional sync `ITransport`, and no new multi-lane transport API.
- [x] 1.2 Verify `ServerNetworkHost` still preserves the same dual-transport constructor shape, starts both lanes when distinct transports are supplied, and attaches inbound handling for both transports.
## 2. Add regression coverage for dual-lane lifecycle behavior
- [x] 2.1 Add or extend edit-mode tests to prove `SharedNetworkRuntime` starts and stops two distinct transport instances while continuing to route sync-lane messages through the configured sync transport.
- [x] 2.2 Add or extend edit-mode tests to prove `ServerNetworkHost` starts two distinct transport instances and records inbound activity from the sync transport without cross-lane protocol changes.
## 3. Validate the MVP shared-network contract
- [x] 3.1 Run `dotnet build Network.EditMode.Tests.csproj -v minimal`.
- [x] 3.2 Run `dotnet test Network.EditMode.Tests.csproj --no-build -v minimal`.

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-28

View File

@ -0,0 +1,55 @@
## Context
`TODO.md` step 2 focuses narrowly on how the shared runtime chooses a delivery lane after the gameplay protocol split. The current code already centralizes that decision in `DefaultMessageDeliveryPolicyResolver`, which is consulted by `MessageManager` before selecting either the reliable transport or the optional sync transport.
This change does not introduce a new transport abstraction or alter the envelope contract. Its purpose is to lock down the default mapping contract so later MVP work on stale filtering, prediction, and dual-transport wiring can assume a stable policy table: latest-wins movement/state traffic uses the sync lane, while shooting and combat-result traffic continue to use reliable ordered delivery.
## Goals / Non-Goals
**Goals:**
- Define the default message-type to delivery-policy mapping used by the shared runtime.
- Keep the mapping small and explicit so `MoveInput` and `PlayerState` are the only gameplay messages promoted to `HighFrequencySync`.
- Preserve reliable ordered fallback for `ShootInput`, `CombatEvent`, and existing control-plane messages.
- Require regression tests that exercise `MessageManager` lane selection through the resolver contract.
**Non-Goals:**
- Wiring two concrete transport instances through all integration entry points.
- Changing stale-sequence filtering, prediction replay, or protobuf field definitions beyond what this mapping step depends on.
- Replacing the resolver with a configurable registry or runtime policy editor.
## Decisions
### Keep the default mapping in a static resolver table
The default runtime contract will remain a small static mapping owned by `DefaultMessageDeliveryPolicyResolver`. `MoveInput` and `PlayerState` are explicitly listed as `HighFrequencySync`, and unresolved message types fall back to `ReliableOrdered`.
Alternative considered: list every message type explicitly in the resolver.
Rejected because the TODO step only needs a narrow exception set. A reliable fallback keeps control traffic and future message types safe unless they are intentionally promoted to the sync lane.
### Make lane selection observable through `MessageManager` regression tests
The design relies on send-path tests rather than resolver-only unit tests. `MessageManager` is the behavior boundary that chooses which transport instance sends the envelope, so routing tests verify the mapping contract and the integration between resolver and transport selection at the same time.
Alternative considered: test only `DefaultMessageDeliveryPolicyResolver.Resolve`.
Rejected because that would not prove the runtime actually routes through the expected transport lane.
### Treat reliable ordered delivery as the default for discrete gameplay events
`ShootInput` and `CombatEvent` remain on the reliable ordered path by omission from the sync mapping table. This avoids expanding latest-wins semantics to discrete gameplay events where silent dropping or unordered handling would be incorrect.
Alternative considered: map all gameplay messages to the sync lane after the protocol split.
Rejected because delivery semantics differ by message intent, and event-style gameplay traffic must preserve reliable ordered behavior.
## Risks / Trade-offs
- [A default fallback can hide newly added message types on the reliable lane] -> Mitigation: keep the spec explicit that only `MoveInput` and `PlayerState` use `HighFrequencySync`, and add routing tests for every split MVP gameplay message.
- [Future transport wiring could drift from the resolver contract] -> Mitigation: keep `MessageManager` tests asserting which transport instance is selected for sync versus reliable policies.
- [This change can look redundant because the current code already implements it] -> Mitigation: use the change to align TODO step 2, specs, and regression coverage so later work has a stable contract to build on.
## Migration Plan
1. Update the `network-sync-strategy` delta spec to define the default resolver mapping for split gameplay messages.
2. Verify `DefaultMessageDeliveryPolicyResolver` maps `MoveInput` and `PlayerState` to `HighFrequencySync` and leaves `ShootInput`/`CombatEvent` on the reliable ordered fallback.
3. Keep or add `MessageManager` routing tests that prove sync-lane and reliable-lane selection for the split MVP gameplay messages.
4. Use this locked mapping as the baseline for later dual-transport integration work.
## Open Questions
- None for this planning slice. The TODO step already defines the target mapping and the current runtime shape is sufficient to implement it.

View File

@ -0,0 +1,26 @@
## Why
The MVP TODO requires a stable default mapping between gameplay message types and delivery lanes so split movement, state, shooting, and combat-result messages do not regress back onto one transport policy. This needs to be formalized now because the routing resolver is the contract that keeps the sync lane limited to latest-wins traffic while preserving reliable delivery for gameplay events.
## What Changes
- Define the default delivery-policy mapping for shared gameplay message routing in the networking runtime.
- Require `MoveInput` and `PlayerState` to resolve to `HighFrequencySync` in the default resolver used by `MessageManager`.
- Require `ShootInput` and `CombatEvent` to continue resolving through the reliable ordered default path instead of the sync lane.
- Add regression coverage that proves movement/state traffic uses the sync lane while shooting/combat-result traffic remains on the reliable lane.
## Capabilities
### New Capabilities
None.
### Modified Capabilities
- `network-sync-strategy`: Clarify the default resolver mapping that sends `MoveInput` and `PlayerState` through the high-frequency sync lane while `ShootInput` and `CombatEvent` remain on the reliable ordered lane.
## Impact
- Affected code: `Assets/Scripts/Network/NetworkApplication/DefaultMessageDeliveryPolicyResolver.cs` and the `MessageManager` send path that consults the resolver.
- Affected behavior: the runtime keeps latest-wins movement/state traffic off the reliable lane by default, while shooting requests and combat results keep reliable ordered delivery semantics.
- Affected tests: edit-mode message-routing tests need explicit assertions for sync-lane and reliable-lane selection for the split MVP gameplay message types.

View File

@ -0,0 +1,19 @@
## MODIFIED Requirements
### Requirement: Hosts assign delivery policies to synchronization message types
The shared networking core SHALL allow hosts to map business message types to delivery policies. The default shared resolver used by `MessageManager` MUST map `MoveInput` and `PlayerState` to `HighFrequencySync`, while `ShootInput`, `CombatEvent`, and control-plane messages MUST resolve to `ReliableOrdered` unless a host intentionally supplies a different resolver.
#### Scenario: Default resolver sends movement and state traffic to the sync lane
- **WHEN** the runtime uses `DefaultMessageDeliveryPolicyResolver` to send `MoveInput` or `PlayerState`
- **THEN** the resolver returns `HighFrequencySync`
- **THEN** `MessageManager` sends that envelope through the sync transport lane when one is configured
#### Scenario: Default resolver keeps shooting and combat events on the reliable lane
- **WHEN** the runtime uses `DefaultMessageDeliveryPolicyResolver` to send `ShootInput` or `CombatEvent`
- **THEN** the resolver returns `ReliableOrdered`
- **THEN** `MessageManager` sends that envelope through the reliable transport lane
#### Scenario: Default resolver preserves reliable control traffic
- **WHEN** the runtime uses `DefaultMessageDeliveryPolicyResolver` to send login, logout, heartbeat, or other session-management messages
- **THEN** the resolver returns `ReliableOrdered`
- **THEN** those messages continue to use the reliable transport path

View File

@ -0,0 +1,17 @@
## 1. Lock The Default Delivery Mapping
- [x] 1.1 Verify `Assets/Scripts/Network/NetworkApplication/DefaultMessageDeliveryPolicyResolver.cs` explicitly maps `MessageType.MoveInput` and `MessageType.PlayerState` to `DeliveryPolicy.HighFrequencySync`.
- [x] 1.2 Verify the default resolver leaves `MessageType.ShootInput`, `MessageType.CombatEvent`, and control-plane messages on the `DeliveryPolicy.ReliableOrdered` fallback path.
- [x] 1.3 Confirm `MessageManager` continues consulting the resolver before selecting the reliable or sync transport lane.
## 2. Protect Lane Selection With Regression Tests
- [x] 2.1 Keep or add edit-mode routing tests proving `MoveInput` uses the sync lane and does not send through the reliable transport.
- [x] 2.2 Keep or add edit-mode routing tests proving `ShootInput` and `CombatEvent` use the reliable lane and do not send through the sync transport.
- [x] 2.3 Keep or add coverage that control-plane traffic still defaults to the reliable ordered lane when the default resolver is used.
## 3. Validate The Step-2 Contract
- [x] 3.1 Build `Network.EditMode.Tests.csproj -v minimal` to verify the delivery-mapping change does not break the shared networking assemblies.
- [x] 3.2 Run `dotnet test Network.EditMode.Tests.csproj --no-build -v minimal` to confirm the routing regression suite passes.
- [x] 3.3 Update `TODO.md` or related implementation notes only if verification shows the step-2 completion markers need correction.

View File

@ -5,21 +5,21 @@ Define how client and server route high-frequency gameplay synchronization traff
## Requirements ## Requirements
### Requirement: Hosts assign delivery policies to synchronization message types ### Requirement: Hosts assign delivery policies to synchronization message types
The shared networking core SHALL allow hosts to map business message types to delivery policies. `MoveInput` and `PlayerState` MUST be assignable to a high-frequency sync policy that is independent from the reliable ordered control policy used by login and lifecycle traffic, while `ShootInput` and `CombatEvent` MUST remain independently routable business messages that can stay on the reliable ordered lane. The shared networking core SHALL allow hosts to map business message types to delivery policies. The default shared resolver used by `MessageManager` MUST map `MoveInput` and `PlayerState` to `HighFrequencySync`, while `ShootInput`, `CombatEvent`, and control-plane messages MUST resolve to `ReliableOrdered` unless a host intentionally supplies a different resolver.
#### Scenario: High-frequency movement and state messages use a dedicated policy #### Scenario: Default resolver sends movement and state traffic to the sync lane
- **WHEN** the client or server sends `MoveInput` or `PlayerState` - **WHEN** the runtime uses `DefaultMessageDeliveryPolicyResolver` to send `MoveInput` or `PlayerState`
- **THEN** the runtime resolves a high-frequency sync delivery policy for that message type - **THEN** the resolver returns `HighFrequencySync`
- **THEN** the message is sent through the sync lane configured for that policy instead of defaulting to reliable ordered delivery - **THEN** `MessageManager` sends that envelope through the sync transport lane when one is configured
#### Scenario: Shooting and combat events keep reliable ordered delivery #### Scenario: Default resolver keeps shooting and combat events on the reliable lane
- **WHEN** the client or server sends `ShootInput` or `CombatEvent` - **WHEN** the runtime uses `DefaultMessageDeliveryPolicyResolver` to send `ShootInput` or `CombatEvent`
- **THEN** the runtime resolves the reliable ordered delivery policy for that message type - **THEN** the resolver returns `ReliableOrdered`
- **THEN** those messages continue to use the reliable transport path - **THEN** `MessageManager` sends that envelope through the reliable transport lane
#### Scenario: Control traffic keeps reliable delivery #### Scenario: Default resolver preserves reliable control traffic
- **WHEN** the runtime sends login, logout, heartbeat, or other session-management messages - **WHEN** the runtime uses `DefaultMessageDeliveryPolicyResolver` to send login, logout, heartbeat, or other session-management messages
- **THEN** the runtime resolves the reliable ordered control policy - **THEN** the resolver returns `ReliableOrdered`
- **THEN** those messages continue to use the reliable transport path - **THEN** those messages continue to use the reliable transport path
### Requirement: Sequenced sync receivers discard stale gameplay updates ### Requirement: Sequenced sync receivers discard stale gameplay updates

View File

@ -31,17 +31,27 @@ The shared message-routing layer SHALL execute received business handlers throug
- **THEN** the shared message-routing layer still processes received messages correctly through that host-selected strategy - **THEN** the shared message-routing layer still processes received messages correctly through that host-selected strategy
### Requirement: Shared core preserves current transport and message contracts ### Requirement: Shared core preserves current transport and message contracts
The shared client/server foundation SHALL preserve the envelope-based business-message contract across client and server hosts while allowing delivery-policy selection behind the shared message-routing layer. Reliable control traffic MUST continue to use the existing `ITransport` contract, and high-frequency sync traffic MUST be composable through a host-agnostic sync strategy without introducing Unity-specific runtime types into the shared networking core. The shared message-type contract MUST allow hosts to distinguish `MoveInput`, `ShootInput`, `CombatEvent`, and `PlayerState` as separate business messages across both delivery lanes. The shared client/server foundation SHALL preserve the envelope-based business-message contract across client and server hosts while allowing delivery-policy selection behind the shared message-routing layer. Reliable control traffic MUST continue to use the existing `ITransport` contract, and high-frequency sync traffic MUST remain composable by supplying a second host-agnostic sync transport instance to `SharedNetworkRuntime` or `ServerNetworkHost` rather than by expanding `ITransport` for MVP-specific lane semantics. The shared message-type contract MUST allow hosts to distinguish `MoveInput`, `ShootInput`, `CombatEvent`, and `PlayerState` as separate business messages across both delivery lanes.
#### Scenario: Shared runtime starts distinct reliable and sync transports
- **WHEN** a client host constructs `SharedNetworkRuntime` with one reliable transport and a different sync transport instance
- **THEN** starting the runtime starts both transport instances
- **THEN** stopping the runtime stops both transport instances while keeping the same shared message-routing contract
#### Scenario: Server host composes both transport lanes without protocol forks
- **WHEN** a server host constructs `ServerNetworkHost` with one reliable transport and a different sync transport instance
- **THEN** it observes inbound activity from both transport lanes through shared host logic
- **THEN** it routes messages with the same envelope and message-type contract instead of defining a lane-specific protocol fork
#### Scenario: Shared hosts exchange the same envelope format across delivery lanes #### Scenario: Shared hosts exchange the same envelope format across delivery lanes
- **WHEN** a client host sends a business message through either the reliable control path or the high-frequency sync path - **WHEN** a client host sends a business message through either the reliable control path or the high-frequency sync path
- **THEN** the payload is encoded with the same shared envelope and message-type contract - **THEN** the payload is encoded with the same shared envelope and message-type contract
- **THEN** the server host decodes and routes it through shared networking logic without a host-specific protocol fork - **THEN** the server host decodes and routes it through shared networking logic without a host-specific protocol fork
#### Scenario: Hosts compose delivery-policy selection without Unity dependencies #### Scenario: Hosts preserve dual-transport composition outside ITransport
- **WHEN** a non-Unity server host constructs the runtime networking stack with reliable control traffic and a high-frequency sync lane - **WHEN** a host needs separate reliable and sync lanes for MVP gameplay traffic
- **THEN** it uses shared delivery-policy abstractions without depending on Unity frame-loop types - **THEN** it provides separate transport instances plus delivery-policy configuration to shared runtime or host entry points
- **THEN** the Unity client can use the same abstractions while still supplying its own host-specific dispatch behavior - **THEN** the shared networking core does not require `ITransport` itself to grow MVP-specific multi-lane APIs
#### Scenario: Shared hosts route split gameplay message identities consistently #### Scenario: Shared hosts route split gameplay message identities consistently
- **WHEN** client or server code sends `MoveInput`, `ShootInput`, `CombatEvent`, or `PlayerState` - **WHEN** client or server code sends `MoveInput`, `ShootInput`, `CombatEvent`, or `PlayerState`