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:
Erik 2026-08-06 06:37:46 +02:00
parent 408c8e8f34
commit fafc0b65d9
2 changed files with 77 additions and 1 deletions

View file

@ -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,