feat(physics): integrate live projectile runtime

Attach the retail projectile driver to canonical LiveEntityRecord ownership, sharing one PhysicsBody and full-cell identity with RemoteMotion regardless of creation order. Apply timestamp-gated state, vector, and position corrections in place, preserve active ordinary-body behavior when Missile clears, and keep renderer, effects, shadows, and spatial buckets synchronized across loaded/pending transitions.

Validate malformed packets before canonical timestamp mutation, validate adopted bodies from their current frame rather than stale CreateObject data, serialize late Setup resolution with streaming DAT reads, and preserve classification across clock anomalies. Retain shadow registrations through temporary leave-world residence while logical teardown remains generation-scoped.

Add 31 App lifecycle/adversarial tests plus Core shadow suspension coverage, and synchronize the retail pseudocode, architecture, milestones, roadmap, and durable physics memory.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-14 12:51:42 +02:00
parent f02b995e4f
commit a51ebc66e9
12 changed files with 2559 additions and 32 deletions

File diff suppressed because it is too large Load diff

View file

@ -93,6 +93,73 @@ public class ShadowObjectRegistryTests
reg.Deregister(999u);
}
[Fact]
public void Suspend_WithdrawsCellsAndUpdatePositionRestoresSameRegistration()
{
var reg = new ShadowObjectRegistry();
const uint entityId = 23u;
reg.Register(
entityId,
0x01000004u,
new Vector3(12f, 12f, 50f),
Quaternion.Identity,
1f,
OffX,
OffY,
LbId);
Assert.True(reg.Suspend(entityId));
Assert.Empty(reg.GetObjectsInCell(LbId | 1u));
Assert.Equal(0, reg.TotalRegistered);
reg.UpdatePosition(
entityId,
new Vector3(36f, 12f, 50f),
Quaternion.Identity,
OffX,
OffY,
LbId,
seedCellId: LbId | 9u);
ShadowEntry restored = Assert.Single(reg.GetObjectsInCell(LbId | 9u));
Assert.Equal(entityId, restored.EntityId);
Assert.Equal(new Vector3(36f, 12f, 50f), restored.Position);
Assert.Equal(1, reg.TotalRegistered);
}
[Fact]
public void Suspend_StateChangeUpdatesRetainedRegistrationBeforeRestore()
{
var reg = new ShadowObjectRegistry();
const uint entityId = 24u;
reg.Register(
entityId,
0x01000004u,
new Vector3(12f, 12f, 50f),
Quaternion.Identity,
1f,
OffX,
OffY,
LbId,
state: 0x40u,
seedCellId: LbId | 1u,
isStatic: false);
Assert.True(reg.Suspend(entityId));
reg.UpdatePhysicsState(entityId, 0x44u);
reg.UpdatePosition(
entityId,
new Vector3(12f, 12f, 50f),
Quaternion.Identity,
OffX,
OffY,
LbId,
seedCellId: LbId | 1u);
ShadowEntry restored = Assert.Single(reg.GetObjectsInCell(LbId | 1u));
Assert.Equal(0x44u, restored.State);
}
// -----------------------------------------------------------------------
// RemoveLandblock
// -----------------------------------------------------------------------
@ -113,6 +180,33 @@ public class ShadowObjectRegistryTests
Assert.Empty(reg.GetObjectsInCell(LbId | 1u));
}
[Fact]
public void RemoveLandblock_DynamicRegistrationRefloodsAfterStreamingReload()
{
var reg = new ShadowObjectRegistry();
const uint entityId = 31u;
reg.Register(
entityId,
0x01000005u,
new Vector3(12f, 12f, 50f),
Quaternion.Identity,
1f,
OffX,
OffY,
LbId,
seedCellId: LbId | 1u,
isStatic: false);
reg.RemoveLandblock(LbId);
Assert.Empty(reg.GetObjectsInCell(LbId | 1u));
reg.RefloodLandblock(LbId);
Assert.Contains(
reg.GetObjectsInCell(LbId | 1u),
entry => entry.EntityId == entityId);
}
// -----------------------------------------------------------------------
// Per-cell query surface (BR-7 / A6.P4 2026-06-11): GetObjectsInCell IS
// the query — retail CObjCell::find_obj_collisions iterates only the