feat(phys L.2g slice 1): ShadowObjectRegistry.UpdatePhysicsState
New mutator that overwrites cached PhysicsState bits on every shadow copy of the named entity. The existing CollisionExemption.ShouldSkip(...) check (acclient_2013_pseudo_c.txt:276782) reads the same cached field, so a post-spawn ETHEREAL flip is now honored on the next resolver tick without any resolver-path change. Retail anchor: CPhysicsObj::set_state at acclient_2013_pseudo_c.txt:283044. Slice 1 scopes to the bare state-write — retail's cosmetic side-effect handlers (0x800 lighting, 0x20 nodraw, 0x4000 hidden) don't fire for the ETHEREAL bit and stay deferred. Three TDD tests cover: ETHEREAL flip from 0->0x4; unregistered-entity no-op; entity spanning multiple cells gets all copies updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2459f287e4
commit
d53891557d
2 changed files with 90 additions and 0 deletions
|
|
@ -126,6 +126,47 @@ public sealed class ShadowObjectRegistry
|
|||
t.State, t.Flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the cached <see cref="ShadowEntry.State"/> bits for an
|
||||
/// already-registered entity. Called by the inbound
|
||||
/// <c>SetState (0xF74B)</c> dispatcher when the server broadcasts a
|
||||
/// post-spawn <c>PhysicsState</c> change — chiefly doors flipping
|
||||
/// <c>ETHEREAL_PS = 0x4</c> on Use, so the
|
||||
/// <see cref="CollisionExemption.ShouldSkip"/> short-circuit can honor
|
||||
/// the new state on the next resolve.
|
||||
///
|
||||
/// <para>
|
||||
/// Retail equivalent: <c>CPhysicsObj::set_state</c> at
|
||||
/// <c>docs/research/named-retail/acclient_2013_pseudo_c.txt:283044</c>
|
||||
/// — direct write `this->state = arg2`. Retail also fires side-effect
|
||||
/// handlers for the 0x800 (lighting), 0x20 (nodraw), 0x4000 (hidden)
|
||||
/// changed bits; ETHEREAL (0x4) doesn't trigger any of them, so slice 1
|
||||
/// scopes to the bare state-write.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Implementation: <see cref="ShadowEntry"/> is a value-type record
|
||||
/// copied into per-cell lists, so we rewrite the copy in each cell the
|
||||
/// entity occupies. Unregistered entities are a no-op (callers don't
|
||||
/// have to gate).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void UpdatePhysicsState(uint entityId, uint newState)
|
||||
{
|
||||
if (!_entityToCells.TryGetValue(entityId, out var cellIds))
|
||||
return; // not registered — no-op
|
||||
|
||||
foreach (var cellId in cellIds)
|
||||
{
|
||||
if (!_cells.TryGetValue(cellId, out var list)) continue;
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (list[i].EntityId == entityId)
|
||||
list[i] = list[i] with { State = newState };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Remove an entity from all cells it was registered in.</summary>
|
||||
public void Deregister(uint entityId)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue