// Pure circle-circle geometry for the DEEP SCAN pulse (js/scan/ScanPulse.js). // No Phaser — kept separately so it is unit-testable in Node // (dev/scan.test.mjs). // // The wavefront is a ship-centered circle of radius r. Only the part inside // the tether region (the UNION of tether circles) is drawn — outside the // union the wave is absorbed, so the pulse stops at the union boundary. // For one tether circle (center A, radius R) vs the origin circle (center // O, radius r), both "windows" are the classic circle-circle intersection: /** * The arc of a circle of radius `a` (around its center) that lies inside a * circle of radius `b`, the two centers `d` apart. * * Returns the half-angle (radians) of the window, centered on the * `a`-center → `b`-center direction: * Math.PI — the whole circle `a` is inside `b` * -1 — no overlap */ export function arcInCircle(a, b, d) { if (a <= 0) return -1; if (d === 0) return a <= b ? Math.PI : -1; const c = (a * a + d * d - b * b) / (2 * a * d); if (c <= -1) return Math.PI; if (c >= 1) return -1; return Math.acos(c); } /** * The wavefront radius at which an origin-centered front first touches a * tether of radius `R` whose anchor is `d` away (0 = concentric circles — * the front grows through the whole rim at once, at r = R). */ export function contactRadius(R, d) { if (d === 0) return R; return Math.abs(R - d); } /** The farthest point of a tether rim from the origin — the point the wave * is absorbed last. The sweep runs to the max of this over all tethers. */ export function farRadius(R, d) { return R + d; }