Fix tether visible-arc logic for exact internal tangency
- Treat k <= -1 as "other zone contains this rim" so a level-1 gate tether at exactly the level-2 anchor's range no longer draws a spurious full circle inside the union. - Update doc comment to clarify external vs internal tangency cases. - Add regression tests covering the d + rInner = rOuter boundary case. - Add spacestation music and surface video assets.
This commit is contained in:
parent
5efc5dfe61
commit
92520a0503
Binary file not shown.
Binary file not shown.
|
|
@ -215,6 +215,25 @@ const dist = (x, y, t) => Math.hypot(x - t.x, y - t.y);
|
|||
return arcs.length === 1 && Math.abs(arcs[0].a1 - arcs[0].a0 - TAU) < 1e-9;
|
||||
})());
|
||||
|
||||
// INTERNAL TANGENCY — the gate case: d + rInner = rOuter EXACTLY (a
|
||||
// level-1 gate tether placed at exactly the level-2 anchor's range:
|
||||
// 5120 + 5120 = 10240). The inner rim lies entirely inside the outer
|
||||
// zone, touching at one point — it contributes nothing to the union
|
||||
// boundary, so the inner tether draws NO line; the outer stays full.
|
||||
// Regression: at k == −1 exactly the old branch fell into "no covered
|
||||
// arc" and drew the whole inner circle as a spurious border inside the
|
||||
// union (visible in-game and on the map).
|
||||
const outerT = new Tether('outer', 0, 0, 1); outerT.radius = 10240;
|
||||
const innerT = new Tether('inner', 5120, 0, 1); innerT.radius = 5120;
|
||||
const fieldT = [outerT, innerT];
|
||||
check('internal tangency: inner rim fully covered → no line inside the union',
|
||||
Tether.visibleArcs(innerT, fieldT).length === 0);
|
||||
check('internal tangency: outer tether stays a full circle',
|
||||
(() => {
|
||||
const arcs = Tether.visibleArcs(outerT, fieldT);
|
||||
return arcs.length === 1 && Math.abs(arcs[0].a1 - arcs[0].a0 - TAU) < 1e-9;
|
||||
})());
|
||||
|
||||
// Partial overlap — EXACT covered length (k from the circle geometry):
|
||||
// covered(θ) where cos(θ−β) ≥ k, k = (rA²+d²−rB²)/(2·rA·d)
|
||||
const A = new Tether('a', 0, 0, 1); // r = 5120
|
||||
|
|
|
|||
|
|
@ -103,9 +103,13 @@ export class Tether {
|
|||
* another zone o ⟺ |P − o| ≤ o.radius ⟺ cos(θ − β) ≥ k, where
|
||||
* β = atan2(o.y − y, o.x − x), k = (r² + d² − o.radius²) / (2·r·d),
|
||||
* d = |center → o|. So each other tether covers the angular interval
|
||||
* [β − arccos k, β + arccos k] (when −1 < k < 1); k < −1 means o
|
||||
* contains this whole rim; k ≥ 1 means no overlap. Union the covered
|
||||
* intervals on the circle; the complement is the visible arcs.
|
||||
* [β − arccos k, β + arccos k] (when −1 < k < 1); k ≤ −1 means o
|
||||
* contains this whole rim — even at EXACT internal tangency (k = −1,
|
||||
* e.g. a level-1 gate tether placed at exactly the level-2 anchor's
|
||||
* range: 5120 + 5120 = 10240), where the rim lies entirely inside o
|
||||
* and contributes no boundary; k ≥ 1 means externally tangent or
|
||||
* disjoint (no covered arc). Union the covered intervals on the
|
||||
* circle; the complement is the visible arcs.
|
||||
*
|
||||
* @returns {Array<{a0:number, a1:number}>} arcs in [0, TAU), a1 > a0, sorted
|
||||
*/
|
||||
|
|
@ -123,8 +127,8 @@ export class Tether {
|
|||
continue;
|
||||
}
|
||||
const k = (r * r + d * d - o.radius * o.radius) / (2 * r * d);
|
||||
if (k < -1) return []; // o contains this disc → the whole rim is covered
|
||||
if (k <= -1 || k >= 1) continue; // tangent or disjoint → no covered arc
|
||||
if (k <= -1) return []; // o contains this rim — strictly, or exactly internally tangent
|
||||
if (k >= 1) continue; // externally tangent or disjoint → no covered arc
|
||||
const beta = Math.atan2(dy, dx);
|
||||
const half = Math.acos(k);
|
||||
covered.push([beta - half, beta + half]);
|
||||
|
|
|
|||
Loading…
Reference in New Issue