Sprung-Physik: Abstiegsphase existiert nicht – Figur teleportiert vom Scheitelpunkt auf den Boden #2

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

⁨⁩⁨Sprung-Physik: Abstiegsphase existiert nicht – Figur teleportiert vom Scheitelpunkt auf den Boden⁩ ⁨⁩#⁨2⁩⁨⁩"
By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Closed⁨⁩⁩
bug
critical
physics

Komponente: CORE (integrateFree)
Betroffene Version: main @ 238ed66, index.html Zeile 685

Beschreibung

Jeder Sprung endet abrupt im Scheitelpunkt: Die Figur steigt bis zum Apex und ist einen Frame
später wieder am Boden
– auch bei 229 px Höhe. Es gibt keine Fallphase.

Bei 25 Sprüngen auf flachem Boden über den echten PULSE.update()-Pfad gemessen:

Messung Ist Soll (AGENTS.md)
Luft frames aufsteigend 1350 —
Luft frames absteigend 0 ≈ 1350
Teleport-Ereignisse (Δy > 60 px/Frame) 25 / 25 Sprünge 0
Größter Sprung pro Frame 229 px —
Luftzeit 0,442 s 0,75 s
Luftstrecke (v = 371 px/s) 165 px jumpDistance = 278 px

Frame-Exakt-Trace (Schrittweite 1/120 s):

i=50  y=302  vy=-78   airborne
i=51  y=301  vy=-58   airborne
i=52  y=301  vy=-38   airborne
i=53  y=301  vy=-18   airborne
i=54  y=530  vy=  0   GROUNDED     <-- Δy = 229 px in einem Frame

Ursache

Die Landungsbedingung in integrateFree() ist invertiert:

// index.html:685
} else if (surf && b.vy >= 0 && (b.y + offPrev) <= surf.y + K.EDGE_LIFT) {
  b.y = surf.y - off; b.vy = 0; b.grounded = true;

(b.y + offPrev) <= surf.y + EDGE_LIFT bedeutet „Fuß über der Oberfläche“ – das ist der
kollisionsfreie Fall. Sobald vy >= 0 wird (genau im Apex), ist die Bedingung wahr und die
Figur wird auf surf.y - off gesnappt. Landen würde >= (Fuß erreicht/unterschreitet die
Oberfläche) plus einen Vor-Frame-Abgleich gegen „Durchsacken“ verlangen.

Der EDGE_LIFT-Gedanke aus AGENTS.md („Fuß-Hitbox beim Übergang über eine Kante um ca. 4–6 px
anheben, damit die Figur nicht an der Kantenhöhe hängen bleibt“) ist dadurch ebenfalls
unwirksam.

Reproduktion

Playwright über window.PULSE (Test-Hook im File, Zeile 2966):

// flacher Boden, Figur auf Boden, ein Sprung, Frame-Tracing
PULSE.press('jump');
for (let i = 0; i < 120; i++) {
  PULSE.update(1 / 120);
  trace.push({ i, y: S.body.y, vy: S.body.vy, g: S.body.grounded });
  if (S.body.grounded && i > 5) break;
}
// -> airborneFrames 54 (nur Aufstieg), letzter Schritt Δy = 229 px

Zähltest über 25 Sprünge: airborneFramesGoingUp: 1350, airborneFramesComingDown: 0, apexTeleportEvents: 25, maxTeleportPxPerFrame: 229.

Auswirkung

  1. Sprungweite ≈ 59 % des Entwurfswerts. jumpDistance(v) (Zeile 761) rechnet mit der
    vollständigen Parabel (2·|JUMP_VELOCITY|/GRAVITY → 0,75 s). Die Generierung von
    Grubenbreiten (maxGapJumpable = 0,72 × jumpDistance, PIT bis 0,92 × maxGapJumpable) und
    die Dokumentation beruhen auf einem Wert, den die Physik nicht liefert.
  2. Keine Landung auf niedrigeren Plattformen / kein Hinabsteigen. Weil die Landung überall
    über der jeweiligen Oberfläche ausgelöst wird, kann die Figur nicht von oben auf einer
    tieferen Plattform zubewegt werden; stattdessen Teleport auf diejenige Fläche, die unter b.x
    liegt.
  3. Sichtbarer Grafikfehler: Figur verschwindet im Apex und erscheint am Boden.
  4. „Kurze Sprünge durch frühes Loslassen“ (AGENTS.md) ist faktisch wirkungslos, weil der
    Absturz den HALTE-Bonus ohnehin sofort beendet.
  5. Der Solver ist selbstkonsistent (nutzt dieselbe Physik), daher fangen die
    10.000-Muster-Tests diesen Fehler nicht
    – die Generierung bleibt spielbar, aber alles
    dokumentierte/verifizierte Sprungverhalten weicht von den zentralen Konstanten ab.

Lösungsvorschlag

Landung auf „Fuß erreicht Oberfläche von oben“ umstellen, mit Vor-Frame-Fenster gegen
Durchsacken und EDGE_LIFT als Kanten-Toleranz:

const feetPrev = b.py + offPrev;          // b.py wird in stepBody bereits vor der Integration gesetzt
const feetNow  = b.y + off;
if (surf && b.vy >= 0 && feetPrev <= surf.y + K.EDGE_LIFT && feetNow >= surf.y - K.EDGE_LIFT) {
  b.y = surf.y - off; b.vy = 0; b.grounded = true;
  b.state = b.roll > 0 ? "ROLLING" : "RUNNING";
  if (ev) ev.landed = true;
}

Danach bitte prüfen/ergänzen:

  • Luftzeit ≈ 0,75 s und jumpDistance(v) stimmen mit der Messung überein (Regressionstest).
  • Landung auf PIT_PLATFORM / PIT_DOUBLE aus größerer Höhe.
  • Coyote Time direkt an der Grubenkante funktioniert weiterhin.
  • PIT-Breiten ggf. neu justieren, wenn die reale Sprungweite wieder steigt.

Dokumentiert in issues/02-sprung-keine-abstiegsphase.md (Commit c171694).

1 comments

⁨⁩⁨Sprung-Physik: Abstiegsphase existiert nicht – Figur teleportiert vom Scheitelpunkt auf den Boden⁩ ⁨⁩#⁨2⁩⁨⁩" By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Closed⁨⁩⁩ bug critical physics > **Komponente:** CORE (`integrateFree`) > **Betroffene Version:** `main` @ `238ed66`, `index.html` Zeile 685 > > ## Beschreibung > > Jeder Sprung endet abrupt im Scheitelpunkt: Die Figur steigt bis zum Apex und ist **einen Frame > später wieder am Boden** – auch bei 229 px Höhe. Es gibt keine Fallphase. > > Bei 25 Sprüngen auf flachem Boden über den echten `PULSE.update()`-Pfad gemessen: > > | Messung | Ist | Soll (AGENTS.md) | > |---|---|---| > | Luft frames **aufsteigend** | 1350 | — | > | Luft frames **absteigend** | **0** | ≈ 1350 | > | Teleport-Ereignisse (Δy > 60 px/Frame) | **25 / 25 Sprünge** | 0 | > | Größter Sprung pro Frame | **229 px** | — | > | Luftzeit | **0,442 s** | 0,75 s | > | Luftstrecke (v = 371 px/s) | **165 px** | `jumpDistance` = 278 px | > > Frame-Exakt-Trace (Schrittweite 1/120 s): > > ```text > i=50 y=302 vy=-78 airborne > i=51 y=301 vy=-58 airborne > i=52 y=301 vy=-38 airborne > i=53 y=301 vy=-18 airborne > i=54 y=530 vy= 0 GROUNDED <-- Δy = 229 px in einem Frame > ``` > > ## Ursache > > Die Landungsbedingung in `integrateFree()` ist invertiert: > > ```js > // index.html:685 > } else if (surf && b.vy >= 0 && (b.y + offPrev) <= surf.y + K.EDGE_LIFT) { > b.y = surf.y - off; b.vy = 0; b.grounded = true; > ``` > > `(b.y + offPrev) <= surf.y + EDGE_LIFT` bedeutet „Fuß **über** der Oberfläche“ – das ist der > *kollisionsfreie* Fall. Sobald `vy >= 0` wird (genau im Apex), ist die Bedingung wahr und die > Figur wird auf `surf.y - off` gesnappt. Landen würde `>=` (Fuß erreicht/unterschreitet die > Oberfläche) plus einen Vor-Frame-Abgleich gegen „Durchsacken“ verlangen. > > Der `EDGE_LIFT`-Gedanke aus AGENTS.md („Fuß-Hitbox beim Übergang über eine Kante um ca. 4–6 px > anheben, damit die Figur nicht an der Kantenhöhe hängen bleibt“) ist dadurch ebenfalls > unwirksam. > > ## Reproduktion > > Playwright über `window.PULSE` (Test-Hook im File, Zeile 2966): > > ```js > // flacher Boden, Figur auf Boden, ein Sprung, Frame-Tracing > PULSE.press('jump'); > for (let i = 0; i < 120; i++) { > PULSE.update(1 / 120); > trace.push({ i, y: S.body.y, vy: S.body.vy, g: S.body.grounded }); > if (S.body.grounded && i > 5) break; > } > // -> airborneFrames 54 (nur Aufstieg), letzter Schritt Δy = 229 px > ``` > > Zähltest über 25 Sprünge: `airborneFramesGoingUp: 1350, airborneFramesComingDown: 0, > apexTeleportEvents: 25, maxTeleportPxPerFrame: 229`. > > ## Auswirkung > > 1. **Sprungweite ≈ 59 % des Entwurfswerts.** `jumpDistance(v)` (Zeile 761) rechnet mit der > vollständigen Parabel (`2·|JUMP_VELOCITY|/GRAVITY` → 0,75 s). Die Generierung von > Grubenbreiten (`maxGapJumpable = 0,72 × jumpDistance`, `PIT` bis 0,92 × maxGapJumpable) und > die Dokumentation beruhen auf einem Wert, den die Physik nicht liefert. > 2. **Keine Landung auf niedrigeren Plattformen / kein Hinabsteigen.** Weil die Landung überall > über der jeweiligen Oberfläche ausgelöst wird, kann die Figur nicht von oben auf einer > tieferen Plattform zubewegt werden; stattdessen Teleport auf diejenige Fläche, die unter `b.x` > liegt. > 3. **Sichtbarer Grafikfehler:** Figur verschwindet im Apex und erscheint am Boden. > 4. **„Kurze Sprünge durch frühes Loslassen“** (AGENTS.md) ist faktisch wirkungslos, weil der > Absturz den HALTE-Bonus ohnehin sofort beendet. > 5. Der Solver ist selbstkonsistent (nutzt dieselbe Physik), daher **fangen die > 10.000-Muster-Tests diesen Fehler nicht** – die Generierung bleibt spielbar, aber alles > dokumentierte/verifizierte Sprungverhalten weicht von den zentralen Konstanten ab. > > ## Lösungsvorschlag > > Landung auf „Fuß erreicht Oberfläche von oben“ umstellen, mit Vor-Frame-Fenster gegen > Durchsacken und `EDGE_LIFT` als Kanten-Toleranz: > > ```js > const feetPrev = b.py + offPrev; // b.py wird in stepBody bereits vor der Integration gesetzt > const feetNow = b.y + off; > if (surf && b.vy >= 0 && feetPrev <= surf.y + K.EDGE_LIFT && feetNow >= surf.y - K.EDGE_LIFT) { > b.y = surf.y - off; b.vy = 0; b.grounded = true; > b.state = b.roll > 0 ? "ROLLING" : "RUNNING"; > if (ev) ev.landed = true; > } > ``` > > Danach bitte prüfen/ergänzen: > * Luftzeit ≈ 0,75 s und `jumpDistance(v)` stimmen mit der Messung überein (Regressionstest). > * Landung auf `PIT_PLATFORM` / `PIT_DOUBLE` aus größerer Höhe. > * Coyote Time direkt an der Grubenkante funktioniert weiterhin. > * `PIT`-Breiten ggf. neu justieren, wenn die reale Sprungweite wieder steigt. > > --- > > *Dokumentiert in `issues/02-sprung-keine-abstiegsphase.md` (Commit `c171694`).* 1 comments
Author
Owner

Behoben in 174aeb7: Landung in integrateFree() heißt jetzt „Fuß erreicht die Oberfläche von oben“ (Vor-Frame-Fenster über b.py gegen Durchsacken, EDGE_LIFT als Kantentoleranz) statt der invertierten Bedingung, die im Scheitelpunkt auf den Boden teleportiert hat. Verifiziert: node tests/issue2.mjs (20 Prüfungen grün: absteigende Luftphase, Parabel-Symmetrie, kein Teleport, Luftzeit, Landung ohne Einsacken, Coyote, 30 fps) und python3 tests/browser_flow.py im echten rAF-Loop.

Behoben in `174aeb7`: Landung in `integrateFree()` heißt jetzt „Fuß erreicht die Oberfläche von oben“ (Vor-Frame-Fenster über `b.py` gegen Durchsacken, `EDGE_LIFT` als Kantentoleranz) statt der invertierten Bedingung, die im Scheitelpunkt auf den Boden teleportiert hat. Verifiziert: `node tests/issue2.mjs` (20 Prüfungen grün: absteigende Luftphase, Parabel-Symmetrie, kein Teleport, Luftzeit, Landung ohne Einsacken, Coyote, 30 fps) und `python3 tests/browser_flow.py` im echten rAF-Loop.
gilli closed this issue 2026-08-31 21:04:01 +02:00
Sign in to join this conversation.
No description provided.