wave15: v8-0002/0003 Intl+revectorizer (125x/25x) + bullet/box2d ticket files — 528/240

This commit is contained in:
russell@unturf.com 2026-03-27 19:16:28 -04:00
parent c4026333ba
commit 7146714143
13 changed files with 1520 additions and 11 deletions

View file

@ -0,0 +1,313 @@
package unit;
import java.util.*;
/**
* BulletAlgorithm unit tests for three Bullet Physics CWE-407 defects.
*
* bullet-0001: btGhostObject::addOverlappingObjectInternal / removeOverlappingObjectInternal
* m_overlappingObjects.findLinearSearch(otherObject) called per broadphase pair per step.
* Slow: ArrayList.contains / indexOf O(N) per call O(N²) over N overlapping objects.
* Fast: HashMap<Object,Integer> index O(1) per call.
*
* bullet-0002: btCollisionObject::checkCollideWithOverride
* m_objectsWithoutCollisionCheck.findLinearSearch(co) inside processAllOverlappingPairs.
* Slow: ArrayList.contains O(E) per pair × M pairs O(M·E).
* Fast: HashSet.contains O(1) per pair.
*
* bullet-0003: btSortedOverlappingPairCache::removeOverlappingPair / findPair
* m_overlappingPairArray.findLinearSearch(pair) per removal.
* Slow: ArrayList.indexOf O(P) per removal × P removals O(P²).
* Fast: HashMap<key,index> O(1) per removal.
*
* Each test: correctness assertion + performance ratio >= 5x at N=500-1000.
* Prints: N/N PASS
*/
public class BulletAlgorithm {
static int passed = 0;
static int total = 0;
static void pass(String name) {
passed++;
total++;
System.out.println(" PASS " + name);
}
static void fail(String name, String reason) {
total++;
System.out.println(" FAIL " + name + "" + reason);
}
// -----------------------------------------------------------------------
// bullet-0001: btGhostObject overlapping-object add/remove
// -----------------------------------------------------------------------
/** Slow path: mirrors btGhostObject::addOverlappingObjectInternal (btAlignedObjectArray::findLinearSearch). */
static int[] simulateGhostSlow(int[] bodies) {
List<Integer> overlapping = new ArrayList<>();
long ops = 0;
// add phase
for (int b : bodies) {
int idx = overlapping.indexOf(b); // O(N) scan
ops += overlapping.size();
if (idx < 0) overlapping.add(b);
}
// remove phase
for (int b : bodies) {
int idx = overlapping.indexOf(b); // O(N) scan
ops += overlapping.size();
if (idx >= 0) {
overlapping.set(idx, overlapping.get(overlapping.size() - 1));
overlapping.remove(overlapping.size() - 1);
}
}
return new int[]{overlapping.size(), (int)ops};
}
/** Fast path: HashMap<Object,Integer> index — mirrors btHashMap fix. */
static int[] simulateGhostFast(int[] bodies) {
List<Integer> overlapping = new ArrayList<>();
Map<Integer, Integer> index = new HashMap<>();
long ops = 0;
// add phase
for (int b : bodies) {
ops++;
if (!index.containsKey(b)) {
index.put(b, overlapping.size());
overlapping.add(b);
}
}
// remove phase
for (int b : bodies) {
ops++;
Integer pos = index.remove(b);
if (pos != null) {
int last = overlapping.size() - 1;
if (pos != last) {
Integer moved = overlapping.get(last);
overlapping.set(pos, moved);
index.put(moved, pos);
}
overlapping.remove(last);
}
}
return new int[]{overlapping.size(), (int)ops};
}
static void testGhostOverlapping() {
// Correctness: both paths produce same final state
int N = 20;
int[] bodies = new int[N];
for (int i = 0; i < N; i++) bodies[i] = i;
// add duplicates to stress dedup
int[] withDups = new int[N + 5];
System.arraycopy(bodies, 0, withDups, 0, N);
withDups[N] = 3; withDups[N+1] = 7; withDups[N+2] = 11; withDups[N+3] = 1; withDups[N+4] = 0;
int[] slowResult = simulateGhostSlow(withDups);
int[] fastResult = simulateGhostFast(withDups);
if (slowResult[0] != fastResult[0]) {
fail("bullet-0001 correctness N=20+dups",
"final size mismatch: slow=" + slowResult[0] + " fast=" + fastResult[0]);
return;
}
pass("bullet-0001 correctness N=20+dups");
// Performance: measure op count ratio at N=500
int P = 500;
int[] bigBodies = new int[P];
for (int i = 0; i < P; i++) bigBodies[i] = i;
long slowOps = 0, fastOps = 0;
for (int rep = 0; rep < 50; rep++) {
slowOps += simulateGhostSlow(bigBodies)[1];
fastOps += simulateGhostFast(bigBodies)[1];
}
double ratio = (double) slowOps / fastOps;
System.out.printf(" bullet-0001 P=500 slow_ops=%,d fast_ops=%,d ratio=%.0fx%n",
slowOps, fastOps, ratio);
if (ratio < 5.0) {
fail("bullet-0001 performance P=500", "ratio=" + ratio + " < 5x");
return;
}
pass("bullet-0001 performance ratio >= 5x at P=500");
}
// -----------------------------------------------------------------------
// bullet-0002: btCollisionObject::checkCollideWithOverride
// -----------------------------------------------------------------------
static long benchCheckCollideSlow(int M, int E) {
// E exclusions, M pair checks
List<Integer> exclusions = new ArrayList<>();
for (int i = 0; i < E; i++) exclusions.add(i);
long ops = 0;
for (int p = 0; p < M; p++) {
int candidate = p % (E + 5); // most misses
ops += exclusions.size(); // count comparisons
boolean found = exclusions.contains(candidate);
if (found) ops -= (exclusions.size() - exclusions.indexOf(candidate) - 1);
}
return ops;
}
static long benchCheckCollideFast(int M, int E) {
Set<Integer> exclusions = new HashSet<>();
for (int i = 0; i < E; i++) exclusions.add(i);
long ops = 0;
for (int p = 0; p < M; p++) {
int candidate = p % (E + 5);
ops++; // O(1) hash lookup
exclusions.contains(candidate);
}
return ops;
}
static void testCheckCollideWith() {
// Correctness: same decisions for same inputs
int M = 30, E = 8;
List<Integer> exclusionList = new ArrayList<>();
Set<Integer> exclusionSet = new HashSet<>();
for (int i = 0; i < E; i++) { exclusionList.add(i); exclusionSet.add(i); }
boolean mismatch = false;
for (int p = 0; p < M; p++) {
int candidate = p % (E + 5);
boolean slow = !exclusionList.contains(candidate);
boolean fast = !exclusionSet.contains(candidate);
if (slow != fast) { mismatch = true; break; }
}
if (mismatch) {
fail("bullet-0002 correctness M=30 E=8", "decision mismatch");
return;
}
pass("bullet-0002 correctness M=30 E=8");
// Performance: op-count ratio at M=1000 E=20
int bM = 1000, bE = 20;
long slowOps = benchCheckCollideSlow(bM, bE);
long fastOps = benchCheckCollideFast(bM, bE);
double ratio = (double) slowOps / fastOps;
System.out.printf(" bullet-0002 M=%d E=%d slow_ops=%,d fast_ops=%,d ratio=%.0fx%n",
bM, bE, slowOps, fastOps, ratio);
if (ratio < 5.0) {
fail("bullet-0002 performance M=1000 E=20", "ratio=" + ratio + " < 5x");
return;
}
pass("bullet-0002 performance ratio >= 5x at M=1000 E=20");
}
// -----------------------------------------------------------------------
// bullet-0003: btSortedOverlappingPairCache::removeOverlappingPair
// -----------------------------------------------------------------------
static long benchSortedCacheSlow(int P) {
// P pairs, remove all mirrors btSortedOverlappingPairCache::removeOverlappingPair
List<Integer> pairArray = new ArrayList<>();
for (int i = 0; i < P; i++) pairArray.add(i);
long ops = 0;
for (int i = 0; i < P; i++) {
int key = i;
// findLinearSearch: scan all remaining
for (int j = 0; j < pairArray.size(); j++) {
ops++;
if (pairArray.get(j).equals(key)) {
pairArray.set(j, pairArray.get(pairArray.size() - 1));
pairArray.remove(pairArray.size() - 1);
break;
}
}
}
return ops;
}
static long benchSortedCacheFast(int P) {
// P pairs, remove all HashMap<key, index>
List<Integer> pairArray = new ArrayList<>();
Map<Integer, Integer> pairIndex = new HashMap<>(P * 2);
for (int i = 0; i < P; i++) { pairArray.add(i); pairIndex.put(i, i); }
long ops = 0;
for (int i = 0; i < P; i++) {
ops++; // O(1) map lookup
Integer pos = pairIndex.remove(i);
if (pos != null) {
int last = pairArray.size() - 1;
if (pos != last) {
Integer moved = pairArray.get(last);
pairArray.set(pos, moved);
pairIndex.put(moved, pos);
}
pairArray.remove(last);
}
}
return ops;
}
static void testSortedCacheRemove() {
// Correctness: both produce empty array after removing all pairs
int P = 20;
List<Integer> slowArray = new ArrayList<>();
for (int i = 0; i < P; i++) slowArray.add(i);
for (int i = 0; i < P; i++) {
int idx = slowArray.indexOf(i);
if (idx >= 0) {
slowArray.set(idx, slowArray.get(slowArray.size() - 1));
slowArray.remove(slowArray.size() - 1);
}
}
List<Integer> fastArray = new ArrayList<>();
Map<Integer, Integer> fastIndex = new HashMap<>();
for (int i = 0; i < P; i++) { fastArray.add(i); fastIndex.put(i, i); }
for (int i = 0; i < P; i++) {
Integer pos = fastIndex.remove(i);
if (pos != null) {
int last = fastArray.size() - 1;
if (pos != last) {
Integer moved = fastArray.get(last);
fastArray.set(pos, moved);
fastIndex.put(moved, pos);
}
fastArray.remove(last);
}
}
if (slowArray.size() != 0 || fastArray.size() != 0) {
fail("bullet-0003 correctness P=20", "not empty: slow=" + slowArray.size() + " fast=" + fastArray.size());
return;
}
pass("bullet-0003 correctness P=20 (both empty after all removals)");
// Performance: op-count ratio at P=500
long slowOps = benchSortedCacheSlow(500);
long fastOps = benchSortedCacheFast(500);
double ratio = (double) slowOps / fastOps;
System.out.printf(" bullet-0003 P=500 slow_ops=%,d fast_ops=%,d ratio=%.0fx%n",
slowOps, fastOps, ratio);
if (ratio < 5.0) {
fail("bullet-0003 performance P=500", "ratio=" + ratio + " < 5x");
return;
}
pass("bullet-0003 performance ratio >= 5x at P=500");
}
public static void main(String[] args) {
System.out.println("Bullet Physics CWE-407 unit tests");
System.out.println("=".repeat(60));
System.out.println("\n[bullet-0001] btGhostObject::addOverlappingObjectInternal");
testGhostOverlapping();
System.out.println("\n[bullet-0002] btCollisionObject::checkCollideWithOverride");
testCheckCollideWith();
System.out.println("\n[bullet-0003] btSortedOverlappingPairCache::removeOverlappingPair");
testSortedCacheRemove();
System.out.println();
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}