feat(physics): port retail projectile integration
Add a pure Core projectile driver for retail clock quanta, final-state acceleration, 50-unit velocity clamping, world-space angular integration, full-3D AlignPath, and oriented Setup-local sphere sweeps. Preserve exact success versus set_frame-only failure semantics, independent Contact/OnWalkable state, and full-cell identity across all landblock directions. Port OBJECTINFO::missile_ignore with explicit weenie/creature metadata, remove the invented transition-step cap, retain PathClipped without arming PerfectClip, and keep authoritative target identity optional. Add malformed-shape/clock, thin-wall, terrain, target-exclusion, frame-spike, failure-state, and boundary conformance coverage. Retire TS-2 and synchronize the architecture, milestones, roadmap, research oracle, and durable physics memory. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
542dcfc384
commit
f02b995e4f
21 changed files with 1669 additions and 263 deletions
|
|
@ -937,7 +937,11 @@ public sealed class PhysicsEngine
|
|||
bool isOnGround,
|
||||
PhysicsBody? body = null,
|
||||
ObjectInfoState moverFlags = ObjectInfoState.None,
|
||||
uint movingEntityId = 0)
|
||||
uint movingEntityId = 0,
|
||||
Vector3? localSphereOrigin = null,
|
||||
Quaternion? beginOrientation = null,
|
||||
Quaternion? endOrientation = null,
|
||||
uint designatedTargetId = 0)
|
||||
{
|
||||
// A6.P3 #98 (2026-05-23) live capture. Filtered to IsPlayer so NPC /
|
||||
// remote ResolveWithTransition calls don't pollute the capture. Snapshot
|
||||
|
|
@ -963,6 +967,8 @@ public sealed class PhysicsEngine
|
|||
// this->object = arg2). The skip itself is at
|
||||
// CObjCell::find_obj_collisions line 308931.
|
||||
transition.ObjectInfo.SelfEntityId = movingEntityId;
|
||||
transition.ObjectInfo.MoverPhysicsState = body?.State ?? PhysicsStateFlags.None;
|
||||
transition.ObjectInfo.TargetId = designatedTargetId;
|
||||
|
||||
// Commit C 2026-04-29 — caller-supplied mover flags drive the
|
||||
// retail PvP exemption block in FindObjCollisions. The local
|
||||
|
|
@ -971,6 +977,11 @@ public sealed class PhysicsEngine
|
|||
// (matches non-player movement, all targets collide).
|
||||
transition.ObjectInfo.State |= moverFlags;
|
||||
|
||||
// CPhysicsObj::get_object_info 0x00511CC0: Missile contributes
|
||||
// PathClipped only. PerfectClip is deliberately not inferred.
|
||||
if (transition.ObjectInfo.MoverPhysicsState.HasFlag(PhysicsStateFlags.Missile))
|
||||
transition.ObjectInfo.State |= ObjectInfoState.PathClipped;
|
||||
|
||||
// frames_stationary_fall gate input: retail reads the mover's GRAVITY state bit
|
||||
// (object_info.object->state & 0x400, pc:272625). Seed it from the body so the ladder
|
||||
// in ValidateTransition runs for gravity movers (the player) and not floating props.
|
||||
|
|
@ -1020,7 +1031,15 @@ public sealed class PhysicsEngine
|
|||
transition.CollisionInfo.SetSlidingNormal(body.SlidingNormal);
|
||||
}
|
||||
|
||||
transition.SpherePath.InitPath(currentPos, targetPos, cellId, sphereRadius, sphereHeight);
|
||||
transition.SpherePath.InitPath(
|
||||
currentPos,
|
||||
targetPos,
|
||||
cellId,
|
||||
sphereRadius,
|
||||
sphereHeight,
|
||||
localSphereOrigin,
|
||||
beginOrientation,
|
||||
endOrientation);
|
||||
|
||||
// #145: supply the carried cell-relative frame anchor to the outdoor
|
||||
// membership pick. body.Position - body.CellPosition.Frame.Origin is the TRUE
|
||||
|
|
@ -1071,65 +1090,69 @@ public sealed class PhysicsEngine
|
|||
// when current is invalid (e.g., airborne this frame), matching retail.
|
||||
if (body is not null)
|
||||
{
|
||||
if (ci.ContactPlaneValid)
|
||||
{
|
||||
body.ContactPlaneValid = true;
|
||||
body.ContactPlane = ci.ContactPlane;
|
||||
body.ContactPlaneCellId = ci.ContactPlaneCellId;
|
||||
body.ContactPlaneIsWater = ci.ContactPlaneIsWater;
|
||||
}
|
||||
else if (ci.LastKnownContactPlaneValid)
|
||||
{
|
||||
body.ContactPlaneValid = true;
|
||||
body.ContactPlane = ci.LastKnownContactPlane;
|
||||
body.ContactPlaneCellId = ci.LastKnownContactPlaneCellId;
|
||||
body.ContactPlaneIsWater = ci.LastKnownContactPlaneIsWater;
|
||||
}
|
||||
else
|
||||
{
|
||||
body.ContactPlaneValid = false;
|
||||
}
|
||||
|
||||
// Publish frames_stationary_fall + carry it to the next frame via the Stationary*
|
||||
// transient bits. Retail encodes these bits in handle_all_collisions (pc:282737-758);
|
||||
// acdream co-locates the encode with the fsf writeback here (STRUCTURAL ADAPTATION,
|
||||
// register) so the round-trip (seed→ladder→writeback→seed) is self-contained in Core.
|
||||
// handle_all_collisions (PhysicsObjUpdate) then only READS body.FramesStationaryFall.
|
||||
body.FramesStationaryFall = ci.FramesStationaryFall;
|
||||
body.TransientState &= ~(TransientStateFlags.StationaryFall
|
||||
| TransientStateFlags.StationaryStop
|
||||
| TransientStateFlags.StationaryStuck);
|
||||
body.TransientState |= ci.FramesStationaryFall switch
|
||||
{
|
||||
1 => TransientStateFlags.StationaryFall,
|
||||
2 => TransientStateFlags.StationaryStop,
|
||||
3 => TransientStateFlags.StationaryStuck,
|
||||
_ => TransientStateFlags.None,
|
||||
};
|
||||
|
||||
if (sp.HasLastWalkablePolygon && sp.LastWalkableVertices is not null)
|
||||
{
|
||||
body.WalkablePolygonValid = true;
|
||||
body.WalkablePlane = sp.LastWalkablePlane;
|
||||
body.WalkableVertices = (Vector3[])sp.LastWalkableVertices.Clone();
|
||||
body.WalkableUp = sp.LastWalkableUp;
|
||||
}
|
||||
else if (!isOnGround && !ci.ContactPlaneValid && !ci.LastKnownContactPlaneValid)
|
||||
{
|
||||
body.WalkablePolygonValid = false;
|
||||
body.WalkableVertices = null;
|
||||
}
|
||||
|
||||
// Retail persists sliding state to the body ONLY on transition
|
||||
// SUCCESS: CPhysicsObj::SetPositionInternal copies the normal at
|
||||
// 0x005154c2 and syncs SLIDING_TS (bit 4) from the transition's
|
||||
// final sliding_normal_valid at 0x005154e1 — and SetPositionInternal
|
||||
// is unreachable when find_valid_position fails (the transition is
|
||||
// discarded whole; the body keeps its prior state). #137 mechanism
|
||||
// 2: an unconditional writeback here could persist a normal retail
|
||||
// would discard.
|
||||
// CPhysicsObj::transition 0x00512DC0 discards its CTransition when
|
||||
// find_valid_position fails. Only SetPositionInternal 0x00515330
|
||||
// publishes contact/fsf/walkable/sliding state, and that function
|
||||
// is unreachable on the UpdateObjectInternal failure branch.
|
||||
if (ok)
|
||||
{
|
||||
if (ci.ContactPlaneValid)
|
||||
{
|
||||
body.ContactPlaneValid = true;
|
||||
body.ContactPlane = ci.ContactPlane;
|
||||
body.ContactPlaneCellId = ci.ContactPlaneCellId;
|
||||
body.ContactPlaneIsWater = ci.ContactPlaneIsWater;
|
||||
}
|
||||
else if (ci.LastKnownContactPlaneValid)
|
||||
{
|
||||
body.ContactPlaneValid = true;
|
||||
body.ContactPlane = ci.LastKnownContactPlane;
|
||||
body.ContactPlaneCellId = ci.LastKnownContactPlaneCellId;
|
||||
body.ContactPlaneIsWater = ci.LastKnownContactPlaneIsWater;
|
||||
}
|
||||
else
|
||||
{
|
||||
body.ContactPlaneValid = false;
|
||||
}
|
||||
|
||||
// Publish frames_stationary_fall + carry it to the next frame via the Stationary*
|
||||
// transient bits. Retail encodes these bits in handle_all_collisions (pc:282737-758);
|
||||
// acdream co-locates the encode with the fsf writeback here (STRUCTURAL ADAPTATION,
|
||||
// register) so the round-trip (seed→ladder→writeback→seed) is self-contained in Core.
|
||||
// handle_all_collisions (PhysicsObjUpdate) then only READS body.FramesStationaryFall.
|
||||
body.FramesStationaryFall = ci.FramesStationaryFall;
|
||||
body.TransientState &= ~(TransientStateFlags.StationaryFall
|
||||
| TransientStateFlags.StationaryStop
|
||||
| TransientStateFlags.StationaryStuck);
|
||||
body.TransientState |= ci.FramesStationaryFall switch
|
||||
{
|
||||
1 => TransientStateFlags.StationaryFall,
|
||||
2 => TransientStateFlags.StationaryStop,
|
||||
3 => TransientStateFlags.StationaryStuck,
|
||||
_ => TransientStateFlags.None,
|
||||
};
|
||||
|
||||
if (sp.HasLastWalkablePolygon && sp.LastWalkableVertices is not null)
|
||||
{
|
||||
body.WalkablePolygonValid = true;
|
||||
body.WalkablePlane = sp.LastWalkablePlane;
|
||||
body.WalkableVertices = (Vector3[])sp.LastWalkableVertices.Clone();
|
||||
body.WalkableUp = sp.LastWalkableUp;
|
||||
}
|
||||
else if (!isOnGround && !ci.ContactPlaneValid && !ci.LastKnownContactPlaneValid)
|
||||
{
|
||||
body.WalkablePolygonValid = false;
|
||||
body.WalkableVertices = null;
|
||||
}
|
||||
|
||||
// Retail persists sliding state to the body ONLY on transition
|
||||
// SUCCESS: CPhysicsObj::SetPositionInternal copies the normal at
|
||||
// 0x005154c2 and syncs SLIDING_TS (bit 4) from the transition's
|
||||
// final sliding_normal_valid at 0x005154e1 — and SetPositionInternal
|
||||
// is unreachable when find_valid_position fails (the transition is
|
||||
// discarded whole; the body keeps its prior state). #137 mechanism
|
||||
// 2: an unconditional writeback here could persist a normal retail
|
||||
// would discard.
|
||||
if (ci.SlidingNormalValid
|
||||
&& ci.SlidingNormal.LengthSquared() > PhysicsGlobals.EpsilonSq)
|
||||
{
|
||||
|
|
@ -1254,7 +1277,11 @@ public sealed class PhysicsEngine
|
|||
ResolveResult resolveResult;
|
||||
if (ok)
|
||||
{
|
||||
bool onGround = ci.ContactPlaneValid
|
||||
bool inContact = ci.ContactPlaneValid;
|
||||
bool onWalkable = PhysicsObjUpdate.IsWalkableContact(
|
||||
inContact,
|
||||
ci.ContactPlane.Normal);
|
||||
bool onGround = inContact
|
||||
|| transition.ObjectInfo.State.HasFlag(ObjectInfoState.OnWalkable);
|
||||
|
||||
resolveResult = new ResolveResult(
|
||||
|
|
@ -1268,7 +1295,10 @@ public sealed class PhysicsEngine
|
|||
sp.CurCellId,
|
||||
onGround,
|
||||
collisionNormalValid,
|
||||
collisionNormal);
|
||||
collisionNormal,
|
||||
Orientation: sp.CurOrientation,
|
||||
InContact: inContact,
|
||||
OnWalkable: onWalkable);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1291,7 +1321,8 @@ public sealed class PhysicsEngine
|
|||
partialOnGround,
|
||||
collisionNormalValid,
|
||||
collisionNormal,
|
||||
Ok: false); // Render Residual A — the sweep failed (find_valid_position == 0)
|
||||
Ok: false,
|
||||
Orientation: sp.CurOrientation); // Render Residual A — the sweep failed (find_valid_position == 0)
|
||||
}
|
||||
|
||||
// A6.P3 #98 capture: emit one JSON Lines record per player call,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue