Route EveUnity live providers through generic client

This commit is contained in:
Meta Vens 2026-07-09 23:13:17 +01:00
commit d21cfecaf5
6 changed files with 93 additions and 6 deletions

View file

@ -1021,7 +1021,13 @@ Recently cut:
`AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest` record, submits movement
through the advertised `gamecult.eve.command.v1` boundary, observes
provider-owned receipts, and refreshes from daemon snapshots without
importing Aetheria product authority into EveUnity. Aetheria also proves the
importing Aetheria product authority into EveUnity. The generic Unity
document-source wrapper now connects live provider sources through
`IEveUnitySceneProviderSurfaceDocumentConnection`, delegates provider refresh
through `IEveUnityProviderRefreshSource`, `Connect()` is idempotent for the
current snapshot, and `EveUnitySceneLiveProviderBridge` forwards receipts
while `EveUnityPlayableWorldLiveClient` owns refresh-on-terminal receipt
policy. Aetheria also proves the
generic Unity scene body, not only a recording sink:
`GenericEveUnityClientHostInstantiatesAetheriaDaemonWorldThroughProviderComponent`
mounts the public provider component through `EveUnityPlayableWorldClientHost`,

View file

@ -65,6 +65,7 @@ namespace GameCult.Eve.UnityScene
public sealed class EveUnitySceneLiveProviderBridgeComponent :
MonoBehaviour,
IEveUnitySceneProviderSurfaceDocumentSource,
IEveUnitySceneProviderSurfaceDocumentConnection,
IEveUnityPlayableWorldAssetManifestDocumentSource,
IEveUnitySceneCommandSink,
IEveUnitySceneCommandReceiptSource,
@ -179,6 +180,7 @@ namespace GameCult.Eve.UnityScene
public sealed class EveUnitySceneLiveProviderBridge :
IEveUnitySceneProviderSurfaceDocumentSource,
IEveUnitySceneProviderSurfaceDocumentConnection,
IEveUnityPlayableWorldAssetManifestDocumentSource,
IEveUnitySceneCommandSink,
IEveUnitySceneCommandReceiptSource,
@ -282,8 +284,6 @@ namespace GameCult.Eve.UnityScene
private void OnCommandReceiptAvailable(EveUnitySceneCommandReceipt receipt)
{
if (receipt == null) throw new ArgumentNullException(nameof(receipt));
if (receipt.ShouldRefreshProviderSurface)
_transport.Refresh();
ReceiptAvailable?.Invoke(receipt);
}
}

View file

@ -19,6 +19,8 @@ namespace GameCult.Eve.UnityScene
void Connect();
void Refresh();
void Disconnect();
}
@ -36,6 +38,13 @@ namespace GameCult.Eve.UnityScene
event Action<EveUnitySceneProviderSurfaceDocument> DocumentAvailable;
}
public interface IEveUnitySceneProviderSurfaceDocumentConnection
{
void Connect();
void Disconnect();
}
public sealed class EveUnitySceneProviderSurfaceDocument
{
public EveUnitySceneProviderSurfaceDocument(
@ -71,12 +80,16 @@ namespace GameCult.Eve.UnityScene
public sealed class EveUnitySceneProviderSurfaceDocumentSource : IEveUnitySceneProviderSurfaceSource, IDisposable
{
private readonly IEveUnitySceneProviderSurfaceDocumentSource _documentSource;
private readonly IEveUnitySceneProviderSurfaceDocumentConnection? _documentConnection;
private readonly IEveUnityProviderRefreshSource? _refreshSource;
private bool _connected;
public EveUnitySceneProviderSurfaceDocumentSource(
IEveUnitySceneProviderSurfaceDocumentSource documentSource)
{
_documentSource = documentSource ?? throw new ArgumentNullException(nameof(documentSource));
_documentConnection = documentSource as IEveUnitySceneProviderSurfaceDocumentConnection;
_refreshSource = documentSource as IEveUnityProviderRefreshSource;
CurrentSnapshot = _documentSource.CurrentDocument.ToSnapshot();
}
@ -96,14 +109,21 @@ namespace GameCult.Eve.UnityScene
return;
_documentSource.DocumentAvailable += OnDocumentAvailable;
_documentConnection?.Connect();
_connected = true;
}
public void Refresh()
{
_refreshSource?.Refresh();
}
public void Disconnect()
{
if (!_connected)
return;
_documentConnection?.Disconnect();
_documentSource.DocumentAvailable -= OnDocumentAvailable;
_connected = false;
}
@ -166,11 +186,18 @@ namespace GameCult.Eve.UnityScene
_connected = true;
}
if (HasAppliedCurrentSnapshot())
return _session.ActiveProjection!;
return ApplySnapshot(_surfaceSource.CurrentSnapshot);
}
public EveUnitySceneProjection Refresh()
{
_surfaceSource.Refresh();
if (HasAppliedCurrentSnapshot())
return _session.ActiveProjection!;
return ApplySnapshot(_surfaceSource.CurrentSnapshot);
}
@ -245,6 +272,17 @@ namespace GameCult.Eve.UnityScene
return projection;
}
private bool HasAppliedCurrentSnapshot()
{
var projection = _session.ActiveProjection;
if (projection == null)
return false;
var snapshot = _surfaceSource.CurrentSnapshot;
return _session.ActiveVersion == snapshot.Version &&
string.Equals(_session.ActiveSourcePointer, snapshot.SourcePointer, StringComparison.Ordinal);
}
private void OnSnapshotAvailable(EveUnitySceneProviderSurfaceSnapshot snapshot)
{
ApplySnapshot(snapshot);

View file

@ -372,7 +372,6 @@ namespace GameCult.Eve.UnityScene.Tests
using var bridge = new EveUnitySceneLiveProviderBridge(transport);
var sceneSink = new FakePlayableWorldSceneSink();
bridge.Connect();
using var runtime = new EveUnityPlayableWorldRuntime(
bridge,
bridge,
@ -1407,6 +1406,10 @@ namespace GameCult.Eve.UnityScene.Tests
{
}
public void Refresh()
{
}
public void Disconnect()
{
}

View file

@ -24,6 +24,8 @@ $aetheriaGameSurfaceBuilder = Join-Path $AetheriaRoot "Packages\org.gamecult.aet
$aetheriaDaemonOperationsClient = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonOperationsClient.cs"
$aetheriaDaemonDocuments = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonDocuments.cs"
$aetheriaRuntimeVerseClient = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeVerseClient.cs"
$eveUnitySceneProviderConnection = Join-Path $eveRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneProviderConnection.cs"
$eveUnitySceneLiveProviderBridge = Join-Path $eveRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneLiveProviderBridge.cs"
if (-not (Test-Path $manifestPath)) {
throw "Aetheria Unity package manifest not found: $manifestPath"
}
@ -69,6 +71,12 @@ if (-not (Test-Path $aetheriaDaemonDocuments)) {
if (-not (Test-Path $aetheriaRuntimeVerseClient)) {
throw "Aetheria runtime Verse client not found: $aetheriaRuntimeVerseClient"
}
if (-not (Test-Path $eveUnitySceneProviderConnection)) {
throw "EveUnity scene provider connection not found: $eveUnitySceneProviderConnection"
}
if (-not (Test-Path $eveUnitySceneLiveProviderBridge)) {
throw "EveUnity scene live provider bridge not found: $eveUnitySceneLiveProviderBridge"
}
$manifest = Get-Content -Raw -LiteralPath $manifestPath | ConvertFrom-Json
$requiredDependencies = @{
@ -120,6 +128,35 @@ foreach ($compileItem in $requiredSceneCompileItems) {
}
}
$sceneProviderConnection = Get-Content -Raw -LiteralPath $eveUnitySceneProviderConnection
foreach ($symbol in @(
"IEveUnitySceneProviderSurfaceDocumentConnection",
"_documentConnection?.Connect();",
"_documentConnection?.Disconnect();",
"_refreshSource?.Refresh();",
"HasAppliedCurrentSnapshot()",
"_session.ActiveSourcePointer"
)) {
if (-not $sceneProviderConnection.Contains($symbol)) {
throw "EveUnity scene provider connection missing live-source lifecycle symbol: $symbol"
}
}
$sceneLiveProviderBridge = Get-Content -Raw -LiteralPath $eveUnitySceneLiveProviderBridge
foreach ($symbol in @(
"IEveUnitySceneProviderSurfaceDocumentConnection",
"IEveUnitySceneLiveProviderTransport",
"CommandReceiptAvailable += OnCommandReceiptAvailable",
"ReceiptAvailable?.Invoke(receipt);"
)) {
if (-not $sceneLiveProviderBridge.Contains($symbol)) {
throw "EveUnity scene live provider bridge missing transport receipt/lifecycle symbol: $symbol"
}
}
if ($sceneLiveProviderBridge.Contains("if (receipt.ShouldRefreshProviderSurface)")) {
throw "EveUnity scene live provider bridge must forward receipts; playable-world live client owns refresh policy"
}
$runtimePackage = Get-Content -Raw -LiteralPath $aetheriaEveRuntimePackage
foreach ($dependency in @(
'"org.gamecult.eve.unity-scene": "0.1.0"',

View file

@ -106,14 +106,14 @@ foreach ($symbol in @("EveUnitySceneClientSession", "EveUnitySceneProviderSurfac
}
$providerConnectionSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneProviderConnection.cs") -Raw
foreach ($symbol in @("IEveUnitySceneProviderSurfaceSource", "IEveUnitySceneCommandSink", "IEveUnitySceneProviderSurfaceDocumentSource", "EveUnitySceneProviderSurfaceDocument", "EveUnitySceneProviderSurfaceDocumentSource", "ToSnapshot", "DocumentAvailable", "EveUnitySceneProviderConnection", "SnapshotAvailable", "CurrentSnapshot", "Connect", "Refresh", "SubmitMoveIntent", "SubmitMoveVectorIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Disconnect")) {
foreach ($symbol in @("IEveUnitySceneProviderSurfaceSource", "IEveUnitySceneCommandSink", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnitySceneProviderSurfaceDocumentConnection", "IEveUnityProviderRefreshSource", "EveUnitySceneProviderSurfaceDocument", "EveUnitySceneProviderSurfaceDocumentSource", "ToSnapshot", "DocumentAvailable", "EveUnitySceneProviderConnection", "SnapshotAvailable", "CurrentSnapshot", "Connect", "Refresh", "_refreshSource?.Refresh();", "SubmitMoveIntent", "SubmitMoveVectorIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Disconnect", "HasAppliedCurrentSnapshot")) {
if (-not $providerConnectionSource.Contains($symbol)) {
throw "EveUnity scene provider connection missing symbol: $symbol"
}
}
$liveProviderBridgeSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneLiveProviderBridge.cs") -Raw
foreach ($symbol in @("IEveUnitySceneLiveProviderTransport", "EveUnitySceneLiveProviderTransportBehaviour", "EveUnitySceneLiveProviderBridgeComponent", "EveUnitySceneLiveProviderBridge", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandSink", "IEveUnitySceneCommandReceiptSource", "IEveUnityProviderRefreshSource", "SubmitCommand", "CommandReceiptAvailable", "ShouldRefreshProviderSurface", "transportBehaviour")) {
foreach ($symbol in @("IEveUnitySceneLiveProviderTransport", "EveUnitySceneLiveProviderTransportBehaviour", "EveUnitySceneLiveProviderBridgeComponent", "EveUnitySceneLiveProviderBridge", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnitySceneProviderSurfaceDocumentConnection", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandSink", "IEveUnitySceneCommandReceiptSource", "IEveUnityProviderRefreshSource", "SubmitCommand", "CommandReceiptAvailable", "ReceiptAvailable?.Invoke(receipt);", "transportBehaviour")) {
if (-not $liveProviderBridgeSource.Contains($symbol)) {
throw "EveUnity scene live provider bridge missing symbol: $symbol"
}
@ -121,6 +121,9 @@ foreach ($symbol in @("IEveUnitySceneLiveProviderTransport", "EveUnitySceneLiveP
if ($liveProviderBridgeSource.Contains("Aetheria")) {
throw "EveUnity scene live provider bridge must remain provider-agnostic and cannot reference Aetheria"
}
if ($liveProviderBridgeSource.Contains("if (receipt.ShouldRefreshProviderSurface)")) {
throw "EveUnity scene live provider bridge must forward receipts; playable-world live client owns refresh policy"
}
$liveClientSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldLiveClient.cs") -Raw
foreach ($symbol in @("EveUnityPlayableWorldLiveClient", "IEveUnitySceneCommandReceiptSource", "EveUnitySceneCommandReceipt", "EveUnitySceneProviderConnection", "EveUnityPlayableWorldPresenter", "ActiveWorld", "LastPresentation", "LastReceipt", "ReceiptAvailable", "Connect", "Refresh", "SubmitMoveIntent", "SubmitMoveVectorIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "ProjectionUpdated", "ShouldRefreshProviderSurface", "IsProviderOwned", "Disconnect")) {