Skip to content

test(collision): count the pairs a full table's merge drops - #360

Merged
siracozmen01 merged 1 commit into
mainfrom
pair-merge-overflow
Nov 5, 2026
Merged

siracozmen01 merged 1 commit into
mainfrom
pair-merge-overflow

Conversation

@siracozmen01

Copy link
Copy Markdown
Collaborator

Problem

The pair table's merge overflow in m2UpdatePairs is reachable from the public API, and no test takes it. It happens when pairs kept from earlier steps plus new candidates run past the table. Its drops are counted in m2Counters.pairsDropped, but only the collection's drop is tested, so a change that lost the merge's pairs silently would pass every suite. m2Joint_GetLocalAxisA's @return also leaves out the misuse its refusals count (#359).

Change

  • test/test_broadphase.c gains TestFullTableMergeIsCounted. The world has no gravity and 24 dynamic discs on one spot with every motion lock set. They want 276 pairs, and a world of 24 shapes keeps 192.
    • The first step keeps 192 pairs and counts the other 84.
    • A second step, with nothing moving, keeps the 192 and drops nothing.
    • One disc is teleported 1 mm. Locked, the other discs never leave their fat boxes, so only it meets its 23 neighbors again. The 23 candidates fit the candidate scratch, so every pair lost now is lost in the merge into the full table. The step keeps 192, and counts between 1 and 23.
  • include/maul2d/joint.h: m2Joint_GetLocalAxisA's @return says that another joint type or a stale id reads zero and counts one misuse, as test(joints): read what a joint's kind does not carry #358 tests.

No result changes: the code is as it was.

Verification

  • test_broadphase passes.
  • Mutants, each built into a scratch library and run against test_broadphase:
Mutant Fails
the merge drops a pair without counting it the teleport step's count
the collection drops pairs without counting them the first step's count, and TestFullPairTableIsCounted
  • tools/check.sh changed passes, the documentation check included. 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 #359

@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 2504388: no change needed. I read the code; I did not run it. Checks were still pending when I read them.

Collection drops and merge drops. src/broadphase.c at this head:

  • pairsDropped is reset at the top of m2UpdatePairs (line 112).
  • The collection counts a drop only once collected reaches pairCapacity (192 here, lines 176-188).
  • The merge counts a drop only once outCount reaches pairCapacity (lines 240-247).

What each step does:

  • Step 1. All 24 shapes are in the moved list. Each pair is collected once, by its lower slot, so 276 candidates are met. The 84 past 192 are dropped at collection. Nothing is kept from before, so the merge drops nothing.
  • Step 2. Nothing moved, so movedCount == 0 and the function returns at line 113 after the reset. That gives 0.
  • Step 3, the teleport. Only the teleported disc is in the moved list. Its fat box meets the 23 others, which have not moved, so the skip at line 157 does not apply and it collects 23 candidates. 23 is far below 192, so the collection cannot drop. Every drop in this step comes from the merge, so the test does separate the two paths.

The bound. The kept list (lines 200-210) drops every pair that touches a moved shape. If x of the teleported disc's pairs were in the table, the merge sees 192 - x kept pairs plus 23 distinct new ones, and drops exactly 23 - x.

  • x can be anything from 0 to 23, so 1 <= drops <= 23 holds for any x of 22 or less, and x = 23 cannot occur here.
  • If the slots follow creation order, the disc is slot 23 and collects nothing of its own in step 1. Its pairs come in as (k, 23), collected by slot k in slot order. The first 192 candidates run out during slot 10's query: slots 0 to 9 give 185, and slot 10 adds 7 of its 13 in tree order. So x is 10 or 11 and the count is 12 or 13.
  • > 0 && <= 23 is a correct bound, not a tight one. It still fails the two mutants you listed: an uncounted merge gives 0, and a merge counted twice gives over 23. Pinning 12 or 13 would pin the tree's query order, so the looser bound is the better choice.

The m2Joint_GetLocalAxisA @return line now states the misuse count, matching the file comment and what #358 tests.

The body has Problem, Change and Verification, and has no column-0 word( or type(scope): line.

@siracozmen01
siracozmen01 merged commit 68848d7 into main Nov 5, 2026
30 checks passed
@siracozmen01
siracozmen01 deleted the pair-merge-overflow branch November 5, 2026 15:32
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.

No test reaches the pair table's merge overflow

1 participant