fix(physics): adopt the settle's resolved cell across an indoor seam (#276 remainder)
SpawnPlacementSettler committed settle.Position but discarded settle.CellId, so a compressed first-gravity-frame settle that crossed a cell boundary left the body's cell at the placement cell until some later resolve corrected it. Now committed through the same guarded channel the per-tick resolve writeback uses (RuntimeOrdinaryPhysicsUpdater): resolved cell when the transition reports one, source cell otherwise, never a zeroed residency. Retail anchor: CPhysicsObj::SetPositionInternal(CTransition const*) 0x00515330 commits both sphere_path.curr_pos.objcell_id and its frame, including EnvCells. WHERE THE DEFECT ACTUALLY BIT — #276's own framing is half wrong, and the half it misses is the whole fix. PhysicsBody.Position's setter already mirrors the world delta into the landblock-local frame and lets LandDefs.AdjustToOutside recompute the 24 m cell index from it, so an outdoor->outdoor settle already landed the right cell and dropping settle.CellId cost nothing there. It cannot do that for an EnvCell: an EnvCell id is not derivable from a position, so the mirror deliberately PRESERVES it. settle.CellId is therefore the only carrier of a cell identity across an indoor seam. The live defect is the issue's parenthetical ("outdoor/EnvCell seam, stacked EnvCells"), not its main clause — and the change is consequently a no-op on the outdoor path that dominates production, corrective only at the seam. That finding is what made the test possible. The three existing settler tests build bodies with NO CellPosition and pass identically with or without this change — shipping against them would have repeated C5b finding D3, a test that passed with its own change reverted. The new test seeds an EnvCell id over plain outdoor terrain instead, so the stale-id preservation is the discriminator and no EnvCell geometry fixture is needed. Sabotage-verified: restoring `body.Position = settle.Position` fails exactly the new test (1 failed / 4) and leaves the other three green — confirming both that the new test discriminates and that the old ones never could. Core suite 4,263 passed / 1 skipped / 0 failed, +1 for the new test. Still open and unverified, deliberately not claimed closed: whether the remote spawn-seed caller (LiveEntityNetworkUpdateController) hands in a body that carries a CellPosition at all. CommitTransitionPosition early-returns on a zero cell, so this fix is an inert no-op there and #276's remote half may survive. The C3c local first-entry caller is confirmed — it passes activation.Body.CellPosition.ObjCellId. Scoping detail in docs/research/2026-08-06-276-remainder-scoping.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
408c8e8f34
commit
fafc0b65d9
2 changed files with 77 additions and 1 deletions
|
|
@ -58,7 +58,28 @@ public static class SpawnPlacementSettler
|
|||
if (!settle.Ok || !settle.InContact)
|
||||
return false;
|
||||
|
||||
body.Position = settle.Position;
|
||||
// #276: adopt the settle's RESOLVED cell, not just its position.
|
||||
// Retail's CPhysicsObj::SetPositionInternal(CTransition const*)
|
||||
// @0x00515330 commits both sphere_path.curr_pos.objcell_id and its
|
||||
// frame, including EnvCells.
|
||||
//
|
||||
// A bare `body.Position = settle.Position` is sufficient only while
|
||||
// the body is OUTDOOR: that setter mirrors the world delta into the
|
||||
// landblock-local frame and LandDefs.AdjustToOutside then recomputes
|
||||
// the 24 m cell index from it. It cannot do so for an EnvCell — an
|
||||
// EnvCell id is not derivable from a position, so the mirror
|
||||
// deliberately preserves it (PhysicsBody.cs, the `not (>= 1 and
|
||||
// <= 0x40)` arm). settle.CellId is therefore the ONLY carrier of a
|
||||
// cell identity across an indoor seam, and dropping it stranded the
|
||||
// body in the placement cell until some later resolve corrected it.
|
||||
//
|
||||
// Identical guarded shape to the per-tick resolve writeback
|
||||
// (RuntimeOrdinaryPhysicsUpdater): a transition reporting no cell
|
||||
// falls back to the source cell rather than zeroing residency.
|
||||
uint resolvedCellId = settle.CellId != 0
|
||||
? settle.CellId
|
||||
: cellId;
|
||||
body.CommitTransitionPosition(resolvedCellId, settle.Position);
|
||||
PhysicsObjUpdate.CommitSetPositionTransition(
|
||||
body,
|
||||
settle.InContact,
|
||||
|
|
|
|||
|
|
@ -75,6 +75,61 @@ public sealed class SpawnPlacementSettlerTests
|
|||
Assert.Equal(originalPosition, body.Position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #276: the settle must adopt the transition's RESOLVED cell, not only
|
||||
/// its position. Retail's
|
||||
/// <c>CPhysicsObj::SetPositionInternal(CTransition const*)</c>
|
||||
/// (<c>0x00515330</c>) commits both <c>curr_pos.objcell_id</c> and the
|
||||
/// frame.
|
||||
///
|
||||
/// <para>
|
||||
/// The body is seeded with an ENVCELL id over plain outdoor terrain,
|
||||
/// which is what makes this discriminating. A bare
|
||||
/// <c>body.Position = settle.Position</c> mirrors the world delta into
|
||||
/// the local frame and lets <c>AdjustToOutside</c> recompute the cell —
|
||||
/// but only for outdoor ids; the mirror deliberately PRESERVES an
|
||||
/// EnvCell id, because one cannot be derived from a position. So the old
|
||||
/// code leaves the stale EnvCell in place and only the resolved-cell
|
||||
/// adoption reaches the outdoor cell the body physically settled in.
|
||||
/// This is the "outdoor/EnvCell seam" half of #276, in the outward
|
||||
/// direction.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SettleLeavingAnEnvCell_AdoptsTheTransitionsResolvedOutdoorCell()
|
||||
{
|
||||
const uint StaleEnvCell = Landblock | 0x0100u;
|
||||
PhysicsEngine engine = BuildFlatEngine();
|
||||
PhysicsBody body = AirborneBody(new Vector3(12f, 12f, 0.25f));
|
||||
body.StageDormantCellFrame(
|
||||
StaleEnvCell,
|
||||
body.Position,
|
||||
body.Position);
|
||||
Assert.Equal(StaleEnvCell, body.CellPosition.ObjCellId);
|
||||
|
||||
bool settled = SpawnPlacementSettler.TrySettle(
|
||||
engine,
|
||||
body,
|
||||
body.Position,
|
||||
Cell,
|
||||
Radius,
|
||||
Height,
|
||||
ObjectInfoState.EdgeSlide,
|
||||
movingEntityId: 0x70000001u,
|
||||
static () => { },
|
||||
static () => { });
|
||||
|
||||
Assert.True(settled);
|
||||
Assert.True(body.InContact);
|
||||
|
||||
// The discriminating assertion: the body no longer claims the EnvCell
|
||||
// it never physically occupied, and its cell is an OUTDOOR landcell
|
||||
// (low word 1..0x40) of the same landblock it settled in.
|
||||
Assert.NotEqual(StaleEnvCell, body.CellPosition.ObjCellId);
|
||||
Assert.Equal(Landblock, body.CellPosition.ObjCellId & 0xFFFF0000u);
|
||||
Assert.InRange(body.CellPosition.ObjCellId & 0xFFFFu, 1u, 0x40u);
|
||||
}
|
||||
|
||||
private static bool TrySettle(PhysicsEngine engine, PhysicsBody body) =>
|
||||
SpawnPlacementSettler.TrySettle(
|
||||
engine,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue