Grundzutaten, die im Land des Users heimisch sind, kommen jetzt aus dem Land
des USERS statt aus dem Gericht-Land (Carbonara-Ei in AT = AT-Ei). Opt-in pro
Zutat ueber neues Feld `heimisch`; Signaturzutaten bleiben am Gericht.
game.html:
- buildTaskFor: neuer Consumer-Zweig (vor Heimat-Regel). correct = [User-Land,
...market], Distraktoren explizit (`distractors`) oder berechnet. Immer 5 Karten.
- _wkUser(): WK_COUNTRY (AT/DE/CH/LI) -> Laender-Slug, Fallback AT.
- isWorldIngredient: Zutat mit `heimisch` wird immer abgefragt (z.B. Semmelbroesel).
- ING-Build: heimisch/market/distractors werden aus der Matrix mitkopiert
(wurden vorher weggelassen -> Felder kamen nie im Spiel an).
- Aufloesungstext: Consumer-Fall ("kaufst du, wo du einkaufst - im eigenen Land").
Daten (production-matrix.json), Thomas-Vorgaben:
- eier: heimisch AT/DE/CH, market DE+NL -> AT-User: AT+DE+NL, DE-User: DE+NL
- kartoffel: heimisch AT/DE/CH, market DE -> AT+DE (alle Gerichte)
- semmelbroesel: heimisch AT/DE, market DE, distractors GR/TR/EG/TN/IE; jetzt abgefragt
admin-weltkueche.html: Consumer-Zweig gespiegelt + AT/DE/CH-Umschalter.
Nebenbefund (NICHT angefasst): correct_for wird im Spiel nie gelesen (nur Admin
las es) -> mehl/majoran/safran/feta-lamm-Kuratierung wirkte bisher nur im Admin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- eier.correct_for.oesterreich: ["deutschland","niederlande"] (war ["slowakei"])
-> AT-Ei-Gerichte zeigen jetzt AT + DE + NL (Thomas-Vorgabe: NL/DE sind die
echten Import-Herkuenfte, SK spielt kaum Rolle). Gilt fuer alle 7 AT-Gerichte
mit Ei; DE-Gerichte zeigen via Heuristik ohnehin schon DE + NL.
- buildTaskFor Heimat-Zweig: Distraktoren auf 5-correct.length gedeckelt, damit
bei 3 richtigen Laendern trotzdem 5 Karten bleiben (Punkte-Norm).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- profil.html: Länderauswahl (AT/DE/CH/LI) im Lehrer-Profil, lädt/zeigt aktuelles Land.
- profile.php: speichert country in teachers.country; gibt country zurück.
- weltkueche.php: injiziert window.WK_COUNTRY = Country::current() (Kaskade Schüler→Klasse→Lehrer→AT) — Fundament für die konsumentenland-basierte Herkunft (Teil B).
Spalte teachers.country existierte bereits, keine Migration nötig.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Auflösung: „Das ist ein Gericht aus <Land> → Zutaten aus <Land>" nur noch bei
ECHTEN Signatur-Zutaten (Tiroler Speck etc.). Grundzutaten (Butter, Mehl) bekommen
die neutrale „aus der Nähe"-Begründung statt der falschen Gericht-Herkunft-Logik.
- admin-weltkueche.html: classify() respektiert jetzt correct_for (zeigt sonst falsche
Herkünfte, z.B. Mehl AT+DE statt AT+Ungarn).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neu: additives `correct_for[heimat]` in buildTaskFor — uebersteuert die Gravitations-
Schaetzung NUR bei kuratierten Zutaten (echte Handelsdaten), alles andere unveraendert.
- mehl→AT+Ungarn (Weizenimport 2023: HU 604k >> DE 41k), kartoffel→AT+Schweiz,
eier→AT+Slowakei (Variation weg vom Dauer-AT+DE), majoran→AT+Deutschland (statt FR),
safran→Spanien+Iran (statt IT mit 1 t), feta/lamm→Griechenland+Bulgarien (Balkan statt FR).
- Datenfixes gegen „echter Lieferant faelschlich als Distraktor": AT raus aus Mozzarella-
climate_possible + als Produzent ergaenzt; CH raus aus Kartoffel-climate_possible +
Produzent; SK als Eier-Produzent; Aegypten-Zwiebel local→global (echter kleiner Lieferant).
- Verifiziert: 7 kuratierte korrekt; keine Regression (DE-Gerichte nutzen weiter Algorithmus).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- app.php: BASE_URL fuer *.geograsim.at war hart 'https://geograsim.at' -> auf
v3.geograsim.at lud die Glossar-API cross-origin -> CORS/NetworkError
("Glossar konnte nicht geladen werden"). Jetzt BASE_URL='https://'.$host
(same-origin auf jeder Umgebung; prod-neutral, geograsim.at bleibt gleich).
- weltkueche/game.html: Zweit-Lieferant hiess pauschal „Nachbarland" — Frankreich
ist aber kein Nachbar Oesterreichs. Jetzt „europaeisches Land in unserer Naehe".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- admin-todo.html: mobile-freundliche ToDo/Review-Liste (Prioritaeten, Haekchen +
Notizfelder je Punkt, localStorage, Notizen-Export), admin-gated, noindex. In
admin-licenses.html Nav + Backend-Werkzeuge verlinkt.
- admin.php: devPin auch bei STAGING=1 (v3, Mail bewusst tot) -> Self-Service-
Admin-Login ohne Mailversand. Prod unveraendert (STAGING dort nie definiert,
2FA-Mail-Pflicht bleibt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Beim Hinzufuegen von firebase/php-jwt + predis war PHPMailer aus composer.json
und dem Autoloader gefallen (Dateien fehlten ganz). Folge: `new PHPMailer` fatalt
-> 500 bei register/reset/2FA/Schueler-Zugang. Auf v3 aufgedeckt und behoben:
composer.json require + installed.json + Autoloader (App/vendor) mit PHPMailer,
firebase, predis. Verifiziert: alle 3 Namespaces laden. KRITISCH fuer Prod-Deploy
(Manifest spiegelt vendor/), sonst waere dort der Mailversand gebrochen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Problem: lokal konnte man sich nicht als Admin anmelden — der 2FA-PIN kam nie
an (kein SMTP), und Mailer::sendAdminPin() warf sogar eine Exception, die den
Login komplett abbrach (leere Antwort → „MFA fehlt").
- admin.php: Mailversand nur auf Prod + in try/catch (Mail-Fehler darf den
Login NIE blocken). Lokal (!IS_PRODUCTION) wird der PIN als `devPin` in der
Login-Antwort zurückgegeben — auf Prod NIEMALS (2FA bleibt Pflicht).
- admin.html: devPin wird lokal automatisch ins PIN-Feld vorausgefüllt + als
Hinweis angezeigt.
Verifiziert (temp Test-Admin): login → devPin → verify → authenticated:true,
admin-weltkueche.html erreichbar. Prod unberührt (IS_PRODUCTION-Gate).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tages-Balken neu kodiert (nach Thomas' Spec):
- Eine Farbe pro Simulation (simColor, stabil per Hash).
- Balkenhöhe = Maß für den Erfolg (mehr Sterne → höher) → Leistungen auf einen
Blick vergleichbar.
- Rahmen kodiert den Zustand: fertig gespielt = geschlossener dünner Rahmen ·
noch dran = rechts offener Rahmen · Abbruch = gestrichelt + ✖ am Ende.
- Beschriftung: 3-Buchstaben-Kürzel (eng) → Wort → Wort+Lektion (Platz).
- Legende entsprechend erneuert.
Datenbasis vorhanden: live.php summary liefert success (inkl. 'aborted'),
stars, completed pro Session. Offen für v2: „läuft noch"-Balken aus aktiven
live_sessions einblenden (Tag-View ist bisher assessments-basiert = Abschlüsse).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diagnose per headless-Sim-Harness (.dontDeploy/tools/tr-balance-harness.mjs):
„alles zubauen" senkte die Umwelt nur 75→50 (über der 45-Schwelle) und der
Exit-Umwelt-Malus war TOTER Flag-Code → man konnte der Umwelt kaum schaden.
- model.js: (1) Exit-Umwelt-Malus wirklich angewandt — env<60 zieht bei
umweltbewussten Gästen Zufriedenheit ab (je tiefer, desto mehr). (2) Über-
tourismus-Drift im 10-s-Takt: dichte Bebauung (>6 Bauwerke) + Verkehr zehren
die Umwelt aus. (3) Einmalige Lern-Warnkarte, wenn Umwelt < 50 kippt.
- data.js: Naturschutzgebiet-Beschreibung klar (rettet die Umwelt +20, Natur-
Attraktionen +15 %, sperrt Bauplätze — Naturschutz vs. Ausbau abwägen).
- main.js: Willkommenskarte erklärt die Umwelt-Mechanik für Schüler.
Messung nachher: „alles zubauen" → Umwelt 75→37 (unter 45), Umweltziel
verfehlt, Ø-Sterne fallen. Feintuning Sterne-Kurve/Ansprüche-Eskalation
bleibt für Thomas' Playtest (Game-Feeling headless nicht bewertbar).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neue admin-geschützte Seite admin-weltkueche.html: spiegelt die Spiel-Logik
(Heimat + Handels-Gravitation Menge÷Entfernung + Markt-Tags) und zeigt pro
Gericht × Zutat die vom Spiel akzeptierten Herkunftsländer (🟢 richtig) und
die Distraktoren (🔴). Filter nach Kategorie + Suche + „nur Auffällige".
Auffällig-Heuristik: Heimat-Zweig-Zutat mit „richtigem" Land > 1500 km
(surft echte Ausreißer, ohne Welt-Zutaten wie Pfeffer falsch zu flaggen) —
aktuell 5 Kandidaten. In Admin-Nav + Backend-Werkzeuge verlinkt.
Verifiziert: 30 Gerichte, Kartoffel/Eier → [AT, DE] (nicht Frankreich).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Problem: bei Pause lief die „Seit"-Zeit im Lehrer-Cockpit weiter, obwohl das
Spiel steht — live-client.js akkumulierte activeMs unabhängig vom Pause-Zustand.
- live-client.js: opt-in Pause-Unterstützung. window.__GGS_LIVE__.setPaused(bool)
friert die activeMs-Buchhaltung ein (lastTickAt wird weiter aktualisiert, damit
beim Fortsetzen nichts nachspringt). __paused zusätzlich im Heartbeat. Default
aus → alle anderen Sims unberührt.
- logistik game.html: Pause-/Auto-Button ruft setPaused(mode==='pause').
Verifiziert (Playwright): activeMs 0→2000 (läuft) → 2000 (Pause eingefroren)
→ 3999 (Fortsetzen).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vorbereitung für den JWT+Redis-Umschalter: alle verstreuten
$_SESSION['teacher_id'|admin_id']-Zugriffe laufen jetzt durch Session::-Helper,
damit der Backend-Tausch später EINE Datei (Session.php) ist statt 18 Stellen.
- Session.php: neue Admin-Helper adminId()/loginAdmin()/logoutAdmin()/
requireAdmin() (mirror der Teacher-Helper). teacherId()/adminId() starten
die Session jetzt lazy (Aufrufer müssen Session::start() nicht mehr).
- 18 Fundstellen kanalisiert (admin.php, admin-accounts, admin-modules,
licenses, levels, waypoints, foto-upload, admin_gate, save_map).
- Country.php + app.php cockpit_href: über Session:: mit class_exists-Fallback
(Bootstrap/Lib-Kontext, Session evtl. nicht geladen).
Verhaltensneutral: 14/14 Lint OK, Helper-Round-Trip 6/6 PASS, Live-Smoke
(admin status/modules/accounts/licenses) weist unauth. Requests wie bisher
mit 401/403 ab. AUTH_BACKEND bleibt 'session' — Live unverändert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vorbereitung für pod-unabhängige Sessions (k8s-Ziel). Ändert am Live-System
NICHTS: AUTH_BACKEND='session' bleibt Default, die neuen Libs sind noch
nirgends verdrahtet.
- composer eingeführt: firebase/php-jwt ^6.11 + predis/predis ^2.4 (pure PHP,
keine Redis-Extension nötig → läuft auf XAMPP wie im Prod-Image). vendor/
committed (kein Composer auf dem Server).
- Jwt.php: HS256-Access-Token (issue/verify), decode() IMMER mit explizitem
Algorithmus (mitigiert Algorithmus-Confusion). 8/8 Tests grün.
- SessionStore.php: predis-gestützter Session/Refresh-Store (sess:<sid>,
Sliding-TTL, revoke=DEL) — die widerrufbare Session hinter dem kurzen JWT.
- app.php: vendor/autoload (guarded), Konstanten AUTH_BACKEND (Default session),
REDIS_URL, JWT_TTL, JWT_REFRESH_TTL. JWT_SECRET bewusst ohne Default.
- docker-compose.dev.yml: Redis (+ redis-commander UI) für lokale Dev-Parität.
- .env.example um Auth-Block ergänzt.
Nächste Phasen: $_SESSION-Zugriffe (293) durch Session:: kanalisieren →
Backend-Umschalter in Session.php → doppelgleisiger Cutover → Reporting auf
student_id-FK ziehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Startbildschirm: Name/Klasscode-Felder entfernt (Kinder sind über die
Plattform-Session identifiziert). Klick auf eine Wegkarte startet die
Partie sofort — separater „Partie starten"-Button entfernt.
- Abschluss: „Ergebnis speichern"-Button entfernt; Speichern passiert schon
automatisch am Partie-Ende (submitAssessment). Hinweis „automatisch
gespeichert" ergänzt. btnSave-Listener null-sicher gemacht.
- Seilbahn: Talstation (Baukörper + Satteldach + Umlenkrad) ergänzt — der
Lift-Anfang wirkte vorher „transparent".
- Responsive/iPad: Panels bekommen Höhen-Deckel + internes Scrollen (decken
nie den ganzen Schirm zu). Neue Media-Query für iPad-Landscape (901–1200px
/ niedrige Höhe): kompaktere Panels, Graphen nach rechts-unten aus dem
Info-Panel-Bereich. Sehr flache Screens blenden die Graphen aus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Filter-JS liest a.dataset.anchorType, aber die Anker-<div>s setzten
kein data-anchor-type (nur data-country). Dadurch war `type === 'fach'`
nie wahr → für ein gewähltes Land wurde nie ein Anker ausgeblendet.
Fix: data-anchor-type am Anker-div ergänzt. Gilt für alle Modul-Seiten.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
teacher.html: Menü „Live" → „Ergebnisse" mit Unteransichten
Tag·Aktuell·Vergleich·Gesamt (alte Ergebnisse-Ansicht ist jetzt „Gesamt").
Neue Kennzahl Qualität (grafischer richtig/falsch-Balken) in Aktuell +
Vergleich + Weltküche-Zeile, Gerichte-Zähler (Fleiß), Labels entschärft
(„Kritisch" → Stark/Solide/Viele Fehler).
Weltküche: korrekte Artikel (der/die/das) für alle 70 Zutaten in
production-matrix.json; verdrehten Herkunfts-Satz + Heimat-Banner neu und
marktbewusst (Ukraine-Distraktor ehrlich erklärt), „man"-Konstruktionen
für Singular/Plural.
_scripts.php: renderPage() injizierte Favicon per strrpos ins letzte
</head> — das lag in teacher.htmls printBatchTickets-JS-String → SyntaxError
zerschoss das ganze Inline-Script auf /teacher. Fix: strpos (erstes </head>).
tourismusregion: neue Karte passstrasse in maps-custom.json (Route bergauf
136 Punkte, 41 Bauslots) aus Editor-Export.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- keine Vorbereiten/paused/Start-freigeben/Pause mehr — Aufgabe ist ab Vergabe
sofort startbar (paused:false), Card zeigt nur "Laeuft" + Beenden
- Schueler-Card immer "Jetzt starten" (kein Warte-Zustand)
- "Aufgabe beenden" jetzt ordentlicher Dialog (#modal-stopassign) statt confirm()
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Modal "Aufgabe erteilen": Frist-Buttons (Heute/Morgen/+1 Woche/Kein Limit),
Default +1 Woche, Tempo-Checkbox "Vorbereiten", Live-Zusammenfassung als
Bestaetigung, Button "Auftrag erteilen"
- Aktive Klassen-Aufgabe-Card: Sim-Bild + Status (Vorbereitet/Laeuft) +
Steuerung "Start freigeben" / "Pause" / "Beenden" direkt auf der Card
(Tempo-Start nicht mehr in der individuellen Freigabe)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- aktive Klassenaufgabe wird automatisch als Vergleichs-Modul vorgewaehlt
(Chip mit 📋 markiert, immer vorne)
- Aufgaben-Banner: begonnen X/N (aus matrix) · gerade aktiv A · Oe Fortschritt
- zeigt Aufgabe auch wenn noch niemand aktiv ist (mit Hinweis)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>