Make the smooth-filter check unable to cover for the hard one

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.
This commit is contained in:
root committed 2026-09-09 15:55:28 +02:00
1 parent 88ec367ab0
commit 75be133374
3 files changed
+83 -25

No files matched your search

+63
View File
@@ -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).
-14
View File
@@ -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)?
+20 -11
View File
@@ -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));