Der Soak-Test prüft ein gestrichenes Feld: body.dead existiert seit #21 nicht mehr – „5 Minuten Spielzeit ohne Tod“ kann nicht mehr fehlschlagen #25
Labels
No labels
accessibility
bug
cleanup
consistency
crash
critical
game-feel
gameplay
high
input
level-generation
low
maintainability
medium
pause
physics
responsive
security
spec-gap
ui
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gilli/pulse-sprint#25
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Komponente:
tests/soak.mjs(Zeilen 123, 165),tests/issue2.mjs(169),tests/issue3.mjs(96, 124),
tests/issue4.mjs(73, 106, 215, 260); Bezug:CORE.makeBody/stepBody(
index.html689–697, 874–933),.forgejo/workflows/tests.ymlBetroffene Version:
main@86a481d,index.html(3.684 Zeilen, MD5777d18c3edbb834aa41fccc7d10a7d18)Beschreibung
Issue #21 (
e4952a0, „Flags ohne Setzer, Zustände ohne Leser“) hatbody.deadersatzlosgestrichen — konsequent, denn das Feld hatte im Produkt keinen Leser. Der Commit hat die
Test-Setzer nachgezogen (
tests/issue2/6/14/15/16/17.mjs,tests/harness.mjs) und imCommit-Message festgehalten: „die Tests lesen jetzt
ev.fellbzw.y > FALL_DEATH_Y“.tests/soak.mjswurde in diesem Commit nicht angefasst (steht nicht in der Dateiliste) undliest an zwei Stellen weiter das Feld:
makeBody(Zeilen 689–697) legt keindeadmehr an,stepBodysetzt keins (Zeile 932 ist nurnoch
if (b.y > K.FALL_DEATH_Y) ev.fell = true;).body.deadist also dauerhaftundefined—die Bedingung ist für jeden Lauf falsch. Damit ist die zentrale Aussage des Soak,
check("<n> Minuten Spielzeit ohne Tod (Solver-Strategie)", death === null, death),eine Leerprüfung: Sie kann nicht mehr rot werden, egal was die Physik macht.
Das ist kein Schönheitsfehler: Der Soak ist der blockierende Langlauf der CI und der
nächtliche 5-Minuten-Soak (
.forgejo/workflows/tests.yml, Cron) — genau das Netz unter denNode-Suiten. Und er reißt genau jetzt ein Loch: Befund #23 stirbt in diesem Modell.
Messprotokoll
1. Der Soak läuft grün über einen Lauf, der stirbt
Dieselbe Seed (
createGenerator(4242), Zeile 90), dasselbe Modell, dieselbe Schrittweite(
K.FIXED_TIMESTEP), nur mit funktionierender Tod-Erkennung(
CORE.hitTest(body, world.obstacles, t, false)oderbody.y > K.FALL_DEATH_Y):Die Zähler des Soak (
greift 35, landet 139) stimmen mit diesem Lauf exakt überein — es istderselbe Verlauf, es fehlt nur die Erkennung des Todes. (Der Soak meldet 18 628 m, weil er nach
dem Tod bei 276 s weiterläuft:
body.deadbremst die Schleife nicht ab, x wächst weiter.)2. Weitere Lesestellen desselben Feldes
tests/issue2.mjs:169check("Block übersprungen", !b.dead && b.x > 1048, {... dead: b.dead})!undefined === truetests/issue3.mjs:96,124if (b.grounded || b.dead) break;/while (t < 60 && !b.dead)tests/issue4.mjs:73,106,215,260if (b.dead) break;tests/soak.mjs:165b.dead = false;3. Was der Soak vor #21 erfasst hätte
body.deadwurde vore4952a0nur im Fall-Zweig gesetzt(
if (b.y > K.FALL_DEATH_Y) { b.dead = true; ev.fell = true; }). Kollisionen hat der Soak alsoauch vorher nicht erkannt — er hat nie
hitTestgerufen. Ein Fix, der nurev.fellnachrüstet, stellt den alten (halben) Zustand wieder her; ganz zugeschaut wurde bei Kollisionen
noch nie.
Ursache im Code
Der Regelfall des Refactorings: Ein Feld wird aus dem Produkt gekippt, die Test-Lesestellen in
einem Rutsch mitgezogen — nur eine Datei (
tests/soak.mjs) bleibt übrig, weil sie weder imCommit noch in einer Suite ist, die das Feld hart erzwingt.
undefinedist falsy, also wird auseinem „Detektiert Tod nicht mehr“ ein „Detektiert: kein Tod“. Genau die stillgrüne
Fehlerklasse, gegen die
tests/issue9.mjs(Schreib-/Lesekontrolle) sonst antritt.Folgeverhalten
.forgejo/workflows/tests.yml, Jobnode:./tests/run-all.sh "$SOAK_MINUTES"— 1 Minute bei Push, 5 Minuten beim nächtlichen Cron).Beide Jobs melden grün: Bei 1 Minute erreicht der Lauf den Tod bei 276 s noch gar nicht, bei
5 Minuten erkennt die Prüfung ihn nicht — der Befund #23 ist also in beiden Einstellungen
unsichtbar, der nächtliche Vollsoak inklusive.
./tests/run-all.shlokal undSKIP_NODE=1 ./tests/run-all.sh(Browser-Job) laufen komplettgrün — der Befund ist ausschließlich über eine eigene Messung sichtbar, nicht über eine
vorhandene Prüfung.
Nicht verletzt (bewusst abgegrenzt)
10 000 Muster, NaN-Kontrolle, Speicherbegrenzung, Schrittweiten-Band 1/100…1/144.
issue1–issue22sind davon nicht falsch-grün; ihreb.dead-Lesungen sindwirkungslose, aber keine falschen Behauptungen (Ausnahme
issue2.mjs:169, siehe Tabelle).Lösungsvorschlag
const dead = ev.fell || body.y > K.FALL_DEATH_Y || !!CORE.hitTest(body, world.obstacles, t, false);— und
b.dead = false;(Zeile 165) ersatzlos streichen.death.whybekommt dann erstmals"hit"aus einer echten Quelle.tests/issue2.mjs:169auf!hitTest(...) && b.x > 1048(bzw.
!ev.fell),issue3/issue4aufev.fell || b.y > K.FALL_DEATH_Y.tests/issue9.mjs(die Suite prüftbereits, dass gestrichene Bezeichner im Dokument 0-mal vorkommen) dieselbe Liste auch gegen
tests/*.mjslaufen lassen — ein Test, der ein gestrichenes Feld liest, ist derselbeDefekt. Günstigste Form: Der Test liest sein eigenes Verzeichnis mit.
Erwarteter Test
node tests/soak.mjs 5auf dem heutigenindex.htmlrotwird („0.5/5 Minuten Spielzeit ohne Tod“ →
{"why":"pit","t":276,...}) und erst nach dem Fixaus #23/#24 wieder grün. Genau dieser Vorher/Nachher-Beweis ist die Erwartung; ohne ihn bleibt
der Soak eine Leerprüfung.
tests/issue9.mjszusätzlich mit der Gegenprüfung: keiner der gestrichenen Bezeichner(
body.dead,S.paused,pit.double, …) kommt intests/noch als Lesezugriff vor.Dokumentiert in
issues/25-soak-todestest-prueft-gestrichenes-feld-body-dead.md(Commit86a481d).Behoben in
fcc770d(Dokubfe6b65). Der Soak meldet den Tod wieder aus echten Quellen: tests/soak.mjs prüft ev.fell || CORE.hitTest(body, world.obstacles, t, false) und stoppt die Schleife am Tod (meldet damit erstmals "hit" aus echter Quelle statt 24 s weiterzulaufen); der tote Schreiber b.dead = false in der Schrittweiten-Schleife ist ersatzlos weg. Nebenbefunde bereinigt: tests/issue2.mjs prüft "Block übersprungen" auf Kollisionsfreiheit echt über CORE.hitTest statt gegen !undefined; tests/issue3.mjs und tests/issue4.mjs brechen bei ev.fell bzw. y > K.FALL_DEATH_Y wieder ab. Nachziehschutz: tests/issue9.mjs (41 → 42 Prüfungen) lässt die Liste der gestrichenen Felder jetzt auch gegen tests/*.mjs laufen (Kommentare vor dem Scan entfernt, issue9.mjs selbst ausgenommen) – der Schutz fand sofort newRecord: false in den rigs von tests/issue18.mjs/tests/issue19.mjs, ebenfalls entfernt.Vorher/Nachher wie gefordert: der fixe Soak auf
86a481dist ROT – FAIL "5 Minuten Spielzeit ohne Tod" :: {"why":"pit","x":171745,"t":276,"kind":"HOOK_CHAIN"}, Zähler greift 35 / landet 139 stimmen exakt mit dem Messprotokoll überein. Auf dem Stand nach #23/#24 ist er GRÜN: 300 s, 18 585 m, 9 ok / 0 fail. Alle Node-Suiten grün; Produktcode unverändert.