LANTERN P1 step 8: light-as-territory relevancy prototype (★)
Per connection, marks every ENEMY ghost OUTSIDE the player's lamp radius IRRELEVANT (monsters in the dark aren't replicated — the gamma test). Modelled on the proven RegionRelevancySystem: SetIsIrrelevant so players/global ghosts stay relevant for free. Shared-set discipline — RegionRelevancySystem is the SOLE clearer (runs every tick); LightRelevancySystem is [UpdateAfter] it and only ADDs light hides via TryAdd (no clear; defensive against a region+light overlap dup). Gated on GymTag (LANTERN-only; legacy region relevancy untouched). Pure decision in LightRelevancyMath (+3 EditMode tests, 500 green). Per-player dim/bright (variable radius) + the L3 gamma-test verification are follow-ups; the ★adversarial netcode review is recommended before this ships. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
|||||||
|
using ProjectM.Simulation;
|
||||||
|
using Unity.Burst;
|
||||||
|
using Unity.Collections;
|
||||||
|
using Unity.Entities;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
using Unity.NetCode;
|
||||||
|
using Unity.Transforms;
|
||||||
|
|
||||||
|
namespace ProjectM.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// LANTERN "light is territory" relevancy prototype (Build Spec Phase 1 step 8): per connection, marks every
|
||||||
|
/// ENEMY ghost OUTSIDE that player's lamp radius IRRELEVANT — so monsters in the dark aren't replicated (the
|
||||||
|
/// gamma test: "nothing to see"). Players + untagged/global ghosts stay relevant for free (SetIsIrrelevant).
|
||||||
|
/// <para>
|
||||||
|
/// SHARED-SET DISCIPLINE: <see cref="RegionRelevancySystem"/> is the SOLE clearer of the <c>GhostRelevancy</c>
|
||||||
|
/// set — its OnUpdate runs every tick (requires only <c>GhostRelevancy</c>), setting the mode + clearing +
|
||||||
|
/// adding its region hides. This system runs <b>[UpdateAfter(RegionRelevancySystem)]</b> and only ADDS light
|
||||||
|
/// hides (never clears) via <c>TryAdd</c> (defensive against a ghost hidden by BOTH region and light for one
|
||||||
|
/// connection — a duplicate <c>Add</c> would throw). Gated on <see cref="GymTag"/> so it runs ONLY in the
|
||||||
|
/// LANTERN gym; the legacy base/expedition game keeps region relevancy alone. Runs in
|
||||||
|
/// <see cref="GhostSimulationSystemGroup"/> before GhostSendSystem reads the set. Per-player dim/bright (a
|
||||||
|
/// variable radius) is the tuning follow-up; the prototype uses <see cref="LightRelevancyMath.LampRadiusDefault"/>.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
[BurstCompile]
|
||||||
|
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||||
|
[UpdateInGroup(typeof(GhostSimulationSystemGroup))]
|
||||||
|
[UpdateAfter(typeof(RegionRelevancySystem))]
|
||||||
|
public partial struct LightRelevancySystem : ISystem
|
||||||
|
{
|
||||||
|
[BurstCompile]
|
||||||
|
public void OnCreate(ref SystemState state)
|
||||||
|
{
|
||||||
|
state.RequireForUpdate<GhostRelevancy>();
|
||||||
|
state.RequireForUpdate<GymTag>(); // prototype: light-as-territory runs only in the LANTERN gym
|
||||||
|
}
|
||||||
|
|
||||||
|
[BurstCompile]
|
||||||
|
public void OnUpdate(ref SystemState state)
|
||||||
|
{
|
||||||
|
// Each in-game connection's player position (per-player lamp radius = the dim/bright follow-up; fixed here).
|
||||||
|
var connPos = new NativeHashMap<int, float3>(8, Allocator.Temp);
|
||||||
|
foreach (var (owner, lt) in
|
||||||
|
SystemAPI.Query<RefRO<GhostOwner>, RefRO<LocalTransform>>().WithAll<PlayerTag>())
|
||||||
|
connPos[owner.ValueRO.NetworkId] = lt.ValueRO.Position;
|
||||||
|
|
||||||
|
if (connPos.IsEmpty) { connPos.Dispose(); return; }
|
||||||
|
|
||||||
|
// The set is already mode=SetIsIrrelevant + cleared + region-populated THIS tick by RegionRelevancySystem.
|
||||||
|
ref var relevancy = ref SystemAPI.GetSingletonRW<GhostRelevancy>().ValueRW;
|
||||||
|
var set = relevancy.GhostRelevancySet;
|
||||||
|
var conns = connPos.GetKeyValueArrays(Allocator.Temp);
|
||||||
|
|
||||||
|
float rSq = LightRelevancyMath.LampRadiusDefault * LightRelevancyMath.LampRadiusDefault;
|
||||||
|
foreach (var (ghost, lt) in
|
||||||
|
SystemAPI.Query<RefRO<GhostInstance>, RefRO<LocalTransform>>().WithAll<EnemyTag>())
|
||||||
|
{
|
||||||
|
int ghostId = ghost.ValueRO.ghostId;
|
||||||
|
if (ghostId == 0) continue; // ghost id not assigned yet this tick
|
||||||
|
|
||||||
|
float3 gp = lt.ValueRO.Position;
|
||||||
|
for (int i = 0; i < conns.Keys.Length; i++)
|
||||||
|
{
|
||||||
|
if (LightRelevancyMath.IsHidden(conns.Values[i], gp, rSq))
|
||||||
|
set.TryAdd(new RelevantGhostForConnection { Connection = conns.Keys[i], Ghost = ghostId }, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conns.Dispose();
|
||||||
|
connPos.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e7c6c7ce249ae9b44821e998e0331a52
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace ProjectM.Simulation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Pure helpers for the LANTERN "light is territory" relevancy prototype (Build Spec Phase 1 step 8): an entity
|
||||||
|
/// outside a player's lamp radius isn't replicated to that player. The decision is a planar (XZ) distance test
|
||||||
|
/// so it's Burst-safe + unit-testable independent of the netcode <c>GhostRelevancy</c> set manipulation.
|
||||||
|
/// </summary>
|
||||||
|
public static class LightRelevancyMath
|
||||||
|
{
|
||||||
|
/// <summary>Prototype lamp radius (world units). Per-player dim/bright (a variable radius = the quiet/blind
|
||||||
|
/// trade) is the tuning follow-up; the prototype uses one fixed radius to prove the gamma test.</summary>
|
||||||
|
public const float LampRadiusDefault = 25f;
|
||||||
|
|
||||||
|
/// <summary>True when the ghost at <paramref name="ghostPos"/> is OUTSIDE the lamp centred on
|
||||||
|
/// <paramref name="playerPos"/> (planar XZ distance beyond the radius) — i.e. hidden from that player.</summary>
|
||||||
|
public static bool IsHidden(float3 playerPos, float3 ghostPos, float radiusSq)
|
||||||
|
=> math.distancesq(playerPos.xz, ghostPos.xz) > radiusSq;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e2224a38c42888d408d54961131dfff3
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using NUnit.Framework;
|
||||||
|
using ProjectM.Simulation;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace ProjectM.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for the LANTERN light-as-territory relevancy DECISION (LightRelevancyMath). The full per-connection
|
||||||
|
/// GhostRelevancy set population is netcode-world (the L3 gamma test); this pins the pure planar-distance rule.
|
||||||
|
/// </summary>
|
||||||
|
public class LightRelevancyMathTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void InsideLamp_IsVisible()
|
||||||
|
{
|
||||||
|
float r = LightRelevancyMath.LampRadiusDefault;
|
||||||
|
Assert.IsFalse(LightRelevancyMath.IsHidden(float3.zero, new float3(r - 1f, 0f, 0f), r * r), "just inside the radius = visible");
|
||||||
|
Assert.IsFalse(LightRelevancyMath.IsHidden(float3.zero, float3.zero, r * r), "at the player = visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OutsideLamp_IsHidden()
|
||||||
|
{
|
||||||
|
float r = LightRelevancyMath.LampRadiusDefault;
|
||||||
|
Assert.IsTrue(LightRelevancyMath.IsHidden(float3.zero, new float3(r + 1f, 0f, 0f), r * r), "just beyond the radius = hidden (nothing to see)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Planar_IgnoresY()
|
||||||
|
{
|
||||||
|
float r = 5f;
|
||||||
|
// Within the planar (XZ) radius but far in Y is still VISIBLE — top-down relevancy ignores height.
|
||||||
|
Assert.IsFalse(LightRelevancyMath.IsHidden(float3.zero, new float3(2f, 1000f, 2f), r * r), "Y is ignored (planar XZ test)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 14e936313eb9a864181228bcee28c321
|
||||||
Reference in New Issue
Block a user