Lower semantic render channels in Unity
This commit is contained in:
parent
890692e30f
commit
e451719520
8 changed files with 78 additions and 28 deletions
|
|
@ -18,6 +18,7 @@ drivers, asset-provider hooks, package release, runtime tests, and captures.
|
|||
- `gamecult.eve.surface.v1`
|
||||
- provider asset-manifest documents
|
||||
- provider command receipts
|
||||
- semantic render-channel inclusion and exclusion policy
|
||||
- Unity input and local presentation assets
|
||||
|
||||
## Outputs
|
||||
|
|
@ -29,15 +30,16 @@ drivers, asset-provider hooks, package release, runtime tests, and captures.
|
|||
|
||||
## Derived State
|
||||
|
||||
`ActiveWorld`, scene objects, transforms, markers, camera pose, asset caches,
|
||||
presentation counters, and receipt display state are projections. They are not
|
||||
world truth.
|
||||
`ActiveWorld`, scene objects, transforms, markers, camera pose, native
|
||||
render-channel layer mappings, asset caches, presentation counters, and receipt
|
||||
display state are projections. They are not world truth.
|
||||
|
||||
## Forbidden Writers
|
||||
|
||||
Unity transforms, input drivers, camera rigs, scene sinks, asset caches, and
|
||||
pending receipts may not mutate or simulate provider world state. Plugin
|
||||
projection shells may not implement Sai, Norn, or TeX semantics.
|
||||
projection shells may not implement Sai, Norn, or TeX semantics. Portable
|
||||
surfaces may not carry Unity layer numbers or camera culling masks.
|
||||
|
||||
## Shared Paths
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,14 @@ id or source index. Camera, selection, and provider UI adapters consume that
|
|||
registry instead of reconstructing provider gameplay objects. It owns
|
||||
presentation lookup only; simulation state and commands remain provider-owned.
|
||||
|
||||
World surfaces may exclude semantic render channels through the
|
||||
`excludedRenderChannels` world-root property. Runtime asset variants map those
|
||||
channel names to Unity layers with `renderChannel.<channel>.unityLayer`
|
||||
metadata. Surfaces never publish Unity layer numbers or camera masks: providers
|
||||
own which semantic content belongs in a view, while EveUnity owns the native
|
||||
layer mapping and subtracts only those layers from the camera's configured
|
||||
culling mask.
|
||||
|
||||
Retained provider feedback is lowered without becoming Unity authority.
|
||||
`EveUnityFeedbackPresenter` emits each new `feedback.event` identity once, and
|
||||
`EveUnityShotReceiptPresenter` does the same for `shot.receipt` trajectories.
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ namespace GameCult.Eve.UnityScene
|
|||
private readonly Dictionary<string, GameObject> _prefabs = new Dictionary<string, GameObject>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, UnityEngine.Object> _nativeAssets = new Dictionary<string, UnityEngine.Object>(StringComparer.Ordinal);
|
||||
private readonly List<AssetBundle> _assetBundles = new List<AssetBundle>();
|
||||
private readonly Dictionary<string, int> _cameraCullingMasks = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, int> _renderChannelLayers = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
private readonly ConcurrentQueue<object> _liveDocuments = new ConcurrentQueue<object>();
|
||||
private CultMeshNode? _node;
|
||||
private CultMeshSnapshotSession? _snapshot;
|
||||
|
|
@ -210,9 +210,9 @@ namespace GameCult.Eve.UnityScene
|
|||
? value : null;
|
||||
}
|
||||
|
||||
public bool TryGetCameraCullingMask(string viewId, out int cullingMask)
|
||||
public bool TryGetRenderChannelLayer(string channel, out int layer)
|
||||
{
|
||||
return _cameraCullingMasks.TryGetValue(viewId ?? "", out cullingMask);
|
||||
return _renderChannelLayers.TryGetValue(channel ?? "", out layer);
|
||||
}
|
||||
|
||||
private void EnsureOpen()
|
||||
|
|
@ -497,7 +497,7 @@ namespace GameCult.Eve.UnityScene
|
|||
_assetBundles.Clear();
|
||||
_prefabs.Clear();
|
||||
_nativeAssets.Clear();
|
||||
_cameraCullingMasks.Clear();
|
||||
_renderChannelLayers.Clear();
|
||||
var selected = catalog.Assets
|
||||
.Select(asset => new
|
||||
{
|
||||
|
|
@ -547,24 +547,20 @@ namespace GameCult.Eve.UnityScene
|
|||
|
||||
private void ReadCameraPolicies(IEnumerable<EveAssetVariant> variants)
|
||||
{
|
||||
_cameraCullingMasks.Clear();
|
||||
_renderChannelLayers.Clear();
|
||||
foreach (var variant in variants)
|
||||
{
|
||||
foreach (var pair in variant.Metadata)
|
||||
{
|
||||
const string prefix = "view.";
|
||||
const string suffix = ".excludeUnityLayers";
|
||||
const string prefix = "renderChannel.";
|
||||
const string suffix = ".unityLayer";
|
||||
if (!pair.Key.StartsWith(prefix, StringComparison.Ordinal) ||
|
||||
!pair.Key.EndsWith(suffix, StringComparison.Ordinal))
|
||||
continue;
|
||||
var viewId = pair.Key.Substring(prefix.Length, pair.Key.Length - prefix.Length - suffix.Length);
|
||||
var mask = -1;
|
||||
foreach (var token in (pair.Value ?? "").Split(','))
|
||||
{
|
||||
if (int.TryParse(token.Trim(), out var layer) && layer >= 0 && layer < 32)
|
||||
mask &= ~(1 << layer);
|
||||
}
|
||||
_cameraCullingMasks[viewId] = mask;
|
||||
var channel = pair.Key.Substring(prefix.Length, pair.Key.Length - prefix.Length - suffix.Length);
|
||||
if (!string.IsNullOrWhiteSpace(channel) &&
|
||||
int.TryParse(pair.Value, out var layer) && layer >= 0 && layer < 32)
|
||||
_renderChannelLayers[channel] = layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,10 +108,10 @@ namespace GameCult.Eve.UnityScene
|
|||
return _transport!.ResolveAsset(asset, assetType);
|
||||
}
|
||||
|
||||
public bool TryGetCameraCullingMask(string viewId, out int cullingMask)
|
||||
public bool TryGetRenderChannelLayer(string channel, out int layer)
|
||||
{
|
||||
EnsureTransport();
|
||||
return _transport!.TryGetCameraCullingMask(viewId, out cullingMask);
|
||||
return _transport!.TryGetRenderChannelLayer(channel, out layer);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace GameCult.Eve.UnityScene
|
|||
{
|
||||
public interface IEveUnityCameraRenderPolicySource
|
||||
{
|
||||
bool TryGetCameraCullingMask(string viewId, out int cullingMask);
|
||||
bool TryGetRenderChannelLayer(string channel, out int layer);
|
||||
}
|
||||
|
||||
public sealed class EveUnityPlayableWorldCameraRig : MonoBehaviour
|
||||
|
|
@ -58,10 +58,15 @@ namespace GameCult.Eve.UnityScene
|
|||
return false;
|
||||
|
||||
var cameraComponent = camera.GetComponent<Camera>();
|
||||
if (cameraComponent != null &&
|
||||
_renderPolicySource != null &&
|
||||
_renderPolicySource.TryGetCameraCullingMask(activeWorld.ViewId, out var cullingMask))
|
||||
if (cameraComponent != null && _renderPolicySource != null)
|
||||
{
|
||||
var cullingMask = cameraComponent.cullingMask;
|
||||
foreach (var channel in activeWorld.ExcludedRenderChannels)
|
||||
{
|
||||
if (_renderPolicySource.TryGetRenderChannelLayer(channel, out var layer) &&
|
||||
layer >= 0 && layer < 32)
|
||||
cullingMask &= ~(1 << layer);
|
||||
}
|
||||
cameraComponent.cullingMask = cullingMask;
|
||||
}
|
||||
var bounds = CalculateVisualBounds(player);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GameCult.Eve.Surface;
|
||||
using GameCult.Mesh;
|
||||
|
||||
|
|
@ -124,7 +125,8 @@ namespace GameCult.Eve.UnityScene
|
|||
worldRoot.GetProp("entityViewPointerId"),
|
||||
worldRoot.GetProp("entityViewSchema"),
|
||||
worldRoot.GetProp("zoneRenderPointerId"),
|
||||
worldRoot.GetProp("zoneRenderSchema"));
|
||||
worldRoot.GetProp("zoneRenderSchema"),
|
||||
ParseStringList(worldRoot.GetProp("excludedRenderChannels")));
|
||||
}
|
||||
|
||||
private static EveUnityPlayableWorldEntity BuildPlayableEntity(EveSurfaceComponent component)
|
||||
|
|
@ -198,6 +200,16 @@ namespace GameCult.Eve.UnityScene
|
|||
string.Equals(value, "1", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> ParseStringList(string value)
|
||||
{
|
||||
return (value ?? "")
|
||||
.Split(',')
|
||||
.Select(part => part.Trim())
|
||||
.Where(part => !string.IsNullOrWhiteSpace(part))
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static EveUnityScenePluginProjection? BuildPluginProjection(EveSurfaceComponent component)
|
||||
{
|
||||
if (SaiVisualNovelAdapter.CanProject(component))
|
||||
|
|
@ -324,7 +336,8 @@ namespace GameCult.Eve.UnityScene
|
|||
string entityViewPointerId = "",
|
||||
string entityViewSchema = "",
|
||||
string zoneRenderPointerId = "",
|
||||
string zoneRenderSchema = "")
|
||||
string zoneRenderSchema = "",
|
||||
IReadOnlyList<string>? excludedRenderChannels = null)
|
||||
{
|
||||
WorldRootId = worldRootId ?? "";
|
||||
StatePointerId = statePointerId ?? "";
|
||||
|
|
@ -342,6 +355,7 @@ namespace GameCult.Eve.UnityScene
|
|||
EntityViewSchema = entityViewSchema ?? "";
|
||||
ZoneRenderPointerId = zoneRenderPointerId ?? "";
|
||||
ZoneRenderSchema = zoneRenderSchema ?? "";
|
||||
ExcludedRenderChannels = excludedRenderChannels ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
public string WorldRootId { get; }
|
||||
|
|
@ -374,6 +388,8 @@ namespace GameCult.Eve.UnityScene
|
|||
|
||||
public string ActionCommand { get; }
|
||||
|
||||
public IReadOnlyList<string> ExcludedRenderChannels { get; }
|
||||
|
||||
public int EntityCount => Entities.Count;
|
||||
|
||||
public IReadOnlyList<EveUnityPlayableWorldEntity> Entities { get; }
|
||||
|
|
|
|||
|
|
@ -332,6 +332,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
Assert.That(projection.PlayableWorld.AssetManifest, Is.EqualTo("cultmesh://aetheria/assets/manifest"));
|
||||
Assert.That(projection.PlayableWorld.InputProfile, Is.EqualTo("arpg-third-person"));
|
||||
Assert.That(projection.PlayableWorld.CameraRig, Is.EqualTo("third-person-orbit"));
|
||||
Assert.That(projection.PlayableWorld.ExcludedRenderChannels, Is.EqualTo(new[] { "map" }));
|
||||
Assert.That(projection.PlayableWorld.PlayerEntityId, Is.EqualTo("player-vanguard"));
|
||||
Assert.That(projection.PlayableWorld.MovementCommand, Is.EqualTo("aetheria.daemon.move_intent"));
|
||||
Assert.That(projection.PlayableWorld.FocusCommand, Is.EqualTo("aetheria.daemon.focus"));
|
||||
|
|
@ -939,6 +940,8 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
var rig = hostObject.AddComponent<EveUnityPlayableWorldCameraRig>();
|
||||
rig.Host = host;
|
||||
rig.CameraTransform = cameraObject.transform;
|
||||
rig.RenderPolicySource = new FixedRenderChannelPolicy("map", 14);
|
||||
var camera = cameraObject.AddComponent<Camera>();
|
||||
|
||||
var player = rootObject.GetComponentInChildren<EveUnityPlayableWorldEntityMarker>();
|
||||
Assert.That(player, Is.Not.Null);
|
||||
|
|
@ -947,6 +950,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
largeVisual.transform.localScale = Vector3.one * 100f;
|
||||
|
||||
Assert.That(rig.ApplyRig(0f), Is.True);
|
||||
Assert.That(camera.cullingMask & (1 << 14), Is.Zero);
|
||||
Assert.That(cameraObject.transform.position.y, Is.GreaterThan(0f));
|
||||
Assert.That(Vector3.Distance(cameraObject.transform.position, largeVisual.transform.position), Is.GreaterThan(100f));
|
||||
}
|
||||
|
|
@ -1562,6 +1566,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
["assetManifest"] = "cultmesh://aetheria/assets/manifest",
|
||||
["inputProfile"] = "arpg-third-person",
|
||||
["cameraRig"] = "third-person-orbit",
|
||||
["excludedRenderChannels"] = "map",
|
||||
["playerEntityId"] = "player-vanguard",
|
||||
["movementCommand"] = "aetheria.daemon.move_intent",
|
||||
["focusCommand"] = "aetheria.daemon.focus",
|
||||
|
|
@ -2120,6 +2125,24 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
}
|
||||
}
|
||||
|
||||
private sealed class FixedRenderChannelPolicy : IEveUnityCameraRenderPolicySource
|
||||
{
|
||||
private readonly string _channel;
|
||||
private readonly int _layer;
|
||||
|
||||
public FixedRenderChannelPolicy(string channel, int layer)
|
||||
{
|
||||
_channel = channel;
|
||||
_layer = layer;
|
||||
}
|
||||
|
||||
public bool TryGetRenderChannelLayer(string channel, out int layer)
|
||||
{
|
||||
layer = _layer;
|
||||
return string.Equals(channel, _channel, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakePlayableWorldProviderComponent :
|
||||
MonoBehaviour,
|
||||
IEveUnitySceneProviderSurfaceDocumentSource,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "org.gamecult.eve.unity-scene",
|
||||
"displayName": "Eve Unity Scene Lowering",
|
||||
"version": "0.2.4",
|
||||
"version": "0.3.0",
|
||||
"unity": "2021.3",
|
||||
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
|
||||
"dependencies": {
|
||||
|
|
|
|||
Reference in a new issue