refactor(runtime): own projectile simulation

Move projectile component identity, prediction invalidation, spatial worksets, authoritative corrections, and the retail physics step into AcDream.Runtime. Keep App as the DAT-shape and presentation adapter so ACE outcomes and visible behavior remain unchanged.
This commit is contained in:
Erik 2026-07-26 14:17:42 +02:00
parent fce8e7b18e
commit 2aee33569f
19 changed files with 1255 additions and 335 deletions

View file

@ -162,7 +162,7 @@ public sealed class ProjectileControllerTests
seedCellId: CellA,
isStatic: false);
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 2.0));
ILiveEntityProjectileRuntime runtime = record.ProjectileRuntime!;
IRuntimeProjectile runtime = record.ProjectileRuntime!;
Assert.True(fixture.Controller.ApplyAuthoritativePosition(
record,
@ -339,6 +339,7 @@ public sealed class ProjectileControllerTests
Assert.Equal(0xAAB40001u, record.FullCellId);
Assert.Equal(0xAAB40001u, record.PhysicsBody!.CellPosition.ObjCellId);
Assert.Equal(record.PhysicsBody.Position, entity.Position);
Assert.False(record.IsSpatiallyVisible);
Assert.True(record.IsSpatiallyProjected);
Assert.False(record.PhysicsBody.InWorld);
@ -514,7 +515,7 @@ public sealed class ProjectileControllerTests
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
Vector3 before = record.WorldEntity!.Position;
ILiveEntityProjectileRuntime runtime = record.ProjectileRuntime!;
IRuntimeProjectile runtime = record.ProjectileRuntime!;
PhysicsBody body = runtime.Body;
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
Assert.True(fixture.Controller.ApplyAuthoritativeState(
@ -565,7 +566,7 @@ public sealed class ProjectileControllerTests
cellLocalPosition: new Vector3(191f, 10f, 50f),
velocity: new Vector3(40f, 0f, 0f));
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(0.5f), 0.0));
ILiveEntityProjectileRuntime runtime = record.ProjectileRuntime!;
IRuntimeProjectile runtime = record.ProjectileRuntime!;
fixture.Controller.Tick(0.05, 0xA9, 0xB4, playerWorldPosition: null);
Assert.Equal(destinationCell, record.FullCellId);
@ -988,7 +989,7 @@ public sealed class ProjectileControllerTests
Assert.Equal(MissileState, record.PhysicsBody!.State);
Assert.True(double.IsFinite(record.PhysicsBody.LastUpdateTime));
ILiveEntityProjectileRuntime runtime = record.ProjectileRuntime!;
IRuntimeProjectile runtime = record.ProjectileRuntime!;
Assert.True(fixture.Controller.ApplyAuthoritativeState(
record, MissileState, 0.1, 1, 1));
Assert.Same(runtime, record.ProjectileRuntime);
@ -1320,14 +1321,14 @@ public sealed class ProjectileControllerTests
LiveEntityRecord first = fixture.Spawn(instance: 7);
Assert.True(fixture.Controller.TryBind(first, ProjectileSetup(), 1.0));
WorldEntity firstEntity = first.WorldEntity!;
ILiveEntityProjectileRuntime firstRuntime = first.ProjectileRuntime!;
IRuntimeProjectile firstRuntime = first.ProjectileRuntime!;
Assert.True(fixture.Live.UnregisterLiveEntity(
new DeleteObject.Parsed(Guid, InstanceSequence: 7),
isLocalPlayer: false));
LiveEntityRecord second = fixture.Spawn(instance: 8);
Assert.True(fixture.Controller.TryBind(second, ProjectileSetup(), 2.0));
ILiveEntityProjectileRuntime secondRuntime = second.ProjectileRuntime!;
IRuntimeProjectile secondRuntime = second.ProjectileRuntime!;
Assert.False(fixture.Controller.TryBind(first, ProjectileSetup(), 3.0));
@ -1457,12 +1458,14 @@ public sealed class ProjectileControllerTests
{
if (loadInitialLandblock)
Spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
Live = LiveEntityRuntimeFixture.Create(Spatial, Resources);
Engine = physicsEngine ?? new PhysicsEngine();
Live = LiveEntityRuntimeFixture.Create(
Spatial,
Resources,
Engine);
Origin.Recenter(1, 1);
Controller = new ProjectileController(
Live,
Engine,
new SetupResolver(() => ResolvedSetup),
new RootPosePublisher(
publishRootPose ?? new Action<WorldEntity>(