Leertaste auf einem fokussierten Button tut nichts: Menü, Pause, Game Over und die Einstellungen sind per Tastatur nur halb bedienbar #20

Closed
opened 2026-09-02 15:40:25 +02:00 by gilli · 3 comments
Owner

⁨⁩⁨Leertaste auf einem fokussierten Button tut nichts: Menü, Pause, Game Over und die Einstellungen sind per Tastatur nur halb bedienbar⁩ ⁨⁩#⁨20⁩⁨⁩"
By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Closed⁨⁩⁩
accessibility
bug
input
medium
ui

Komponente: INPUT (init, keydown-Listener), UI (Overlays, Einstellungs-Schalter)
Betroffene Version: main @ 42f3cce, index.html (3.333 Zeilen, MD5 aae5f94c1dc05903f2c214d255d2fe9d),
Zeile 2393 (preventDefault), 327/399 (Hinweise), 368 (#settings), 395 (#help), 62 (.panel{…overflow:auto})

Beschreibung

Der Tastatureingang von Issue #8 verhindert seitdem bedingungslos die Browser-Standardaktion
für Leertaste und Pfeiltasten:

// index.html:2393
if (!chord && ["Space", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].indexOf(code) >= 0) ev.preventDefault();

Die Prüfung kennt weder den Zustand des Spiels noch das Fokusziel. preventDefault() auf
dem keydown-Space entwertet aber die Standardaktion eines <button>: der Klick unterbleibt.
Das Spiel läuft dann nur den Spiel-Teil ab (press("jump")), der in Menü, Pause und Game Over
nichts bewirkt. Dasselbe gilt für <input type="checkbox">, deren einzige Tastenbetätigung die
Leertaste ist – die vier Einstellungs-Schalter sind damit per Tastatur nicht schaltbar, obwohl
sie dafür gedacht sind (.toggle input:focus-visible + .switch{outline…}, Zeile 136).

Pfeiltasten: #help .panel und #gameover .panel sind overflow:auto (Zeile 62). Auf kurzen
Bühnen läuft die Hilfetabelle über – ArrowUp/ArrowDown scrollen das Panel nicht, weil die
Standardaktion global verhindert wird.

Messung

Headless-Chromium auf der gebauten Testseite, echte keyboard-Eingaben:

Aktion erwartet gemessen
Menü, #btnStart fokussiert, Leertaste Countdown startet Zustand bleibt MENU
Menü, #btnStart fokussiert, Enter Countdown startet COUNTDOWN ✔
Menü, #btnHelp fokussiert, Leertaste Hilfe öffnet Zustand bleibt MENU
Menü, #btnHelp fokussiert, Enter Hilfe öffnet HELP ✔
Einstellungen, #chkSound fokussiert, Leertaste Schalter kippt checked bleibt true
Hilfe bei 900 × 380, Button im Panel fokussiert, ArrowDown Panel scrollt scrollTop bleibt 0

Überlauf des Hilfe-Panels bei 900 × 380: scrollHeight 551 px gegen clientHeight 330 px –
221 px Inhalt sind ohne Maus nicht erreichbar.

Auswirkung

  • Barrierefreiheit ist gegen AGENTS.md („alle interaktive Bedienung per Tastatur“) verstoßen:
    Leertaste ist die Konvention für „fokussiertes Bedienelement auslösen“ und genau die Taste,
    die das Spiel selbst als Hauptaktion bewirbt (Leer = Springen). Wer im Game-Over-Dialog
    „Nochmal spielen“ mit Leertaste erwartet, bekommt nichts.
  • Die Einstellungs-Schalter (Ton, Reduzierte Bewegung, Haken automatisch greifen,
    Leuchteffekte) sind für Tastatur- und Screenreader-Nutzende nicht bedienbar; Enter
    schaltet Checkboxen per Definition nicht. Es bleibt nur die Maus oder das Tastaturkürzel M
    für Ton – für die anderen drei gibt es kein Kürzel.
  • Überlappende Panels (Hilfe, Game Over auf kurzen Viewports) sind nicht scrollbar.
  • Erfasst wird das von keiner Suite: tests/browser_issue8.py prüft nur die Modifikatoren
    und Escape, alle anderen Browser-Suiten schalten über PULSE.*-Aufrufe, nicht über
    Button-Aktivierung.

Lösungsvorschlag

  1. Dem Fokusziel den Vorrang lassen und die Aktion nur übernehmen, wenn das Spiel die
    Taste wirklich besitzt:

    const tgt = ev.target;
    const inWidget = tgt && tgt.closest &&
      tgt.closest('button, input, select, textarea, a[href], [role="button"]');
    const visibleOverlay = ["menu", "pause", "gameover", "settings", "help"]
      .some((n) => !document.getElementById(n).classList.contains("hidden"));
    if (!inWidget && !chord && ["Space", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].indexOf(code) >= 0) ev.preventDefault();
    if (inWidget && visibleOverlay) return;   // Bedienelement auslösen lassen, keine Sprung-/Roll-Aktion
    

    Im laufenden Spiel (kein sichtbares Overlay) bleiben Leertaste und Pfeiltasten beim Spiel –
    auch dann, wenn gerade ein HUD-Button fokussiert ist.

  2. Alternative, falls Punkt 1 zu aggressiv ist: preventDefault nur bei
    S.state === "PLAYING" || S.state === "COUNTDOWN" ausführen.

  3. Für die Schalter zusätzlich prüfen, ob Space wirklich durchkommt (die opacity:0-Checkbox
    ist fokussierbar, das ist richtig so – es fehlt nur die Tat).

Tests

Neue Browser-Suite tests/browser_issue20.py (echte Eingaben, kein PULSE.*-Aufruf):

  • Jedes sichtbare Overlay-Statement der Form „Button fokussieren → Leertaste → erwarteter
    Zustand“: #btnStart → COUNTDOWN, #btnHelp → HELP, #btnSettings → SETTINGS,
    #btnResume → PLAYING, #btnRetry → COUNTDOWN, #btnGoMenu/#btnPauseMenu → MENU,
    #btnSettingsBack/#btnHelpBack → MENU.
  • Jeder der vier Schalter: Fokus + Leertaste kippt checked und der Wert liegt danach in
    STORE.data.settings (Persistenz nicht vergessen).
  • Kurzes Viewport (900 × 380): Hilfe öffnen, #btnHelpBack fokussieren, ArrowDown →
    panel.scrollTop > 0.
  • Gegen die Auslieferung rot: alle Leertasten-Fälle wie oben gemessen (MENU bleibt MENU,
    checked bleibt true, scrollTop bleibt 0).
  • Die Prüfungen aus tests/browser_issue8.py (Modifikatoren, Escape, ev.repeat) müssen
    unverändert grün bleiben – die Lösung darf Strg+F nicht wieder durchlässig machen.

Dokumentiert in issues/20-leertaste-auf-fokussiertem-button-wird-verhindert.md (Commit 42f3cce).

⁨3⁩ comments

⁨⁩⁨Leertaste auf einem fokussierten Button tut nichts: Menü, Pause, Game Over und die Einstellungen sind per Tastatur nur halb bedienbar⁩ ⁨⁩#⁨20⁩⁨⁩" By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Closed⁨⁩⁩ accessibility bug input medium ui > **Komponente:** INPUT (`init`, `keydown`-Listener), UI (Overlays, Einstellungs-Schalter) > **Betroffene Version:** `main` @ `42f3cce`, `index.html` (3.333 Zeilen, MD5 `aae5f94c1dc05903f2c214d255d2fe9d`), > Zeile 2393 (`preventDefault`), 327/399 (Hinweise), 368 (`#settings`), 395 (`#help`), 62 (`.panel{…overflow:auto}`) > > ## Beschreibung > > Der Tastatureingang von Issue #8 verhindert seitdem bedingungslos die Browser-Standardaktion > für Leertaste und Pfeiltasten: > > ```js > // index.html:2393 > if (!chord && ["Space", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].indexOf(code) >= 0) ev.preventDefault(); > ``` > > Die Prüfung kennt weder den Zustand des Spiels noch das **Fokusziel**. `preventDefault()` auf > dem `keydown`-`Space` entwertet aber die Standardaktion eines `<button>`: der Klick unterbleibt. > Das Spiel läuft dann nur den Spiel-Teil ab (`press("jump")`), der in Menü, Pause und Game Over > nichts bewirkt. Dasselbe gilt für `<input type="checkbox">`, deren einzige Tastenbetätigung die > Leertaste ist – die vier Einstellungs-Schalter sind damit per Tastatur nicht schaltbar, obwohl > sie dafür gedacht sind (`.toggle input:focus-visible + .switch{outline…}`, Zeile 136). > > Pfeiltasten: `#help .panel` und `#gameover .panel` sind `overflow:auto` (Zeile 62). Auf kurzen > Bühnen läuft die Hilfetabelle über – `ArrowUp`/`ArrowDown` scrollen das Panel nicht, weil die > Standardaktion global verhindert wird. > > ## Messung > > Headless-Chromium auf der gebauten Testseite, echte `keyboard`-Eingaben: > > | Aktion | erwartet | gemessen | > |---|---|---| > | Menü, `#btnStart` fokussiert, **Leertaste** | Countdown startet | Zustand bleibt `MENU` | > | Menü, `#btnStart` fokussiert, **Enter** | Countdown startet | `COUNTDOWN` ✔ | > | Menü, `#btnHelp` fokussiert, **Leertaste** | Hilfe öffnet | Zustand bleibt `MENU` | > | Menü, `#btnHelp` fokussiert, **Enter** | Hilfe öffnet | `HELP` ✔ | > | Einstellungen, `#chkSound` fokussiert, **Leertaste** | Schalter kippt | `checked` bleibt `true` | > | Hilfe bei 900 × 380, Button im Panel fokussiert, **ArrowDown** | Panel scrollt | `scrollTop` bleibt `0` | > > Überlauf des Hilfe-Panels bei 900 × 380: `scrollHeight` 551 px gegen `clientHeight` 330 px – > 221 px Inhalt sind ohne Maus nicht erreichbar. > > ## Auswirkung > > * Barrierefreiheit ist gegen `AGENTS.md` („alle interaktive Bedienung per Tastatur“) verstoßen: > Leertaste ist die Konvention für „fokussiertes Bedienelement auslösen“ und genau die Taste, > die das Spiel selbst als Hauptaktion bewirbt (`Leer` = Springen). Wer im Game-Over-Dialog > „Nochmal spielen“ mit Leertaste erwartet, bekommt nichts. > * Die Einstellungs-Schalter (Ton, Reduzierte Bewegung, Haken automatisch greifen, > Leuchteffekte) sind für Tastatur- und Screenreader-Nutzende **nicht** bedienbar; `Enter` > schaltet Checkboxen per Definition nicht. Es bleibt nur die Maus oder das Tastaturkürzel `M` > für Ton – für die anderen drei gibt es kein Kürzel. > * Überlappende Panels (Hilfe, Game Over auf kurzen Viewports) sind nicht scrollbar. > * Erfasst wird das von keiner Suite: `tests/browser_issue8.py` prüft nur die *Modifikatoren* > und `Escape`, alle anderen Browser-Suiten schalten über `PULSE.*`-Aufrufe, nicht über > Button-Aktivierung. > > ## Lösungsvorschlag > > 1. **Dem Fokusziel den Vorrang lassen** und die Aktion nur übernehmen, wenn das Spiel die > Taste wirklich besitzt: > > ```js > const tgt = ev.target; > const inWidget = tgt && tgt.closest && > tgt.closest('button, input, select, textarea, a[href], [role="button"]'); > const visibleOverlay = ["menu", "pause", "gameover", "settings", "help"] > .some((n) => !document.getElementById(n).classList.contains("hidden")); > if (!inWidget && !chord && ["Space", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].indexOf(code) >= 0) ev.preventDefault(); > if (inWidget && visibleOverlay) return; // Bedienelement auslösen lassen, keine Sprung-/Roll-Aktion > ``` > > Im laufenden Spiel (kein sichtbares Overlay) bleiben Leertaste und Pfeiltasten beim Spiel – > auch dann, wenn gerade ein HUD-Button fokussiert ist. > 2. Alternative, falls Punkt 1 zu aggressiv ist: `preventDefault` nur bei > `S.state === "PLAYING" || S.state === "COUNTDOWN"` ausführen. > 3. Für die Schalter zusätzlich prüfen, ob `Space` wirklich durchkommt (die `opacity:0`-Checkbox > ist fokussierbar, das ist richtig so – es fehlt nur die Tat). > > ## Tests > > Neue Browser-Suite `tests/browser_issue20.py` (echte Eingaben, kein `PULSE.*`-Aufruf): > > * Jedes sichtbare Overlay-Statement der Form „Button fokussieren → Leertaste → erwarteter > Zustand“: `#btnStart` → `COUNTDOWN`, `#btnHelp` → `HELP`, `#btnSettings` → `SETTINGS`, > `#btnResume` → `PLAYING`, `#btnRetry` → `COUNTDOWN`, `#btnGoMenu`/`#btnPauseMenu` → `MENU`, > `#btnSettingsBack`/`#btnHelpBack` → `MENU`. > * Jeder der vier Schalter: Fokus + Leertaste kippt `checked` **und** der Wert liegt danach in > `STORE.data.settings` (Persistenz nicht vergessen). > * Kurzes Viewport (900 × 380): Hilfe öffnen, `#btnHelpBack` fokussieren, `ArrowDown` → > `panel.scrollTop > 0`. > * Gegen die Auslieferung rot: alle Leertasten-Fälle wie oben gemessen (`MENU` bleibt `MENU`, > `checked` bleibt `true`, `scrollTop` bleibt `0`). > * Die Prüfungen aus `tests/browser_issue8.py` (Modifikatoren, `Escape`, `ev.repeat`) müssen > unverändert grün bleiben – die Lösung darf `Strg+F` nicht wieder durchlässig machen. > > --- > > *Dokumentiert in `issues/20-leertaste-auf-fokussiertem-button-wird-verhindert.md` (Commit `42f3cce`).* ⁨3⁩ comments
Author
Owner

Behoben in 5c84877 (Doku in 38c101c)

Geändert – index.html, INPUT-Modul:

  • neu: NAV_KEYS, OVERLAY_IDS und INPUT.widgetOwns(target) – prüft, ob ein fokussiertes
    Bedienelement (button, input, select, textarea, a[href], [role="button"]) unter einem
    sichtbaren Overlay (menu, pause, gameover, settings, help) steht.
  • keydown-Listener: die Leertaste und die Pfeiltasten werden nicht mehr verhindert, wenn
    widgetOwns(ev.target) – in dem Fall wird die Taste außerdem komplett nicht als
    Spielaktion umgesetzt (sonst läge beim „Fortsetzen“ per Leertaste ein Sprung im Puffer).
  • Ohne Overlay bleibt alles wie bisher: Prevented und Sache des Spiels – auch wenn ein
    HUD-Knopf (#btnPause, #btnFocus) den Fokus hält, sonst würde jeder Sprung pausieren.
  • Nicht angetastet: der Modifikator-/Wiederholungsfilter aus Issue #8 steht unverändert vor
    allen Aktionen, die Buchstabenkürzel (P/R/M/F/W, Escape) laufen unabhängig vom
    Fokusziel, keyup ebenfalls.

Verifikation – neue Suite tests/browser_issue20.py (60 Prüfungen, echte
keyboard-Eingaben, an der Messstelle kein einziger PULSE.*-Aufruf):

  • Zehn Overlay-Fälle „Knopf fokussieren → Leertaste → Zustand“: #btnStart→COUNTDOWN,
    #btnHelp→HELP, #btnSettings→SETTINGS, #btnResume→PLAYING, #btnRestart→COUNTDOWN,
    #btnPauseMenu/#btnGoMenu/#btnSettingsBack/#btnHelpBack→MENU; Enter als Kontrolle.
  • Alle vier Schalter: Fokus + Leertaste kippt checked, der Wert liegt in S.settings und
    im localStorage (pulseSprintSaveV1), kein Mitschleifen der Nachbarn, und nach reload()
    stehen die Werte aus dem Speicher wieder im Spiel.
  • Hilfe bei 900 × 380: Panel überläuft (579/330 px), ArrowDown auf dem fokussierten
    #btnHelpBack bleibt als Standardaktion erhalten und wird nicht zur Rolle umgebogen
    (rollBuffer 0, Zustand bleibt HELP), die Seite scrollt nicht.
  • Gegenseite (Issue #8 bleibt zu): im Lauf ist die Leertaste verhindert, springt, löst aber den
    fokussierten HUD-Knopf weder per Klick noch über den Fokus aus; ArrowDown rollt; Escape
    pausiert; Fortsetzen per Leertaste legt keinen Sprung in den Puffer.

Gegen git show HEAD:index.html gemessen: 27 Prüfungen rot (MENU bleibt MENU in allen
drei Menü-Fällen, PAUSED/GAME_OVER ebenso, checked bleibt für alle vier Schalter true,
ArrowDown prevented plus rollBuffer 0,1, jumpBuffer 0,12 beim Fortsetzen). Nach dem Fix
grün – ./tests/run-all.sh 1 komplett: 12 Node-Suiten + Soak + 11 Browser-Suiten.

Zwei Abweichungen vom Befund, beide messbar:

  1. Die erwartete scrollTop-Prüfung ist in diesem Chromium nicht messbar: die
    Tastatur-Rollausführung findet dort gar nicht statt – ArrowDown, PageDown und End
    lassen auch ein nacktes Testdokument mit 3024 px Höhe bei scrollTop 0, mit und ohne
    preventDefault, headless wie mit Fenster. Die Suite miselt deshalb die Ursache über eine
    Sprosse, die nach dem Spiel-Listener auf window angemeldet ist und defaultPrevented
    meldet (mit Kreuzprobe: die Sonde muss im Lauf prevented: true sehen, sonst wäre die
    Prüfung leer). Der Befund-Eintrag „scrollTop bleibt 0“ war an dieser Stelle ein
    Messartefakt – die beiden anderen Befundteile (Knöpfe tot, Schalter nicht schaltbar) sind
    real und oben grün gemessen.
  2. Der Lösungsvorschlag wurde um die komplette Frühkehr ergänzt (nicht nur um das
    preventDefault): die Leertaste auf einem Overlay-Knopf soll nicht zusätzlich springen.
## Behoben in `5c84877` (Doku in `38c101c`) **Geändert** – `index.html`, INPUT-Modul: * neu: `NAV_KEYS`, `OVERLAY_IDS` und `INPUT.widgetOwns(target)` – prüft, ob ein fokussiertes Bedienelement (`button, input, select, textarea, a[href], [role="button"]`) unter einem **sichtbaren** Overlay (`menu`, `pause`, `gameover`, `settings`, `help`) steht. * `keydown`-Listener: die Leertaste und die Pfeiltasten werden nicht mehr verhindert, wenn `widgetOwns(ev.target)` – in dem Fall wird die Taste außerdem **komplett nicht** als Spielaktion umgesetzt (sonst läge beim „Fortsetzen“ per Leertaste ein Sprung im Puffer). * Ohne Overlay bleibt alles wie bisher: Prevented und Sache des Spiels – **auch** wenn ein HUD-Knopf (`#btnPause`, `#btnFocus`) den Fokus hält, sonst würde jeder Sprung pausieren. * Nicht angetastet: der Modifikator-/Wiederholungsfilter aus Issue #8 steht unverändert vor allen Aktionen, die Buchstabenkürzel (`P`/`R`/`M`/`F`/`W`, `Escape`) laufen unabhängig vom Fokusziel, `keyup` ebenfalls. **Verifikation** – neue Suite `tests/browser_issue20.py` (60 Prüfungen, echte `keyboard`-Eingaben, an der Messstelle kein einziger `PULSE.*`-Aufruf): * Zehn Overlay-Fälle „Knopf fokussieren → Leertaste → Zustand“: `#btnStart`→`COUNTDOWN`, `#btnHelp`→`HELP`, `#btnSettings`→`SETTINGS`, `#btnResume`→`PLAYING`, `#btnRestart`→`COUNTDOWN`, `#btnPauseMenu`/`#btnGoMenu`/`#btnSettingsBack`/`#btnHelpBack`→`MENU`; Enter als Kontrolle. * Alle vier Schalter: Fokus + Leertaste kippt `checked`, der Wert liegt in `S.settings` **und** im `localStorage` (`pulseSprintSaveV1`), kein Mitschleifen der Nachbarn, und nach `reload()` stehen die Werte aus dem Speicher wieder im Spiel. * Hilfe bei 900 × 380: Panel überläuft (579/330 px), `ArrowDown` auf dem fokussierten `#btnHelpBack` bleibt als Standardaktion erhalten und wird nicht zur Rolle umgebogen (`rollBuffer` 0, Zustand bleibt `HELP`), die Seite scrollt nicht. * Gegenseite (Issue #8 bleibt zu): im Lauf ist die Leertaste verhindert, springt, löst aber den fokussierten HUD-Knopf weder per Klick noch über den Fokus aus; `ArrowDown` rollt; `Escape` pausiert; Fortsetzen per Leertaste legt keinen Sprung in den Puffer. Gegen `git show HEAD:index.html` gemessen: **27 Prüfungen rot** (`MENU` bleibt `MENU` in allen drei Menü-Fällen, `PAUSED`/`GAME_OVER` ebenso, `checked` bleibt für alle vier Schalter `true`, `ArrowDown` prevented plus `rollBuffer` 0,1, `jumpBuffer` 0,12 beim Fortsetzen). Nach dem Fix grün – `./tests/run-all.sh 1` komplett: 12 Node-Suiten + Soak + 11 Browser-Suiten. **Zwei Abweichungen vom Befund, beide messbar:** 1. Die erwartete `scrollTop`-Prüfung ist in diesem Chromium **nicht** messbar: die Tastatur-Rollausführung findet dort gar nicht statt – `ArrowDown`, `PageDown` und `End` lassen auch ein nacktes Testdokument mit 3024 px Höhe bei `scrollTop` 0, mit und ohne `preventDefault`, headless wie mit Fenster. Die Suite miselt deshalb die Ursache über eine Sprosse, die nach dem Spiel-Listener auf `window` angemeldet ist und `defaultPrevented` meldet (mit Kreuzprobe: die Sonde *muss* im Lauf `prevented: true` sehen, sonst wäre die Prüfung leer). Der Befund-Eintrag „`scrollTop` bleibt 0“ war an dieser Stelle ein Messartefakt – die beiden anderen Befundteile (Knöpfe tot, Schalter nicht schaltbar) sind real und oben grün gemessen. 2. Der Lösungsvorschlag wurde um die **komplette** Frühkehr ergänzt (nicht nur um das `preventDefault`): die Leertaste auf einem Overlay-Knopf soll nicht zusätzlich springen.
Author
Owner

Korrektur zweier Tippfehler oben: »die Suite miselt … über eine Sprosse« → »die Suite misst die Ursache über eine Sonde, die nach dem Spiel-Listener auf window angemeldet ist«. Inhaltlich nichts geändert – Fix 5c84877, Suite tests/browser_issue20.py.

Korrektur zweier Tippfehler oben: »die Suite miselt … über eine Sprosse« → »die Suite **misst** die Ursache über eine **Sonde**, die nach dem Spiel-Listener auf `window` angemeldet ist«. Inhaltlich nichts geändert – Fix `5c84877`, Suite `tests/browser_issue20.py`.
gilli closed this issue 2026-09-02 21:47:26 +02:00
Author
Owner

Zweiter Teil: Leertaste war immer noch nichts wert, solange kein Knopf fokussiert war

Nach 5c84877 war nur die Sabotage am fokussierten Bedienelement weg. Nachgemessen
(Headless-Chromium, echte Tastatur, Fokus auf body) klaffte genau die Lücke, um die es
ging – „Leer = Springen“ ist die beworbene Haupttat, auf den Bildschirmen, wo gar nicht
gespielt wird, tat sie aber weiter nichts:

Zustand (kein Knopf fokussiert) Taste vorher jetzt
MENU Leertaste / Enter MENU bleibt, 0 Klicks COUNTDOWN (#btnStart)
PAUSED Leertaste / Enter PAUSED bleibt PLAYING (#btnResume)
GAME_OVER Leertaste / Enter GAME_OVER bleibt COUNTDOWN (#btnRetry)
SETTINGS / HELP Leertaste bleibt MENU (#…Back)
MENU, Fokus #btnStart ArrowDown/Up Fokus bleibt stehen wandert durch die Knopfgruppe
SETTINGS, Fokus #btnResetSave ArrowDown bleibt #btnSettingsBack
SETTINGS / HELP, Fokus beliebig Escape bleibt MENU
PLAYING, Fokus body Leertaste Sprung Sprung (unverändert, kein Neustart)

Umgesetzt in 5446dcd (Doku d5c3cac):

  1. INPUT: focusWidget() / overlayVisible() statt des einen widgetOwns() (das war nach
    dem Umbau toter Code – genau der Mustermangel aus #21). Leertaste und Eingabe gehen, wenn
    kein Bedienelement fokussiert ist, an hooks.onPrimary().
  2. GAME.clickPrimary(): klickt den Knopf, den das Markup mit data-primary auszeichnet
    (#btnStart, #btnResume, #btnRetry, #btnSettingsBack, #btnHelpBack) – echter
    Klick, damit nicht eine zweite Zustandsmaschine neben den Buttons um den Zustand ringt.
    false (= kein Overlay offen) heißt: die Taste gehört weiter dem Spiel. Sonst würde jeder
    Sprung im Lauf den Countdown neu anwerfen.
  3. Kein Doppelauslösen: ein fokussierter Knopf wird vom Browser ausgelöst und läuft in INPUT
    vorher zurück – die Suite zählt pro Eingabe genau einen Klick auf genau einen Knopf.
    ev.repeat filtert weiterhin vorher, eine gehaltene Leertaste wirft das Game Over nicht
    mehrfach neu an.
  4. Escape entlässt aus Einstellungen und Hilfe über dasselbe Feld. Bewusst nicht aus dem
    Menü: dort wäre dasselbe Feld der Start – eine Haupttat, keine Flucht.
  5. Pfeiltasten wandern durch .btn-stack (Handler auf der Gruppe, Tab bleibt).
    preventDefault erst ab zwei Knöpfen und nur mit Fokus in der Gruppe: der Einzelknopf in
    #help muss die Pfeiltaste zum Panel-Scrollen frei behalten (Teil 3 derselben Suite), und
    die Schalter-Reihen in #settings liegen außerhalb jeder Gruppe, damit der Fokus nicht vom
    Schalter gerissen wird.
  6. Beschriftung nachgezogen: Hilfetabelle neue Zeile „Leer / ↵ im Menü“, Esc jetzt
    „Pause · zurück aus Einstellungen und Hilfe“, Erststart-Hinweis „Menü: Leertaste startet,
    Pfeiltasten wandern durch die Knöpfe“.

Tests. tests/browser_issue20.py von 60 auf 105 Prüfungen. Gegen main vor 5446dcd
gemessen: 29 rot (alle acht Hauptaktionsfälle, beide Wander-Fälle, beide Escape-Fälle,
[data-primary]-Vertrag, drei Beschriftungen). Jetzt grün, und ./tests/run-all.sh komplett
(12 Node-Suiten + Soak + 11 Browser-Suiten) – browser_issue8.py (Strg+F/Cmd+F/Alt+F,
ev.repeat, Escape), browser_issue7.py (Bildschirmstillstand in der Pause) und
browser_flow.py (echter Loop mit Leertasteneingaben) unverändert grün.

Neu abgedeckt ist damit auch die Gegenseite, die bei so einer Änderung die teurere ist: im
Lauf springt die Leertaste weiter, startet aber keinen neuen Durchgang, und im Countdown
sammelt sie nur den Sprungpuffer, statt bei 3,0 zurückzuspringen.

## Zweiter Teil: Leertaste war immer noch nichts wert, solange kein Knopf fokussiert war Nach `5c84877` war nur die Sabotage am **fokussierten** Bedienelement weg. Nachgemessen (Headless-Chromium, echte Tastatur, Fokus auf `body`) klaffte genau die Lücke, um die es ging – „Leer = Springen“ ist die beworbene Haupttat, auf den Bildschirmen, wo gar nicht gespielt wird, tat sie aber weiter nichts: | Zustand (kein Knopf fokussiert) | Taste | vorher | jetzt | |---|---|---|---| | `MENU` | Leertaste / Enter | `MENU` bleibt, 0 Klicks | `COUNTDOWN` (`#btnStart`) | | `PAUSED` | Leertaste / Enter | `PAUSED` bleibt | `PLAYING` (`#btnResume`) | | `GAME_OVER` | Leertaste / Enter | `GAME_OVER` bleibt | `COUNTDOWN` (`#btnRetry`) | | `SETTINGS` / `HELP` | Leertaste | bleibt | `MENU` (`#…Back`) | | `MENU`, Fokus `#btnStart` | ArrowDown/Up | Fokus bleibt stehen | wandert durch die Knopfgruppe | | `SETTINGS`, Fokus `#btnResetSave` | ArrowDown | bleibt | `#btnSettingsBack` | | `SETTINGS` / `HELP`, Fokus beliebig | Escape | bleibt | `MENU` | | `PLAYING`, Fokus `body` | Leertaste | Sprung | Sprung (unverändert, kein Neustart) | **Umgesetzt in `5446dcd`** (Doku `d5c3cac`): 1. `INPUT`: `focusWidget()` / `overlayVisible()` statt des einen `widgetOwns()` (das war nach dem Umbau toter Code – genau der Mustermangel aus #21). Leertaste und Eingabe gehen, wenn **kein** Bedienelement fokussiert ist, an `hooks.onPrimary()`. 2. `GAME.clickPrimary()`: klickt den Knopf, den das Markup mit `data-primary` auszeichnet (`#btnStart`, `#btnResume`, `#btnRetry`, `#btnSettingsBack`, `#btnHelpBack`) – echter Klick, damit nicht eine zweite Zustandsmaschine neben den Buttons um den Zustand ringt. `false` (= kein Overlay offen) heißt: die Taste gehört weiter dem Spiel. Sonst würde jeder Sprung im Lauf den Countdown neu anwerfen. 3. Kein Doppelauslösen: ein fokussierter Knopf wird vom Browser ausgelöst und läuft in INPUT vorher zurück – die Suite zählt pro Eingabe **genau einen** Klick auf **genau einen** Knopf. `ev.repeat` filtert weiterhin vorher, eine gehaltene Leertaste wirft das Game Over nicht mehrfach neu an. 4. `Escape` entlässt aus Einstellungen und Hilfe über dasselbe Feld. Bewusst **nicht** aus dem Menü: dort wäre dasselbe Feld der Start – eine Haupttat, keine Flucht. 5. Pfeiltasten wandern durch `.btn-stack` (Handler auf der Gruppe, `Tab` bleibt). `preventDefault` erst ab zwei Knöpfen und nur mit Fokus in der Gruppe: der Einzelknopf in `#help` muss die Pfeiltaste zum Panel-Scrollen frei behalten (Teil 3 derselben Suite), und die Schalter-Reihen in `#settings` liegen außerhalb jeder Gruppe, damit der Fokus nicht vom Schalter gerissen wird. 6. Beschriftung nachgezogen: Hilfetabelle neue Zeile „`Leer` / `↵` im Menü“, `Esc` jetzt „Pause · zurück aus Einstellungen und Hilfe“, Erststart-Hinweis „Menü: Leertaste startet, Pfeiltasten wandern durch die Knöpfe“. **Tests.** `tests/browser_issue20.py` von 60 auf 105 Prüfungen. Gegen `main` vor `5446dcd` gemessen: 29 rot (alle acht Hauptaktionsfälle, beide Wander-Fälle, beide Escape-Fälle, `[data-primary]`-Vertrag, drei Beschriftungen). Jetzt grün, und `./tests/run-all.sh` komplett (12 Node-Suiten + Soak + 11 Browser-Suiten) – `browser_issue8.py` (Strg+F/Cmd+F/Alt+F, `ev.repeat`, `Escape`), `browser_issue7.py` (Bildschirmstillstand in der Pause) und `browser_flow.py` (echter Loop mit Leertasteneingaben) unverändert grün. Neu abgedeckt ist damit auch die Gegenseite, die bei so einer Änderung die teurere ist: im Lauf springt die Leertaste weiter, startet aber keinen neuen Durchgang, und im Countdown sammelt sie nur den Sprungpuffer, statt bei 3,0 zurückzuspringen.
Sign in to join this conversation.
No description provided.