feat(A): port find_visible_child_cell + AdjustPosition (Render Residual A primitives)

The two Core physics primitives retail's SmartBox::update_viewer calls down into,
ported verbatim (TDD, 7 new tests):

- CellTransit.FindVisibleChildCell (CEnvCell::find_visible_child_cell, pc:311397):
  return the cell whose cell-BSP point_in_cell contains a world point — start cell
  first, then (stab-list mode) the start's VisibleCellIds or (portal mode) its
  direct portals. Sibling of FindCellList. Mirrors FindCellList's null-CellBSP skip
  (CellTransit.cs:518) so a cell lacking hydrated CellBSP doesn't spuriously claim
  every point via PointInsideCellBsp's null-node "inside" default.

- PhysicsEngine.AdjustPosition (CPhysicsObj::AdjustPosition, pc:280009): resolve a
  point's cell from a seed. Indoor (>=0x100) → FindVisibleChildCell(stab-list);
  outdoor → landcell snap (same grid lookup as ResolveCellId). The seen_outside
  sub-fallback is deferred (off the cottage/cellar path; spec §6).

Both are unwired into any production path — they land the machinery update_viewer's
start-cell + fallback 1 need (and that residual C also needs). The App SweepEye
orchestration that calls them lands next.

Decomp-faithful per the live-capture finding: A's V1 sweep already contains the eye
(eyeInRoot=Y 99.75%, never void); this completes A as a verbatim port. Spec:
docs/superpowers/specs/2026-06-05-residual-a-camera-collision-design.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-05 10:56:16 +02:00
parent 0ffc3f5be9
commit 5177b54bbe
4 changed files with 336 additions and 0 deletions

View file

@ -397,6 +397,55 @@ public sealed class PhysicsEngine
return fallbackCellId;
}
/// <summary>
/// Verbatim port of <c>CPhysicsObj::AdjustPosition</c>
/// (<c>acclient_2013_pseudo_c.txt:280009</c>): resolve which cell actually
/// contains <paramref name="worldPoint"/>, given a seed cell. Indoor
/// (<c>objcell_id ≥ 0x100</c>, :280020) → <see cref="CellTransit.FindVisibleChildCell"/>
/// in stab-list mode (retail <c>arg5 = 1</c>, :280028); outdoor (:280050) →
/// snap to the landcell under the point (retail <c>LandDefs::adjust_to_outside</c>,
/// the same grid lookup <see cref="ResolveCellId"/> uses). Returns
/// <c>found = false</c> with the seed id unchanged when no cell resolves
/// (retail <c>return 0</c>, :280065).
///
/// <para>
/// <c>SmartBox::update_viewer</c> calls this to seat the camera sweep's start
/// cell at the head-pivot (:280032, indoor branch only) and again as fallback 1
/// at the sought eye (:280078). Retail's indoor <c>seen_outside →
/// adjust_to_outside</c> sub-fallback (:280037-280046) is deferred — not on the
/// cottage/cellar camera path (see the design spec §6).
/// </para>
/// </summary>
public (uint cellId, bool found) AdjustPosition(uint seedCellId, Vector3 worldPoint)
{
if (seedCellId == 0u) return (seedCellId, false);
if ((seedCellId & 0xFFFFu) >= 0x0100u)
{
// Indoor: find_visible_child_cell(this, point, arg3 = 1) (:280028).
if (DataCache is null) return (seedCellId, false);
uint child = CellTransit.FindVisibleChildCell(DataCache, seedCellId, worldPoint, useStabList: true);
return child != 0u ? (child, true) : (seedCellId, false);
}
// Outdoor: LandDefs::adjust_to_outside — snap to the landcell under the
// point (same grid lookup as ResolveCellId, lines 363-371). No building
// re-entry here: AdjustPosition's outdoor branch is the bare landcell snap.
foreach (var kvp in _landblocks)
{
var lb = kvp.Value;
float localX = worldPoint.X - lb.WorldOffsetX;
float localY = worldPoint.Y - lb.WorldOffsetY;
if (localX >= 0f && localX < 192f && localY >= 0f && localY < 192f)
{
uint lowCellId = lb.Terrain.ComputeOutdoorCellId(localX, localY);
return ((kvp.Key & 0xFFFF0000u) | lowCellId, true);
}
}
return (seedCellId, false);
}
/// <summary>
/// Resolve an entity's movement from <paramref name="currentPos"/> by
/// applying <paramref name="delta"/> (XY only) and computing the correct Z