Neustart setzt die Laufzeit-Marker nicht zurück: ab dem zweiten Lauf fehlt der Tintenschweif, Landestaub und Werusch schweigen #19

Closed
opened 2026-09-02 15:40:24 +02:00 by gilli · 1 comment
Owner

⁨⁩⁨Neustart setzt die Laufzeit-Marker nicht zurück: ab dem zweiten Lauf fehlt der Tintenschweif, Landestaub und Werusch schweigen⁩ ⁨⁩#⁨19⁩⁨⁩"
By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Open⁨⁩⁩
bug
consistency
game-feel
medium

Komponente: GAME (startRun, resetWorld, simStep)
Betroffene Version: main @ 42f3cce, index.html (3.333 Zeilen, MD5 aae5f94c1dc05903f2c214d255d2fe9d),
Zeilen 2604–2616 (resetWorld), 2618–2641 (startRun), 2769–2770 (Staub), 2780–2781 (Werusch), 2836–2838 (Tintenschweif)

Beschreibung

Drei Marker vergleichen gegen Werte aus dem vorherigen Lauf, werden aber beim Neustart
nicht zurückgesetzt:

Marker Verwendung Bedingung
S.lastTrailX Tintenspur alle ~16 px !S.lastTrailX || body.x - S.lastTrailX > 16 (Zeile 2836)
S.lastDust Landestaub (Partikel + AUDIO.land()) S.runTime - S.lastDust > 0.12 (Zeile 2769)
S.whooshT Werusch beim Pendeln S.runTime - S.whooshT > 0.26 (Zeile 2780)

startRun() setzt S.trail.length = 0, S.runTime = 0, S.acc = 0 – die drei Marker nicht.
Da die Körper-x bei jedem Lauf wieder bei K.START_X (600) beginnt und S.runTime wieder bei 0,
sind beide Vergleiche im neuen Lauf erst mal negativ und bleiben es, bis der neue Lauf den
alten Wert überholt hat.

Messung

Headless-Chromium auf der gebauten Testseite, requestAnimationFrame stillgelegt, Schritt
1/120, flacher Boden, jeweils ein Sprung bis zur Landung (200 Schritte):

Lauf Landung S.lastDust danach Tintenpunkte S.lastTrailX
A (frischer Start) ja 0,7417 (Staub ausgelöst) 24 1 530
B (direkt danach, startRun) ja 0,7417 – unverändert, Staub ausgelassen 0 1 530
C (Vorlauf mit lastDust = 40, lastTrailX = 40 000) ja 40 – unverändert 0 40 000

Lauf B ist der Normalfall: jeder zweite, dritte, … Lauf beginnt ohne Tintenschweif und ohne
Landungsrückmeldung. Lauf C ist der Fall „nach einem guten Lauf“: der Tintenschweif erscheint
überhaupt nicht mehr (die Strecke des Vorlaufs wird nie erreicht), Staub und Werusch schweigen
solange, wie der neue Lauf unter der Laufzeit des Vorlaufs bleibt – bei einem 40-Sekunden-Vorlauf
also praktisch den ganzen neuen Lauf.

Der Tintenschweif ist zusätzlich an !S.settings.reduced hängt; in der Standardeinstellung
(ohne reduzierte Bewegung) ist er genau das visuelle Feedback, das hier fehlt.

Auswirkung

  • Der Tintenschweif – das einzige Bewegungsfeedback an der Figur selbst – fehlt am Anfang jedes
    weiteren Laufs bzw. (nach einem langen Lauf) komplett. Ein „das Spiel ist nach dem Neustart
    totaler“ Eindruck entsteht, ohne dass etwas kaputtgeht.
  • Landungen sind im zweiten und jedem weiteren Lauf stumm und ohne Staub: AUDIO.land(),
    Staubpartikel und die Landungsrückmeldung fallen aus, obwohl ev.landed feuert.
  • Beim Pendeln fehlt der Werusch (AUDIO.swing), solange S.runTime < S.whooshT.
  • Schwer zu finden, weil der erste Lauf nach dem Seitenaufruf immer korrekt aussieht –
    sämtliche bisherigen Suiten messen genau diesen ersten Lauf.

Lösungsvorschlag

  1. In startRun() ergänzen: S.lastDust = 0; S.whooshT = 0; S.lastTrailX = 0;
    (direkt neben S.trail.length = 0), zusätzlich S.hookHint = null.
  2. Robuster und damit absicherbar: die Marker relativ zur Laufzeit führen
    (if (S.runTime < S.lastDust) S.lastDust = 0;) oder in resetWorld() zurücksetzen,
    damit jeder Weg über resetWorld(seed) dieselche saubere Ausgangslage bekommt –
    resetWorld ist die Stelle, die S.trail ohnehin leert.
  3. Präventiv: Beim Einführen neuer S.last*-Marker gehört der Reset in startRun() –
    ein Test, der alle S.last*-Felder gegen eine Whitelist prüft, hält das offen.

Tests

  • Deterministisch (tests/issue19.mjs über PULSE.update(1/120) bzw. CORE-Harness):
    zwei Läufe direkt hintereinander, jeweils ein Sprung. Schranke: S.lastDust ist nach der
    Landung in Lauf 2 > 0 und kleiner als S.runTime; S.trail.length > 0 nach 300 px
    Strecke. Gegen die Auslieferung rot: lastDust unverändert 0,7417, trail.length 0.
  • Browser: Screenshots des frühen Laufabschnitts (erste 400 px) aus Lauf 1 und Lauf 2
    vergleichen – der Schweif muss in beiden zu sehen sein (prefers-reduced-motion aus).
  • Nachlauf: tests/browser_issue7.py (Stillstand) und tests/browser_flow.py dürfen
    weiterhin grün sein; soak.mjs ist von den Markern unberührt.

Dokumentiert in issues/19-laufzeitmarker-beim-neustart-nicht-zurueckgesetzt.md (Commit 42f3cce).

⁨0⁩ comments

⁨⁩⁨Neustart setzt die Laufzeit-Marker nicht zurück: ab dem zweiten Lauf fehlt der Tintenschweif, Landestaub und Werusch schweigen⁩ ⁨⁩#⁨19⁩⁨⁩" By ⁨⁩⁨gilli⁩⁨⁩ — ⁨⁨⁩Open⁨⁩⁩ bug consistency game-feel medium > **Komponente:** GAME (`startRun`, `resetWorld`, `simStep`) > **Betroffene Version:** `main` @ `42f3cce`, `index.html` (3.333 Zeilen, MD5 `aae5f94c1dc05903f2c214d255d2fe9d`), > Zeilen 2604–2616 (`resetWorld`), 2618–2641 (`startRun`), 2769–2770 (Staub), 2780–2781 (Werusch), 2836–2838 (Tintenschweif) > > ## Beschreibung > > Drei Marker vergleichen gegen Werte aus dem **vorherigen** Lauf, werden aber beim Neustart > nicht zurückgesetzt: > > | Marker | Verwendung | Bedingung | > |---|---|---| > | `S.lastTrailX` | Tintenspur alle ~16 px | `!S.lastTrailX \|\| body.x - S.lastTrailX > 16` (Zeile 2836) | > | `S.lastDust` | Landestaub (Partikel + `AUDIO.land()`) | `S.runTime - S.lastDust > 0.12` (Zeile 2769) | > | `S.whooshT` | Werusch beim Pendeln | `S.runTime - S.whooshT > 0.26` (Zeile 2780) | > > `startRun()` setzt `S.trail.length = 0`, `S.runTime = 0`, `S.acc = 0` – die drei Marker nicht. > Da die Körper-x bei jedem Lauf wieder bei `K.START_X` (600) beginnt und `S.runTime` wieder bei 0, > sind beide Vergleiche im neuen Lauf erst mal **negativ** und bleiben es, bis der neue Lauf den > alten Wert überholt hat. > > ## Messung > > Headless-Chromium auf der gebauten Testseite, `requestAnimationFrame` stillgelegt, Schritt > 1/120, flacher Boden, jeweils ein Sprung bis zur Landung (200 Schritte): > > | Lauf | Landung | `S.lastDust` danach | Tintenpunkte | `S.lastTrailX` | > |---|---|---|---|---| > | A (frischer Start) | ja | **0,7417** (Staub ausgelöst) | 24 | 1 530 | > | B (direkt danach, `startRun`) | ja | **0,7417** – unverändert, Staub ausgelassen | **0** | 1 530 | > | C (Vorlauf mit `lastDust = 40`, `lastTrailX = 40 000`) | ja | **40** – unverändert | **0** | 40 000 | > > Lauf B ist der Normalfall: jeder zweite, dritte, … Lauf beginnt ohne Tintenschweif und ohne > Landungsrückmeldung. Lauf C ist der Fall „nach einem guten Lauf“: der Tintenschweif erscheint > überhaupt nicht mehr (die Strecke des Vorlaufs wird nie erreicht), Staub und Werusch schweigen > solange, wie der neue Lauf unter der Laufzeit des Vorlaufs bleibt – bei einem 40-Sekunden-Vorlauf > also praktisch den ganzen neuen Lauf. > > Der Tintenschweif ist zusätzlich an `!S.settings.reduced` hängt; in der Standardeinstellung > (ohne reduzierte Bewegung) ist er genau das visuelle Feedback, das hier fehlt. > > ## Auswirkung > > * Der Tintenschweif – das einzige Bewegungsfeedback an der Figur selbst – fehlt am Anfang jedes > weiteren Laufs bzw. (nach einem langen Lauf) komplett. Ein „das Spiel ist nach dem Neustart > totaler“ Eindruck entsteht, ohne dass etwas kaputtgeht. > * Landungen sind im zweiten und jedem weiteren Lauf stumm und ohne Staub: `AUDIO.land()`, > Staubpartikel und die Landungsrückmeldung fallen aus, obwohl `ev.landed` feuert. > * Beim Pendeln fehlt der Werusch (`AUDIO.swing`), solange `S.runTime < S.whooshT`. > * Schwer zu finden, weil der erste Lauf nach dem Seitenaufruf immer korrekt aussieht – > sämtliche bisherigen Suiten messen genau diesen ersten Lauf. > > ## Lösungsvorschlag > > 1. In `startRun()` ergänzen: `S.lastDust = 0; S.whooshT = 0; S.lastTrailX = 0;` > (direkt neben `S.trail.length = 0`), zusätzlich `S.hookHint = null`. > 2. Robuster und damit absicherbar: die Marker relativ zur Laufzeit führen > (`if (S.runTime < S.lastDust) S.lastDust = 0;`) oder in `resetWorld()` zurücksetzen, > damit jeder Weg über `resetWorld(seed)` dieselche saubere Ausgangslage bekommt – > `resetWorld` ist die Stelle, die `S.trail` ohnehin leert. > 3. Präventiv: Beim Einführen neuer `S.last*`-Marker gehört der Reset in `startRun()` – > ein Test, der alle `S.last*`-Felder gegen eine Whitelist prüft, hält das offen. > > ## Tests > > * Deterministisch (`tests/issue19.mjs` über `PULSE.update(1/120)` bzw. CORE-Harness): > zwei Läufe direkt hintereinander, jeweils ein Sprung. Schranke: `S.lastDust` ist nach der > Landung in Lauf 2 **> 0** und kleiner als `S.runTime`; `S.trail.length > 0` nach 300 px > Strecke. Gegen die Auslieferung rot: `lastDust` unverändert 0,7417, `trail.length` 0. > * Browser: Screenshots des frühen Laufabschnitts (erste 400 px) aus Lauf 1 und Lauf 2 > vergleichen – der Schweif muss in beiden zu sehen sein (`prefers-reduced-motion` aus). > * Nachlauf: `tests/browser_issue7.py` (Stillstand) und `tests/browser_flow.py` dürfen > weiterhin grün sein; `soak.mjs` ist von den Markern unberührt. > > --- > > *Dokumentiert in `issues/19-laufzeitmarker-beim-neustart-nicht-zurueckgesetzt.md` (Commit `42f3cce`).* ⁨0⁩ comments
Author
Owner

Behoben in 45cff20 („Neustart setzt die Laufzeit-Marker zurück: Staub, Werusch und Schweif leben wieder (#19)“).

Was geändert wurde: resetWorld() setzt jetzt S.lastDust, S.whooshT, S.lastTrailX und S.hookHint zurück, direkt neben S.trail.length = 0. resetWorld ist die Stelle, die jeder Weg in einen frischen Lauf passiert (startRun() wie boot()), damit bekommt jede Route dieselbe saubere Ausgangslage – nicht nur die über startRun(). Die drei Marker vergleichen gegen Werte desselben Laufs (lastDust/whooshT gegen S.runTime, lastTrailX gegen body.x); ohne Reset blieben die Werte des Vorläufers stehen und der neue Lauf schweigte, bis er sie überholt hatte. Die Demo ist unberührt (simStepDemo nutzt die Marker nicht), der boot()-Aufruf resetWorld(12345) setzt sie auf die Werte aus dem S-Literal stehend zurück.

Was der Test abdeckt:

  • tests/issue19.mjs (31 Prüfungen, gegen die Auslieferung 20 rot) – resetWorld/startRun/simStep per Klammerzähler aus dem Dokument, geschritten mit echtem CORE.stepBody. Statisch: jedes S.last*-Feld und die vier Marker müssen in resetWorld/startRun zugewiesen werden (Whitelist leer) – ein künftiger Marker ohne Reset lässt die Suite rot. Verhalten: zwei Läufe direkt hintereinander (je Tipp-Sprung) und der Befund-Vorlauf mit 40 s / 40 000 px – nach dem Neustart gilt lastDust/whooshT ≤ runTime, lastTrailX ≤ body.x, hookHint null; die Landung in Lauf 2 löst Staub + AUDIO.land() aus, der Schweif malt nach 300 px, der Werusch kommt am Seil (echter attachHook, Seil klemmt auf K.ROPE_MAX, keine assistierte Landung).
  • tests/browser_issue19.py (16 Prüfungen, vorher 12 rot) – derselbe Vertrag im echten Spiel mit echter Eingabe über INPUT.press (rAF per init-script tot, Schritt 1/120). Dazu der Pixelnachweis des Befunds: Lauf 1 und Lauf 2 werden an derselben Stelle gerendert (flache Welt mit fester Segment-id, Generator angehalten, Uhren angepinnt) – der Schweif muss in beiden gemalt sein; gegen die Auslieferung differierte das Schweif-Band um 478 Pixel, jetzt 0.

Verifikation: beide Suiten waren gegen main @ b8222b9 rot und sind nach dem Fix grün; ./tests/run-all.sh komplett grün (16 Node-Suiten + Soak + 13 Browser-Suiten). Die dokumentierte Bildraten-Unschärfe von browser_flow.py trat wechselnd auf alter und neuer Fassung auf (dy 20,9/24,4 gegen 20,6/23,4 px Schranke) und ist nicht von dieser Änderung verursacht. CI-Lauf 8 (Push auf main): beide Jobs success, Logs enden mit „alle Suiten grün“.

Behoben in `45cff20` („Neustart setzt die Laufzeit-Marker zurück: Staub, Werusch und Schweif leben wieder (#19)“). **Was geändert wurde:** `resetWorld()` setzt jetzt `S.lastDust`, `S.whooshT`, `S.lastTrailX` und `S.hookHint` zurück, direkt neben `S.trail.length = 0`. `resetWorld` ist die Stelle, die jeder Weg in einen frischen Lauf passiert (`startRun()` wie `boot()`), damit bekommt jede Route dieselbe saubere Ausgangslage – nicht nur die über `startRun()`. Die drei Marker vergleichen gegen Werte desselben Laufs (`lastDust`/`whooshT` gegen `S.runTime`, `lastTrailX` gegen `body.x`); ohne Reset blieben die Werte des Vorläufers stehen und der neue Lauf schweigte, bis er sie überholt hatte. Die Demo ist unberührt (`simStepDemo` nutzt die Marker nicht), der `boot()`-Aufruf `resetWorld(12345)` setzt sie auf die Werte aus dem `S`-Literal stehend zurück. **Was der Test abdeckt:** * `tests/issue19.mjs` (31 Prüfungen, gegen die Auslieferung 20 rot) – `resetWorld`/`startRun`/`simStep` per Klammerzähler aus dem Dokument, geschritten mit echtem `CORE.stepBody`. Statisch: jedes `S.last*`-Feld und die vier Marker müssen in `resetWorld`/`startRun` zugewiesen werden (Whitelist leer) – ein künftiger Marker ohne Reset lässt die Suite rot. Verhalten: zwei Läufe direkt hintereinander (je Tipp-Sprung) und der Befund-Vorlauf mit 40 s / 40 000 px – nach dem Neustart gilt `lastDust`/`whooshT` ≤ `runTime`, `lastTrailX` ≤ `body.x`, `hookHint` null; die Landung in Lauf 2 löst Staub + `AUDIO.land()` aus, der Schweif malt nach 300 px, der Werusch kommt am Seil (echter `attachHook`, Seil klemmt auf `K.ROPE_MAX`, keine assistierte Landung). * `tests/browser_issue19.py` (16 Prüfungen, vorher 12 rot) – derselbe Vertrag im echten Spiel mit echter Eingabe über `INPUT.press` (rAF per init-script tot, Schritt 1/120). Dazu der Pixelnachweis des Befunds: Lauf 1 und Lauf 2 werden an derselben Stelle gerendert (flache Welt mit fester Segment-id, Generator angehalten, Uhren angepinnt) – der Schweif muss in beiden gemalt sein; gegen die Auslieferung differierte das Schweif-Band um 478 Pixel, jetzt 0. **Verifikation:** beide Suiten waren gegen `main` @ `b8222b9` rot und sind nach dem Fix grün; `./tests/run-all.sh` komplett grün (16 Node-Suiten + Soak + 13 Browser-Suiten). Die dokumentierte Bildraten-Unschärfe von `browser_flow.py` trat wechselnd auf alter und neuer Fassung auf (dy 20,9/24,4 gegen 20,6/23,4 px Schranke) und ist nicht von dieser Änderung verursacht. CI-Lauf 8 (Push auf `main`): beide Jobs success, Logs enden mit „alle Suiten grün“.
gilli closed this issue 2026-09-04 01:59:34 +02:00
Sign in to join this conversation.
No description provided.