Per-Sim leistung_pct: alle 14 Sims senden das eine Leistungs-% (0-100)
Jede Sim liefert jetzt selbst leistung_pct in GGS_LIVE_STATE UND im Submit (Fundament fuer das einheitliche %-System) — kein Rueckrechnen aus Sternen mehr, keine 0-3-Sonderfaelle. Formeln je Sim wie vereinbart: - klima/fluss/kofferdetektiv/heli: = vorhandener 0-100-score - weltkueche: richtige/(richtige+falsche)*100 (Fehler in %) - busfahrt/fluggesellschaft: topoPunkte/(Auftraege*3)*100 - farmer: gewichteter totalScore; energiemanager: ausgegl./gesamt -15/Blackout - eu-werkstatt: firstTry/Schritte -5/Muelleimer; sonnensystem: Erstversuch-Quote - tourismustal: Ziele+Zufriedenheit+Natur gewichtet; tourismusregion: gewichteter score - logistik: Kontostand->% (sendet erstmals eine Kennzahl, auch an /api/assessment) Teacher _leistungPct + live.php extractScore bevorzugen bereits diesen Wert. Additive Aenderungen, bestehende Logik/Sterne unveraendert; alle 15 Dateien syntaktisch geprueft. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2378,6 +2378,7 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
budget: Math.round(game.budget),
|
budget: Math.round(game.budget),
|
||||||
topoPoints: game.score,
|
topoPoints: game.score,
|
||||||
topoMax: totalOrders * 3,
|
topoMax: totalOrders * 3,
|
||||||
|
leistung_pct: (totalOrders*3)===0 ? null : Math.max(0,Math.min(100,Math.round(game.score/(totalOrders*3)*100))),
|
||||||
wonOrders: wonCount,
|
wonOrders: wonCount,
|
||||||
lostOrders: lostCount,
|
lostOrders: lostCount,
|
||||||
lastCityTitle: lastCity ? lastCity.title : null,
|
lastCityTitle: lastCity ? lastCity.title : null,
|
||||||
@@ -2421,6 +2422,7 @@ function submitBusfahrtTour() {
|
|||||||
totalOrders: total,
|
totalOrders: total,
|
||||||
quizFirstTry: quizFirstTry,
|
quizFirstTry: quizFirstTry,
|
||||||
eventCount: (game.eventLog || []).length,
|
eventCount: (game.eventLog || []).length,
|
||||||
|
leistung_pct: (total*3)===0 ? null : Math.max(0,Math.min(100,Math.round(game.score/(total*3)*100))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2022,6 +2022,11 @@
|
|||||||
totalGap: Math.round(totalGap * 10) / 10,
|
totalGap: Math.round(totalGap * 10) / 10,
|
||||||
totalSurplus: Math.round(totalSurplus * 10) / 10,
|
totalSurplus: Math.round(totalSurplus * 10) / 10,
|
||||||
blackouts: blackouts,
|
blackouts: blackouts,
|
||||||
|
// Einheitliche Leistungskennzahl (0–100): Quote ausgeglichener Blöcke minus
|
||||||
|
// 15 Punkte je Blackout. Nenner 0 (kein Tag gespielt) → 0.
|
||||||
|
leistung_pct: blocksTotal > 0
|
||||||
|
? Math.max(0, Math.min(100, Math.round(blocksBalanced / blocksTotal * 100) - 15 * blackouts))
|
||||||
|
: 0,
|
||||||
stars: stars,
|
stars: stars,
|
||||||
perDay: perDay
|
perDay: perDay
|
||||||
};
|
};
|
||||||
@@ -2757,6 +2762,15 @@
|
|||||||
try { achCount = Object.keys((emLsLoad().achievements)||{}).length; } catch (_) {}
|
try { achCount = Object.keys((emLsLoad().achievements)||{}).length; } catch (_) {}
|
||||||
|
|
||||||
var lastSet = (currentDayIdx + 1 >= setTotal && phase === 'done');
|
var lastSet = (currentDayIdx + 1 >= setTotal && phase === 'done');
|
||||||
|
|
||||||
|
// Einheitliche Leistungskennzahl (0–100): Quote ausgeglichener Blöcke über die
|
||||||
|
// bisher gespielten Tage minus 15 Punkte je Blackout. Nenner 0 (noch kein Tag
|
||||||
|
// abgeschlossen) → 0.
|
||||||
|
var emLmDenom = daysCompleted * DAY.blocks.length;
|
||||||
|
var emLeistungPct = emLmDenom > 0
|
||||||
|
? Math.max(0, Math.min(100, Math.round(setBalancedSoFar / emLmDenom * 100) - 15 * (setRun.blackouts || 0)))
|
||||||
|
: 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Sim-Identität + Fortschritt
|
// Sim-Identität + Fortschritt
|
||||||
setId: SETS[currentSetIdx].id,
|
setId: SETS[currentSetIdx].id,
|
||||||
@@ -2780,6 +2794,7 @@
|
|||||||
setDaysDone: daysCompleted,
|
setDaysDone: daysCompleted,
|
||||||
setBlocksBalanced: setBalancedSoFar,
|
setBlocksBalanced: setBalancedSoFar,
|
||||||
blackouts: setRun.blackouts || 0,
|
blackouts: setRun.blackouts || 0,
|
||||||
|
leistung_pct: emLeistungPct,
|
||||||
// Cross-app Konvention
|
// Cross-app Konvention
|
||||||
health: health,
|
health: health,
|
||||||
healthLabel: label,
|
healthLabel: label,
|
||||||
|
|||||||
@@ -5030,6 +5030,11 @@ function showEnd() {
|
|||||||
firstTryHits: state.firstTryHits,
|
firstTryHits: state.firstTryHits,
|
||||||
totalAttempts: state.totalAttempts,
|
totalAttempts: state.totalAttempts,
|
||||||
muelleimerCount: state.muelleimerCount,
|
muelleimerCount: state.muelleimerCount,
|
||||||
|
// Einheitliche Leistungskennzahl (0–100): Anteil Auf-Anhieb-Treffer minus
|
||||||
|
// 5 Punkte je Mülleimer-Wurf. Nenner 0 (keine Schritte) → 0.
|
||||||
|
leistung_pct: stepsTotal > 0
|
||||||
|
? Math.max(0, Math.min(100, Math.round(state.firstTryHits / stepsTotal * 100) - 5 * state.muelleimerCount))
|
||||||
|
: 0,
|
||||||
attemptsByStep: state.attemptsByStep,
|
attemptsByStep: state.attemptsByStep,
|
||||||
detoursByStep: state.detoursByStep,
|
detoursByStep: state.detoursByStep,
|
||||||
reshufflesByStep: state.reshufflesByStep,
|
reshufflesByStep: state.reshufflesByStep,
|
||||||
@@ -5295,6 +5300,12 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
var reshufflesThisStep = (state.reshufflesByStep && state.reshufflesByStep[stepIdx]) || 0;
|
var reshufflesThisStep = (state.reshufflesByStep && state.reshufflesByStep[stepIdx]) || 0;
|
||||||
var secondsElapsed = state.startedAt ? Math.round((Date.now() - state.startedAt) / 1000) : 0;
|
var secondsElapsed = state.startedAt ? Math.round((Date.now() - state.startedAt) / 1000) : 0;
|
||||||
|
|
||||||
|
// Einheitliche Leistungskennzahl (0–100): Anteil Auf-Anhieb-Treffer minus
|
||||||
|
// 5 Punkte je Mülleimer-Wurf. Nenner 0 (keine Schritte) → 0.
|
||||||
|
var leistungPct = stepsTotal > 0
|
||||||
|
? Math.max(0, Math.min(100, Math.round((state.firstTryHits || 0) / stepsTotal * 100) - 5 * (state.muelleimerCount || 0)))
|
||||||
|
: 0;
|
||||||
|
|
||||||
// Health-Ampel (für die Lehrer-Live-Übersicht der nächsten Phase)
|
// Health-Ampel (für die Lehrer-Live-Übersicht der nächsten Phase)
|
||||||
var health = 'good';
|
var health = 'good';
|
||||||
if (screen === 'play') {
|
if (screen === 'play') {
|
||||||
@@ -5332,6 +5343,7 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
firstTryHits: state.firstTryHits || 0,
|
firstTryHits: state.firstTryHits || 0,
|
||||||
totalAttempts: state.totalAttempts || 0,
|
totalAttempts: state.totalAttempts || 0,
|
||||||
muelleimerCount: state.muelleimerCount || 0,
|
muelleimerCount: state.muelleimerCount || 0,
|
||||||
|
leistung_pct: leistungPct,
|
||||||
attemptsThisStep: attemptsThisStep,
|
attemptsThisStep: attemptsThisStep,
|
||||||
reshuffles: state.totalReshuffles || 0,
|
reshuffles: state.totalReshuffles || 0,
|
||||||
reshufflesThisStep: reshufflesThisStep,
|
reshufflesThisStep: reshufflesThisStep,
|
||||||
|
|||||||
@@ -1544,6 +1544,8 @@ async function submitAssessment(stars, score, breakdown) {
|
|||||||
countries_used: breakdown.countries.value,
|
countries_used: breakdown.countries.value,
|
||||||
climates_used: breakdown.climates.value,
|
climates_used: breakdown.climates.value,
|
||||||
year_reached: game.year,
|
year_reached: game.year,
|
||||||
|
// Einheitliche Leistungskennzahl (0–100): gewichtete Gesamtwertung (totalScore).
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round(score))),
|
||||||
},
|
},
|
||||||
action_log,
|
action_log,
|
||||||
},
|
},
|
||||||
@@ -1859,6 +1861,16 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
// (siehe END_REPORT_TRIGGER_YEAR = 11). Skala bezogen auf 10 Jahre, gekappt.
|
// (siehe END_REPORT_TRIGGER_YEAR = 11). Skala bezogen auf 10 Jahre, gekappt.
|
||||||
const targetYear = (typeof END_REPORT_TRIGGER_YEAR === 'number') ? (END_REPORT_TRIGGER_YEAR - 1) : 10;
|
const targetYear = (typeof END_REPORT_TRIGGER_YEAR === 'number') ? (END_REPORT_TRIGGER_YEAR - 1) : 10;
|
||||||
const yearProgress = Math.max(0, Math.min(100, Math.round((game.year / targetYear) * 100)));
|
const yearProgress = Math.max(0, Math.min(100, Math.round((game.year / targetYear) * 100)));
|
||||||
|
// Einheitliche Leistungskennzahl (0–100): dieselbe gewichtete Gesamtwertung wie
|
||||||
|
// im Endbericht/Submit, live aus dem aktuellen Stand berechnet. Bricht die
|
||||||
|
// Berechnung wider Erwarten, fällt sie sicher auf die Trefferquote (hit_rate) als
|
||||||
|
// dokumentierten Proxy zurück, damit die Live-Telemetrie nie ausfällt.
|
||||||
|
let leistungPct = hitRate; // Proxy: Trefferquote
|
||||||
|
try {
|
||||||
|
leistungPct = Math.max(0, Math.min(100, Math.round(computeFinalScore().score)));
|
||||||
|
} catch (e) {
|
||||||
|
leistungPct = hitRate; // Proxy: Trefferquote
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
simId: 'farmer',
|
simId: 'farmer',
|
||||||
phase: 'Jahr ' + game.year + ' von ' + targetYear,
|
phase: 'Jahr ' + game.year + ' von ' + targetYear,
|
||||||
@@ -1872,6 +1884,7 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
hit_rate: hitRate,
|
hit_rate: hitRate,
|
||||||
co2_kg: totalCo2,
|
co2_kg: totalCo2,
|
||||||
diseases: diseases,
|
diseases: diseases,
|
||||||
|
leistung_pct: leistungPct,
|
||||||
crops_used: new Set(reports.map(r => r.cropId)).size,
|
crops_used: new Set(reports.map(r => r.cropId)).size,
|
||||||
countries: new Set(reports.filter(r => r.countryName).map(r => r.countryName)).size,
|
countries: new Set(reports.filter(r => r.countryName).map(r => r.countryName)).size,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
|||||||
@@ -708,6 +708,7 @@ function submitTourToBackend() {
|
|||||||
totalOrders: total,
|
totalOrders: total,
|
||||||
quizFirstTry,
|
quizFirstTry,
|
||||||
quizFailCount: game.reports.reduce((s,r)=>s+Math.max(0,(r.quizAttemptsCount||1)-1), 0),
|
quizFailCount: game.reports.reduce((s,r)=>s+Math.max(0,(r.quizAttemptsCount||1)-1), 0),
|
||||||
|
leistung_pct: (total*3)===0 ? null : Math.max(0,Math.min(100,Math.round(game.score/(total*3)*100))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -737,6 +738,7 @@ window.GGS_LIVE_STATE = function() {
|
|||||||
lastMargin: lastR ? lastR.margin : null,
|
lastMargin: lastR ? lastR.margin : null,
|
||||||
lastTzDiff: lastR ? lastR.tzDiffHours : null,
|
lastTzDiff: lastR ? lastR.tzDiffHours : null,
|
||||||
quizFirstTry: reports.filter(r => r.quizFirstTry).length,
|
quizFirstTry: reports.filter(r => r.quizFirstTry).length,
|
||||||
|
leistung_pct: (game.tourOrders.length*3)===0 ? null : Math.max(0,Math.min(100,Math.round(game.score/(game.tourOrders.length*3)*100))),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1983,6 +1983,7 @@ function submitReflection(levelNum, question, answer) {
|
|||||||
if (window.flAudio) flAudio.play(summary.stars >= 3 ? 'level-won' : 'level-lost');
|
if (window.flAudio) flAudio.play(summary.stars >= 3 ? 'level-won' : 'level-lost');
|
||||||
submitAssessment({
|
submitAssessment({
|
||||||
level: game.level, stars: summary.stars, score: summary.score,
|
level: game.level, stars: summary.stars, score: summary.score,
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round(summary.score))),
|
||||||
duration_ms: 0, completed: summary.completed,
|
duration_ms: 0, completed: summary.completed,
|
||||||
results: { final_biodiv: summary.params.biodiv, final_economy: summary.params.economy,
|
results: { final_biodiv: summary.params.biodiv, final_economy: summary.params.economy,
|
||||||
final_flood: summary.params.flood, final_food: summary.params.food,
|
final_flood: summary.params.flood, final_food: summary.params.food,
|
||||||
@@ -2153,6 +2154,7 @@ function submitReflection(levelNum, question, answer) {
|
|||||||
population: Math.round(p.population || 0),
|
population: Math.round(p.population || 0),
|
||||||
food: Math.round(p.food || 0),
|
food: Math.round(p.food || 0),
|
||||||
score: score,
|
score: score,
|
||||||
|
leistung_pct: score,
|
||||||
health: health,
|
health: health,
|
||||||
settlementsLost: settlementsLost,
|
settlementsLost: settlementsLost,
|
||||||
gameOver: !!st.gameOver,
|
gameOver: !!st.gameOver,
|
||||||
|
|||||||
@@ -501,6 +501,18 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
if (!parts.length) return null;
|
if (!parts.length) return null;
|
||||||
return Math.round(parts.reduce(function(a,b){return a+b;},0)/parts.length);
|
return Math.round(parts.reduce(function(a,b){return a+b;},0)/parts.length);
|
||||||
})(),
|
})(),
|
||||||
|
// Gleicher Wert wie score (0–100), als vereinheitlichte Leistungs-Kennzahl.
|
||||||
|
leistung_pct: (function(){
|
||||||
|
if (!phase || phase === 'mission' || phase === 'briefing') return null;
|
||||||
|
if (landingDone && game.totalStars != null) return Math.round(Math.max(0,Math.min(5,game.totalStars))/5*100);
|
||||||
|
var parts = [];
|
||||||
|
var clicks = (game.planCorrect||0) + (game.planAttempts||0);
|
||||||
|
if (clicks > 0) parts.push(Math.round((game.planCorrect||0) / clicks * 100));
|
||||||
|
if (startDone && game.startScore != null) parts.push(Math.max(0,Math.min(5,game.startScore))/5*100);
|
||||||
|
if (landingDone && game.landingScore != null) parts.push(Math.max(0,Math.min(5,game.landingScore))/5*100);
|
||||||
|
if (!parts.length) return null;
|
||||||
|
return Math.round(parts.reduce(function(a,b){return a+b;},0)/parts.length);
|
||||||
|
})(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -786,6 +798,7 @@ function submitHeliMission() {
|
|||||||
level: window.HELI_FORCED_LEVEL || null,
|
level: window.HELI_FORCED_LEVEL || null,
|
||||||
stars: stars,
|
stars: stars,
|
||||||
score: score,
|
score: score,
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round(score))),
|
||||||
duration_ms: dt,
|
duration_ms: dt,
|
||||||
completed: !game.missionFailed,
|
completed: !game.missionFailed,
|
||||||
results: {
|
results: {
|
||||||
|
|||||||
@@ -1333,6 +1333,7 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
// Laufender Punktestand (0–100, gleiche Formel wie am Ende) → Ampelfarbe im
|
// Laufender Punktestand (0–100, gleiche Formel wie am Ende) → Ampelfarbe im
|
||||||
// Lehrer-Live (Tag-Ansicht färbt laufende Balken danach ein).
|
// Lehrer-Live (Tag-Ansicht färbt laufende Balken danach ein).
|
||||||
score: _liveKlimaScore(),
|
score: _liveKlimaScore(),
|
||||||
|
leistung_pct:_liveKlimaScore(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// Live-Score aus dem aktuellen Stand — nur während des Spiels sinnvoll.
|
// Live-Score aus dem aktuellen Stand — nur während des Spiels sinnvoll.
|
||||||
@@ -3876,6 +3877,7 @@ function endLevel(won, reason) {
|
|||||||
action: 'submit_assessment',
|
action: 'submit_assessment',
|
||||||
data: {
|
data: {
|
||||||
level: state.difficulty, stars, score,
|
level: state.difficulty, stars, score,
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round(score))),
|
||||||
duration_ms: durationMs, completed: state.levelWon,
|
duration_ms: durationMs, completed: state.levelWon,
|
||||||
results: {
|
results: {
|
||||||
final_co2: Math.round(state.co2Ppm),
|
final_co2: Math.round(state.co2Ppm),
|
||||||
|
|||||||
@@ -1573,6 +1573,7 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
// Laufender Punktestand (0–100, gleiche Formel wie am Ende) → Ampelfarbe im
|
// Laufender Punktestand (0–100, gleiche Formel wie am Ende) → Ampelfarbe im
|
||||||
// Lehrer-Live (Tag-Ansicht färbt laufende Balken danach ein).
|
// Lehrer-Live (Tag-Ansicht färbt laufende Balken danach ein).
|
||||||
score: _liveKlimaScore(),
|
score: _liveKlimaScore(),
|
||||||
|
leistung_pct:_liveKlimaScore(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// Live-Score aus dem aktuellen Stand — nur während des Spiels sinnvoll.
|
// Live-Score aus dem aktuellen Stand — nur während des Spiels sinnvoll.
|
||||||
@@ -5347,6 +5348,7 @@ function endLevel(won, reason) {
|
|||||||
action: 'submit_assessment',
|
action: 'submit_assessment',
|
||||||
data: {
|
data: {
|
||||||
level: state.difficulty, stars, score,
|
level: state.difficulty, stars, score,
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round(score))),
|
||||||
duration_ms: durationMs, completed: state.levelWon,
|
duration_ms: durationMs, completed: state.levelWon,
|
||||||
results: {
|
results: {
|
||||||
final_co2: Math.round(state.co2Ppm),
|
final_co2: Math.round(state.co2Ppm),
|
||||||
|
|||||||
@@ -3454,6 +3454,7 @@ const Platform = {
|
|||||||
level: S.cfg.level,
|
level: S.cfg.level,
|
||||||
stars: Game.stars(),
|
stars: Game.stars(),
|
||||||
score: Game.score100(),
|
score: Game.score100(),
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round(Game.score100()))),
|
||||||
duration_ms: (window.__GGS_LIVE__ && window.__GGS_LIVE__.activeMs) || 0,
|
duration_ms: (window.__GGS_LIVE__ && window.__GGS_LIVE__.activeMs) || 0,
|
||||||
completed: true,
|
completed: true,
|
||||||
results: Game.results(),
|
results: Game.results(),
|
||||||
@@ -3487,6 +3488,7 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
phase: phaseLabel,
|
phase: phaseLabel,
|
||||||
progressPct: Math.round((solved + (cs && cs.found && !cs.solved ? 0.5 : 0)) / S.cases.length * 100),
|
progressPct: Math.round((solved + (cs && cs.found && !cs.solved ? 0.5 : 0)) / S.cases.length * 100),
|
||||||
score: Game.score100(),
|
score: Game.score100(),
|
||||||
|
leistung_pct: Game.score100(),
|
||||||
health: wrongHere >= 3 || (cs && cs.timeoutHit && !cs.solved) ? 'struggle' : wrongHere >= 1 ? 'ok' : 'good',
|
health: wrongHere >= 3 || (cs && cs.timeoutHit && !cs.solved) ? 'struggle' : wrongHere >= 1 ? 'ok' : 'good',
|
||||||
cases_solved: solved,
|
cases_solved: solved,
|
||||||
cases_total: S.cases.length,
|
cases_total: S.cases.length,
|
||||||
|
|||||||
@@ -974,6 +974,7 @@ window.pickText = function(opts) {
|
|||||||
action: 'end',
|
action: 'end',
|
||||||
attemptId: activeAttemptId,
|
attemptId: activeAttemptId,
|
||||||
finalBalance: Math.round(game.balance || 0),
|
finalBalance: Math.round(game.balance || 0),
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round((game.balance || 0) / 16000 * 100))),
|
||||||
success: !!success,
|
success: !!success,
|
||||||
stats: stats || {},
|
stats: stats || {},
|
||||||
};
|
};
|
||||||
@@ -5536,6 +5537,7 @@ window.pickText = function(opts) {
|
|||||||
results: {
|
results: {
|
||||||
success: success,
|
success: success,
|
||||||
finalBalance: Math.round(game.balance || 0),
|
finalBalance: Math.round(game.balance || 0),
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round((game.balance || 0) / 16000 * 100))),
|
||||||
minTarget: (game._config && game._config.minTargetEarnings) || 0,
|
minTarget: (game._config && game._config.minTargetEarnings) || 0,
|
||||||
completed: a.completed,
|
completed: a.completed,
|
||||||
lateRatio: a.lateRatio,
|
lateRatio: a.lateRatio,
|
||||||
@@ -5572,6 +5574,7 @@ window.pickText = function(opts) {
|
|||||||
simId: 'logistik',
|
simId: 'logistik',
|
||||||
levelId: levelNum || 1,
|
levelId: levelNum || 1,
|
||||||
progressPct: pct,
|
progressPct: pct,
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round((game.balance || 0) / 16000 * 100))),
|
||||||
phase: 'Runde ' + ((game.progression && game.progression.round) || 1),
|
phase: 'Runde ' + ((game.progression && game.progression.round) || 1),
|
||||||
balance: Math.round(game.balance || 0),
|
balance: Math.round(game.balance || 0),
|
||||||
ordersDelivered: delivered,
|
ordersDelivered: delivered,
|
||||||
|
|||||||
@@ -3514,6 +3514,17 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
var screen = document.getElementById('obs-overlay');
|
var screen = document.getElementById('obs-overlay');
|
||||||
var inObs = screen && !screen.classList.contains('obs-hidden');
|
var inObs = screen && !screen.classList.contains('obs-hidden');
|
||||||
var quote = total > 0 ? Math.round(done/total*100) : 0;
|
var quote = total > 0 ? Math.round(done/total*100) : 0;
|
||||||
|
// Erst-Treffer-Quote live (bevorzugt), sonst Fortschritt
|
||||||
|
var ftLive = 0, hasFt = false;
|
||||||
|
try {
|
||||||
|
if (obsState && obsState.firstTry && obsState.tasks) {
|
||||||
|
hasFt = true;
|
||||||
|
obsState.tasks.forEach(function(t){ if (obsState.firstTry.has(t.id)) ftLive++; });
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
var leistungPct = (hasFt && total > 0)
|
||||||
|
? Math.max(0, Math.min(100, Math.round(ftLive/total*100)))
|
||||||
|
: quote;
|
||||||
var health = 'good';
|
var health = 'good';
|
||||||
if (inObs) {
|
if (inObs) {
|
||||||
if (quote >= 75) health = 'good';
|
if (quote >= 75) health = 'good';
|
||||||
@@ -3535,6 +3546,7 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
taskId: t ? t.id : null,
|
taskId: t ? t.id : null,
|
||||||
completed: done,
|
completed: done,
|
||||||
progressPct: quote,
|
progressPct: quote,
|
||||||
|
leistung_pct: leistungPct,
|
||||||
simDate: simDate,
|
simDate: simDate,
|
||||||
timeDays: dayInt,
|
timeDays: dayInt,
|
||||||
paused: (typeof paused === 'boolean') ? paused : false,
|
paused: (typeof paused === 'boolean') ? paused : false,
|
||||||
@@ -3989,6 +4001,7 @@ function obsShowEnd() {
|
|||||||
completed: done, total: total,
|
completed: done, total: total,
|
||||||
firstTryCount: firstTryN,
|
firstTryCount: firstTryN,
|
||||||
firstTryQuote: Math.round(quote*100),
|
firstTryQuote: Math.round(quote*100),
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round(quote*100))),
|
||||||
scoreQuote: Math.round(quote*100),
|
scoreQuote: Math.round(quote*100),
|
||||||
firstTry: (firstTryN >= total), // legacy: alle auf Anhieb
|
firstTry: (firstTryN >= total), // legacy: alle auf Anhieb
|
||||||
stars: stars,
|
stars: stars,
|
||||||
|
|||||||
@@ -270,6 +270,26 @@ function pumpCards() {
|
|||||||
// Wird vom live-client.js alle ~4 s an /api/live gesendet.
|
// Wird vom live-client.js alle ~4 s an /api/live gesendet.
|
||||||
window.GGS_LIVE_STATE = function () {
|
window.GGS_LIVE_STATE = function () {
|
||||||
if (!state) return null;
|
if (!state) return null;
|
||||||
|
// Einheitliche Leistungskennzahl: gewichteter Score live (gleiche Formel wie finish()),
|
||||||
|
// Fallback avgStars/5*100, falls Teil-Stats fehlen.
|
||||||
|
const _avg = currentAvg(state);
|
||||||
|
let _leistung_pct;
|
||||||
|
try {
|
||||||
|
const _st = state.stats;
|
||||||
|
const _nonCar = _st.transports.foot + _st.transports.bike + _st.transports.bus;
|
||||||
|
const _totalSp = Math.max(1, _st.spawned);
|
||||||
|
let _lenkung = (_nonCar / _totalSp) * 100;
|
||||||
|
if (state.buildings.some(b => b.type === 'bushaltestelle')) _lenkung = Math.min(100, _lenkung + 10);
|
||||||
|
const _scoreLive = (_avg / 5) * 100 * 0.35
|
||||||
|
+ Math.min(100, (state.revenueTotal / 2500) * 100) * 0.25
|
||||||
|
+ state.env * 0.20
|
||||||
|
+ _lenkung * 0.10
|
||||||
|
+ (state.budget >= 0 ? 100 : Math.max(0, 100 * (1 + state.budget / 1000))) * 0.10;
|
||||||
|
_leistung_pct = Math.round(_scoreLive);
|
||||||
|
} catch (_) {
|
||||||
|
_leistung_pct = Math.round((_avg / 5) * 100);
|
||||||
|
}
|
||||||
|
_leistung_pct = Math.max(0, Math.min(100, _leistung_pct));
|
||||||
return {
|
return {
|
||||||
scenarioId: state.map?.id, // Karten-Key = Lektions-Gruppierung
|
scenarioId: state.map?.id, // Karten-Key = Lektions-Gruppierung
|
||||||
mapId: state.map?.id,
|
mapId: state.map?.id,
|
||||||
@@ -286,6 +306,7 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
goalsDone: GOALS.filter(g => g.check(state)).length,
|
goalsDone: GOALS.filter(g => g.check(state)).length,
|
||||||
goalsTotal: GOALS.length,
|
goalsTotal: GOALS.length,
|
||||||
ended: !!state.ended,
|
ended: !!state.ended,
|
||||||
|
leistung_pct: _leistung_pct,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -311,6 +332,7 @@ function submitAssessment() {
|
|||||||
level: state.map?.id,
|
level: state.map?.id,
|
||||||
stars: Math.max(0, Math.min(5, Math.round(r.avgStars))),
|
stars: Math.max(0, Math.min(5, Math.round(r.avgStars))),
|
||||||
score: r.score,
|
score: r.score,
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round(r.score))),
|
||||||
duration_ms: Date.now() - (state._startedAt || Date.now()),
|
duration_ms: Date.now() - (state._startedAt || Date.now()),
|
||||||
completed: goalsDone === r.goals.length,
|
completed: goalsDone === r.goals.length,
|
||||||
results: {
|
results: {
|
||||||
|
|||||||
@@ -207,6 +207,12 @@ function onTileTap(c, r, pointerType) {
|
|||||||
// stabile Schlüssel, didaktisch sinnvoll (Cockpit-Spalten).
|
// stabile Schlüssel, didaktisch sinnvoll (Cockpit-Spalten).
|
||||||
window.GGS_LIVE_STATE = function () {
|
window.GGS_LIVE_STATE = function () {
|
||||||
if (!state) return null;
|
if (!state) return null;
|
||||||
|
const _goalsDone = state.scenario.goals.filter(g => g.check(state)).length;
|
||||||
|
const _goalsTotal = state.scenario.goals.length;
|
||||||
|
const _goalPct = _goalsTotal > 0 ? (_goalsDone / _goalsTotal) * 100 : 0;
|
||||||
|
const _leistung_pct = Math.max(0, Math.min(100, Math.round(
|
||||||
|
0.5 * _goalPct + 0.3 * (state.satisfaction || 0) + 0.2 * (state.nature || 0)
|
||||||
|
)));
|
||||||
return {
|
return {
|
||||||
scene: state.scenario.id,
|
scene: state.scenario.id,
|
||||||
scenarioId: state.scenario.id, // Plattform-Konvention für Lektions-Gruppierung
|
scenarioId: state.scenario.id, // Plattform-Konvention für Lektions-Gruppierung
|
||||||
@@ -228,6 +234,7 @@ window.GGS_LIVE_STATE = function () {
|
|||||||
goalsTotal: state.scenario.goals.length,
|
goalsTotal: state.scenario.goals.length,
|
||||||
questsDone: state.questIndex,
|
questsDone: state.questIndex,
|
||||||
bottleneck: state.bottleneck,
|
bottleneck: state.bottleneck,
|
||||||
|
leistung_pct: _leistung_pct,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -252,6 +259,10 @@ function submitAssessment(completed) {
|
|||||||
level: state.scenario.id,
|
level: state.scenario.id,
|
||||||
stars,
|
stars,
|
||||||
score: Math.round((goals / total) * 100),
|
score: Math.round((goals / total) * 100),
|
||||||
|
leistung_pct: Math.max(0, Math.min(100, Math.round(
|
||||||
|
0.5 * (total > 0 ? (goals / total) * 100 : 0)
|
||||||
|
+ 0.3 * (state.satisfaction || 0) + 0.2 * (state.nature || 0)
|
||||||
|
))),
|
||||||
duration_ms: Date.now() - (state._startedAt || Date.now()),
|
duration_ms: Date.now() - (state._startedAt || Date.now()),
|
||||||
completed,
|
completed,
|
||||||
results: {
|
results: {
|
||||||
|
|||||||
@@ -810,6 +810,7 @@ window.GGS_LIVE_STATE = function() {
|
|||||||
score: state.score || 0,
|
score: state.score || 0,
|
||||||
correctClicks: state.totalCorrect || 0,
|
correctClicks: state.totalCorrect || 0,
|
||||||
wrongClicks: state.totalWrong || 0,
|
wrongClicks: state.totalWrong || 0,
|
||||||
|
leistung_pct: ((state.totalCorrect||0)+(state.totalWrong||0))===0 ? null : Math.max(0,Math.min(100,Math.round((state.totalCorrect||0)/((state.totalCorrect||0)+(state.totalWrong||0))*100))),
|
||||||
completed: completedDish,
|
completed: completedDish,
|
||||||
completedTotal: totalDish,
|
completedTotal: totalDish,
|
||||||
badgesEarned: (state.badges||[]).length,
|
badgesEarned: (state.badges||[]).length,
|
||||||
@@ -849,6 +850,7 @@ function wkSubmitProgress(dish, perfect){
|
|||||||
completed: state.completed.length, total: DISHES.length,
|
completed: state.completed.length, total: DISHES.length,
|
||||||
score: state.score, dishScore: (current && current.dishScore) || 0,
|
score: state.score, dishScore: (current && current.dishScore) || 0,
|
||||||
correctClicks: state.totalCorrect || 0, wrongClicks: state.totalWrong || 0,
|
correctClicks: state.totalCorrect || 0, wrongClicks: state.totalWrong || 0,
|
||||||
|
leistung_pct: ((state.totalCorrect||0)+(state.totalWrong||0))===0 ? null : Math.max(0,Math.min(100,Math.round((state.totalCorrect||0)/((state.totalCorrect||0)+(state.totalWrong||0))*100))),
|
||||||
mistakesInDish: mis, perfect: !!perfect
|
mistakesInDish: mis, perfect: !!perfect
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user