Weltküche: Herkunft per Handels-Gravitation + Punktefix „alle falsch = 0"

- Zweit-Lieferant im Heimat-Zweig jetzt via Handels-Gravitation (Menge ÷
  Entfernung zur Heimat, Haversine über Länder-Koordinaten) statt roher
  Weltproduktion. AT-Eier → Deutschland (468 km) statt Frankreich (916 km),
  obwohl FR mehr Eier erzeugt. Distraktoren bleiben nie echte AT-Lieferanten.
  Verifiziert: Eier/Kartoffel/Milch → [Österreich, Deutschland]; alle 70
  Zutaten bauen fehlerfrei.
- Punktefix: Strafe pro Fehlklick = 100 ÷ Anzahl Distraktoren → „alle Falschen
  zuerst" ergibt IMMER 0 (bei 4 Distraktoren −25, bei 2 −50). Task-Banner
  zeigt die tatsächliche Strafe + „alle falsch zuerst = 0".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 15:09:16 +02:00
parent c1f90e255d
commit e23ebd960e
+24 -3
View File
@@ -1031,6 +1031,17 @@ function ingWas(ing, sTask, ingKey) {
return a + ' ' + (ing ? ing.nm : '');
}
// Entfernung zweier Länder (Haversine, km) über die Länder-Koordinaten.
// Für die Handels-Gravitation (Menge ÷ Entfernung) beim Zweit-Lieferanten.
function _wkDist(a, b) {
const A = C[a], B = C[b];
if (!A || !B || A.lat == null || B.lat == null) return 3000;
const R = 6371, toR = Math.PI / 180;
const dLat = (B.lat - A.lat) * toR, dLng = (B.lng - A.lng) * toR;
const s = Math.sin(dLat / 2) ** 2 + Math.cos(A.lat * toR) * Math.cos(B.lat * toR) * Math.sin(dLng / 2) ** 2;
return Math.max(60, 2 * R * Math.asin(Math.min(1, Math.sqrt(s))));
}
function buildTaskFor(ingKey) {
if (_taskCache[ingKey]) return _taskCache[ingKey];
const ing = ING[ingKey];
@@ -1054,9 +1065,14 @@ function buildTaskFor(ingKey) {
// (global/ungetaggt). „world-far"/„local" (wächst dort, landet aber nicht in unseren
// Regalen) zählen NICHT als korrekt.
const mrank = m => (m === 'eu' ? 0 : (m === 'world-far' || m === 'local') ? 2 : 1);
// Zweit-Lieferant = realistisch für UNSEREN Markt via Handels-Gravitation:
// Menge ÷ Entfernung zur Heimat. Ein naher großer Nachbar schlägt einen
// fernen Großproduzenten (AT-Eier → Deutschland, NICHT Frankreich), obwohl
// Frankreich mehr Eier erzeugt. Markt-Tier bleibt vorrangig (EU vor global).
const grav = ([c, info]) => (info.t || 0) / _wkDist(heimat, c);
const realistic = Object.entries(ing.producers)
.filter(([c, info]) => C[c] && c !== heimat && mrank(info.market) < 2)
.sort((a, b) => (mrank(a[1].market) - mrank(b[1].market)) || ((b[1].t || 0) - (a[1].t || 0)))
.sort((a, b) => (mrank(a[1].market) - mrank(b[1].market)) || (grav(b) - grav(a)))
.map(([c]) => c);
const correct = [heimat, ...realistic.slice(0, 1)]; // Heimat + eine realistische Zweit-Wahl (wie normaler Zweig: 2 Korrekte)
if (correct.length < 2) { // Garantie: es gibt IMMER eine zweite Wahl
@@ -1231,9 +1247,11 @@ function loadIngredient(){
: (task && task.signature
? 'aus welchem Land stammt diese Zutat in <b>diesem Gericht</b>?'
: 'aus welchem Land kommt diese Zutat häufig zu uns auf den Markt?');
const _nd = Math.max(1, task ? (task.choices.length - task.correct.length) : 4);
const _per = Math.round(100 / _nd);
document.getElementById('taskBanner').innerHTML=
`🔎 Suche: <b>${ing.nm}</b> ${frage}`
+ ` <span style="opacity:.75;font-weight:600;white-space:nowrap">· richtig <b>+100</b>, jeder Fehlklick <b>25</b> (¼)</span>`;
+ ` <span style="opacity:.75;font-weight:600;white-space:nowrap">· richtig <b>+100</b>, jeder Fehlklick <b>${_per}</b> (alle falsch zuerst = 0)</span>`;
document.getElementById('resultCard').className="result-card";
drawMarkers(ing); renderIngList();
}
@@ -1270,7 +1288,10 @@ function guess(key){
playSfx('click-country');
if (cls === 'correct') {
b.classList.add('correct');
const pts = Math.max(0, 100 - 25 * (current.wrongHere || 0)); // richtig = 100; jeder Fehlklick auf dieser Zutat: 25 (¼)
// Strafe pro Fehlklick so, dass „alle Falschen zuerst" IMMER 0 ergibt:
// 100 ÷ Anzahl Distraktoren. Bei 4 Distraktoren = 25 (¼), bei 2 = 50.
const numDistr = Math.max(1, task ? (task.choices.length - task.correct.length) : 4);
const pts = Math.max(0, Math.round(100 - (100 / numDistr) * (current.wrongHere || 0)));
state.score+=pts; current.dishScore+=pts;
state.totalCorrect++; current.wrongStreak=0; current.found.push(key);
map.flyTo([c.lat,c.lng],3,{duration:.7});