Sprung-Eingabepuffer wirkt nicht: 120 ms konfiguriert, real ≤ 1 Simulationsschritt (~8 ms) #4

Closed
opened 2026-08-31 21:01:53 +02:00 by gilli · 1 comment
Owner

⁨⁩⁨Sprung-Eingabepuffer wirkt nicht: 120 ms konfiguriert, real ≤ 1 Simulationsschritt (~8 ms)⁩ ⁨⁩#⁨4⁩⁨⁩"
By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Closed⁨⁩⁩
bug
game-feel
high
input

Komponente: INPUT (consume), GAME (playerCmd)
Betroffene Version: main @ 238ed66, index.html Zeilen 2086, 2544–2548

Beschreibung

AGENTS.md verlangt einen Sprung-Eingabepuffer von ca. 100–140 ms, damit ein Sprung kurz vor der
Landung noch annimmt. Gemessen wird der Puffer mit K.JUMP_BUFFER = 0.12 gesetzt, aber schon
nach dem ersten Simulationsschritt wieder verworfen:

{
  "specBufferMs": 120,
  "bufferRightAfterPress": 120,
  "bufferAfterEachFrame_ms": [0, 0, 0, 0, 0],
  "airborneAtPress": true,
  "jumpedAtTouchdown": false
}

Test: Sprung ausführen, in der Luft jump drücken, Landung abwarten → kein Puffer-Sprung.

Ursache

Zwei Stellen zusammen nehmen dem Puffer seine Wirkung:

// index.html:2081-2092  INPUT.consume()
if (state.jumpBuffer > 0) { c.jump = true; state.jumpBuffer = 0; }   // wird PRO Simulationsschritt gerufen

consume() läuft in der Fixzeitschleife (FIXED_TIMESTEP = 1/120). Der_next step, der die
Puffer-Schranke sieht, verwertet sie – unabhängig davon, ob die Figur überhaupt springen kann.

// index.html:2544-2548  GAME.playerCmd()
if (c.jump) {
  if (body.grounded || body.coyote > 0) out.jump = true;
  else out.grab = true;          // in der Luft: Eingabe wird zum Greifversuch UMGEWEGET und ist verbraucht
}

Eine in der Luft gedrückte Sprungtaste wird also zum (evtl. erfolglosen) Greifversuch
degradiert und ist danach weg. Der in tickTimers() herunterzählende Puffer überlebt damit
höchstens einen einzigen Fixschritt.

Reproduktion

Playwright über window.PULSE:

// in der Luft drücken
PULSE.press('jump');                      // Sprung 1
PULSE.update(1/120);                      // airborne
PULSE.press('jump');                      // Eingabe ~1 Frame nach dem Absprung, soll puffern
const b0 = INPUT.state.jumpBuffer;        // 0.12
PULSE.update(1/120);                      // -> INPUT.state.jumpBuffer === 0
// danach bis zur Landung weiterschalten: kein Sprung bei touchdown

Auswirkung

  • Kein Eingabeverzeihen bei der Landung nach großen Sprüngen / nach dem Loslassen eines Schwungs –
    der häufigsten Timing-Situation im Spiel.
  • Autogriff/„Sprung in der Luft = Haken greifen“ überlagert die Erwartung „Springen puffern“.
  • Abweichung von einer explizit formulierten Vorgabe aus AGENTS.md.

Lösungsvorschlag

  1. consume() soll die jump-Kante nur verbrauchen, wenn sie auch ausführbar ist; sonst der
    Puffer weiterlaufen lassen:
function consume(canJump) {
  const c = { jumpHeld: state.jumpHeld, /* ... */ };
  if (state.jumpBuffer > 0) {
    if (canJump) { c.jump = true; state.jumpBuffer = 0; }
    else if (!hooksAvailable) { /* Puffer verfallen lassen oder für Hook nutzen */ }
  }
  ...
}
  1. Alternativ explizit zwei Kanäle führen (jumpBuffer und grabBuffer) und in der Luft zuerst
    grabBuffer anbieten, jumpBuffer bis zur Landung laufen lassen.
  2. Regressionstest: Eingabe 20/40/70/100 ms vor touchdown → Sprung erfolgt bei Landung;
    Eingabe 200 ms vorher → verfällt.

Dokumentiert in issues/04-sprung-eingabepuffer-wirkt-nicht.md (Commit c171694).

1 comments

⁨⁩⁨Sprung-Eingabepuffer wirkt nicht: 120 ms konfiguriert, real ≤ 1 Simulationsschritt (~8 ms)⁩ ⁨⁩#⁨4⁩⁨⁩" By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Closed⁨⁩⁩ bug game-feel high input > **Komponente:** INPUT (`consume`), GAME (`playerCmd`) > **Betroffene Version:** `main` @ `238ed66`, `index.html` Zeilen 2086, 2544–2548 > > ## Beschreibung > > AGENTS.md verlangt einen Sprung-Eingabepuffer von ca. 100–140 ms, damit ein Sprung kurz vor der > Landung noch annimmt. Gemessen wird der Puffer mit `K.JUMP_BUFFER = 0.12` gesetzt, aber schon > nach dem ersten Simulationsschritt wieder verworfen: > > ```json > { > "specBufferMs": 120, > "bufferRightAfterPress": 120, > "bufferAfterEachFrame_ms": [0, 0, 0, 0, 0], > "airborneAtPress": true, > "jumpedAtTouchdown": false > } > ``` > > Test: Sprung ausführen, in der Luft `jump` drücken, Landung abwarten → **kein** Puffer-Sprung. > > ## Ursache > > Zwei Stellen zusammen nehmen dem Puffer seine Wirkung: > > ```js > // index.html:2081-2092 INPUT.consume() > if (state.jumpBuffer > 0) { c.jump = true; state.jumpBuffer = 0; } // wird PRO Simulationsschritt gerufen > ``` > > `consume()` läuft in der Fixzeitschleife (`FIXED_TIMESTEP = 1/120`). Der_next step, der die > Puffer-Schranke sieht, verwertet sie – unabhängig davon, ob die Figur überhaupt springen kann. > > ```js > // index.html:2544-2548 GAME.playerCmd() > if (c.jump) { > if (body.grounded || body.coyote > 0) out.jump = true; > else out.grab = true; // in der Luft: Eingabe wird zum Greifversuch UMGEWEGET und ist verbraucht > } > ``` > > Eine in der Luft gedrückte Sprungtaste wird also zum (evtl. erfolglosen) Greifversuch > degradiert und ist danach weg. Der in `tickTimers()` herunterzählende Puffer überlebt damit > höchstens einen einzigen Fixschritt. > > ## Reproduktion > > Playwright über `window.PULSE`: > > ```js > // in der Luft drücken > PULSE.press('jump'); // Sprung 1 > PULSE.update(1/120); // airborne > PULSE.press('jump'); // Eingabe ~1 Frame nach dem Absprung, soll puffern > const b0 = INPUT.state.jumpBuffer; // 0.12 > PULSE.update(1/120); // -> INPUT.state.jumpBuffer === 0 > // danach bis zur Landung weiterschalten: kein Sprung bei touchdown > ``` > > ## Auswirkung > > * Kein Eingabeverzeihen bei der Landung nach großen Sprüngen / nach dem Loslassen eines Schwungs – > der häufigsten Timing-Situation im Spiel. > * Autogriff/„Sprung in der Luft = Haken greifen“ überlagert die Erwartung „Springen puffern“. > * Abweichung von einer explizit formulierten Vorgabe aus AGENTS.md. > > ## Lösungsvorschlag > > 1. `consume()` soll die jump-Kante **nur verbrauchen, wenn sie auch ausführbar ist**; sonst der > Puffer weiterlaufen lassen: > > ```js > function consume(canJump) { > const c = { jumpHeld: state.jumpHeld, /* ... */ }; > if (state.jumpBuffer > 0) { > if (canJump) { c.jump = true; state.jumpBuffer = 0; } > else if (!hooksAvailable) { /* Puffer verfallen lassen oder für Hook nutzen */ } > } > ... > } > ``` > > 2. Alternativ explizit zwei Kanäle führen (`jumpBuffer` und `grabBuffer`) und in der Luft zuerst > `grabBuffer` anbieten, `jumpBuffer` bis zur Landung laufen lassen. > 3. Regressionstest: Eingabe 20/40/70/100 ms vor touchdown → Sprung erfolgt bei Landung; > Eingabe 200 ms vorher → verfällt. > > --- > > *Dokumentiert in `issues/04-sprung-eingabepuffer-wirkt-nicht.md` (Commit `c171694`).* 1 comments
Author
Owner

Behoben in c171694: Der Puffer wird nur verbraucht, wenn die Eingabe ausführbar ist – INPUT.consume(av) bekommt vom Spiel { canJump, canGrab, hanging } (in playerCmd()), sonst läuft der Puffer weiter. playerCmd() biegt eine Luft-Sprungkante nicht mehr still auf grab um. Verifiziert: node tests/issue4.mjs (17 Prüfungen grün: Eingaben 20/40/70/100 ms vor touchdown zählen, 200 ms verfallen, Coyote, consume(av)-Vertrag) und python3 tests/browser_smoke.py im echten Spiel.

Behoben in `c171694`: Der Puffer wird nur verbraucht, wenn die Eingabe ausführbar ist – `INPUT.consume(av)` bekommt vom Spiel `{ canJump, canGrab, hanging }` (in `playerCmd()`), sonst läuft der Puffer weiter. `playerCmd()` biegt eine Luft-Sprungkante nicht mehr still auf `grab` um. Verifiziert: `node tests/issue4.mjs` (17 Prüfungen grün: Eingaben 20/40/70/100 ms vor touchdown zählen, 200 ms verfallen, Coyote, `consume(av)`-Vertrag) und `python3 tests/browser_smoke.py` im echten Spiel.
gilli closed this issue 2026-08-31 21:04:02 +02:00
Sign in to join this conversation.
No description provided.