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
|
||||
Reference in New Issue
Block a user