Das ist ein Output der KI CLAUDE auf eine meiner Anfragen!
Ich schaue mir die loseLife-Funktion und den gesamten Eliminierungsfluss genau an.
Den Bug habe ich gefunden! In showRoundResult — Zeile 1304:
if(state.eliminated[p]&&!(losers&&losers.includes(p))) continue;
Diese Bedingung lässt einen bereits eliminierten Spieler durch, wenn er in der losers-Liste steht. Das passiert genau dann, wenn ein Spieler in dieser Runde schwimmt UND gleichzeitig wieder der Verlierer ist — loseLife() setzt ihn auf eliminated=true, aber dann taucht er noch in losers auf. Die Folge: showRoundResult zeigt ihn mit ? und ? (aus dem swim-Check auf Zeile 1315 — aber state.swimming[p] ist jetzt schon false), und beim nächsten Durchlauf durch showRoundResult wird der showRoundResult-Human-eliminated-Check zu früh getriggert. Das eigentliche Problem aber: loseLife wird doppelt aufgerufen für denselben Spieler, wenn announceSchnauz / announceFeuer → showRoundResult → und der endRound-Pfad beide dieselben Losers berechnen.
Nein — nochmal genauer:
Jetzt habe ich den Bug klar vor mir. In showRoundResult, Zeile 1304:
if(state.eliminated[p]&&!(losers&&losers.includes(p))) continue;
Das schließt eliminierte Spieler ein, wenn sie in losers sind — also genau der Spieler, der gerade in dieser Runde durch Schwimmen eliminiert wurde. Er wird angezeigt. Soweit OK. Aber das eigentliche Problem ist subtiler:
Der echte Bug: renderLives() zeigt für einen schwimmenden Spieler alle 3 Dots rot+pulsierend. Wenn dieser Spieler dann nochmal verliert, ruft loseLife() ihn von swimming=true auf eliminated=true. Aber showRoundResult zeigt ihn danach mit state.swimming[p] — das ist jetzt false (korrekt) — aber in der result-row steht swim=state.swimming[p]?' ?':'', also kein ?. Allerdings: state.eliminated[p] ist true, also steht da ?.
Das ist eigentlich kein Anzeigeproblem... Lass mich den wirklichen Doppelschwimm-Pfad finden:
Jetzt habe ich den Bug exakt identifiziert. Er ist in highlightActiveZone(), Zeile 1714:
z.classList.toggle('swimming', state.swimming[p]);
Das CSS .swimming::after erzeugt den roten Pulsierrahmen. Aber: state.swimming[p] bleibt true für die gesamte Runde, in der der Spieler schwimmt — auch wenn er in dieser Runde wieder verliert und loseLife() ihn auf eliminated=true setzt (und swimming auf false).
Warte — nein. loseLife setzt swimming[p]=false und eliminated[p]=true sofort. Das ist korrekt.
Der Bug muss woanders liegen. Der Spieler erscheint visuell als zweimal schwimmend. Das deutet auf ein Render-Timing-Problem hin: render() wird nach loseLife() aufgerufen, aber dann kommt nochmal ein render() aus einem anderen Pfad mit einem veralteten State-Snapshot. Oder: Die swimming-Dots bleiben durch CSS-Animation im DOM hängen, weil innerHTML nur bei eliminated überschrieben wird.
Moment — ich sehe es jetzt: In renderLives():
for(let l=0;l<3;l++){
const isSwimming=state.swimming[p];
const lost=l>=state.lives[p]&&!isSwimming;
html+=`<span ...${isSwimming?' swimming':''}...></span>`;
}
if(state.eliminated[p]) html='?';
Wenn lives[p]===0 und swimming[p]===true: alle 3 Dots bekommen .swimming → rot pulsierend. ? Korrekt.
Dann loseLife() nächste Runde: swimming→false, eliminated→true. renderLives zeigt ?. ?
Aber! — was wenn lives[p] bereits 0 ist und swimming[p]===false (noch nicht geschwommen, hat einfach 0 Leben)? Das kann nicht passieren wegen loseLife-Logik...
Lass mich den einzigen echten Doppel-Schwimm-Pfad prüfen: Was wenn ein Spieler beim Feuer alle anderen verlieren ein Leben — und ein Spieler hat bereits 0 Leben und schwimmt? Dann loseLife(p): swimming[p] ist true → swimming=false, eliminated=true. Korrekt.
Aber: in announceFeuer werden die scores vor loseLife berechnet, die losers-Liste enthält den schwimmenden Spieler. Dann showRoundResult(p, scores, losers) — und dort:
if(state.eliminated[p]&&!(losers&&losers.includes(p))) continue;
Der eliminierte Spieler wird angezeigt. Sein state.swimming[p] ist jetzt false. Sein state.eliminated[p] ist true. Zeige ?. Korrekt.
Ich muss den Bug aus einer anderen Richtung angehen — wann kann swimming zweimal true sein?
Gefunden! startRound() setzt swimming und lives nicht zurück — nur state-Felder die runden-intern sind. swimming und lives und eliminated bleiben aus dem vorherigen newGame()-Aufruf oder der Initialisierung. Das ist gewollt — sie sollen über Runden bestehen bleiben. Das ist korrekt.
Aber ich sehe jetzt das eigentliche Problem: loseLife kann einen Spieler der bereits schwimmt erneut in den Swimming-Zustand versetzen, wenn die Logik falsch greift. Konkret:
function loseLife(p){
if(state.swimming[p]){ // schwimmt schon → eliminieren
state.swimming[p]