feat(physics): S2 chunk 1a - retail cell array and per-part entries beside the collision flood

Campaign OVERHAUL S2 chunk 1 (Core half). ShadowObjectRegistry accepts the
object's whole visual part array as an optional registration input and
computes retail's CELLARRAY from it as a retained side product:

- route per CPhysicsObj::calc_cross_cells_static @0x00515160: cylsphere
  (CObjCell::find_cell_list @0x0052b9f0 over the Setup's authored
  cylspheres, carried as the Cylinder shapes of the COLLISION dispatch) when
  (state & 0x10000) == 0 and a cylsphere exists, else the bbox route
  (find_bbox_cell_list @0x00510fc0 via CellTransit.BuildShadowCellSetFromParts
  over every visual part: physics sphere else drawing sphere, part box);
- per-cell part entries in array-then-part order for every part
  (CPartArray::AddPartsShadow @0x00517e40), clip planes required iff the
  array has more than one cell;
- Deregister clears everything (remove_shadows_from_cells @0x00511230);
  UpdatePosition/CommitSetPosition recompute; ReplaceMultiPartPayload swaps
  the part array without re-flooding.

_entityToCells and _cells are untouched; no production caller passes the
part array yet (chunk 1b), so this is a zero-behavior change. The lead
corrected the implementer's route decision, which had inspected the visual
part array for cylinders; retail's GetNumCylsphere reads Setup collision
data. Core Physics 2,202/2,202.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-02 21:20:40 +02:00
parent 015b660d0f
commit 059b8883ab
3 changed files with 788 additions and 6 deletions

View file

@ -114,6 +114,19 @@ internal sealed class CollisionWorldState
internal Dictionary<uint, int> ShadowOwnerIndices { get; } = new();
internal Stack<int> ShadowOwnerFreeSlots { get; } = new();
// ── Campaign OVERHAUL S2 chunk 1 ────────────────────────────────────────
// Retail's whole-part-array CELLARRAY (CPhysicsObj::calc_cross_cells_static
// 0x00515160) and its sibling per-cell part-entry product
// (CPartArray::AddPartsShadow 0x00517e40), retained beside the existing
// ShadowEntity* fields. A NEW product only: nothing above touches it,
// and nothing in production reads it yet (see ShadowObjectRegistry).
internal Dictionary<uint, IReadOnlyList<ShadowShape>>
ShadowEntityRetailPartArrays { get; } = new();
internal Dictionary<uint, List<uint>> ShadowEntityRetailCellArrays { get; } = new();
internal Dictionary<uint, RetailCellArrayRoute>
ShadowEntityRetailCellArrayRoutes { get; } = new();
internal Dictionary<uint, List<RetailPartEntry>> RetailPartEntriesByCell { get; } = new();
// ── O1 per-prefix installed-key ledgers ────────────────────────────────
// Every mutation of the five landblock-scoped world maps goes through the
// typed helpers below so these ledgers stay exact. The seal's landblock-