The 1ca412d part-offset expansion fixed the staircase but still rested
on the 5 m promise one level down: a SINGLE part whose mesh extends
more than 5 m from its own origin (offset 0 -> box +-5 m) keeps the
gaze-dependent vanish. Per the user's mandate ("it must work for every
case"), the bound now derives from the dat VERTEX data - the same
vertices that get drawn - so no synthetic containment promise remains.
Oracle context (read this session): retail has NO whole-entity
visibility volume - CPhysicsPart::Draw (0x0050d7a0) viewcone-checks
each part's dat-authored CGfxObj.drawing_sphere at the part's own
world position (RenderDeviceD3D::DrawMesh 0x005a0860). Retail's bound
IS data; ours was a promise. Our per-ENTITY granularity stays (a
deliberate batching-era choice, WB-owned per the inventory) but the
volume is now data-derived and conservative: visually identical by
construction, never culls what retail would draw.
- GfxObjBounds: per-GfxObj vertex AABB, cached by id (parts repeat
heavily); LocalBoundsAccumulator: union of part-transformed AABB
corners (conservative-correct under any affine transform).
- WorldEntity.SetLocalBounds + RefreshAabb preferred path: rotate the
root-local bounds' 8 corners into world axes + DefaultAabbRadius
margin (absorbs animated-pose drift vs the rest-pose bounds; keeps
small objects at their historical box size). Offset heuristic stays
as the fallback for boundless fixtures.
- All four hydration sites wired (outdoor stabs, scenery incl. baked
scale, interior cell statics, server live spawns).
Tests: tall-single-part coverage (the case 1ca412d could not see),
rotation-following, accumulator union. Suites: App 246+1skip / Core
1434+2skip / UI 420 / Net 294.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
User re-gate after 2163308/987313a: run-from-town stairs FIXED, barrel
GONE - but the stairs still vanish by VIEWING ANGLE (visible climbing
down, gone climbing up; same at the tower top). The gate3 probe data
exonerates everything downstream: the entity always draws with correct
batches when it reaches the dispatcher (cache hit:119, restZ correct,
zero WALK-REJECTs, never clip-culled) - so the vanish lives in the one
gaze-dependent gate the probe cannot see: the bounds-based cullers.
WorldEntity.RefreshAabb was a fixed +-5 m box around the entity ANCHOR.
The staircase's 43 parts spiral 15 m ABOVE the anchor, and BOTH
visibility gates derive from the box: the dispatcher's per-entity
frustum cull AND RetailPViewRenderer.EntitySphere (the viewcone sphere
= this box's bounding sphere). Looking up the spiral put the anchor's
neighborhood out of view -> the whole entity culled while 15 m of it
stood in front of the camera; looking down kept the anchor in view ->
visible. Exactly the reported asymmetry.
Fix: expand the box by the largest MeshRef part-translation magnitude
(rotation-invariant, so entity.Rotation needs no handling; identity-
part entities get offset 0 - behavior unchanged; scenery scale is
already baked into the part transforms).
Suites: App 246+1skip / Core 1431+2skip / UI 420 / Net 294.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds AabbMin/AabbMax (per-entity world-space bounding box) and AabbDirty
flag to WorldEntity. RefreshAabb() recomputes the box from Position ±5 m
(DefaultAabbRadius). SetPosition() writes Position and marks the cache
dirty so the dispatcher calls RefreshAabb on first read rather than
carrying stale bounds.
AabbDirty defaults to true on construction — freshly-built entities have
zero AabbMin/AabbMax until RefreshAabb is called. Two new conformance tests
verify the ±5 m geometry and the dirty/clean state machine.
Per Phase A.5 spec §4.6 Change #2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>