refactor(D.5.4): retire _liveEntityInfoByGuid; selection resolves from ClientObjectTable

The one weenie table now holds every object's name+type, so the redundant
Name+ItemType dictionary is gone (retail: one weenie_object_table).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-18 16:48:13 +02:00
parent 50cee50df1
commit a9d40addac

View file

@ -837,7 +837,6 @@ public sealed class GameWindow : IDisposable
/// keys the render list; this parallel dictionary keys by server guid. /// keys the render list; this parallel dictionary keys by server guid.
/// </summary> /// </summary>
private readonly Dictionary<uint, AcDream.Core.World.WorldEntity> _entitiesByServerGuid = new(); private readonly Dictionary<uint, AcDream.Core.World.WorldEntity> _entitiesByServerGuid = new();
private readonly Dictionary<uint, LiveEntityInfo> _liveEntityInfoByGuid = new();
/// <summary> /// <summary>
/// Latest <see cref="AcDream.Core.Net.WorldSession.EntitySpawn"/> for each /// Latest <see cref="AcDream.Core.Net.WorldSession.EntitySpawn"/> for each
/// guid. Captured at the end of <see cref="OnLiveEntitySpawnedLocked"/> so /// guid. Captured at the end of <see cref="OnLiveEntitySpawnedLocked"/> so
@ -854,9 +853,6 @@ public sealed class GameWindow : IDisposable
// far-range sends fire the wire packet immediately at SendUse/SendPickUp // far-range sends fire the wire packet immediately at SendUse/SendPickUp
// time. Cleared before the deferred send fires — single-fire, no retry. // time. Cleared before the deferred send fires — single-fire, no retry.
private (uint Guid, bool IsPickup)? _pendingPostArrivalAction; private (uint Guid, bool IsPickup)? _pendingPostArrivalAction;
private readonly record struct LiveEntityInfo(
string? Name,
AcDream.Core.Items.ItemType ItemType);
private static bool IsPlayerGuid(uint guid) => (guid & 0xFF000000u) == 0x50000000u; private static bool IsPlayerGuid(uint guid) => (guid & 0xFF000000u) == 0x50000000u;
private const double ServerControlledVelocityStaleSeconds = 0.60; private const double ServerControlledVelocityStaleSeconds = 0.60;
private int _liveSpawnReceived; // diagnostics private int _liveSpawnReceived; // diagnostics
@ -1302,7 +1298,7 @@ public sealed class GameWindow : IDisposable
// live state from this GameWindow instance every frame: // live state from this GameWindow instance every frame:
// - selected guid → _selectedGuid (set by PickAndStoreSelection) // - selected guid → _selectedGuid (set by PickAndStoreSelection)
// - entity resolver → position from _entitiesByServerGuid + // - entity resolver → position from _entitiesByServerGuid +
// itemType / PWD bits from cached LiveEntityInfo + last spawn // itemType from ClientObjectTable (Objects) + last spawn
// - camera → _cameraController.Active or (zero) when not // - camera → _cameraController.Active or (zero) when not
// yet ready, in which case the panel bails on viewport==0. // yet ready, in which case the panel bails on viewport==0.
_targetIndicator = new AcDream.App.UI.TargetIndicatorPanel( _targetIndicator = new AcDream.App.UI.TargetIndicatorPanel(
@ -1311,9 +1307,7 @@ public sealed class GameWindow : IDisposable
{ {
if (!_entitiesByServerGuid.TryGetValue(guid, out var entity)) if (!_entitiesByServerGuid.TryGetValue(guid, out var entity))
return null; return null;
uint rawItemType = 0; uint rawItemType = (uint)LiveItemType(guid);
if (_liveEntityInfoByGuid.TryGetValue(guid, out var info))
rawItemType = (uint)info.ItemType;
uint pwdBits = 0; uint pwdBits = 0;
uint? useability = null; uint? useability = null;
if (_lastSpawnByGuid.TryGetValue(guid, out var spawn)) if (_lastSpawnByGuid.TryGetValue(guid, out var spawn))
@ -2706,12 +2700,6 @@ public sealed class GameWindow : IDisposable
$"itemType={itemTypeStr} animParts={animPartCount} texChanges={texChangeCount} subPalettes={subPalCount}"); $"itemType={itemTypeStr} animParts={animPartCount} texChanges={texChangeCount} subPalettes={subPalCount}");
} }
_liveEntityInfoByGuid[spawn.Guid] = new LiveEntityInfo(
spawn.Name,
spawn.ItemType is { } rawItemType
? (AcDream.Core.Items.ItemType)rawItemType
: AcDream.Core.Items.ItemType.None);
// Target the statue specifically for full diagnostic dump: Name match // Target the statue specifically for full diagnostic dump: Name match
// is cheap and gives us exactly one entity's worth of log regardless // is cheap and gives us exactly one entity's worth of log regardless
// of arrival order. // of arrival order.
@ -3717,7 +3705,6 @@ public sealed class GameWindow : IDisposable
// clear using the same guid the next spawn/update would use. // clear using the same guid the next spawn/update would use.
_remoteDeadReckon.Remove(serverGuid); _remoteDeadReckon.Remove(serverGuid);
_remoteLastMove.Remove(serverGuid); _remoteLastMove.Remove(serverGuid);
_liveEntityInfoByGuid.Remove(serverGuid);
_entitiesByServerGuid.Remove(serverGuid); _entitiesByServerGuid.Remove(serverGuid);
_lastSpawnByGuid.Remove(serverGuid); _lastSpawnByGuid.Remove(serverGuid);
if (_selectedGuid == serverGuid) if (_selectedGuid == serverGuid)
@ -3809,8 +3796,7 @@ public sealed class GameWindow : IDisposable
// Per-Door UM dispatch trail; grep [door-cycle] in launch.log to verify door animation. // Per-Door UM dispatch trail; grep [door-cycle] in launch.log to verify door animation.
if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabled if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabled
&& _liveEntityInfoByGuid.TryGetValue(update.Guid, out var doorInfo) && IsDoorName(LiveName(update.Guid)))
&& IsDoorName(doorInfo.Name))
{ {
Console.WriteLine(System.FormattableString.Invariant( Console.WriteLine(System.FormattableString.Invariant(
$"[door-cycle] guid=0x{update.Guid:X8} stance=0x{stance:X4} cmd=0x{(command ?? 0u):X4}")); $"[door-cycle] guid=0x{update.Guid:X8} stance=0x{stance:X4} cmd=0x{(command ?? 0u):X4}"));
@ -11590,9 +11576,7 @@ public sealed class GameWindow : IDisposable
// RadarBlipColor are produced for the just-picked entity. // RadarBlipColor are produced for the just-picked entity.
// Helps verify whether a "green NPC" really is flagged as // Helps verify whether a "green NPC" really is flagged as
// Vendor server-side or whether our lookup is wrong. // Vendor server-side or whether our lookup is wrong.
uint rawItemType = 0; uint rawItemType = (uint)LiveItemType(guid);
if (_liveEntityInfoByGuid.TryGetValue(guid, out var info))
rawItemType = (uint)info.ItemType;
uint pwdBits = 0; uint pwdBits = 0;
uint? pickUseability = null; uint? pickUseability = null;
float? pickUseRadius = null; float? pickUseRadius = null;
@ -11649,8 +11633,7 @@ public sealed class GameWindow : IDisposable
// Retail string at acclient_2013_pseudo_c.txt:1033115 // Retail string at acclient_2013_pseudo_c.txt:1033115
// (data_7e2a70): "The %s cannot be used". // (data_7e2a70): "The %s cannot be used".
bool isCreature = _liveEntityInfoByGuid.TryGetValue(sel, out var info) bool isCreature = (LiveItemType(sel) & AcDream.Core.Items.ItemType.Creature) != 0;
&& (info.ItemType & AcDream.Core.Items.ItemType.Creature) != 0;
if (isCreature) if (isCreature)
{ {
@ -11891,8 +11874,7 @@ public sealed class GameWindow : IDisposable
// Mirror InstallSpeculativeTurnToTarget's per-type radius heuristic. // Mirror InstallSpeculativeTurnToTarget's per-type radius heuristic.
float useRadius = 0.6f; float useRadius = 0.6f;
if (_liveEntityInfoByGuid.TryGetValue(targetGuid, out var info) if ((LiveItemType(targetGuid) & AcDream.Core.Items.ItemType.Creature) != 0)
&& (info.ItemType & AcDream.Core.Items.ItemType.Creature) != 0)
{ {
useRadius = 3.0f; useRadius = 3.0f;
} }
@ -11919,8 +11901,7 @@ public sealed class GameWindow : IDisposable
// Per-type use radius — same heuristic as the picker's // Per-type use radius — same heuristic as the picker's
// radiusForGuid callback. // radiusForGuid callback.
float useRadius = 0.6f; float useRadius = 0.6f;
if (_liveEntityInfoByGuid.TryGetValue(targetGuid, out var info) if ((LiveItemType(targetGuid) & AcDream.Core.Items.ItemType.Creature) != 0)
&& (info.ItemType & AcDream.Core.Items.ItemType.Creature) != 0)
{ {
useRadius = 3.0f; useRadius = 3.0f;
} }
@ -12001,10 +11982,8 @@ public sealed class GameWindow : IDisposable
return false; return false;
if (!_entitiesByServerGuid.ContainsKey(guid)) if (!_entitiesByServerGuid.ContainsKey(guid))
return false; return false;
if (!_liveEntityInfoByGuid.TryGetValue(guid, out var info))
return false;
return (info.ItemType & AcDream.Core.Items.ItemType.Creature) != 0; return (LiveItemType(guid) & AcDream.Core.Items.ItemType.Creature) != 0;
} }
@ -12171,8 +12150,7 @@ public sealed class GameWindow : IDisposable
// `ItemUseable = null`; without the fallback the M1 "click NPC" // `ItemUseable = null`; without the fallback the M1 "click NPC"
// flow regresses. The diagnostic line below lets us measure // flow regresses. The diagnostic line below lets us measure
// how often this branch fires in real play. // how often this branch fires in real play.
if (_liveEntityInfoByGuid.TryGetValue(guid, out var info) if ((LiveItemType(guid) & AcDream.Core.Items.ItemType.Creature) != 0)
&& (info.ItemType & AcDream.Core.Items.ItemType.Creature) != 0)
{ {
if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeUseabilityFallbackEnabled) if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeUseabilityFallbackEnabled)
Console.WriteLine(System.FormattableString.Invariant( Console.WriteLine(System.FormattableString.Invariant(
@ -12280,11 +12258,15 @@ public sealed class GameWindow : IDisposable
return (it & SmallItemMask) != 0u; return (it & SmallItemMask) != 0u;
} }
private AcDream.Core.Items.ItemType LiveItemType(uint guid) =>
Objects.Get(guid)?.Type ?? AcDream.Core.Items.ItemType.None;
private string? LiveName(uint guid) => Objects.Get(guid)?.Name;
private string DescribeLiveEntity(uint guid) private string DescribeLiveEntity(uint guid)
{ {
if (_liveEntityInfoByGuid.TryGetValue(guid, out var info) var name = LiveName(guid);
&& !string.IsNullOrWhiteSpace(info.Name)) if (!string.IsNullOrWhiteSpace(name)) return name!;
return info.Name!;
return $"0x{guid:X8}"; return $"0x{guid:X8}";
} }