Skip to content

test(api): cover a capsule bullet, hull and reader refusals - #356

Merged
siracozmen01 merged 2 commits into
mainfrom
reader-refusal-tests
Nov 5, 2026
Merged

siracozmen01 merged 2 commits into
mainfrom
reader-refusal-tests

Conversation

@siracozmen01

@siracozmen01 siracozmen01 commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A coverage run of main shows four paths a host can reach that no test takes: a capsule-only bullet's bounding radius, m2ComputeHull's refusal of too little input, the geometry readers' type check, and m2World_Validate with a null world. A change to any of them would pass every suite, and a capsule bullet with no bounding radius would tunnel (#355).

Change

  • test/test_ccd.c:
    • TestCapsuleBulletStops: a bullet whose only shape is a capsule, fired at 600 m/s, stops at a 0.1 m wall.
    • It and TestSegmentBulletStops start at x = -9.8 instead of -10. One step moves them 10 m, so unswept they land at x = 0.2, past the wall. From -10 they landed exactly on the wall's center, so a skipped sweep failed only through the way a symmetric overlap resolves.
  • test/test_shapes.c:
    • TestHullRefusesTooLittle: a null cloud, two points and a null output are each refused with m2_errorInvalid, and leave the output as it was. So are two points with a radius of 1e30, which the point count refuses before the radius check would give m2_errorLimit. Three points make a triangle.
    • TestGeometryReadersCheckTheType: the circle, polygon, capsule and segment readers on a shape of another type read zero and count one misuse each. The matching reader reads its shape and counts nothing.
  • test/test_world.c, in TestValidateAndCounters: m2World_Validate(NULL) is false.

A test-only change.

Verification

  • test_ccd, test_shapes and test_world pass.
  • Mutants, each built into a scratch library and run against the three suites:
Mutant Fails
a capsule adds nothing to a bullet's bounding radius the capsule bullet stops
a segment adds nothing to a bullet's bounding radius the segment bullet stops
m2ComputeHull does not check for fewer than three points the two points with a radius of 1e30 (m2_errorLimit)
m2ComputeHull does not check for a null cloud test_shapes crashes on the null cloud
a geometry reader skips the type check the zero reads, the misuse count, and the matching read
m2World_Validate passes a null world the null world check
  • tools/check.sh changed passes. The changed files compile clean with gcc-14 and clang-20 under the strict flags.
  • test_golden prints the 13 lines of test/hashes.txt unchanged in the scalar (linux-clang-20-asan) and AVX2 (linux-clang-20) presets.

Closes #355

@siracozmen01 siracozmen01 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at 742e5d7: changes needed (2). Read, not run.

  1. The equivalence claim for count < 3 does not hold, and one input tells the two apart.

    • In ComputeHullWorker (geometry.c:319-342), the original refuses fewer than three points with m2_errorInvalid before any other check.
    • The mutant goes on to m2CheckCount and then PointsBoundsCheck, which answers m2_errorLimit for a point or a radius past M2_LOCAL_LIMIT.
    • So m2ComputeHull(cloud, 2, 1e30f, &hull) is m2_errorInvalid on main and m2_errorLimit under the mutant. So is two points with one at {1e30f, 0}.
    • For valid points with count 0, 1 or 2, the two do agree. WeldPoints keeps at most count points, so n < 3 returns m2_errorInvalid, and *polygonOut is written only on success.
    • Either add that two-point case with a far radius, if the header promises m2_errorInvalid for fewer than three points whatever else is wrong, or drop "no input tells the two apart" from the body, which becomes the commit message.
  2. The capsule bullet lands exactly on the wall's center without its sweep, so the test does not show tunneling.

    • From x = -10 at 600 m/s, one step of 1/60 s (four substeps of 2.5 m, exact in double) ends at x = 0.0, the middle of the 0.1 m wall.
    • Without the sweep, a discrete step therefore does not jump past the wall. It ends fully overlapped, and whether the body ends right or left of 0 then depends on which side the contact's normal picks at a symmetric overlap.
    • The mutant failing today shows that tie went +x, not that the body passed through untouched. TestSegmentBulletStops has the same setup.
    • Starting at, say, x = -9.8 puts the swept-less step at +0.2, wholly past the wall. Then x < 0 fails only by real tunneling.

What holds:

  • "Output unchanged" is checked against a real sentinel, not zeros. hull starts as m2MakeBox(2, 2) (count 4, first vertex x = -2), and after the three refusals the test checks count == 4 && vertices[0].x == -2.0f. A refusal that zeroed or rebuilt the output would fail it. It checks two fields, not the whole struct, but a partial write that kept both is not a plausible path, since the worker copies the whole m2Polygon or nothing.
  • The geometry readers: four wrong-type reads give zero and exactly four misuses, and the matching read counts none. m2World_Validate(NULL) is false.

@siracozmen01 siracozmen01 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delta 742e5d7..bc6eb3c: ready. Both findings are fixed. Read, not run.

  • m2ComputeHull(cloud, 2, 1.0e30f, &hull) == m2_errorInvalid is the input that tells the count < 3 check apart. Without it, the two points reach PointsBoundsCheck, and the radius past M2_LOCAL_LIMIT gives m2_errorLimit. The "equivalent mutant" paragraph is gone from the body.
  • TestCapsuleBulletStops and TestSegmentBulletStops start at x = -9.8. One step of 10 m without the sweep lands at x = 0.2, wholly past the 0.1 m wall (its far face is at 0.05). So x < 0 fails only by real tunneling, and the comment and body say so. The capsule's 0.05 radius still leaves its near edge at 0.15, past the wall.

@siracozmen01
siracozmen01 merged commit 46d5a9a into main Nov 5, 2026
31 checks passed
@siracozmen01
siracozmen01 deleted the reader-refusal-tests branch November 5, 2026 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A capsule bullet, hull refusals and reader refusals go untested

1 participant