CNA-BUG-003: BoundingBox::Contains(const BoundingFrustum&) never returns Disjoint and answers Contains when only the first frustum corner is outside

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

BoundingBox::Contains(frustum) classifies the frustum from its corners only, so a frustum wholly separate from the box is reported as Intersects, and one whose first corner alone lies outside is reported as Contains.

Identifier
CNA-BUG-003
Category
Bug
Subsystem
Math & geometry
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Medium (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
ContainmentType BoundingBox::Contains(const BoundingFrustum&) const; C ABI cna_bounding_box_contains_frustum

Expected behaviour

The header documents the function as checking "whether a frustum is outside, inside, or overlapping this box". XNA 4.0 returns Disjoint when frustum.Intersects(box) is false, Intersects when any of the eight frustum corners lies outside the box, and Contains otherwise (read from the IL of the genuine XNA 4.0 Microsoft.Xna.Framework assembly; not executed). Testing the frustum's corners against the box is the right test for the Contains answer, because the box is convex and the frustum is the hull of its corners; what is missing is the separate disjointness test.

Actual behaviour at TARGET

BoundingBox.cpp walks the corners and stops at the first one outside the box. If none is outside it returns Contains; if the first outside corner is not corner 0 it returns Intersects (both correct). If corner 0 is outside, a second loop over corners 1 to 7 returns Intersects at the first corner that is not inside and Contains if all seven are inside. So:

  • a frustum entirely separate from the box (every corner outside) is reported as Intersects instead of Disjoint;
  • a frustum whose corner 0 (the near-plane, left, top intersection) alone pokes out of the box is reported as Contains instead of Intersects.

The function never returns Disjoint. It is a line-for-line port of FNA's C# implementation (read at FNA commit 1358793, 2026-08-01); FNA's comment above it reads "TODO: bad done here need a fix", which CNA replaced with "Legacy behavior: classify the frustum by testing its corners against this box".

Source locations

Evidence

Checked by reading at 009d40f5; nothing was built or executed for this entry. Both wrong outcomes follow from the control flow of the two loops. An earlier description of this defect as "answering the reverse question" (quoting FNA's comment) is not accurate at TARGET: the corner direction is right for the Contains answer, and the defect is the missing disjointness test plus the corner-0 fallback. The expected behaviour was taken from the XNA 4.0 IL.

Focused reproduction

Illustrative; not compiled or run for this entry.

BoundingBox box(Vector3(0.0f, 0.0f, 0.0f), Vector3(1.0f, 1.0f, 1.0f));
BoundingFrustum f(Matrix::CreateLookAt(Vector3(0.0f, 0.0f, -100.0f), Vector3(0.0f, 0.0f, -200.0f), Vector3::Up) *
                  Matrix::CreatePerspectiveFieldOfView(MathHelper::PiOver4, 1.0f, 1.0f, 50.0f));
ContainmentType c = box.Contains(f);     // the frustum looks away from the box
// XNA: ContainmentType::Disjoint     TARGET code: ContainmentType::Intersects

Current tests

BoundingBoxTest.ContainsFrustumDisjoint in BoundingBoxTests.cpp builds exactly the disjoint case above but asserts only EXPECT_NE(result, ContainmentType::Contains) with the comment "result must be Disjoint or Intersects (not Contains)", so its name promises the answer the code cannot give. The C ABI smoke test checks one Contains case. Nothing exercises the corner-0 fallback.

Regression test

Tighten ContainsFrustumDisjoint to EXPECT_EQ(result, ContainmentType::Disjoint), and add a case in which only corner 0 of the frustum (GetCorners()[0]) lies outside the box, expecting Intersects.

Blast radius

Affected: BoundingBox::Contains(const BoundingFrustum&) and cna_bounding_box_contains_frustum. Not affected: BoundingFrustum::Contains(BoundingBox) and BoundingFrustum::Intersects(BoundingBox), which classify the box against the frustum planes and are what the culling helpers use, and BoundingBox::Intersects(const BoundingFrustum&), which delegates to the frustum. No in-tree module other than the C ABI calls the affected function.

Workaround

Call frustum.Intersects(box) first and treat false as Disjoint; then test each of frustum.GetCorners() with box.Contains(corner) and answer Intersects if any is outside.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index