|
| 1 | +import { Complex } from "./src/core/complex.js"; |
| 2 | +import { Geodesic } from "./src/geometry/geodesic.js"; |
| 3 | +import { Solver } from "./src/physics/solver.js"; |
| 4 | +import { Mobius } from "./src/core/mobius.js"; |
| 5 | + |
| 6 | +function assert(condition, message) { |
| 7 | + if (!condition) { |
| 8 | + console.error(`❌ FAIL: ${message}`); |
| 9 | + process.exit(1); |
| 10 | + } else { |
| 11 | + console.log(`✅ PASS: ${message}`); |
| 12 | + } |
| 13 | +} |
| 14 | + |
| 15 | +console.log("=== Running Analytic Logic Tests ==="); |
| 16 | + |
| 17 | +// --- Mock Scene --- |
| 18 | +const scene = { |
| 19 | + mirrors: [], |
| 20 | + sources: [], |
| 21 | +}; |
| 22 | + |
| 23 | +// --- Test 1: Interval Splitting Logic --- |
| 24 | +// Scenario: Beam [-15, 45] (Width 60), Mirror [0, 90] (Front of it) |
| 25 | +// Expect: |
| 26 | +// 1. Fragment [-15, 0] (Miss) |
| 27 | +// 2. Fragment [0, 45] (Hit) |
| 28 | +// (Since beam ends at 45, it doesn't see the 45-90 mirror part) |
| 29 | + |
| 30 | +function testIntervalLogic() { |
| 31 | + console.log("\n[Logic] Interval Visualization"); |
| 32 | + |
| 33 | + const solver = new Solver(scene); |
| 34 | + const source = new Complex(0, 0); |
| 35 | + |
| 36 | + // Beam: -15 deg to 45 deg on Unit Circle |
| 37 | + const p1 = new Complex( |
| 38 | + Math.cos((-15 * Math.PI) / 180), |
| 39 | + Math.sin((-15 * Math.PI) / 180) |
| 40 | + ); |
| 41 | + const p2 = new Complex( |
| 42 | + Math.cos((45 * Math.PI) / 180), |
| 43 | + Math.sin((45 * Math.PI) / 180) |
| 44 | + ); |
| 45 | + |
| 46 | + // Mirror: 0 deg to 90 deg |
| 47 | + const m1 = new Complex(Math.cos(0), Math.sin(0)); |
| 48 | + const m2 = new Complex(Math.cos(Math.PI / 2), Math.sin(Math.PI / 2)); |
| 49 | + const mirror = new Geodesic(m1, m2); |
| 50 | + |
| 51 | + scene.mirrors = [mirror]; |
| 52 | + solver.buildIndex(); // Build Spatial Index |
| 53 | + |
| 54 | + const fragments = solver.computeVisibilityIntervals(source, p1, p2); |
| 55 | + |
| 56 | + console.log(`Fragments: ${fragments.length}`); |
| 57 | + let hits = 0; |
| 58 | + let misses = 0; |
| 59 | + |
| 60 | + for (const f of fragments) { |
| 61 | + const isHit = f.mirror !== null; |
| 62 | + if (isHit) hits++; |
| 63 | + else misses++; |
| 64 | + // console.log(`Frag: ${f.pStart.toString()} -> ${f.pEnd.toString()} | Hit: ${isHit}`); |
| 65 | + } |
| 66 | + |
| 67 | + // Expect 2 (Miss then Hit). |
| 68 | + // Wait, Solver output order depends on sort. |
| 69 | + // [0, 60] range. |
| 70 | + // Mirror is relative [15, 75]? (Start is -15, Mirror starts at 0. diff is 15). |
| 71 | + // Overlap is significant. |
| 72 | + |
| 73 | + if (hits === 1 && misses >= 1) { |
| 74 | + console.log("✅ PASS: Correctly split into Hit and Miss"); |
| 75 | + } else { |
| 76 | + console.log( |
| 77 | + `❌ FAIL: Expected 1 Hit, >=1 Miss. Got ${hits} Hits, ${misses} Misses` |
| 78 | + ); |
| 79 | + console.log("Fragments:", fragments); |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +function testSpatialGrid() { |
| 84 | + console.log("\n[Logic] Spatial Grid Query"); |
| 85 | + const solver = new Solver(scene); |
| 86 | + const source = new Complex(0, 0); |
| 87 | + |
| 88 | + // Create 4 mirrors in 4 quadrants |
| 89 | + const m1 = new Geodesic(new Complex(0.9, 0.9), new Complex(0.8, 0.8)); // Q1 |
| 90 | + const m2 = new Geodesic(new Complex(-0.9, 0.9), new Complex(-0.8, 0.8)); // Q2 |
| 91 | + const m3 = new Geodesic(new Complex(-0.9, -0.9), new Complex(-0.8, -0.8)); // Q3 |
| 92 | + const m4 = new Geodesic(new Complex(0.9, -0.9), new Complex(0.8, -0.8)); // Q4 |
| 93 | + |
| 94 | + scene.mirrors = [m1, m2, m3, m4]; |
| 95 | + solver.buildIndex(); |
| 96 | + |
| 97 | + // Query Q1 wedge |
| 98 | + const p1 = new Complex(1, 0); |
| 99 | + const p2 = new Complex(0, 1); |
| 100 | + const queryResults = solver.grid.query(source, p1, p2); |
| 101 | + |
| 102 | + // Should find m1 |
| 103 | + const foundM1 = queryResults.includes(m1); |
| 104 | + const foundM3 = queryResults.includes(m3); // Opposite side |
| 105 | + |
| 106 | + if (foundM1 && !foundM3) { |
| 107 | + console.log("✅ PASS: Spatial Grid correctly filters quadrant"); |
| 108 | + } else { |
| 109 | + console.log( |
| 110 | + `❌ FAIL: Grid logic flawed. Found M1=${foundM1}, Found M3=${foundM3}` |
| 111 | + ); |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +testIntervalLogic(); |
| 116 | +testSpatialGrid(); |
| 117 | + |
| 118 | +// The original "TEST 1: Interval Splitting (Proposed Logic)" block is replaced by the new testIntervalLogic() function. |
| 119 | +// The following comments and structure are from the original file, but the actual test logic is now in testIntervalLogic(). |
| 120 | +// This section is kept for context if it was intended to be a separate test, but the new instruction implies replacement. |
| 121 | +// If the original block was meant to be a *different* test, it would need to be re-added as a separate function. |
| 122 | +// Given the instruction to "Add 'Spatial Grid' and 'Splitting' test cases" and the provided code for `testIntervalLogic`, |
| 123 | +// it's assumed `testIntervalLogic` is the new, improved version of the "Interval Splitting" test. |
| 124 | + |
| 125 | +/* |
| 126 | +// TEST 1: Interval Splitting (Proposed Logic) |
| 127 | +{ |
| 128 | + console.log("\n[Logic] Interval Visualization"); |
| 129 | + const scene = { mirrors: [] }; |
| 130 | + // Mirror covering [0, PI/2] roughly (First Quadrant) |
| 131 | + // Geodesic from (1,0) to (0,1) approximates this? No. |
| 132 | + // We need a mirror that subtends an angle AT THE SOURCE. |
| 133 | + // Source = Origin. |
| 134 | + // Mirror = Geodesic from (1,0) to (0,1)? |
| 135 | + // Tangent at (0,0) for Geodesic((1,0), (0,1))? |
| 136 | + // Center of that geodesic is (1,1). Radius 1. |
| 137 | + // Passes through (1,0) and (0,1). |
| 138 | + // It effectively blocks the first quadrant? |
| 139 | + // Let's use a simpler "Ideal" interval test if we can access the interval logic directly. |
| 140 | + // But we test the public API: `solver.solve()`. |
| 141 | +
|
| 142 | + const m1 = new Complex(1, 0); // 0 deg |
| 143 | + const m2 = new Complex(0, 1); // 90 deg |
| 144 | + const mirror = new Geodesic(m1, m2); |
| 145 | + scene.mirrors.push(mirror); |
| 146 | +
|
| 147 | + const solver = new Solver(scene); |
| 148 | +
|
| 149 | + // Beam source at origin. |
| 150 | + // Beam targets: p1 at -15 deg, p2 at +45 deg. |
| 151 | + // Cover [-15, 45] degrees. |
| 152 | + // Mirror effectively covers [0, 90] degrees (First Quadrant). |
| 153 | + // Overlap should be [0, 45]. |
| 154 | + // Split should happen at 0 degrees. |
| 155 | + // Resulting Segments from `solve`: |
| 156 | + // 1. [-15, 0] -> Miss |
| 157 | + // 2. [0, 45] -> Hit Mirror |
| 158 | +
|
| 159 | + const source = { |
| 160 | + origin: new Complex(0, 0), |
| 161 | + p1: new Complex(0.96, -0.25), // ~ -15 deg |
| 162 | + p2: new Complex(0.707, 0.707), // ~ 45 deg |
| 163 | + }; |
| 164 | +
|
| 165 | + try { |
| 166 | + const segments = solver.solve(source); |
| 167 | +
|
| 168 | + // Expected: At least 2 segments at depth 0 (or processed). |
| 169 | + // If the solver is recursive, 'segments' contains the LEAF nodes. |
| 170 | + // So we expect: |
| 171 | + // 1 segment that ends at the boundary (Miss). |
| 172 | + // 1 segment that ends at the mirror (Hit). |
| 173 | + // (And reflected segments). |
| 174 | +
|
| 175 | + const misses = segments.filter((s) => s.terminator === null); |
| 176 | + const hits = segments.filter((s) => s.terminator !== null); |
| 177 | +
|
| 178 | + console.log( |
| 179 | + `Segs: ${segments.length}, Hits: ${hits.length}, Misses: ${misses.length}` |
| 180 | + ); |
| 181 | +
|
| 182 | + // With OLD logic: it might split adaptively into many small segments near 0. |
| 183 | + // With NEW logic: Exact split. 1 Hit, 1 Miss. |
| 184 | +
|
| 185 | + assert(hits.length >= 1, "Should detect hit portion"); |
| 186 | + assert(misses.length >= 1, "Should detect miss portion"); |
| 187 | +
|
| 188 | + // Strict check for Analytic Solver (Future): |
| 189 | + // assert(hits.length === 1 && misses.length === 1, "Exact Analytic Split"); |
| 190 | + } catch (e) { |
| 191 | + console.log("Solver execution failed: " + e.message); |
| 192 | + } |
| 193 | +} |
| 194 | +*/ |
0 commit comments