refactor(world): canonicalize live physics host ownership

This commit is contained in:
Erik 2026-07-21 14:05:34 +02:00
parent 5882b308c1
commit fcb66198fc
21 changed files with 1546 additions and 323 deletions

View file

@ -49,6 +49,7 @@ public sealed class TargetManager
private readonly IPhysicsObjHost _host;
private TargetInfo? _targetInfo; // retail target_info (watcher role)
private IPhysicsObjHost? _targetHost; // exact App incarnation token
private Dictionary<uint, TargettedVoyeurInfo>? _voyeurTable; // retail voyeur_table (watched role)
private double _lastUpdateTime; // retail last_update_time (throttle base)
@ -67,6 +68,12 @@ public sealed class TargetManager
/// quantum, 0 when tracking nothing.</summary>
public double GetTargetQuantum() => _targetInfo?.Quantum ?? 0.0;
/// <summary>The pointer-like target identity captured by SetTarget.</summary>
public IPhysicsObjHost? GetRelationshipTarget(uint objectId) =>
_targetInfo is { } info && info.ObjectId == objectId
? _targetHost
: null;
// ── watcher role ───────────────────────────────────────────────────────
/// <summary>
@ -101,7 +108,8 @@ public sealed class TargetManager
LastUpdateTime: _host.CurTime);
var target = _host.GetObjectA(objectId);
target?.AddVoyeur(_host.Id, radius, quantum);
_targetHost = target;
target?.AddVoyeur(_host, radius, quantum);
}
/// <summary>
@ -115,8 +123,9 @@ public sealed class TargetManager
return;
_targetInfo = ti with { Quantum = quantum };
var target = _host.GetObjectA(ti.ObjectId);
target?.AddVoyeur(_host.Id, ti.Radius, quantum);
var target = _targetHost ?? _host.GetObjectA(ti.ObjectId);
_targetHost = target;
target?.AddVoyeur(_host, ti.Radius, quantum);
}
/// <summary>
@ -128,9 +137,10 @@ public sealed class TargetManager
if (_targetInfo is not { } ti)
return;
var target = _host.GetObjectA(ti.ObjectId);
target?.RemoveVoyeur(_host.Id);
var target = _targetHost ?? _host.GetObjectA(ti.ObjectId);
target?.RemoveVoyeur(_host.Id, _host);
_targetInfo = null;
_targetHost = null;
}
/// <summary>
@ -141,10 +151,17 @@ public sealed class TargetManager
/// time, recomputes the self→target interpolated heading (falls back to +Z
/// when degenerate), fans the snapshot to the host, and drops the
/// subscription on an ExitWorld status.
/// Receives the update only from the exact target incarnation that
/// established this relationship. The sender token stays outside the
/// retail TargetInfo payload.
/// </summary>
public void ReceiveUpdate(TargetInfo update)
public void ReceiveUpdate(TargetInfo update, IPhysicsObjHost sender)
{
if (_targetInfo is not { } ti || ti.ObjectId != update.ObjectId)
ArgumentNullException.ThrowIfNull(sender);
if (_targetInfo is not { } ti
|| ti.ObjectId != update.ObjectId
|| _targetHost is null
|| !ReferenceEquals(_targetHost, sender))
return;
// Copy radius/quantum/positions/velocity/status from the wire; keep our
@ -181,25 +198,53 @@ public sealed class TargetManager
/// radius/quantum in place (no immediate send); otherwise creates the entry
/// and pushes an immediate initial snapshot (<c>Ok</c>).
/// </summary>
public void AddVoyeur(uint watcherId, float radius, double quantum)
public void AddVoyeur(IPhysicsObjHost watcher, float radius, double quantum)
{
ArgumentNullException.ThrowIfNull(watcher);
uint watcherId = watcher.Id;
_voyeurTable ??= new Dictionary<uint, TargettedVoyeurInfo>();
if (_voyeurTable.TryGetValue(watcherId, out var existing))
{
existing.Radius = radius;
existing.Quantum = quantum;
return;
if (ReferenceEquals(existing.WatcherHost, watcher))
{
existing.Radius = radius;
existing.Quantum = quantum;
return;
}
// A later INSTANCE_TS reused the GUID. Replace the pointer-like
// subscription; the retiring watcher can no longer remove it via
// its exact identity token.
_voyeurTable.Remove(watcherId);
}
var voyeur = new TargettedVoyeurInfo(watcherId, radius, quantum);
var voyeur = new TargettedVoyeurInfo(
watcherId,
radius,
quantum,
watcher);
_voyeurTable[watcherId] = voyeur;
SendVoyeurUpdate(voyeur, _host.Position, TargetStatus.Ok);
}
/// <summary>Retail <c>TargetManager::RemoveVoyeur</c> (0x0051ad90).</summary>
public bool RemoveVoyeur(uint watcherId)
=> _voyeurTable?.Remove(watcherId) ?? false;
/// <summary>
/// Retail <c>TargetManager::RemoveVoyeur</c> (0x0051ad90). Removes a
/// relationship only when it still belongs to the exact watcher
/// incarnation that established it in the App lifetime model.
/// </summary>
public bool RemoveVoyeur(uint watcherId, IPhysicsObjHost expectedWatcher)
{
ArgumentNullException.ThrowIfNull(expectedWatcher);
if (_voyeurTable is null
|| !_voyeurTable.TryGetValue(watcherId, out var existing)
|| !ReferenceEquals(existing.WatcherHost, expectedWatcher))
{
return false;
}
return _voyeurTable.Remove(watcherId);
}
/// <summary>
/// Retail <c>TargetManager::HandleTargetting</c> (0x0051aa90). THE per-tick
@ -280,8 +325,7 @@ public sealed class TargetManager
Quantum: voyeur.Quantum,
Velocity: _host.Velocity);
var voyeurObj = _host.GetObjectA(voyeur.ObjectId);
voyeurObj?.ReceiveTargetUpdate(info);
voyeur.WatcherHost.ReceiveTargetUpdate(info, _host);
}
/// <summary>