Rollen ist eine feste Zeitschablone: Loslassen endet die Rolle nie, Halten wirkt nur 0,24 s und ist nach 0,3 s ausgeschöpft #14

Closed
opened 2026-08-31 21:02:00 +02:00 by gilli · 2 comments
Owner

⁨⁩⁨Rollen ist eine feste Zeitschablone: Loslassen endet die Rolle nie, Halten wirkt nur 0,24 s und ist nach 0,3 s ausgeschöpft⁩ ⁨⁩#⁨14⁩⁨⁩"
By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Closed⁨⁩⁩
bug
game-feel
input
medium

Komponente: CORE (stepBody), CORE.K, INPUT/GAME (Zeigerfreigabe)
Betroffene Version: main @ c171694, index.html Zeilen 408–409, 619, 741–766, 902, 2144–2172, 2244–2258, 2633

Befund

Die Roll-Länge ist eine Zeitfunktion, keine Eingabegröße. Es gibt zwar einen Halte-Zweig, aber
der ist nach oben gedeckelt und wirkt nicht als „Rolle = Haltedauer“:

// index.html:741-742
const ROLL_HOLD_MAX = 0.86;
function rollDuration(speed) { return clamp(0.34 + 130 / Math.max(120, speed), 0.48, 0.62); }

// index.html:746-750
if (b.roll > 0 && cmd.rollHeld && b.grounded && b.rollTime < ROLL_HOLD_MAX) { b.rollTime += dt; b.roll += dt; }
if (b.roll > 0) {
  b.roll -= dt;
  if (b.roll <= 0) { b.roll = 0; b.rollCd = K.ROLL_COOLDOWN; … }
}

Drei Dinge folgen daraus:

  1. Nie kürzer als die Basis. rollDuration() setzt die Rolle beim Antippen auf 0,48 … 0,62 s.
    Ein Loslassen in derselben Sekunde verkürzt nichts – es gibt keinen Abbruch. Einziger Ausweg
    ist ein Sprung (doJump() löscht b.roll, Zeile 619), also eine andere Aktion.
  2. Das Halten friert nur ein statt zu verlängern. b.roll += dt im Halte-Zweig wird von
    b.roll -= dt zwei Zeilen tiefer exakt aufgehoben; gezählt wird allein b.rollTime. Nach dem
    Loslassen läuft die volle Basisdauer weiter. Effekt: Gesamtrolle =
    min(Haltezeit, 0,86 − Basis) + Basis.
  3. Nach ~0,3 s Halten ist die Eingabe wirkungslos. Bei 0,86 − 0,62 = 0,24 s Zuschlag sitzt
    die Obergrenze bei 0,86 s. Ab dann ist die Rolldauer unabhängig davon, ob die Taste 0,3 s oder
    3 s gehalten wird – und wegen ROLL_COOLDOWN (0,2 s) startet eine dauerhaft gehaltene Taste
    auch keine zweite Rolle. Für den Spieler ist das exakt die gemeldete „feste Zeit“.

Der steuerbare Bereich beträgt damit 0,24 s bei Starttempo, 0,33 s bei Endtempo.

Messprotokoll

Deterministisch, CORE.stepBody über den Test-Harness, dt = K.FIXED_TIMESTEP (1/120), flacher
Boden, Rolle mit der Kante in Schritt 1, rollHeld danach wie angegeben:

Halten Tempo 360 (rollDuration 0,620) Tempo 480 (0,611) Tempo 640 (0,543)
0 s (nur tippen) 0,633 s 0,625 s 0,558 s
0,1 s 0,733 s 0,725 s 0,658 s
0,3 s 0,875 s 0,875 s 0,850 s
0,5 s 0,875 s 0,875 s 0,883 s
3,0 s 0,875 s 0,875 s 0,883 s

Im echten Dokument mit echten Tastaturevents (Playwright, requestAnimationFrame stillgelegt,
PULSE.update(1/120) geschaltet, S und Pfeil runter identisch):

Eingabe gemessene Roll-Dauer
Tippen (keyup nach ~30 ms) 0,650 s
Halten 300 ms 0,867 s
Halten 1500 ms 0,867 s (die Rolle endet bei ~0,87 s, während die Taste noch gedrückt ist)

Nebenbefund zur Formel: die untere Klemme 0.48 ist im spielbaren Tempo-Band
(SPEED_START 360 … SPEED_MAX 640) unerreichbar – 0.34 + 130/640 = 0,543. Der Wert ist
totes Tuning; die obere Klemme 0,62 greift dagegen bis Tempo ~380.

Teil 2 – die Zahl liegt außerhalb von CORE.K, K.ROLL_TIME ist eine Attrappe

ROLL_HOLD_MAX = 0,86 (Zeile 741) ist eine Balancing-Konstante im Modul, nicht in CORE.K –
gegen die Regel, dass CORE.K die einzige Quelle für Physik- und Balancing-Zahlen ist.
Gleichzeitig steht in CORE.K:

// index.html:408-409
ROLL_TIME: 0.5,
ROLL_COOLDOWN: 0.2,

K.ROLL_TIME ist aber nicht die Rolldauer: die einzige Lesestelle ist die
Abstandsberechnung für JUMP_ROLL/ROLL_JUMP (Zeile 902, K.ROLL_TIME * v + 130). Die echte
Dauer liefert rollDuration() (0,54 … 0,62 s) bzw. 0,86 s im Haltefall. Die Konstante, die das
Generierungs-Layout berechnet, beschreibt also ein Verhalten, das das Spiel so nicht zeigt –
und genau die Zahl, die das Spiel zeigt, hat keinen Platz in CORE.K.

Teil 3 – Zeigerfreigabe passt nicht zur Zeigerauslösung (verklemmtes rollHeld)

Die Rolle-Auslösung per Wisch entscheidet die Wischrichtung, die Freigabe entscheidet die
Bildschirmhälfte:

// index.html:2244  pointermove  → Wisch nach unten = Rollen
if (p.y < pt.sy) press("jump"); else press("roll");
// index.html:2258  pointerup    → aber: Hälfte bestimmt, welcher Halte-Zustand gelöscht wird
if (pt.side === "left") state.jumpHeld = false; else state.rollHeld = false;

Wisch nach unten auf der linken Hälfte löst press("roll") aus, beim Loslassen wird
jumpHeld gelöscht und state.rollHeld bleibt true. Messung im echten Dokument:

  • Wisch nach unten links, Zeiger im selben Moment losgelassen → 0,8667 s Rolle, also der
    Halte-Maximalwert, ohne dass jemand gehalten hat.
  • Spiegelbild Wisch nach oben rechts (press("jump"), Zeiger los) → Sprungscheitel 229,3 px
    statt 165,0 px (+39 %), weil jumpHeld stehen bleibt und JUMP_HOLD_BOOST beim nächsten
    Sprung zieht.

Andere Ursache als Issue #8 Teil 2 (dort: pointerleave/pointercancel rufen up() nie), gleiche
Funktion, gleiche Folge – gehört im selben Fix mit erledigen.

Teil 4 – Roll-Eingabe in der Luft verfällt kommentarlos

INPUT.consume() leert den Roll-Puffer unbedingt, playerCmd() verwirft die Kante dann, wenn die
Figur nicht am Boden ist:

// index.html:2170
if (state.rollBuffer > 0) { c.roll = true; state.rollBuffer = 0; }
// index.html:2633
if (c.roll) { if (body.grounded) out.roll = true; }

Rollen ist laut Spec „nur am Boden“, aber wer die Taste über einen Sprung hinweg gedrückt hält,
um direkt nach der Landung zu rollen, bekommt nichts – gemessen: bei der Landung roll = 0,
state = RUNNING, trotz durchgehend gehaltener Taste. Für den Sprung ist genau das in Issue #4
behoben worden (consume(av) mit Verfügbarkeits-Flags); für Rollen fehlt das.

Was NICHT betroffen ist (geprüft)

Die Muster sind nicht an das Halten gekoppelt. 160 generierte Muster (20 Seeds, je 8 Muster) mit
der vom Solver gewählten Strategie, einmal wie vom Solver gefahren (rollHeld unter Toren true,
Zeile 1097), einmal mit erzwungenem Loslassen (cmd.rollHeld = false): jeweils 0 Misserfolge.
Die engste rollpflichtige Passage (74 px breites Hindernis + 26 px Hitbox bei Tempo 360) braucht
0,278 s, die Tipp-Rolle liefert 0,62 s. Es ist also kein Fairness-, sondern ein Steuerungs- und
Konsistenzproblem.

Reproduktion

  1. python3 -m http.server oder Datei direkt öffnen, Lauf starten.
  2. S kurz antippen → Rolle 0,63 s. S 1,5 s halten → Rolle endet ebenfalls nach ~0,87 s.
    Dazwischen liegen 0,24 s Steuerbereich, und die Rolle endet niemals, solange die Taste noch
    gehalten wird.
  3. Zeiger: Wisch nach unten auf der linken Hälfte, Zeiger sofort los → Rolle 0,87 s
    (PULSE.S.body.rollTime läuft bis 0,86, INPUT-Halte-Flag bleibt true).
  4. Dauerhaft gehaltene S-Taste: eine Rolle, danach Stillstand (kein Rollen in Serie).

Lösungsvorschlag

  • Semantik entscheiden und in CORE.K schreiben, z. B.
ROLL_MIN_TIME: 0.26,      // kürzeste Rolle: vorher beendet auch Loslassen sie nicht
ROLL_TIME: 0.5,           // Dauer ohne Taste (nur noch hier, nicht auch als Layout-Attrappe)
ROLL_HOLD_MAX: 0.9,       // maximale Rolle bei gehaltener Taste
ROLL_COOLDOWN: 0.2,
  • Halte-Logik so bauen, dass die Rolle mit der Taste endet (frühestens nach ROLL_MIN_TIME):
    mit der Kante cmd.roll starten, bei !cmd.rollHeld && b.rollTime >= K.ROLL_MIN_TIME abbrechen und
    ev.rollEnd werfen, bei gehaltener Taste bis K.ROLL_HOLD_MAX weiterrollen. Der Abbruch muss
    die Hitbox sofort auf die stehende Box zurücksetzen – das ist der Punkt der ganzen Änderung:
    Wer vor einer Lichtschranke rollt und loslässt, darf nicht 0,6 s lang gedeckt bleiben, sondern
    muss sich auf !rollHeld verlassen können.
  • K.ROLL_TIME aus der Generierungsformel (Zeile 902) durch eine Funktion ersetzen, die die
    tatsächlich spielbare Rolle beschreibt (rollDuration(v)), damit Layout und Physik dieselbe
    Zahl sehen.
  • Zeigerfreigabe an die Auslösung binden: beim press im Pointer-Datensatz notieren, welches
    Halte-Flag gesetzt wurde (pt.held = "roll" | "jump"), und genau dieses in up() löschen –
    plus pointercancel/pointerleave wie in Issue #8 Teil 2.
  • Roll-Puffer verfügbarkeitsgesteuert verbrauchen wie beim Sprung: INPUT.consume(av)
    mit canRoll = body.grounded && b.roll <= 0 && b.rollCd <= 0.
  • Tastaturtext/Touch-Hinweis ergänzen, falls Halten spielrelevant bleibt
    („Rollen: Taste halten = länger rollen“), sonst ist die Mechanik unsichtbar.
  • Tests: Tippen = ROLL_TIME, Halten = bis ROLL_HOLD_MAX (Grenzwert + ein Schritt darüber),
    Loslassen nach ROLL_MIN_TIME beendet die Rolle und stellt die stehende Hitbox wieder her,
    Loslassen vor ROLL_MIN_TIME nicht; Wisch nach unten links + sofortiges Loslassen ergibt
    ROLL_TIME (nicht den Halte-Wert) und setzt kein Halte-Flag; Rolle bei Landung mit gehaltener
    Taste zählt.

Dokumentiert in issues/14-roll-eingabe-feste-dauer-statt-haltedauer.md (Commit 724008b).

⁨2⁩ comments

⁨⁩⁨Rollen ist eine feste Zeitschablone: Loslassen endet die Rolle nie, Halten wirkt nur 0,24 s und ist nach 0,3 s ausgeschöpft⁩ ⁨⁩#⁨14⁩⁨⁩" By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Closed⁨⁩⁩ bug game-feel input medium > **Komponente:** CORE (`stepBody`), `CORE.K`, INPUT/GAME (Zeigerfreigabe) > **Betroffene Version:** `main` @ `c171694`, `index.html` Zeilen 408–409, 619, 741–766, 902, 2144–2172, 2244–2258, 2633 > > ## Befund > > Die Roll-Länge ist eine Zeitfunktion, keine Eingabegröße. Es gibt zwar einen Halte-Zweig, aber > der ist nach oben gedeckelt und wirkt nicht als „Rolle = Haltedauer“: > > ```js > // index.html:741-742 > const ROLL_HOLD_MAX = 0.86; > function rollDuration(speed) { return clamp(0.34 + 130 / Math.max(120, speed), 0.48, 0.62); } > > // index.html:746-750 > if (b.roll > 0 && cmd.rollHeld && b.grounded && b.rollTime < ROLL_HOLD_MAX) { b.rollTime += dt; b.roll += dt; } > if (b.roll > 0) { > b.roll -= dt; > if (b.roll <= 0) { b.roll = 0; b.rollCd = K.ROLL_COOLDOWN; … } > } > ``` > > Drei Dinge folgen daraus: > > 1. **Nie kürzer als die Basis.** `rollDuration()` setzt die Rolle beim Antippen auf 0,48 … 0,62 s. > Ein Loslassen in derselben Sekunde verkürzt nichts – es gibt keinen Abbruch. Einziger Ausweg > ist ein Sprung (`doJump()` löscht `b.roll`, Zeile 619), also eine andere Aktion. > 2. **Das Halten friert nur ein statt zu verlängern.** `b.roll += dt` im Halte-Zweig wird von > `b.roll -= dt` zwei Zeilen tiefer exakt aufgehoben; gezählt wird allein `b.rollTime`. Nach dem > Loslassen läuft die *volle* Basisdauer weiter. Effekt: Gesamtrolle = > `min(Haltezeit, 0,86 − Basis) + Basis`. > 3. **Nach ~0,3 s Halten ist die Eingabe wirkungslos.** Bei `0,86 − 0,62 = 0,24 s` Zuschlag sitzt > die Obergrenze bei 0,86 s. Ab dann ist die Rolldauer unabhängig davon, ob die Taste 0,3 s oder > 3 s gehalten wird – und wegen `ROLL_COOLDOWN` (0,2 s) startet eine dauerhaft gehaltene Taste > auch keine zweite Rolle. Für den Spieler ist das exakt die gemeldete „feste Zeit“. > > Der steuerbare Bereich beträgt damit **0,24 s bei Starttempo, 0,33 s bei Endtempo**. > > ### Messprotokoll > > Deterministisch, `CORE.stepBody` über den Test-Harness, `dt = K.FIXED_TIMESTEP` (1/120), flacher > Boden, Rolle mit der Kante in Schritt 1, `rollHeld` danach wie angegeben: > > | Halten | Tempo 360 (`rollDuration` 0,620) | Tempo 480 (0,611) | Tempo 640 (0,543) | > |---|---|---|---| > | 0 s (nur tippen) | 0,633 s | 0,625 s | 0,558 s | > | 0,1 s | 0,733 s | 0,725 s | 0,658 s | > | 0,3 s | 0,875 s | 0,875 s | 0,850 s | > | 0,5 s | 0,875 s | 0,875 s | 0,883 s | > | 3,0 s | 0,875 s | 0,875 s | 0,883 s | > > Im echten Dokument mit echten Tastaturevents (`Playwright`, `requestAnimationFrame` stillgelegt, > `PULSE.update(1/120)` geschaltet, `S` und `Pfeil runter` identisch): > > | Eingabe | gemessene Roll-Dauer | > |---|---| > | Tippen (keyup nach ~30 ms) | 0,650 s | > | Halten 300 ms | 0,867 s | > | Halten 1500 ms | 0,867 s (die Rolle endet bei ~0,87 s, während die Taste noch gedrückt ist) | > > Nebenbefund zur Formel: die untere Klemme `0.48` ist im spielbaren Tempo-Band > (`SPEED_START` 360 … `SPEED_MAX` 640) unerreichbar – `0.34 + 130/640 = 0,543`. Der Wert ist > totes Tuning; die obere Klemme 0,62 greift dagegen bis Tempo ~380. > > ## Teil 2 – die Zahl liegt außerhalb von `CORE.K`, `K.ROLL_TIME` ist eine Attrappe > > `ROLL_HOLD_MAX = 0,86` (Zeile 741) ist eine Balancing-Konstante im Modul, nicht in `CORE.K` – > gegen die Regel, dass `CORE.K` die einzige Quelle für Physik- und Balancing-Zahlen ist. > Gleichzeitig steht in `CORE.K`: > > ```js > // index.html:408-409 > ROLL_TIME: 0.5, > ROLL_COOLDOWN: 0.2, > ``` > > `K.ROLL_TIME` ist aber **nicht** die Rolldauer: die einzige Lesestelle ist die > Abstandsberechnung für `JUMP_ROLL`/`ROLL_JUMP` (Zeile 902, `K.ROLL_TIME * v + 130`). Die echte > Dauer liefert `rollDuration()` (0,54 … 0,62 s) bzw. 0,86 s im Haltefall. Die Konstante, die das > Generierungs-Layout berechnet, beschreibt also ein Verhalten, das das Spiel so nicht zeigt – > und genau die Zahl, die das Spiel zeigt, hat keinen Platz in `CORE.K`. > > ## Teil 3 – Zeigerfreigabe passt nicht zur Zeigerauslösung (verklemmtes `rollHeld`) > > Die Rolle-Auslösung per Wisch entscheidet die **Wischrichtung**, die Freigabe entscheidet die > **Bildschirmhälfte**: > > ```js > // index.html:2244 pointermove → Wisch nach unten = Rollen > if (p.y < pt.sy) press("jump"); else press("roll"); > // index.html:2258 pointerup → aber: Hälfte bestimmt, welcher Halte-Zustand gelöscht wird > if (pt.side === "left") state.jumpHeld = false; else state.rollHeld = false; > ``` > > Wisch nach unten auf der **linken** Hälfte löst `press("roll")` aus, beim Loslassen wird > `jumpHeld` gelöscht und `state.rollHeld` bleibt `true`. Messung im echten Dokument: > > * Wisch nach unten links, Zeiger im selben Moment losgelassen → **0,8667 s** Rolle, also der > Halte-Maximalwert, ohne dass jemand gehalten hat. > * Spiegelbild Wisch nach oben rechts (`press("jump")`, Zeiger los) → Sprungscheitel **229,3 px** > statt 165,0 px (+39 %), weil `jumpHeld` stehen bleibt und `JUMP_HOLD_BOOST` beim nächsten > Sprung zieht. > > Andere Ursache als Issue #8 Teil 2 (dort: `pointerleave`/`pointercancel` rufen `up()` nie), gleiche > Funktion, gleiche Folge – gehört im selben Fix mit erledigen. > > ## Teil 4 – Roll-Eingabe in der Luft verfällt kommentarlos > > `INPUT.consume()` leert den Roll-Puffer unbedingt, `playerCmd()` verwirft die Kante dann, wenn die > Figur nicht am Boden ist: > > ```js > // index.html:2170 > if (state.rollBuffer > 0) { c.roll = true; state.rollBuffer = 0; } > // index.html:2633 > if (c.roll) { if (body.grounded) out.roll = true; } > ``` > > Rollen ist laut Spec „nur am Boden“, aber wer die Taste über einen Sprung hinweg gedrückt hält, > um direkt nach der Landung zu rollen, bekommt nichts – gemessen: bei der Landung `roll = 0`, > `state = RUNNING`, trotz durchgehend gehaltener Taste. Für den Sprung ist genau das in Issue #4 > behoben worden (`consume(av)` mit Verfügbarkeits-Flags); für Rollen fehlt das. > > ## Was NICHT betroffen ist (geprüft) > > Die Muster sind nicht an das Halten gekoppelt. 160 generierte Muster (20 Seeds, je 8 Muster) mit > der vom Solver gewählten Strategie, einmal wie vom Solver gefahren (`rollHeld` unter Toren `true`, > Zeile 1097), einmal mit erzwungenem Loslassen (`cmd.rollHeld = false`): jeweils **0 Misserfolge**. > Die engste rollpflichtige Passage (74 px breites Hindernis + 26 px Hitbox bei Tempo 360) braucht > 0,278 s, die Tipp-Rolle liefert 0,62 s. Es ist also kein Fairness-, sondern ein Steuerungs- und > Konsistenzproblem. > > ## Reproduktion > > 1. `python3 -m http.server` oder Datei direkt öffnen, Lauf starten. > 2. `S` kurz antippen → Rolle 0,63 s. `S` 1,5 s halten → Rolle endet ebenfalls nach ~0,87 s. > Dazwischen liegen 0,24 s Steuerbereich, und die Rolle endet niemals, solange die Taste noch > gehalten wird. > 3. Zeiger: Wisch nach unten auf der linken Hälfte, Zeiger sofort los → Rolle 0,87 s > (`PULSE.S.body.rollTime` läuft bis 0,86, `INPUT`-Halte-Flag bleibt `true`). > 4. Dauerhaft gehaltene `S`-Taste: eine Rolle, danach Stillstand (kein Rollen in Serie). > > ## Lösungsvorschlag > > * Semantik entscheiden und in `CORE.K` schreiben, z. B. > > ```js > ROLL_MIN_TIME: 0.26, // kürzeste Rolle: vorher beendet auch Loslassen sie nicht > ROLL_TIME: 0.5, // Dauer ohne Taste (nur noch hier, nicht auch als Layout-Attrappe) > ROLL_HOLD_MAX: 0.9, // maximale Rolle bei gehaltener Taste > ROLL_COOLDOWN: 0.2, > ``` > > * Halte-Logik so bauen, dass die Rolle **mit der Taste endet** (frühestens nach `ROLL_MIN_TIME`): > mit der Kante `cmd.roll` starten, bei `!cmd.rollHeld && b.rollTime >= K.ROLL_MIN_TIME` abbrechen und > `ev.rollEnd` werfen, bei gehaltener Taste bis `K.ROLL_HOLD_MAX` weiterrollen. Der Abbruch muss > die Hitbox sofort auf die stehende Box zurücksetzen – das ist der Punkt der ganzen Änderung: > Wer vor einer Lichtschranke rollt und loslässt, darf nicht 0,6 s lang gedeckt bleiben, sondern > muss sich auf `!rollHeld` verlassen können. > * `K.ROLL_TIME` aus der Generierungsformel (Zeile 902) durch eine Funktion ersetzen, die die > tatsächlich spielbare Rolle beschreibt (`rollDuration(v)`), damit Layout und Physik dieselbe > Zahl sehen. > * Zeigerfreigabe an die Auslösung binden: beim `press` im Pointer-Datensatz notieren, welches > Halte-Flag gesetzt wurde (`pt.held = "roll" | "jump"`), und genau dieses in `up()` löschen – > plus `pointercancel`/`pointerleave` wie in Issue #8 Teil 2. > * Roll-Puffer verfügbarkeitsgesteuert verbrauchen wie beim Sprung: `INPUT.consume(av)` > mit `canRoll = body.grounded && b.roll <= 0 && b.rollCd <= 0`. > * Tastaturtext/Touch-Hinweis ergänzen, falls Halten spielrelevant bleibt > („Rollen: Taste halten = länger rollen“), sonst ist die Mechanik unsichtbar. > * Tests: Tippen = `ROLL_TIME`, Halten = bis `ROLL_HOLD_MAX` (Grenzwert + ein Schritt darüber), > Loslassen nach `ROLL_MIN_TIME` beendet die Rolle und stellt die stehende Hitbox wieder her, > Loslassen vor `ROLL_MIN_TIME` nicht; Wisch nach unten links + sofortiges Loslassen ergibt > `ROLL_TIME` (nicht den Halte-Wert) und setzt kein Halte-Flag; Rolle bei Landung mit gehaltener > Taste zählt. > > --- > > *Dokumentiert in `issues/14-roll-eingabe-feste-dauer-statt-haltedauer.md` (Commit `724008b`).* ⁨2⁩ comments
Author
Owner

Behoben in 5884804 (main). Alle vier Teile:

Teil 1 – Rolle = Haltedauer. stepBody beendet eine Rolle, die die Taste je erlebt hat, mit dem Loslassen; die Hitbox steht im selben Simulationsschritt wieder auf der Stand-Box. Zeitachse jetzt in CORE.K: Tippen = ROLL_TIME 0,5 s, frühestmögliches Abbruchfenster ROLL_MIN_TIME 0,26 s, Obergrenze ROLL_HOLD_MAX 0,9 s, ROLL_COOLDOWN 0,2 s. Ein Loslassen vor ROLL_MIN_TIME lässt die Rolle auf die Tipp-Dauer auslaufen (Wisch + sofortige Freigabe = volle Rolle, kein Aufstehen im Tor).

Teil 2 – Konstanten. ROLL_HOLD_MAX ist aus dem Modul in CORE.K umgezogen; zusätzlich ROLL_MIN_TIME und ROLL_BUFFER (war hart 0,1 in press()). rollDuration(v) liefert exakt K.ROLL_TIME, und die Generierungsformel für ROLL_JUMP rechnet mit rollDuration(v) statt der Attrappe K.ROLL_TIME * v – Layout und Physik dieselbe Zahl. Tipp-Rolle damit 0,50 s statt 0,54–0,62 s; die engste rollpflichtige Passage (0,278 s) bleibt frei, Solver/Soak über das Schrittweitenband 1/100…1/144 geeicht.

Teil 3 – Zeigerfreigabe. pointerdown notiert nicht mehr die Hälfte, jeder Zeiger trägt sein ausgelöstes Halte-Flag (pt.held); pointerup/pointercancel/pointerleave löschen genau dieses (Abbruch/Verlassen ohne Tap-Auslösung, Issue-#8-Teil-2-Muster). Damit kein feststeckendes rollHeld beim Links-Wisch und kein jumpHeld-Restboost beim Rechts-Wisch.

Teil 4 – Roll-Puffer. INPUT.consume(av) bekommt canRoll/airborne von playerCmd und verbraucht die Roll-Kante nur bei Verfügbarkeit (Muster aus Issue #4). Eine in der Luft gehaltene Rolle-Taste überlebt die Flugzeit und rollt bei der Landung; ein frisch gefasster Haken wird davon nicht ausgelöst (rollBufferFromAir – löst weiter nur frische Drücke aus dem Hängen). Gehaltene Taste startet keine Serienrolle.

Steuerungstexte ergänzt („halten = länger rollen, loslassen = Ende“). Der Autopilot hält die Rolle jetzt, bis die stehende Hitbox inkl. SOLVER_MARGIN am Balken vorbei ist – die Solver-Verifikation der Muster bleibt unverändert grün.

Verifikation: tests/issue14.mjs (extrahiertes INPUT + playerCmd + echtes stepBody: Konstanten in K, Tippen = ROLL_TIME bei 360/480/640, Halten bis ROLL_HOLD_MAX inkl. Grenzwert + Schritt, Loslassen nach/vor ROLL_MIN_TIME, Hitbox-Rückkehr, Cooldown, Luftpuffer-Matrix, Seil-Freigabe, Anti-Serienrolle) und tests/browser_issue14.py (echte Pointer-/Tastaturevents: Wisch runter links + sofortige Freigabe = ROLL_TIME ohne Flag-Rest, Wisch hoch rechts ohne jumpHeld-Rest – Folgesprung normale Scheitelhöhe, Loslassen nach 0,33 s endet in ≤ 2 Schritten, Dauerhalten = ROLL_HOLD_MAX). Beide Suiten waren gegen den Altstand rot und sind jetzt grün; ./tests/run-all.sh komplett grün (inkl. 5-min-Soak, Browser Smoke/Flow).

Behoben in `5884804` (main). Alle vier Teile: **Teil 1 – Rolle = Haltedauer.** `stepBody` beendet eine Rolle, die die Taste je erlebt hat, mit dem Loslassen; die Hitbox steht im selben Simulationsschritt wieder auf der Stand-Box. Zeitachse jetzt in `CORE.K`: Tippen = `ROLL_TIME` 0,5 s, frühestmögliches Abbruchfenster `ROLL_MIN_TIME` 0,26 s, Obergrenze `ROLL_HOLD_MAX` 0,9 s, `ROLL_COOLDOWN` 0,2 s. Ein Loslassen vor `ROLL_MIN_TIME` lässt die Rolle auf die Tipp-Dauer auslaufen (Wisch + sofortige Freigabe = volle Rolle, kein Aufstehen im Tor). **Teil 2 – Konstanten.** `ROLL_HOLD_MAX` ist aus dem Modul in `CORE.K` umgezogen; zusätzlich `ROLL_MIN_TIME` und `ROLL_BUFFER` (war hart 0,1 in `press()`). `rollDuration(v)` liefert exakt `K.ROLL_TIME`, und die Generierungsformel für `ROLL_JUMP` rechnet mit `rollDuration(v)` statt der Attrappe `K.ROLL_TIME * v` – Layout und Physik dieselbe Zahl. Tipp-Rolle damit 0,50 s statt 0,54–0,62 s; die engste rollpflichtige Passage (0,278 s) bleibt frei, Solver/Soak über das Schrittweitenband 1/100…1/144 geeicht. **Teil 3 – Zeigerfreigabe.** `pointerdown` notiert nicht mehr die Hälfte, jeder Zeiger trägt sein ausgelöstes Halte-Flag (`pt.held`); `pointerup`/`pointercancel`/`pointerleave` löschen genau dieses (Abbruch/Verlassen ohne Tap-Auslösung, Issue-#8-Teil-2-Muster). Damit kein feststeckendes `rollHeld` beim Links-Wisch und kein `jumpHeld`-Restboost beim Rechts-Wisch. **Teil 4 – Roll-Puffer.** `INPUT.consume(av)` bekommt `canRoll`/`airborne` von `playerCmd` und verbraucht die Roll-Kante nur bei Verfügbarkeit (Muster aus Issue #4). Eine in der Luft gehaltene Rolle-Taste überlebt die Flugzeit und rollt bei der Landung; ein frisch gefasster Haken wird davon nicht ausgelöst (`rollBufferFromAir` – löst weiter nur frische Drücke aus dem Hängen). Gehaltene Taste startet keine Serienrolle. Steuerungstexte ergänzt („halten = länger rollen, loslassen = Ende“). Der Autopilot hält die Rolle jetzt, bis die stehende Hitbox inkl. `SOLVER_MARGIN` am Balken vorbei ist – die Solver-Verifikation der Muster bleibt unverändert grün. **Verifikation:** `tests/issue14.mjs` (extrahiertes `INPUT` + `playerCmd` + echtes `stepBody`: Konstanten in `K`, Tippen = `ROLL_TIME` bei 360/480/640, Halten bis `ROLL_HOLD_MAX` inkl. Grenzwert + Schritt, Loslassen nach/vor `ROLL_MIN_TIME`, Hitbox-Rückkehr, Cooldown, Luftpuffer-Matrix, Seil-Freigabe, Anti-Serienrolle) und `tests/browser_issue14.py` (echte Pointer-/Tastaturevents: Wisch runter links + sofortige Freigabe = `ROLL_TIME` ohne Flag-Rest, Wisch hoch rechts ohne `jumpHeld`-Rest – Folgesprung normale Scheitelhöhe, Loslassen nach 0,33 s endet in ≤ 2 Schritten, Dauerhalten = `ROLL_HOLD_MAX`). Beide Suiten waren gegen den Altstand rot und sind jetzt grün; `./tests/run-all.sh` komplett grün (inkl. 5-min-Soak, Browser Smoke/Flow).
Author
Owner

Siehe Verifikationskommentar: Fix in 5884804, abgedeckt durch tests/issue14.mjs und tests/browser_issue14.py, komplette Suite grün.

Siehe Verifikationskommentar: Fix in 5884804, abgedeckt durch tests/issue14.mjs und tests/browser_issue14.py, komplette Suite grün.
gilli closed this issue 2026-08-31 23:31:13 +02:00
Sign in to join this conversation.
No description provided.