banyaro/tests/js/test-nav-loop-closestidx.js
rene f7370028da KI-Vision-Model, Breed-Scraper, Karte/Routen + Release v1292
Parallele Arbeit (auf Staging mitgetestet): KI-Vision-Model (VISION_MODEL in
ki.py/routes, im KI-Status sichtbar), Breed-Scraper-Anpassungen
(breed_enricher/breed_evaluator, evaluate_enrichment mit user_id),
Karten-/Routen-Änderungen (map.js, routes.js), kleinere UI-Anpassungen
(admin.js, components.css), docker-compose, MARKETING, nav-loop-Test.

Version-Bump auf 1292 (VERSION, sw.js, app.js, index.html, landing.html).
2026-06-14 20:23:21 +02:00

98 lines
4.7 KiB
JavaScript

// Navi-Erst-Fix bei RUNDEN: der Startindex darf nicht ans Track-Ende springen.
//
// Spiegelt die _closestIdx-Erst-Fix-Logik aus js/pages/routes.js (_startNav). An einem
// Start/Ende-Knoten einer Runde ist der ENDPUNKT oft ein paar Meter näher als der
// Startpunkt; die alte globale Suche sprang dann sofort ans Track-Ende → 100 % / 0 km ab
// Sekunde 1 (Angie, Deining-Runde 09.06.2026). Bei Änderung BEIDE Stellen anpassen.
//
// Hinweis: bewusst eine Nachbildung — die echte Funktion ist eine Closure in _startNav
// und nicht exportierbar, ohne routes.js umzubauen.
const _haversineKm = (lat1, lon1, lat2, lon2) => {
const R = 6371, dLat = (lat2 - lat1) * Math.PI / 180, dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat / 2) ** 2 +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) ** 2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
};
// Erst-Fix-Index für gegebenen track + Userposition (1:1 aus routes.js).
function firstFixIdx(track, lat, lon) {
const search = (from, to) => {
let best = from, bestD = Infinity;
for (let i = from; i <= to; i++) {
const d = _haversineKm(lat, lon, track[i].lat, track[i].lon);
if (d < bestD) { bestD = d; best = i; }
}
return { best, bestD };
};
const isLoop = track.length > 2 &&
_haversineKm(track[0].lat, track[0].lon,
track[track.length - 1].lat, track[track.length - 1].lon) < 0.06;
const g = search(0, track.length - 1);
if (isLoop) {
const win = Math.min(track.length - 1, Math.max(30, Math.floor(track.length * 0.15)));
const s = search(0, win);
return { idx: s.bestD < 0.15 ? s.best : g.best, isLoop };
}
const s = search(0, Math.min(track.length - 1, 30));
return { idx: (s.bestD - g.bestD) * 1000 < 25 ? s.best : g.best, isLoop };
}
// Die ALTE Logik (vor dem Fix) — nur zum Beweis, dass der Fix wirklich etwas ändert.
function firstFixIdxOld(track, lat, lon) {
const search = (from, to) => {
let best = from, bestD = Infinity;
for (let i = from; i <= to; i++) {
const d = _haversineKm(lat, lon, track[i].lat, track[i].lon);
if (d < bestD) { bestD = d; best = i; }
}
return { best, bestD };
};
const g = search(0, track.length - 1);
const s = search(0, Math.min(track.length - 1, 30));
return (s.bestD - g.bestD) * 1000 < 25 ? s.best : g.best;
}
// --- Synthetische Deining-artige Runde -------------------------------------
const C = { lat: 48.07, lon: 11.50 };
const mLat = m => m / 111320;
const mLon = (m, lat) => m / (111320 * Math.cos(lat * Math.PI / 180));
// Punkt auf einem Kreis: Winkel von Nord, im Uhrzeigersinn.
const onCircle = (deg, r) => {
const rad = deg * Math.PI / 180;
return { lat: C.lat + mLat(r * Math.cos(rad)), lon: C.lon + mLon(r * Math.sin(rad), C.lat) };
};
const N = 40, R = 80; // 40 Punkte auf 80-m-Kreis, lange Runde von 0°→329°
const track = [];
for (let i = 0; i < N; i++) track.push(onCircle(i / (N - 1) * 329, R));
// User steht 3 m außerhalb des ENDpunkts (329°) → näher am Ende als am Start.
const user = onCircle(329, R + 3);
const startEndM = _haversineKm(track[0].lat, track[0].lon,
track[N - 1].lat, track[N - 1].lon) * 1000;
const dStart = _haversineKm(user.lat, user.lon, track[0].lat, track[0].lon) * 1000;
const dEnd = _haversineKm(user.lat, user.lon, track[N - 1].lat, track[N - 1].lon) * 1000;
console.log(`Runde: Start↔Ende ${startEndM.toFixed(0)} m | User→Start ${dStart.toFixed(0)} m, User→Ende ${dEnd.toFixed(0)} m`);
// 1. Loop wird erkannt (Start ≈ Ende < 60 m)
const res = firstFixIdx(track, user.lat, user.lon);
if (!res.isLoop) throw new Error('Runde nicht als Loop erkannt');
// 2. Erst-Fix landet im STARTbereich, NICHT am Track-Ende
console.log('Erst-Fix-Index:', res.idx, '(von', N - 1 + ')');
if (res.idx > Math.floor(N * 0.15)) throw new Error(`Erst-Fix sprang weg vom Start (idx ${res.idx})`);
// 3. Beweis: die alte Logik wäre hier ans Ende gesprungen (100 %)
const old = firstFixIdxOld(track, user.lat, user.lon);
console.log('Alte Logik-Index:', old);
if (old !== N - 1) throw new Error('Erwartet: alte Logik springt ans Ende — Testfall trifft den Bug nicht mehr');
// 4. Punkt-zu-Punkt-Route (kein Loop): User am Start → 0 %, am Ende → bleibt sinnvoll
const ptp = [];
for (let i = 0; i < N; i++) ptp.push({ lat: C.lat + mLat(i * 25), lon: C.lon }); // 25-m-Schritte nach Norden
const ptpRes = firstFixIdx(ptp, ptp[0].lat, ptp[0].lon);
if (ptpRes.isLoop) throw new Error('Gerade Route fälschlich als Loop erkannt');
if (ptpRes.idx !== 0) throw new Error(`Punkt-zu-Punkt am Start sollte idx 0 sein, war ${ptpRes.idx}`);
console.log('\nALLE NAV-LOOP-TESTS BESTANDEN');