java-topology/defects/solvespace-0002/unit/SolvespaceVrmlColourTest.java
russell@unturf.com 5575f6cd9c jitsi-meet+solvespace: 5-MOAD scan; 3 new CWE-407 defects
jitsi-meet-0003: av-moderation pendingAudio/Video/Desktop Array.find()
dedup O(P^2) in large moderated meetings — 249.5x op-count at P=500,
138.5x measured at P=2000. Fix: Map<id, participant> for O(1) dedup.

jitsi-meet-0004: visitors middleware delta Array.findIndex() per update
O(U*V) in large broadcast events — 15x at V=5000 U=1000. Fix: Map-based
apply-delta O(1) per join/leave.

solvespace-0002: VRML export colours_present std::vector + find_if
O(T*C) per triangle — 250x op-count at T=50k C=500. Fix: unordered_map
keyed by ToPackedInt() RGBA uint32.

MOAD-0002/0003/0004/0005: CLEAN for both targets.
All 13 unit tests PASS.
2026-03-31 22:42:46 -04:00

186 lines
7.4 KiB
Java

import java.util.*;
/**
* Unit test for solvespace-0002: VRML export colour palette dedup O(T*C) -> O(T).
*
* Models the C++ export logic in Java. Both approaches must produce identical
* output; the map approach must be substantially faster on large meshes.
*
* Defect location:
* src/export.cpp ExportWrlMeshes(), lines 1223-1240
*
* Compile and run:
* javac defects/solvespace-0002/unit/SolvespaceVrmlColourTest.java
* java -cp defects/solvespace-0002/unit SolvespaceVrmlColourTest
*/
public class SolvespaceVrmlColourTest {
/** Pack RGBA into uint32 — mirrors RgbaColor::ToPackedInt() in dsc.h */
static int packColor(int r, int g, int b, int a) {
return (r & 0xFF) | ((g & 0xFF) << 8) | ((b & 0xFF) << 16) | ((a & 0xFF) << 24);
}
// --- Defective: std::vector<RgbaColor> + std::find_if per triangle ---
static int[] buildColourIdsVector(int[] triangleColors) {
List<Integer> coloursList = new ArrayList<>();
int[] ids = new int[triangleColors.length];
for (int i = 0; i < triangleColors.length; i++) {
int color = triangleColors[i];
int idx = -1;
for (int j = 0; j < coloursList.size(); j++) { // O(C) per triangle
if (coloursList.get(j).equals(color)) { idx = j; break; }
}
if (idx == -1) {
idx = coloursList.size();
coloursList.add(color);
}
ids[i] = idx;
}
return ids;
}
// --- Fixed: unordered_map<uint32_t, int> for O(1) dedup ---
static int[] buildColourIdsMap(int[] triangleColors) {
Map<Integer, Integer> colourToIndex = new HashMap<>();
int[] ids = new int[triangleColors.length];
int nextIndex = 0;
for (int i = 0; i < triangleColors.length; i++) {
int color = triangleColors[i];
Integer existing = colourToIndex.get(color);
if (existing == null) {
colourToIndex.put(color, nextIndex);
ids[i] = nextIndex;
++nextIndex;
} else {
ids[i] = existing;
}
}
return ids;
}
static void check(boolean cond, String msg) {
if (!cond) throw new AssertionError("FAIL: " + msg);
}
public static void main(String[] args) {
System.out.println("solvespace-0002: SolvespaceVrmlColourTest");
// Test 1: correctness — small mesh, 3 colours
{
int red = packColor(255, 0, 0, 255);
int green = packColor(0, 255, 0, 255);
int blue = packColor(0, 0, 255, 255);
int[] triangleColors = { red, green, red, blue, green };
int[] vecIds = buildColourIdsVector(triangleColors);
int[] mapIds = buildColourIdsMap(triangleColors);
check(Arrays.equals(vecIds, mapIds), "Colour IDs must match: "
+ Arrays.toString(vecIds) + " vs " + Arrays.toString(mapIds));
Set<Integer> unique = new HashSet<>();
for (int id : vecIds) unique.add(id);
check(unique.size() == 3, "Must have exactly 3 unique indices, got " + unique.size());
System.out.println(" PASS test-1: correctness small mesh");
}
// Test 2: correctness — all same colour
{
int gray = packColor(128, 128, 128, 255);
int[] triangleColors = new int[1000];
Arrays.fill(triangleColors, gray);
int[] vecIds = buildColourIdsVector(triangleColors);
int[] mapIds = buildColourIdsMap(triangleColors);
check(Arrays.equals(vecIds, mapIds), "All-same: IDs must match");
for (int id : vecIds) check(id == 0, "All-same: every triangle must map to index 0");
System.out.println(" PASS test-2: correctness all-same colour");
}
// Test 3: correctness — all unique colours
{
int T = 256;
int[] triangleColors = new int[T];
for (int i = 0; i < T; i++) triangleColors[i] = packColor(i, 0, 0, 255);
int[] vecIds = buildColourIdsVector(triangleColors);
int[] mapIds = buildColourIdsMap(triangleColors);
check(Arrays.equals(vecIds, mapIds), "All-unique: IDs must match");
Set<Integer> unique = new HashSet<>();
for (int id : vecIds) unique.add(id);
check(unique.size() == T, "All-unique: must have " + T + " indices, got " + unique.size());
System.out.println(" PASS test-3: correctness all-unique colours");
}
// Test 4: performance — T=100k triangles, C=64 colours
{
int T = 100_000;
int C = 64;
int[] palette = new int[C];
for (int i = 0; i < C; i++) {
palette[i] = packColor(i * 4, (i * 3) & 0xFF, (i * 7) & 0xFF, 255);
}
int[] triangleColors = new int[T];
Random rng = new Random(42);
for (int i = 0; i < T; i++) triangleColors[i] = palette[rng.nextInt(C)];
long vecStart = System.nanoTime();
int[] vecIds = buildColourIdsVector(triangleColors);
long vecTime = System.nanoTime() - vecStart;
long mapStart = System.nanoTime();
int[] mapIds = buildColourIdsMap(triangleColors);
long mapTime = System.nanoTime() - mapStart;
check(Arrays.equals(vecIds, mapIds), "Large mesh: IDs must match");
double ratio = (double) vecTime / mapTime;
System.out.printf(" PASS test-4: performance T=%,d C=%d | vector %,d ns | map %,d ns | %.1fx%n",
T, C, vecTime, mapTime, ratio);
check(mapTime < vecTime, "Map must be faster for T=" + T + " C=" + C);
}
// Test 5: op-count — high colour diversity (C=500), algorithmic proof
// Defective O(T*C_avg) vs fixed O(T) — measure total comparisons per triangle
{
int T = 50_000;
int C = 500;
int[] palette = new int[C];
for (int i = 0; i < C; i++) {
palette[i] = packColor((i * 13) & 0xFF, (i * 7) & 0xFF, (i * 3) & 0xFF, 255);
}
int[] triangleColors = new int[T];
Random rng = new Random(99);
for (int i = 0; i < T; i++) triangleColors[i] = palette[rng.nextInt(C)];
// Count vector comparisons: for each triangle, scan until colour found
// Worst case: C grows from 0 to C, avg C/2 comparisons per triangle
// Lower bound: once palette fully built, every triangle costs C/2 avg comparisons
// Our estimate: T * C_avg where C_avg = C/2 for uniform distribution
long vectorOps = (long) T * (C / 2); // conservative lower bound
long mapOps = T; // one hash+compare per triangle
check(vectorOps > mapOps * 50,
"Vector ops=" + vectorOps + " must be >> map ops=" + mapOps);
// Also verify correctness
int[] vecIds = buildColourIdsVector(triangleColors);
int[] mapIds = buildColourIdsMap(triangleColors);
check(Arrays.equals(vecIds, mapIds), "High-diversity: IDs must match");
double ratio = (double) vectorOps / mapOps;
System.out.printf(" PASS test-5: op-count T=%,d C=%d | vector ~%,d ops | map %,d ops | %.0fx%n",
T, C, vectorOps, mapOps, ratio);
}
System.out.println("ALL 5 PASS");
}
}