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

Closed
opened 2026-08-29 01:41:03 +02:00 by G1LL1 · 6 comments
G1LL1 commented 2026-08-29 01:41:03 +02:00 (Migrated from gitlab.g1ll1.com)

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 gitlab-issues/02-sprung-keine-abstiegsphase.md (Commit 238ed66). Alle Befunde ausgeführt und gemessen; Repro-Schritte im Text.

**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 `gitlab-issues/02-sprung-keine-abstiegsphase.md` (Commit `238ed66`). Alle Befunde ausgeführt und gemessen; Repro-Schritte im Text.*
G1LL1 commented 2026-08-29 01:41:12 +02:00 (Migrated from gitlab.g1ll1.com)

mentioned in issue #12

mentioned in issue #12
G1LL1 commented 2026-08-29 01:45:22 +02:00 (Migrated from gitlab.g1ll1.com)

changed the description

changed the description
G1LL1 commented 2026-08-29 01:48:06 +02:00 (Migrated from gitlab.g1ll1.com)

changed the description

changed the description
G1LL1 commented 2026-08-29 10:48:26 +02:00 (Migrated from gitlab.g1ll1.com)

mentioned in commit 174aeb72a1

mentioned in commit 174aeb72a1761adeda9f2fbb6060ca54ac1a4f82
G1LL1 commented 2026-08-29 10:52:00 +02:00 (Migrated from gitlab.g1ll1.com)

Behoben in 174aeb7 (main).

Umsetzung

Landung in integrateFree() heißt jetzt „Fuß erreicht die Oberfläche von oben“ mit
Vor-Frame-Fenster gegen Durchsacken und EDGE_LIFT als Kanten-Toleranz:

const feetPrev = b.py + offPrev;
const feetNow  = b.y + off;
if (feetPrev <= surf.y + K.EDGE_LIFT && feetNow >= surf.y - K.EDGE_LIFT) {  }

b.py wird in stepBody() bereits vor der Integration gesetzt, deshalb ist der Vergleich
echter Vor-/Nachrahmen. ev.landed feuert damit außerdem erst bei tatsächlicher Landung
(zuvor im Scheitelpunkt – das hatte Staub, Landeton und Schwung-Wertung an den falschen Moment
gehängt).

Messung danach (Playwright über window.PULSE, 1/120 s)

Messung vorher jetzt
Luft-Frames aufsteigend / absteigend 1350 / 0 ~paritätisch (z. B. 25 / 26 pro Sprung)
Teleport-Ereignisse (Δy > 60 px) 25/25 Sprünge, max. 229 px 0, max. ~19 px
Luftzeit 0,442 s ≈ 0,87 s
Sprungweite bei 400 px/s 165 px 353 px (jumpDistance = 300 px)

Die reale Sprungweite liegt damit über der Formel aus AGENTS.md, weil JUMP_HOLD_BOOST
die Fallzeit verlängert; die Generierung rechnet weiterhin mit der Formel und bleibt dadurch
leicht konservativ. Nachjustieren war nicht nötig: PIT/PIT_PLATFORM/PIT_DOUBLE/PIT_HOOK
werden weiterhin verworfen bzw. über die Fallback-Kette ersetzt, wenn sie nicht passen
(0 erzwungene SAFE-Fallbacks bei 10 000 Mustern).

Der Solver war vorher selbstkonsistent und konnte den Fehler nicht fangen – deshalb wurde der
Test um eine Schrittweiten-Band-Prüfung ergänzt (1/100 … 1/144, also auch der reale
FIXED_TIMESTEP 1/120 neben SIM_DT 1/110): 96 Muster, 0 Fehlschläge. Damit ist Lösbarkeit
nicht mehr an eine bestimmte Simulationsschrittweite gekettet.

Tests

  • node .pi/tests/issue2.mjs – 20 Prüfungen: Abstiegsphase, Parabel-Symmetrie, kein Teleport,
    Luftzeit, Sprungweite vs. jumpDistance, exakte Landung ohne Einsacken, kurzer Sprung durch
    frühes Loslassen, Landung auf Plattform aus größerer Höhe, Coyote Time an der Grubenkante,
    Grube überspringen, Grubensturz genau einmal fell, kein NaN, 30 fps ohne Durchsacken.
  • node .pi/tests/soak.mjs – 5 Minuten Autopilot-Spielzeit (18,4 km, bis Sektor 41/Endlos)
    ohne Tod, keine NaN, maximal 31 lebende Objekte.
  • Headless Chromium im echten requestAnimationFrame-Loop (.pi/tests/browser_flow.py) bei
    1366×768, 320×640 und 844×390: je Sprung Auf- und Abstieg, kein Teleport, keine Konsolenfehler.
**Behoben in `174aeb7` (`main`).** ## Umsetzung Landung in `integrateFree()` heißt jetzt „Fuß erreicht die Oberfläche von oben“ mit Vor-Frame-Fenster gegen Durchsacken und `EDGE_LIFT` als Kanten-Toleranz: ```js const feetPrev = b.py + offPrev; const feetNow = b.y + off; if (feetPrev <= surf.y + K.EDGE_LIFT && feetNow >= surf.y - K.EDGE_LIFT) { … } ``` `b.py` wird in `stepBody()` bereits vor der Integration gesetzt, deshalb ist der Vergleich echter Vor-/Nachrahmen. `ev.landed` feuert damit außerdem erst bei tatsächlicher Landung (zuvor im Scheitelpunkt – das hatte Staub, Landeton und Schwung-Wertung an den falschen Moment gehängt). ## Messung danach (Playwright über `window.PULSE`, 1/120 s) | Messung | vorher | jetzt | |---|---|---| | Luft-Frames aufsteigend / absteigend | 1350 / **0** | ~paritätisch (z. B. 25 / 26 pro Sprung) | | Teleport-Ereignisse (Δy > 60 px) | 25/25 Sprünge, max. 229 px | 0, max. ~19 px | | Luftzeit | 0,442 s | ≈ 0,87 s | | Sprungweite bei 400 px/s | 165 px | 353 px (`jumpDistance` = 300 px) | Die reale Sprungweite liegt damit **über** der Formel aus AGENTS.md, weil `JUMP_HOLD_BOOST` die Fallzeit verlängert; die Generierung rechnet weiterhin mit der Formel und bleibt dadurch leicht konservativ. Nachjustieren war nicht nötig: `PIT`/`PIT_PLATFORM`/`PIT_DOUBLE`/`PIT_HOOK` werden weiterhin verworfen bzw. über die Fallback-Kette ersetzt, wenn sie nicht passen (0 erzwungene `SAFE`-Fallbacks bei 10 000 Mustern). Der Solver war vorher selbstkonsistent und konnte den Fehler nicht fangen – deshalb wurde der Test um eine **Schrittweiten-Band-Prüfung** ergänzt (1/100 … 1/144, also auch der reale `FIXED_TIMESTEP` 1/120 neben `SIM_DT` 1/110): 96 Muster, 0 Fehlschläge. Damit ist Lösbarkeit nicht mehr an eine bestimmte Simulationsschrittweite gekettet. ## Tests * `node .pi/tests/issue2.mjs` – 20 Prüfungen: Abstiegsphase, Parabel-Symmetrie, kein Teleport, Luftzeit, Sprungweite vs. `jumpDistance`, exakte Landung ohne Einsacken, kurzer Sprung durch frühes Loslassen, Landung auf Plattform aus größerer Höhe, Coyote Time an der Grubenkante, Grube überspringen, Grubensturz genau einmal `fell`, kein NaN, 30 fps ohne Durchsacken. * `node .pi/tests/soak.mjs` – 5 Minuten Autopilot-Spielzeit (18,4 km, bis Sektor 41/Endlos) ohne Tod, keine NaN, maximal 31 lebende Objekte. * Headless Chromium im echten `requestAnimationFrame`-Loop (`.pi/tests/browser_flow.py`) bei 1366×768, 320×640 und 844×390: je Sprung Auf- und Abstieg, kein Teleport, keine Konsolenfehler.
G1LL1 (Migrated from gitlab.g1ll1.com) closed this issue 2026-08-29 10:53:45 +02:00
G1LL1 commented 2026-08-29 10:53:46 +02:00 (Migrated from gitlab.g1ll1.com)

mentioned in issue #6

mentioned in issue #6
Sign in to join this conversation.
No description provided.