diff --git a/Assets/Scripts/Network/NetworkApplication/NetworkIntegrationFactory.cs b/Assets/Scripts/Network/NetworkApplication/NetworkIntegrationFactory.cs new file mode 100644 index 0000000..ba6751e --- /dev/null +++ b/Assets/Scripts/Network/NetworkApplication/NetworkIntegrationFactory.cs @@ -0,0 +1,110 @@ +using System; +using Network.NetworkHost; +using Network.NetworkTransport; + +namespace Network.NetworkApplication +{ + public static class NetworkIntegrationFactory + { + public static SharedNetworkRuntime CreateClientRuntime( + string serverIp, + int reliablePort, + INetworkMessageDispatcher dispatcher, + int? syncPort = null, + SessionReconnectPolicy reconnectPolicy = null, + Func utcNowProvider = null, + IMessageDeliveryPolicyResolver deliveryPolicyResolver = null, + SyncSequenceTracker syncSequenceTracker = null, + ClockSyncState clockSync = null, + Func transportFactory = null) + { + if (dispatcher == null) + { + throw new ArgumentNullException(nameof(dispatcher)); + } + + ValidateDualPortConfiguration(reliablePort, syncPort); + + transportFactory ??= static (ip, port) => new KcpTransport(ip, port); + + var reliableTransport = transportFactory(serverIp, reliablePort) + ?? throw new InvalidOperationException("Reliable transport factory returned null."); + var syncTransport = syncPort.HasValue + ? transportFactory(serverIp, syncPort.Value) + : null; + + if (syncPort.HasValue && syncTransport == null) + { + throw new InvalidOperationException("Sync transport factory returned null."); + } + + return new SharedNetworkRuntime( + reliableTransport, + dispatcher, + reconnectPolicy, + utcNowProvider, + syncTransport, + deliveryPolicyResolver, + syncSequenceTracker, + clockSync); + } + + public static ServerNetworkHost CreateServerHost( + int reliablePort, + int? syncPort = null, + INetworkMessageDispatcher dispatcher = null, + SessionReconnectPolicy reconnectPolicy = null, + Func utcNowProvider = null, + IMessageDeliveryPolicyResolver deliveryPolicyResolver = null, + SyncSequenceTracker syncSequenceTracker = null, + Func transportFactory = null) + { + ValidateDualPortConfiguration(reliablePort, syncPort); + + transportFactory ??= static port => new KcpTransport(port); + + var reliableTransport = transportFactory(reliablePort) + ?? throw new InvalidOperationException("Reliable transport factory returned null."); + var syncTransport = syncPort.HasValue + ? transportFactory(syncPort.Value) + : null; + + if (syncPort.HasValue && syncTransport == null) + { + throw new InvalidOperationException("Sync transport factory returned null."); + } + + return new ServerNetworkHost( + reliableTransport, + dispatcher, + reconnectPolicy, + utcNowProvider, + syncTransport, + deliveryPolicyResolver, + syncSequenceTracker); + } + + private static void ValidateDualPortConfiguration(int reliablePort, int? syncPort) + { + if (reliablePort <= 0) + { + throw new ArgumentOutOfRangeException(nameof(reliablePort), "Reliable port must be positive."); + } + + if (!syncPort.HasValue) + { + return; + } + + if (syncPort.Value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(syncPort), "Sync port must be positive."); + } + + if (syncPort.Value == reliablePort) + { + throw new ArgumentException("Sync port must differ from reliable port.", nameof(syncPort)); + } + } + } +} diff --git a/Assets/Scripts/Network/NetworkApplication/NetworkIntegrationFactory.cs.meta b/Assets/Scripts/Network/NetworkApplication/NetworkIntegrationFactory.cs.meta new file mode 100644 index 0000000..edcf3c2 --- /dev/null +++ b/Assets/Scripts/Network/NetworkApplication/NetworkIntegrationFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bfefd18b5face1d4496541e42b3a011b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NetworkManager.cs b/Assets/Scripts/NetworkManager.cs index 3f84599..3e6eddc 100644 --- a/Assets/Scripts/NetworkManager.cs +++ b/Assets/Scripts/NetworkManager.cs @@ -3,13 +3,15 @@ using System.Net; using System.Threading.Tasks; using Network.Defines; using Network.NetworkApplication; -using Network.NetworkTransport; using UnityEngine; using Vector3 = UnityEngine.Vector3; public class NetworkManager : MonoBehaviour { private const int MaxNetworkMessagesPerFrame = 32; + private const string DefaultServerIp = "127.0.0.1"; + private const int DefaultReliablePort = 8080; + private const int DefaultSyncPort = 8081; public static NetworkManager Instance; private SharedNetworkRuntime _networkRuntime; @@ -18,6 +20,9 @@ public class NetworkManager : MonoBehaviour private Task _networkDrainTask = Task.CompletedTask; [SerializeField] private GameObject _wrongWindow; [SerializeField] private bool _enableNetworkDiagnosticsOverlay = true; + [SerializeField] private string _serverIp = DefaultServerIp; + [SerializeField] private int _reliablePort = DefaultReliablePort; + [SerializeField] private int _syncPort = DefaultSyncPort; private void Awake() { @@ -28,9 +33,13 @@ public class NetworkManager : MonoBehaviour private IEnumerator InitNetwork() { - var transport = new KcpTransport("127.0.0.1", 8080); var dispatcher = new MainThreadNetworkDispatcher(); - _networkRuntime = new SharedNetworkRuntime(transport, dispatcher); + int? syncPort = _syncPort > 0 ? _syncPort : null; + _networkRuntime = NetworkIntegrationFactory.CreateClientRuntime( + _serverIp, + _reliablePort, + dispatcher, + syncPort: syncPort); _networkRuntime.LifecycleChanged += HandleLifecycleChanged; var startTask = _networkRuntime.StartAsync(); @@ -227,4 +236,4 @@ public class NetworkManager : MonoBehaviour _networkRuntime.MessageManager.SendMessage(request, MessageType.LogoutRequest); Debug.Log($"Sent logout request to player {playerId}"); } -} \ No newline at end of file +} diff --git a/Assets/Tests/EditMode/Network/SharedNetworkFoundationTests.cs b/Assets/Tests/EditMode/Network/SharedNetworkFoundationTests.cs index bd47304..8cdf072 100644 --- a/Assets/Tests/EditMode/Network/SharedNetworkFoundationTests.cs +++ b/Assets/Tests/EditMode/Network/SharedNetworkFoundationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using Google.Protobuf; @@ -141,6 +142,89 @@ namespace Tests.EditMode.Network Assert.That(session.SessionManager.State, Is.EqualTo(ConnectionState.TransportConnected)); } + [Test] + public void NetworkIntegrationFactory_CreateClientRuntime_WithSyncPort_UsesDistinctTransportsPerLane() + { + var createdTransports = new Dictionary(); + var runtime = NetworkIntegrationFactory.CreateClientRuntime( + "127.0.0.1", + 8080, + new ImmediateNetworkMessageDispatcher(), + syncPort: 8081, + transportFactory: (serverIp, port) => + { + var transport = new FakeTransport(); + createdTransports.Add(port, transport); + return transport; + }); + var moveInput = new MoveInput + { + PlayerId = "shared-player", + Tick = 77, + MoveX = 1f + }; + + runtime.MessageManager.SendMessage(moveInput, MessageType.MoveInput); + runtime.MessageManager.SendMessage(new Heartbeat(), MessageType.Heartbeat); + + Assert.That(createdTransports.Keys, Is.EquivalentTo(new[] { 8080, 8081 })); + Assert.That(runtime.Transport, Is.SameAs(createdTransports[8080])); + Assert.That(runtime.SyncTransport, Is.SameAs(createdTransports[8081])); + Assert.That(createdTransports[8080].SendCallCount, Is.EqualTo(1)); + Assert.That(createdTransports[8081].SendCallCount, Is.EqualTo(1)); + } + + [Test] + public void NetworkIntegrationFactory_CreateServerHost_WithSyncPort_UsesDistinctTransportsPerLane() + { + var createdTransports = new Dictionary(); + var host = NetworkIntegrationFactory.CreateServerHost( + 9000, + syncPort: 9001, + transportFactory: port => + { + var transport = new FakeTransport(); + createdTransports.Add(port, transport); + return transport; + }); + var moveInput = new MoveInput + { + PlayerId = "server-player", + Tick = 88, + MoveY = 1f + }; + + host.MessageManager.SendMessage(moveInput, MessageType.MoveInput); + host.MessageManager.SendMessage(new Heartbeat(), MessageType.Heartbeat); + + Assert.That(createdTransports.Keys, Is.EquivalentTo(new[] { 9000, 9001 })); + Assert.That(host.Transport, Is.SameAs(createdTransports[9000])); + Assert.That(host.SyncTransport, Is.SameAs(createdTransports[9001])); + Assert.That(createdTransports[9000].SendCallCount, Is.EqualTo(1)); + Assert.That(createdTransports[9001].SendCallCount, Is.EqualTo(1)); + } + + [Test] + public void NetworkIntegrationFactory_CreateServerHost_WithoutSyncPort_PreservesSingleTransportFallback() + { + var reliableTransport = new FakeTransport(); + var host = NetworkIntegrationFactory.CreateServerHost( + 9000, + transportFactory: _ => reliableTransport); + var moveInput = new MoveInput + { + PlayerId = "fallback-player", + Tick = 99, + MoveX = -1f + }; + + host.MessageManager.SendMessage(moveInput, MessageType.MoveInput); + + Assert.That(host.Transport, Is.SameAs(reliableTransport)); + Assert.That(host.SyncTransport, Is.Null); + Assert.That(reliableTransport.SendCallCount, Is.EqualTo(1)); + } + private static byte[] BuildEnvelope(MessageType type, IMessage payload) { return new Envelope diff --git a/TODO.md b/TODO.md index e595726..6c9d9df 100644 --- a/TODO.md +++ b/TODO.md @@ -117,15 +117,15 @@ Acceptance: ### 9. Wire Dual Transports In The Integration Layer -- [ ] Update the client integration entry point, likely [`Assets/Scripts/NetworkManager.cs`](D:/Learn/GameLearn/UnityProjects/NetworkFW/Assets/Scripts/NetworkManager.cs) -- [ ] Update the server startup integration point -- [ ] Instantiate one reliable transport and one sync transport -- [ ] Ensure runtime construction uses both transports instead of a single shared instance +- [x] Update the client integration entry point, likely [`Assets/Scripts/NetworkManager.cs`](D:/Learn/GameLearn/UnityProjects/NetworkFW/Assets/Scripts/NetworkManager.cs) +- [x] Update the server startup integration point +- [x] Instantiate one reliable transport and one sync transport +- [x] Ensure runtime construction uses both transports instead of a single shared instance Acceptance: -- [ ] Runtime uses logical dual-lane routing backed by two transport instances -- [ ] Logging or tests confirm movement/state traffic and reliable event traffic are separated +- [x] Runtime uses logical dual-lane routing backed by two transport instances +- [x] Logging or tests confirm movement/state traffic and reliable event traffic are separated ### 10. Build And Test diff --git a/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/.openspec.yaml b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/.openspec.yaml new file mode 100644 index 0000000..65bf7c9 --- /dev/null +++ b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-28 diff --git a/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/design.md b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/design.md new file mode 100644 index 0000000..0bc2881 --- /dev/null +++ b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/design.md @@ -0,0 +1,55 @@ +## Context + +The shared networking layer already separates reliable messaging from sync-heavy traffic conceptually, but the runtime integration path still tends to compose a single transport dependency and leaves lane selection to host-specific setup. This makes the dual-transport design incomplete: shared services can express sync intent, yet composition can still collapse both lanes into one implicit path. The repository also requires preserving the existing client single-session flow and avoiding Unity-specific dependencies in shared networking code. + +## Goals / Non-Goals + +**Goals:** +- Allow integration-layer composition to pass both reliable and sync transports into shared networking services. +- Keep the single-transport path valid by falling back to the reliable transport when no dedicated sync transport is configured. +- Centralize lane selection inside shared networking/session code so client and server hosts follow the same wiring rules. +- Add regression coverage for both client single-session and server multi-session composition paths. + +**Non-Goals:** +- Redesign transport implementations or message schemas. +- Introduce Unity-only adapters into `Assets/Scripts/Network/`. +- Change message delivery policy beyond what is required to connect existing sync traffic to the proper transport. + +## Decisions + +### Decision: Treat sync transport as an optional secondary dependency +The integration layer will accept a primary reliable transport and an optional sync transport. Shared services will retain a deterministic fallback to the primary transport when the secondary dependency is absent. + +This preserves existing callers and lets the change land without forcing all hosts to upgrade in one step. + +Alternative considered: require dual transports everywhere. Rejected because it would break the current single-session client setup and create unnecessary migration pressure. + +### Decision: Keep transport ownership at session/runtime composition boundaries +Session managers, message managers, or equivalent composition roots should receive both transport references during construction so downstream routing logic can stay host-agnostic. + +This keeps transport selection in shared code and prevents Unity or host bootstrapping layers from re-implementing routing rules. + +Alternative considered: inject a host-side selector callback. Rejected because it spreads transport policy across hosts and makes regression coverage weaker. + +### Decision: Encode fallback behavior in requirements and tests +The change will specify that sync-routed traffic uses the sync transport when available and otherwise uses the primary reliable transport. Tests should cover both client single-session and server multi-session variants. + +Alternative considered: rely on implementation comments only. Rejected because the fallback contract is easy to regress during future refactors. + +## Risks / Trade-offs + +- [Risk] Integration constructors may grow more complex with optional transport parameters. → Mitigation: keep the extra dependency scoped to composition roots and default the sync transport explicitly. +- [Risk] Hosts may accidentally provide mismatched transport instances across session types. → Mitigation: express wiring requirements in specs and add regression tests for composition paths. +- [Risk] Existing tests may only cover single-session client behavior. → Mitigation: add server multi-session coverage as part of the task list. + +## Migration Plan + +1. Extend composition APIs to accept the optional sync transport without breaking existing call sites. +2. Update shared routing/session initialization to store and use both dependencies. +3. Add or adjust edit-mode tests for fallback and dedicated sync-lane behavior. +4. Rollback, if needed, by removing the optional sync transport wiring while retaining the original single-transport constructor path. + +## Open Questions + +- Which concrete integration types currently own transport composition for client and server entry points? +- Are there any message categories besides sync traffic that should explicitly target the secondary transport at composition time? diff --git a/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/proposal.md b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/proposal.md new file mode 100644 index 0000000..175b175 --- /dev/null +++ b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/proposal.md @@ -0,0 +1,26 @@ +## Why + +The networking stack already distinguishes between reliable gameplay messaging and high-frequency sync traffic, but the integration layer still assumes a single transport path in its runtime wiring. Step 9 is needed now to expose the dual-transport design at composition time so hosts can route each lane consistently without breaking the existing single-session client path. + +## What Changes + +- Wire the integration layer so session/runtime composition can accept both a primary reliable transport and a sync transport. +- Preserve backward-compatible behavior when only the primary transport is provided by continuing to use the reliable path for all traffic that lacks a dedicated sync lane. +- Update message/session integration contracts so transport selection is resolved in shared networking code rather than by host-specific call sites. +- Add regression coverage for client single-session and server multi-session integration paths that depend on dual-transport wiring. + +## Capabilities + +### New Capabilities +- None. + +### Modified Capabilities +- `shared-network-foundation`: Extend the shared composition contract so network managers and related services can be constructed with dual transports while preserving the existing single-transport fallback. +- `network-session-lifecycle`: Update runtime/session wiring requirements so sessions initialize and retain both reliable and sync transport dependencies where available. +- `network-sync-strategy`: Require integration-layer routing to connect sync traffic to the dedicated sync transport instead of relying on host-side manual wiring. + +## Impact + +- Affected code under `Assets/Scripts/Network/` for integration/composition, session initialization, and transport-aware message dispatch. +- Edit-mode regression tests under `Assets/Tests/EditMode/Network/`. +- No Unity-specific dependency changes; Unity adapters should remain outside shared networking code. diff --git a/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/specs/network-session-lifecycle/spec.md b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/specs/network-session-lifecycle/spec.md new file mode 100644 index 0000000..e52b04b --- /dev/null +++ b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/specs/network-session-lifecycle/spec.md @@ -0,0 +1,12 @@ +## ADDED Requirements + +### Requirement: Sessions retain dual transport wiring +Session lifecycle components SHALL initialize session-scoped networking services with both the primary reliable transport and the optional sync transport supplied by the integration layer. + +#### Scenario: Client single-session initialization with dual transports +- **WHEN** the client integration path creates a single session and a sync transport is configured +- **THEN** the session-scoped services SHALL retain both transport references for subsequent message routing + +#### Scenario: Server multi-session initialization with fallback transport +- **WHEN** the server integration path creates session-scoped services without a dedicated sync transport +- **THEN** each session SHALL continue to initialize successfully and SHALL use the primary reliable transport as the fallback lane diff --git a/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/specs/network-sync-strategy/spec.md b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/specs/network-sync-strategy/spec.md new file mode 100644 index 0000000..2e77d25 --- /dev/null +++ b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/specs/network-sync-strategy/spec.md @@ -0,0 +1,12 @@ +## ADDED Requirements + +### Requirement: Integration wiring enforces sync lane selection +The networking stack SHALL route sync-designated traffic through the sync transport when the integration layer provides one, and SHALL fall back to the primary reliable transport when it does not. + +#### Scenario: Dedicated sync transport available +- **WHEN** sync-designated traffic is sent from a session whose integration wiring includes a sync transport +- **THEN** the traffic SHALL be dispatched on the sync transport instead of the primary reliable transport + +#### Scenario: Dedicated sync transport unavailable +- **WHEN** sync-designated traffic is sent from a session whose integration wiring does not include a sync transport +- **THEN** the traffic SHALL be dispatched on the primary reliable transport without failing session operation diff --git a/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/specs/shared-network-foundation/spec.md b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/specs/shared-network-foundation/spec.md new file mode 100644 index 0000000..9520d48 --- /dev/null +++ b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/specs/shared-network-foundation/spec.md @@ -0,0 +1,12 @@ +## ADDED Requirements + +### Requirement: Shared network composition accepts dual transports +The shared networking composition layer SHALL allow construction of network managers and related shared services with a primary reliable transport and an optional sync transport. + +#### Scenario: Integration receives both transports +- **WHEN** a host composes the shared networking stack with both a reliable transport and a sync transport +- **THEN** the shared composition path SHALL retain both dependencies for downstream routing and session services + +#### Scenario: Integration receives only one transport +- **WHEN** a host composes the shared networking stack with only the reliable transport +- **THEN** the shared composition path SHALL remain valid and SHALL treat the reliable transport as the fallback lane for traffic without a dedicated secondary transport diff --git a/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/tasks.md b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/tasks.md new file mode 100644 index 0000000..ae572c6 --- /dev/null +++ b/openspec/changes/archive/2026-03-28-wire-dual-transports-integration-layer/tasks.md @@ -0,0 +1,17 @@ +## 1. Extend Integration Composition + +- [x] 1.1 Identify the shared integration/composition entry points that currently construct networking services with a single transport. +- [x] 1.2 Update the relevant constructors or factory methods to accept an optional sync transport alongside the primary reliable transport. +- [x] 1.3 Preserve backward-compatible call paths so existing single-transport composition still builds and defaults correctly. + +## 2. Wire Dual Transports Through Session Services + +- [x] 2.1 Update session-scoped networking services to retain both transport references provided by the integration layer. +- [x] 2.2 Route sync-designated traffic to the sync transport when present and fall back to the reliable transport otherwise. +- [x] 2.3 Ensure the shared wiring keeps host-specific transport policy out of Unity-only or integration call sites. + +## 3. Verify Regression Coverage + +- [x] 3.1 Add or update edit-mode tests for client single-session composition with both reliable and sync transports. +- [x] 3.2 Add or update edit-mode tests for server multi-session composition when no dedicated sync transport is provided. +- [x] 3.3 Run `dotnet build Network.EditMode.Tests.csproj -v minimal` and `dotnet test Network.EditMode.Tests.csproj --no-build -v minimal` after implementation. diff --git a/openspec/specs/network-session-lifecycle/spec.md b/openspec/specs/network-session-lifecycle/spec.md index 92fd500..d5f9c03 100644 --- a/openspec/specs/network-session-lifecycle/spec.md +++ b/openspec/specs/network-session-lifecycle/spec.md @@ -41,4 +41,15 @@ The shared networking core SHALL manage timeout detection, disconnect transition #### Scenario: Login failure is distinct from transport disconnect - **WHEN** authentication or login fails while the transport session is still active - **THEN** the shared lifecycle reports a login-failed state for that managed session -- **THEN** hosts can handle that failure separately from a transport disconnect or heartbeat timeout \ No newline at end of file +- **THEN** hosts can handle that failure separately from a transport disconnect or heartbeat timeout + +### Requirement: Sessions retain dual transport wiring +Session lifecycle components SHALL initialize session-scoped networking services with both the primary reliable transport and the optional sync transport supplied by the integration layer. + +#### Scenario: Client single-session initialization with dual transports +- **WHEN** the client integration path creates a single session and a sync transport is configured +- **THEN** the session-scoped services SHALL retain both transport references for subsequent message routing + +#### Scenario: Server multi-session initialization with fallback transport +- **WHEN** the server integration path creates session-scoped services without a dedicated sync transport +- **THEN** each session SHALL continue to initialize successfully and SHALL use the primary reliable transport as the fallback lane diff --git a/openspec/specs/network-sync-strategy/spec.md b/openspec/specs/network-sync-strategy/spec.md index e8adb61..17e32f6 100644 --- a/openspec/specs/network-sync-strategy/spec.md +++ b/openspec/specs/network-sync-strategy/spec.md @@ -59,4 +59,15 @@ The shared networking core SHALL process server-tick or clock-synchronization sa #### Scenario: Hosts can consume smoothed clock data for prediction - **WHEN** prediction or reconciliation code needs the current server-time estimate - **THEN** it reads that estimate from the clock-sync strategy or state object -- **THEN** it does not query `SessionManager` for authoritative clock ownership \ No newline at end of file +- **THEN** it does not query `SessionManager` for authoritative clock ownership + +### Requirement: Integration wiring enforces sync lane selection +The networking stack SHALL route sync-designated traffic through the sync transport when the integration layer provides one, and SHALL fall back to the primary reliable transport when it does not. + +#### Scenario: Dedicated sync transport available +- **WHEN** sync-designated traffic is sent from a session whose integration wiring includes a sync transport +- **THEN** the traffic SHALL be dispatched on the sync transport instead of the primary reliable transport + +#### Scenario: Dedicated sync transport unavailable +- **WHEN** sync-designated traffic is sent from a session whose integration wiring does not include a sync transport +- **THEN** the traffic SHALL be dispatched on the primary reliable transport without failing session operation diff --git a/openspec/specs/shared-network-foundation/spec.md b/openspec/specs/shared-network-foundation/spec.md index b81caef..94b4577 100644 --- a/openspec/specs/shared-network-foundation/spec.md +++ b/openspec/specs/shared-network-foundation/spec.md @@ -69,4 +69,15 @@ The shared network foundation SHALL include host-agnostic session lifecycle orch #### Scenario: Server host composes shared foundation with multi-session orchestration - **WHEN** a non-Unity server host constructs the runtime networking stack for multiple remote peers - **THEN** it uses the shared transport and message-routing foundation together with shared multi-session lifecycle orchestration -- **THEN** server-specific cleanup, admission, and gameplay reactions stay in the server host adapter rather than forking the shared lifecycle contract \ No newline at end of file +- **THEN** server-specific cleanup, admission, and gameplay reactions stay in the server host adapter rather than forking the shared lifecycle contract + +### Requirement: Shared network composition accepts dual transports +The shared networking composition layer SHALL allow construction of network managers and related shared services with a primary reliable transport and an optional sync transport. + +#### Scenario: Integration receives both transports +- **WHEN** a host composes the shared networking stack with both a reliable transport and a sync transport +- **THEN** the shared composition path SHALL retain both dependencies for downstream routing and session services + +#### Scenario: Integration receives only one transport +- **WHEN** a host composes the shared networking stack with only the reliable transport +- **THEN** the shared composition path SHALL remain valid and SHALL treat the reliable transport as the fallback lane for traffic without a dedicated secondary transport