fix(interaction): bind selection to live incarnations

Carry local WorldEntity identity through render hits, lighting pulses, and deferred movement actions so GUID reuse cannot target a replacement. Reset all session-owned selection and ItemHolder state and prevent combat auto-target during teardown.
This commit is contained in:
Erik 2026-07-21 07:07:58 +02:00
parent c271383714
commit 047a4c83b5
19 changed files with 374 additions and 42 deletions

View file

@ -10,28 +10,28 @@ public sealed class RetailSelectionLightingPulseTests
double now = 10d;
var pulse = new RetailSelectionLightingPulse(() => now);
pulse.Start(0x5000_1234u);
AssertLighting(pulse, 0x5000_1234u, RetailSelectionLighting.High);
pulse.Start(0x5000_1234u, 1234u);
AssertLighting(pulse, 0x5000_1234u, 1234u, RetailSelectionLighting.High);
now = 10.199;
pulse.Tick();
AssertLighting(pulse, 0x5000_1234u, RetailSelectionLighting.High);
AssertLighting(pulse, 0x5000_1234u, 1234u, RetailSelectionLighting.High);
now = 10.2;
pulse.Tick();
AssertLighting(pulse, 0x5000_1234u, RetailSelectionLighting.Low);
AssertLighting(pulse, 0x5000_1234u, 1234u, RetailSelectionLighting.Low);
now = 10.4;
pulse.Tick();
AssertLighting(pulse, 0x5000_1234u, RetailSelectionLighting.High);
AssertLighting(pulse, 0x5000_1234u, 1234u, RetailSelectionLighting.High);
now = 10.6;
pulse.Tick();
AssertLighting(pulse, 0x5000_1234u, RetailSelectionLighting.Low);
AssertLighting(pulse, 0x5000_1234u, 1234u, RetailSelectionLighting.Low);
now = 10.8;
pulse.Tick();
Assert.False(pulse.TryGet(0x5000_1234u, out _));
Assert.False(pulse.TryGet(0x5000_1234u, 1234u, out _));
}
[Fact]
@ -39,12 +39,12 @@ public sealed class RetailSelectionLightingPulseTests
{
double now = 1d;
var pulse = new RetailSelectionLightingPulse(() => now);
pulse.Start(0x5000_1234u);
pulse.Start(0x5000_1234u, 1234u);
now = 20d;
pulse.Tick();
AssertLighting(pulse, 0x5000_1234u, RetailSelectionLighting.Low);
AssertLighting(pulse, 0x5000_1234u, 1234u, RetailSelectionLighting.Low);
}
[Fact]
@ -52,24 +52,35 @@ public sealed class RetailSelectionLightingPulseTests
{
double now = 4d;
var pulse = new RetailSelectionLightingPulse(() => now);
pulse.Start(0x5000_0001u);
pulse.Start(0x5000_0001u, 1u);
now = 4.2;
pulse.Tick();
AssertLighting(pulse, 0x5000_0001u, RetailSelectionLighting.Low);
AssertLighting(pulse, 0x5000_0001u, 1u, RetailSelectionLighting.Low);
pulse.Start(0x5000_0002u);
pulse.Start(0x5000_0002u, 2u);
Assert.False(pulse.TryGet(0x5000_0001u, out _));
AssertLighting(pulse, 0x5000_0002u, RetailSelectionLighting.High);
Assert.False(pulse.TryGet(0x5000_0001u, 1u, out _));
AssertLighting(pulse, 0x5000_0002u, 2u, RetailSelectionLighting.High);
}
[Fact]
public void GuidReuseDoesNotTransferPulseToReplacementIncarnation()
{
var pulse = new RetailSelectionLightingPulse(() => 1d);
pulse.Start(0x5000_0001u, 10u);
Assert.False(pulse.TryGet(0x5000_0001u, 11u, out _));
AssertLighting(pulse, 0x5000_0001u, 10u, RetailSelectionLighting.High);
}
private static void AssertLighting(
RetailSelectionLightingPulse pulse,
uint serverGuid,
uint localEntityId,
RetailSelectionLighting expected)
{
Assert.True(pulse.TryGet(serverGuid, out var actual));
Assert.True(pulse.TryGet(serverGuid, localEntityId, out var actual));
Assert.Equal(expected, actual);
}
}