From 75be13337420c1d7c8a49b25e10ffff97a1d6a3e Mon Sep 17 00:00:00 2001 From: root Date: Wed, 9 Sep 2026 15:55:28 +0200 Subject: [PATCH] Make the smooth-filter check unable to cover for the hard one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pixel-art check falls over sporadically, and its follow-up hid it. That one only asked whether some pixel across the border is blended – but in the failing case the `nearest` reading is already blurred, so the `linear` one is too, and the check passed while saying nothing about whether the switch had any effect at all. It now also demands that the picture actually changed against the `nearest` reading: the summed colour distance over the five sample points must exceed 150. Measured on the readings recorded in the note, a working switch drifts 425 and a hung one 0 – and 30 even if every channel were off by two, so the threshold is not brittle against noise. Exact equality was the first draft and would have gone green on a single wobbling bit. Both checks now carry `__gm.textureFilter(file)` in their message, so a failure says by itself whether the model never switched, the switch missed the GPU sampler, or the pixels were measured wrong. The cause of the sporadic failure is still unknown; four hypotheses are measured and ruled out in the note, which is filed with that stated plainly. --- todo-notes/done/flaky-pixel-art-filter.md | 63 +++++++++++++++++++++++ todo-notes/flaky-pixel-art-filter.md | 14 ----- tools/e2e/12-textures.mjs | 31 +++++++---- 3 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 todo-notes/done/flaky-pixel-art-filter.md delete mode 100644 todo-notes/flaky-pixel-art-filter.md diff --git a/todo-notes/done/flaky-pixel-art-filter.md b/todo-notes/done/flaky-pixel-art-filter.md new file mode 100644 index 00000000..4a83f080 --- /dev/null +++ b/todo-notes/done/flaky-pixel-art-filter.md @@ -0,0 +1,63 @@ +Der E2E-Check `pixel-art filtering keeps the border between two quadrants hard` faellt +**sporadisch** um — am 2026-09-09 zweimal in vier Laeufen, jeweils ohne dass eine Aenderung +Texturen beruehrt haette. + +- gruen: `["#e63c3c","#e63c3c","#3cc85a","#3cc85a","#3cc85a"]` — harte Kante, `nearest` greift +- rot: `["#c65641","#a67047","#868a4c","#66a552","#3cc85a"]` — weicher Verlauf, also `linear` + +## Erledigt: der Folge-Check deckt nichts mehr zu + +Der zweite Check (`switching the filter back to smooth …`) verglich nur `soft.some(c => !pure(c))` +und meldete damit auch dann gruen, wenn **beide** Filter denselben Verlauf zeigten — ein haengender +Wechsel liess genau einen Check fallen, der zweite verdeckte ihn. + +Er verlangt jetzt zusaetzlich, dass sich das Bild gegenueber `nearest` messbar geaendert hat +(Summe der Farbabstaende ueber die fuenf Punkte > 150). An den oben aufgezeichneten Reihen +gerechnet: echter Wechsel 425, haengender 0 — und 30 selbst dann, wenn jeder Kanal um zwei +danebenlaege. Beide Checks legen ausserdem `__gm.textureFilter(datei)` in ihren Fehlertext. + +## Das Messgeraet + +`__gm.textureFilter(datei)` liest **beides**: was das Modell will (`scaleMode`) und was der Treiber +tatsaechlich weiss (`gl.getTexParameter` an der hochgeladenen Textur). Ein FAIL beantwortet damit +von selbst, was bisher Deutungssache war: + +| Messung | Bedeutung | +|---|---| +| `scaleMode` linear, Bild weich | das Modell hat nie umgeschaltet | +| `scaleMode` nearest, `gpu` linear | der Wechsel erreichte den Sampler nicht — Produktfehler | +| beide nearest, Bild trotzdem weich | der Test misst falsch | + +Es bindet die Source ueber `renderer.texture.bindSource` statt per rohem `bindTexture` — sonst +verfaelscht die Messung Pixis Vorstellung davon, was gebunden ist, und stiftet selbst Flakiness. + +## Offen: die Ursache + +Vier Hypothesen sind gemessen und **widerlegt** — nicht erneut probieren: + +| Hypothese | Messung | Ergebnis | +|---|---|---| +| Der Wechsel erreicht den GPU-Sampler nicht | 30 Wechsel, Modell und `gl.getTexParameter` gelesen | 60/60 einig | +| Der Vorlauf der elf Module hinterlaesst etwas | 15 Runden nach echtem Vorlauf | alle gruen | +| Verlorener GPU-Prozess (Chrome meldete `exit_code=34`) | Kontext gezielt verloren und wiederhergestellt | `nearest` ueberlebt beides, auch mit Redraw | +| Genau **ein** Wechsel auf frischer Seite, wie im Test | 12 Runden, je neuer Seed und Seitenaufbau | alle gruen | + +Dass in ueber 60 gezielten Versuchen nichts faellt, waehrend der echte Lauf in ein bis zwei von +vier Laeufen umfaellt, ist der eigentliche Widerspruch. + +Naechste Spur bleibt die Umgebung: Chrome meldet beim Start auf der Windows-Maschine +`GPU process exited unexpectedly: exit_code=34`. Wie oft das passiert, ist allerdings **unbekannt** — +`harness.mjs` gibt die Browser-stderr nur bei einer Exception oder bei `RUN DIED` aus, in gruenen +Laeufen also nie. Solange das so ist, laesst sich gar nicht messen, ob der Absturz mit dem +Fehlschlag korreliert. Wer hier weitermacht, protokolliert die stderr zuerst bei jedem Lauf. + +Die Sonden dazu waren Wegwerfware und sind geloescht. Wer weitermacht, baut sie neu — beide +waren duenn: die eine fuhr den echten Modul-Vorlauf und danach eine Messschleife aus +Filterwechsel + `__gm.textureFilter`, die andere erzwang ueber `WEBGL_lose_context` einen +Kontextverlust und mass danach dasselbe. + +## Warum diese Notiz trotz offener Ursache in `done/` liegt + +Abgelegt am 2026-09-09 auf Anweisung. **Erledigt ist der Testmangel**, den die Notiz beschrieb, +plus das Messgeraet — die **Ursache der Sporadik nicht**. Faellt der Check wieder um, ist das hier +der Stand: vier widerlegte Hypothesen und die einzige offene Spur (stderr zuerst protokollieren). diff --git a/todo-notes/flaky-pixel-art-filter.md b/todo-notes/flaky-pixel-art-filter.md deleted file mode 100644 index f4feee72..00000000 --- a/todo-notes/flaky-pixel-art-filter.md +++ /dev/null @@ -1,14 +0,0 @@ -Der E2E-Check `pixel-art filtering keeps the border between two quadrants hard` faellt -**sporadisch** um — heute (2026-09-09) zweimal in vier Laeufen, jeweils ohne dass eine Aenderung -Texturen beruehrt haette. - -- gruen: `["#e63c3c","#e63c3c","#3cc85a","#3cc85a","#3cc85a"]` — harte Kante, `nearest` greift -- rot: `["#c65641","#a67047","#868a4c","#66a552","#3cc85a"]` — weicher Verlauf, also `linear` - -Der Folge-Check (`switching the filter back to smooth …`) meldet im Fehlerfall fuer **beide** -Filter denselben Verlauf, ist aber trotzdem gruen — er vergleicht nicht gegen den Erwartungswert. -Das heisst: wenn der Filterwechsel haengt, faellt genau ein Check, und der zweite deckt es zu. - -Zu klaeren: laedt der Test die Textur zu frueh aus (Timing), oder wird `scaleMode` beim ersten -Setzen manchmal nicht auf die schon hochgeladene GPU-Textur durchgereicht (dann ist es ein echter -Produktfehler und kein Testproblem)? diff --git a/tools/e2e/12-textures.mjs b/tools/e2e/12-textures.mjs index d785b640..70fb0342 100644 --- a/tools/e2e/12-textures.mjs +++ b/tools/e2e/12-textures.mjs @@ -162,28 +162,37 @@ export async function textures(t) { } return points; }; - const setFilter = (value) => - mutate( + // Read the pixels first, then ask the driver what it actually has: a failure then says by + // itself whether the model never switched, the switch missed the GPU sampler, or the pixels + // were measured wrong. This check has fallen over sporadically without any of the three ever + // being distinguishable from the message. + const sample = async (value) => { + await mutate( `__gm.state.project.assets.find((a) => a.id === ${JSON.stringify(texture.id)}).props.filter = ${JSON.stringify(value)}`, ); + await sleep(600); + const pixels = await acrossBorder(); + return { pixels, filter: await json(`__gm.textureFilter(${JSON.stringify(texture.file)})`) }; + }; - await setFilter('nearest'); - await sleep(600); - const hard = await acrossBorder(); + const hard = await sample('nearest'); const pure = (c) => colourDistance(c, '#e63c3c') < 60 || colourDistance(c, '#3cc85a') < 60; check( 'pixel-art filtering keeps the border between two quadrants hard', - hard.every(pure), + hard.pixels.every(pure), JSON.stringify(hard), ); - await setFilter('linear'); - await sleep(600); - const soft = await acrossBorder(); + const soft = await sample('linear'); + // The drift — that the picture actually changed — is what makes this a test of the switch. + // Were the switch to hang, both readings would be the blurred one, and "some pixel is blended" + // alone would pass and hide the failure of the check above. Measured on the recorded readings: + // a working switch drifts 425, a hung one 0, and 30 even if every channel were off by two. + const drift = hard.pixels.reduce((sum, c, i) => sum + colourDistance(c, soft.pixels[i]), 0); check( 'switching the filter back to smooth blends the border without a reload', - soft.some((c) => !pure(c)), - JSON.stringify({ nearest: hard, linear: soft }), + soft.pixels.some((c) => !pure(c)) && drift > 150, + JSON.stringify({ drift, nearest: hard, linear: soft }), ); await shot('33-texture-filter-smooth'); await mutate(enlarge(160, 120));