// Unit test for regamedll-cs-0001: CLocalNav::NodeExists O(N) linear scan // inside BFS pathfinding loop, making hostage FindPath() O(N^2). // // Defect: NodeExists() scans all existing nodes linearly to check if a // coordinate pair already exists. Called 8 times per BFS expansion step // (once per AddPathNode), with up to MAX_NODES=100 nodes, yielding // O(100*100) = O(10,000) comparisons per FindPath() call. // // Fix: Replace linear scan with unordered_set keyed on packed (offsetX, // offsetY) coordinate pairs for O(1) lookup. // // This test verifies correctness and measures the operation count // reduction. #include #include #include #include #include #include // Simulate the defect: linear scan NodeExists static int g_unpatched_ops = 0; struct FakeNode { int offsetX; int offsetY; }; static const int MAX_NODES = 100; static FakeNode g_nodes[MAX_NODES]; static int g_nodeCount = 0; // UNPATCHED: Linear scan of all nodes int NodeExists_Unpatched(int offsetX, int offsetY) { for (int i = g_nodeCount - 1; i >= 0; i--) { g_unpatched_ops++; if (g_nodes[i].offsetX == offsetX && g_nodes[i].offsetY == offsetY) { return i; } } return -1; } void AddNode_Unpatched(int offsetX, int offsetY) { if (g_nodeCount >= MAX_NODES) return; g_nodes[g_nodeCount].offsetX = offsetX; g_nodes[g_nodeCount].offsetY = offsetY; g_nodeCount++; } // PATCHED: Hash set lookup static int g_patched_ops = 0; static std::unordered_set g_nodeCoordSet; static FakeNode g_pnodes[MAX_NODES]; static int g_pnodeCount = 0; static int64_t PackCoord(int offsetX, int offsetY) { return (static_cast(offsetX) << 32) | static_cast(offsetY); } int NodeExists_Patched(int offsetX, int offsetY) { g_patched_ops++; int64_t key = PackCoord(offsetX, offsetY); if (g_nodeCoordSet.find(key) == g_nodeCoordSet.end()) return -1; return 0; } void AddNode_Patched(int offsetX, int offsetY) { if (g_pnodeCount >= MAX_NODES) return; g_pnodes[g_pnodeCount].offsetX = offsetX; g_pnodes[g_pnodeCount].offsetY = offsetY; g_pnodeCount++; g_nodeCoordSet.insert(PackCoord(offsetX, offsetY)); } // Simulate BFS expansion: add nodes in 8 directions from each explored node, // checking NodeExists before each add (same as AddPathNode logic). void SimulateBFS_Unpatched() { g_nodeCount = 0; g_unpatched_ops = 0; // Seed with 8 initial neighbors (like AddPathNodes(INVALID)) int dx[] = {1, -1, 0, 0, 1, 1, -1, -1}; int dy[] = {0, 0, 1, -1, 1, -1, 1, -1}; for (int d = 0; d < 8; d++) { if (NodeExists_Unpatched(dx[d], dy[d]) == -1) { AddNode_Unpatched(dx[d], dy[d]); } } // Expand from each node (simulating BFS loop) for (int n = 0; n < g_nodeCount && g_nodeCount < MAX_NODES; n++) { int bx = g_nodes[n].offsetX; int by = g_nodes[n].offsetY; for (int d = 0; d < 8; d++) { int nx = bx + dx[d]; int ny = by + dy[d]; if (NodeExists_Unpatched(nx, ny) == -1) { AddNode_Unpatched(nx, ny); } } } } void SimulateBFS_Patched() { g_pnodeCount = 0; g_patched_ops = 0; g_nodeCoordSet.clear(); int dx[] = {1, -1, 0, 0, 1, 1, -1, -1}; int dy[] = {0, 0, 1, -1, 1, -1, 1, -1}; for (int d = 0; d < 8; d++) { if (NodeExists_Patched(dx[d], dy[d]) == -1) { AddNode_Patched(dx[d], dy[d]); } } for (int n = 0; n < g_pnodeCount && g_pnodeCount < MAX_NODES; n++) { int bx = g_pnodes[n].offsetX; int by = g_pnodes[n].offsetY; for (int d = 0; d < 8; d++) { int nx = bx + dx[d]; int ny = by + dy[d]; if (NodeExists_Patched(nx, ny) == -1) { AddNode_Patched(nx, ny); } } } } // Test PackCoord correctness with negative coordinates void TestPackCoord() { // Distinct coordinates must produce distinct keys assert(PackCoord(0, 0) != PackCoord(1, 0)); assert(PackCoord(0, 0) != PackCoord(0, 1)); assert(PackCoord(-1, -1) != PackCoord(1, 1)); assert(PackCoord(-1, 0) != PackCoord(0, -1)); assert(PackCoord(5, -3) != PackCoord(-3, 5)); // Same coordinates must produce same key assert(PackCoord(7, -2) == PackCoord(7, -2)); assert(PackCoord(-12, 12) == PackCoord(-12, 12)); printf("PASS: PackCoord correctness\n"); } // Test that patched version produces same node set as unpatched void TestCorrectness() { SimulateBFS_Unpatched(); SimulateBFS_Patched(); // Both must produce exactly the same number of nodes assert(g_nodeCount == g_pnodeCount); // Verify every unpatched node exists in patched set for (int i = 0; i < g_nodeCount; i++) { int64_t key = PackCoord(g_nodes[i].offsetX, g_nodes[i].offsetY); assert(g_nodeCoordSet.count(key) == 1); } printf("PASS: Correctness (both produce %d nodes)\n", g_nodeCount); } // Test operation count reduction void TestPerformance() { SimulateBFS_Unpatched(); int unpatched = g_unpatched_ops; SimulateBFS_Patched(); int patched = g_patched_ops; double ratio = (double)unpatched / (double)patched; printf(" Unpatched ops: %d\n", unpatched); printf(" Patched ops: %d\n", patched); printf(" Ratio: %.1fx\n", ratio); // Unpatched should be significantly worse (at least 5x more operations) assert(ratio >= 5.0); printf("PASS: Performance (%.1fx reduction)\n", ratio); } int main() { printf("regamedll-cs-0001: CLocalNav::NodeExists O(N) -> O(1) hash set\n"); printf("Hostage pathfinding FindPath() O(N^2) -> O(N)\n\n"); TestPackCoord(); TestCorrectness(); TestPerformance(); printf("\nAll tests passed.\n"); return 0; }