Gemessen: 165 Pruefdateien, 129 verschiedene Nummern -- NEUNZEHN
doppelt, vier davon dreifach. Niemand hatte das gewollt; jede neue
Pruefung wurde von einer vorhandenen abgeschrieben, und die Nummer kam
mit. Meine eigene Notiz sagte "sieben" -- auch eine Bestandsliste
altert.
Der Waechter faengt den Schaden ab, aber er kann nur melden, was schon
passiert ist: Zwei Pruefungen mit derselben Nummer koennen nie
gleichzeitig laufen, und ein liegengebliebener Prozess der einen laesst
die andere abbrechen mit einer Meldung, die wie ein Befund aussieht.
Genau das ist mir am 20.09. zweimal passiert.
Jetzt leitet jede Datei ihre Nummer aus ihrer STELLE IM ALPHABET ab
(eigenerPort in helfer-port.mjs), zwei je Datei. Nicht ueber eine
Pruefsumme: Bei 165 Namen in 4900 Nummern waeren nach dem
Geburtstagsproblem rund DREI Zusammenstoesse zu erwarten -- ein Hash
tauscht eine sichtbare Doppelung gegen eine unsichtbare. Die Stelle im
Alphabet ist eindeutig von der Bauart her.
--- ZWEI FEHLER AUF DEM WEG, BEIDE LEHRREICH -------------------------
1. DER ERSTE VERSUCH WAR GRUEN UND KAPUTT. Ersetzt wurde mit einem
Muster: "([^"]*4231[^"]*)". Das hielt
{ host: "127.0.0.1", port: 4231, path: "/404.html" }
fuer eine Zeichenkette -- ein Muster kann eine oeffnende nicht von
einer schliessenden Anfuehrung unterscheiden. Heraus kam
{ host: "127.0.0.1`, port: ${PORT}, path: `/404.html" }
also GUELTIGER Code ohne port-Feld. `node --check` sagte gruen fuer
alle 164 Dateien. Aufgefallen ist es erst, weil ich vier Vertreter
gegen eine vorher gemessene Grundlinie laufen liess: pruef-schranke
39/0 vorher, 38/1 nachher.
Alles zurueckgenommen und mit einem Zerleger neu gemacht, der weiss,
ob eine Stelle Code, Zeichenkette, Vorlage, Kommentar oder
regulaerer Ausdruck ist. In pruef-ics stand die Nummer in einem
regulaeren Ausdruck -- der wird jetzt gebaut statt hingeschrieben.
2. EIN MODUL, DAS BEIM IMPORTIEREN ARBEITET, IST EINE FALLE. Der
zweite Durchgang importierte den ersten, um seine Mechanik zu
benutzen -- und fuehrte dessen Hauptlauf gleich mit aus. Die
zweiten Nummern wurden dadurch als erste behandelt, zwei Aufrufe
bekamen dieselbe Nummer, und in pruef-content stand `const PORT`
zweimal.
--- WAS DAS DAUERHAFT HAELT -----------------------------------------
pruef-portnummern.mjs (neu, 8 Pruefungen) fragt nicht "welche Nummern
sind doppelt", sondern "wer traegt ueberhaupt noch eine von Hand ein"
und "wer startet einen Server, ohne seine Nummer abzuleiten". Die
zweite Frage hat sofort etwas gefunden, das in KEINER Doppelungsliste
stand: pruef-push-weg belegte 4341 und 4342, rief den Waechter aber
gar nicht auf -- dieselbe Nummer wie pruef-agentur. Eine Liste zeigt
nur, was auf ihr steht.
Mit Gegenprobe: Eine unbekannte Datei bekommt keine geratene Nummer,
sondern einen Abbruch, und eine dritte Nummer je Datei gibt es nicht.
--- NACHGEMESSEN ----------------------------------------------------
Sechzehn Pruefungen gegen ihre vorher gemessene Grundlinie, je eine
Vertreterin jeder umgestellten Bauweise (eine Nummer, zwei Nummern,
Nummer in einer Zeichenkette, in einer Vorlage, in einem regulaeren
Ausdruck, dynamische Einfuhr, Nachtlauf mit zwei Laeufen):
crew-adresse 132/0 · schranke 39/0 · content 45/0 · entwicklung 46/0
anruf 127/0 · ics 37/0 · push-weg 20/0 · arten 28/0 · video 67/0
kanalzeile 14/0 · agentur 62/0 · spicy 83/0 · creator-anlegen 50/0
push-ziel 10/0 · portnummern 8/0
Alle exakt wie vorher. Zwei waren schon vorher rot und sind es
unveraendert geblieben (crew-wand-bild 41/4, teilen 14/3) -- per
`git stash` belegt, nicht angenommen.
Co-Authored-By: Claude Opus 5 <[email protected]>
357 lines
16 KiB
JavaScript
357 lines
16 KiB
JavaScript
/* =====================================================================
|
|
DIE KACHELN: FARBEN UND UNIVERSUM (17.09.2026)
|
|
|
|
Filipe, zwei Wünsche in einem Satz:
|
|
|
|
„ich will das jede kachel eine andere farbe hat, es soll keine die
|
|
gleiche farben haben bitte und auch keine die sich irgendwie
|
|
ähnlich sind ... die farben sollen auch richtig geil und speziell
|
|
sein."
|
|
|
|
„der hintergrund von allen kacheln haben wir richtung universum
|
|
mäßig gemacht, aber bin noch nicht ganz zufrieden ... nicht
|
|
einfach so paar weiße punkte sondern richtig geil hochwertiger
|
|
geiler universum als hintergrund in den kacheln überall."
|
|
|
|
BEIDES WAR BERECHTIGT, und beides ließ sich messen:
|
|
|
|
kleinster Farbabstand 0,0529 (Rückmeldung / Steckbrief)
|
|
Paare unter 0,10 26 von 666
|
|
schlechtester Kontrast 1,64:1 (Ton 35 — unlesbar)
|
|
Universum auf GENAU EINER Kachel
|
|
|
|
Der Grund für das erste: Ein Werkzeug rechnete die Farben, kannte
|
|
aber nur 21 — seit dem 08.09. waren sechzehn von Hand dazugekommen.
|
|
Es meldete weiter „alle Bedingungen erfüllt" und meinte einen Stand,
|
|
den es nicht mehr gab.
|
|
|
|
---------------------------------------------------------------------
|
|
WAS HIER GEPRÜFT WIRD — und warum es drei Abschnitte braucht:
|
|
|
|
1. Die Farben stehen weit genug auseinander (OKLab, alle Paare).
|
|
2. Jede hält 4,5:1 gegen den dunklen Grund — sie trägt auch Text.
|
|
3. DER TEXT AUF DER FERTIGEN KACHEL ist lesbar.
|
|
|
|
Der dritte ist der, den sonst niemand macht, und der einzige, der
|
|
die Frage wirklich beantwortet: Zwischen Textfarbe und `--flaeche`
|
|
liegen inzwischen SIEBEN Ebenen (drei Nebel, Sterne, Milchstraße,
|
|
Vignette, Glanz) plus das durchscheinende Bühnenbild. Aus dem CSS
|
|
lässt sich das nicht mehr ausrechnen — gemessen wird deshalb am
|
|
Bildschirmfoto, an den echten Bildpunkten.
|
|
|
|
Aufrufen mit: node server/pruef-kachel-universum.mjs
|
|
===================================================================== */
|
|
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { eigenerPort } from "./helfer-port.mjs";
|
|
import { pngLesen, pixel, kontrast } from "./helfer-png.mjs";
|
|
|
|
const HIER = dirname(fileURLToPath(import.meta.url));
|
|
const CSS = join(HIER, "..", "workspace", "assets", "css", "start.css");
|
|
const GRUND = [10, 18, 30]; // #0a121e
|
|
|
|
let fehler = 0, geprueft = 0, offen = 0;
|
|
const melde = (t) => console.log(t);
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
const unklar = (t) => { offen++; console.log(" -- " + t); };
|
|
|
|
/* ---------- OKLab ---------------------------------------------------- */
|
|
const linear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
|
|
const srgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);
|
|
function oklab(hex) {
|
|
const [r, g, b] = srgb(hex).map(linear);
|
|
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
return [
|
|
0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
|
|
1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
|
|
0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
|
|
];
|
|
}
|
|
const abstand = (a, b) => {
|
|
const x = oklab(a), y = oklab(b);
|
|
return Math.hypot(x[0] - y[0], x[1] - y[1], x[2] - y[2]);
|
|
};
|
|
const alsRgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
|
|
/* =======================================================================
|
|
1. DIE FARBEN STEHEN AUSEINANDER
|
|
======================================================================= */
|
|
melde("=== 1. Die Farben ===");
|
|
|
|
const cssText = readFileSync(CSS, "utf8");
|
|
const TOENE = new Map(
|
|
[...cssText.matchAll(/\[data-ton="(\d+)"\]\s*\{\s*--ton:\s*(#[0-9a-fA-F]{6})/g)]
|
|
.map((m) => [Number(m[1]), m[2].toLowerCase()]));
|
|
|
|
/* DIE ANZAHL KOMMT AUS DER DATEI. Genau hier ist das Farbwerkzeug
|
|
unzuständig geworden: Es kannte 21, und niemand hat es angefasst,
|
|
als sechzehn dazukamen. Eine Zahl, die niemand pflegt, kann nicht
|
|
veralten. */
|
|
ok(TOENE.size >= 20, `${TOENE.size} Farben in start.css gefunden`);
|
|
|
|
/* Werden alle auch benutzt? Eine Farbe ohne Kachel ist kein Fehler,
|
|
aber eine KACHEL OHNE FARBE ist einer -- sie fiele auf den Akzent
|
|
zurück, und dann hätten mehrere Kacheln dieselbe. */
|
|
{
|
|
const quellen = ["workspace.js", "../workspace/assets/js/bereiche.js",
|
|
"../workspace/assets/js/start.js", "../workspace/assets/js/kopf.js",
|
|
"../workspace/assets/js/befinden.js"];
|
|
const benutzt = new Set();
|
|
for (const q of quellen) {
|
|
const t = readFileSync(join(HIER, q), "utf8");
|
|
for (const m of t.matchAll(/ton:\s*(\d+)/g)) benutzt.add(Number(m[1]));
|
|
}
|
|
const ohne = [...benutzt].filter((n) => !TOENE.has(n));
|
|
ok(ohne.length === 0,
|
|
ohne.length ? `Kacheln ohne Farbe: Ton ${ohne.join(", ")}`
|
|
: `alle ${benutzt.size} benutzten Tonnummern haben eine Farbe`);
|
|
}
|
|
|
|
const liste = [...TOENE.entries()].map(([n, hex]) => ({ n, hex }));
|
|
const paare = [];
|
|
for (let i = 0; i < liste.length; i++) {
|
|
for (let j = i + 1; j < liste.length; j++) {
|
|
paare.push({ a: liste[i], b: liste[j], d: abstand(liste[i].hex, liste[j].hex) });
|
|
}
|
|
}
|
|
paare.sort((x, y) => x.d - y.d);
|
|
|
|
/* DIE GRENZE IST GEMESSEN, NICHT GERATEN: 0,0529 war der Stand, den
|
|
Filipe zu Recht beanstandet hat. Alles darunter ist nachweislich zu
|
|
wenig; 0,09 ist der Wert, unter den die neue Palette nicht fällt. */
|
|
const GRENZE = 0.09;
|
|
ok(paare[0].d >= GRENZE,
|
|
`kleinster Abstand ${paare[0].d.toFixed(4)} (Grenze ${GRENZE}) `
|
|
+ `-- ${paare[0].a.hex}/${paare[0].b.hex}`);
|
|
{
|
|
const eng = paare.filter((p) => p.d < 0.10).length;
|
|
ok(eng === 0,
|
|
eng ? `${eng} Paare unter 0,10: ${paare.slice(0, 3).map((p) => `${p.a.hex}/${p.b.hex}`).join(", ")}`
|
|
: `kein einziges der ${paare.length} Paare liegt unter 0,10`);
|
|
}
|
|
{
|
|
/* KEINE ZWEI GLEICHEN. Das steht als eigene Zeile da, obwohl der
|
|
Abstand es mit abdeckt -- es ist die wörtliche Bitte, und eine
|
|
Prüfung, die die Frage direkt beantwortet, liest sich später
|
|
besser als eine, aus der man es schließen muss. */
|
|
const doppelt = liste.length - new Set(liste.map((x) => x.hex)).size;
|
|
ok(doppelt === 0, doppelt ? `${doppelt} Farben kommen doppelt vor` : "keine Farbe kommt zweimal vor");
|
|
}
|
|
|
|
/* Jede Farbe trägt Text -- sie muss gegen den dunklen Grund halten. */
|
|
{
|
|
const schlecht = liste.filter((x) => kontrast(alsRgb(x.hex), GRUND) < 4.5);
|
|
ok(schlecht.length === 0,
|
|
schlecht.length
|
|
? `unter 4,5:1 -> ${schlecht.map((x) => `Ton ${x.n} ${x.hex} `
|
|
+ `${kontrast(alsRgb(x.hex), GRUND).toFixed(2)}:1`).join(", ")}`
|
|
: `alle halten 4,5:1 gegen den Grund (schlechteste `
|
|
+ `${Math.min(...liste.map((x) => kontrast(alsRgb(x.hex), GRUND))).toFixed(2)}:1)`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. DAS UNIVERSUM IST DA -- UND ÜBERALL
|
|
|
|
Gezählt wird im CSS: Wie viele Ebenen hat der Kachelhintergrund? Ein
|
|
Universum aus „ein paar weißen Punkten" hat drei, das gebaute hat
|
|
siebzehn. Die Zahl allein sagt nichts über Schönheit -- sie sagt,
|
|
dass die Ebenen NICHT VERSEHENTLICH verschwinden, und genau das ist
|
|
schon einmal passiert (die Leuchtschiene, 07.09.: Regeln blieben,
|
|
Inhalt weg).
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Das Universum im Hintergrund ===");
|
|
{
|
|
const block = cssText.slice(cssText.indexOf(".kachel {"),
|
|
cssText.indexOf("background-size:", cssText.indexOf(".kachel {")));
|
|
/* DIE GROESSEN, NICHT NUR DIE ANZAHL.
|
|
|
|
Der erste Anlauf zaehlte `radial-gradient(...px` -- und `0px` ist
|
|
auch eine Zahl. Setzt man alle Sterne auf null, blieb die Zaehlung
|
|
bei zwoelf und die Pruefung gruen, waehrend das Universum leer war.
|
|
|
|
UND WARUM NICHT AM BILD GEMESSEN WIRD: Der zweite Anlauf zaehlte
|
|
Lichtpunkte im Bildschirmfoto. Auch das trug nicht -- die Kachel
|
|
ist halbdurchsichtig, darunter liegt das Buehnenbild, und ein Foto
|
|
hat selbst Punktstruktur. Gemessen: 459 Punkte mit Sternen, 441
|
|
ohne. Vier Prozent Unterschied sind kein Nachweis, sondern
|
|
Rauschen. Eine Pruefung, die das als "bestanden" wertet, prueft
|
|
nichts -- lieber eine ehrliche schwache als eine starke, die luegt. */
|
|
const groessen = [...block.matchAll(/radial-gradient\(\s*([\d.]+)px/g)]
|
|
.map((m) => Number(m[1]));
|
|
const sterne = groessen.filter((g) => g >= 0.7).length;
|
|
const nebel = (block.match(/radial-gradient\(\s*\d+%/g) || []).length;
|
|
ok(sterne >= 10,
|
|
`${sterne} Sternebenen mit sichtbarer Groesse `
|
|
+ `(${groessen.length} Punktebenen insgesamt, kleinste `
|
|
+ `${Math.min(...groessen).toFixed(1)}px)`);
|
|
ok(nebel >= 3, `${nebel} Nebel- und Tiefenebenen`);
|
|
{
|
|
/* GEZAEHLT, NICHT NUR GESUCHT. Der erste Anlauf fragte, ob
|
|
`var(--ton)` im Block VORKOMMT -- das tut es auch im
|
|
Grundverlauf, der seit jeher da ist. Die Gegenprobe (einem Nebel
|
|
die Kachelfarbe wegnehmen) blieb deshalb gruen. Gezaehlt werden
|
|
jetzt die FLAECHIGEN Verlaeufe, die den Ton wirklich tragen. */
|
|
const nebelMitTon = (block.match(
|
|
/radial-gradient\(\s*\d+%[^)]*\)[^,]*var\(--ton\)|radial-gradient\(\s*\d+%[^;]*?color-mix\(in srgb, var\(--ton\)/g) || []).length;
|
|
ok(nebelMitTon >= 2,
|
|
`${nebelMitTon} Nebel tragen die Farbe der Kachel `
|
|
+ "-- jede Kachel ihr eigenes Universum");
|
|
}
|
|
ok(/118deg|milchstra/i.test(block), "und es gibt eine Schräge (die Milchstraße)");
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. DER TEXT DARAUF IST LESBAR
|
|
|
|
Der Abschnitt, den sonst niemand macht. Gemessen am Bildschirmfoto:
|
|
Zwischen Textfarbe und Fläche liegen sieben Ebenen plus das
|
|
durchscheinende Bühnenbild -- aus dem CSS ist das nicht mehr
|
|
auszurechnen.
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Der Text auf der fertigen Kachel ===");
|
|
|
|
const PORT = await eigenerPort(import.meta, "pruef-kachel-universum");
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-kachel-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
process.env.PORT = String(PORT);
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
|
|
|
|
const express = (await import("express")).default;
|
|
const ec = express.response.cookie;
|
|
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
|
|
const { notbremse } = await import("./helfer-notbremse.mjs");
|
|
await import("./index.js");
|
|
notbremse(180_000, "pruef-kachel-universum");
|
|
await new Promise((r) => setTimeout(r, 800));
|
|
|
|
{
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const salt = randomBytes(16).toString("hex");
|
|
const hash = scryptSync("CODE-DOGI-0001", salt, 64,
|
|
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
|
|
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
|
|
.run("Filipe", "admin", hash, salt, 32768, new Date().toISOString());
|
|
d.close();
|
|
}
|
|
|
|
let chromium;
|
|
try {
|
|
({ chromium } = await import(
|
|
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"));
|
|
} catch { chromium = null; }
|
|
|
|
if (!chromium) {
|
|
unklar("kein Playwright gefunden -- die Kacheln wurden nicht angesehen");
|
|
} else {
|
|
const browser = await chromium.launch();
|
|
try {
|
|
const kontext = await browser.newContext({
|
|
viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2,
|
|
});
|
|
const seite = await kontext.newPage();
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/`, { waitUntil: "domcontentloaded" });
|
|
await seite.click('.rolle[data-rolle="admin"]');
|
|
await seite.fill("#code", "CODE-DOGI-0001");
|
|
await seite.click("button[type=submit]");
|
|
await seite.waitForURL("**/start.html", { timeout: 15000 });
|
|
await seite.waitForTimeout(2500);
|
|
|
|
/* ERST ZU DEN KACHELN SCROLLEN. Beim ersten Lauf wurden NULL
|
|
gemessen: Der Filter verlangt, dass die Kachel ganz im Bild
|
|
liegt, und oben steht die Zentrale mit Uhr und Zahlen. Eine
|
|
Messung, die nichts findet, ist keine Messung -- sie sah nur
|
|
aus wie eine. */
|
|
/* In die Mitte des Kachelfeldes, nicht an seinen Anfang: Dort
|
|
stehen mehrere Reihen gleichzeitig im Bild, und je mehr Kacheln
|
|
gemessen werden, desto eher ist die schlechteste dabei. */
|
|
const anzahl = await seite.locator(".kachel").count();
|
|
await seite.locator(".kachel").nth(Math.min(6, anzahl - 1)).scrollIntoViewIfNeeded();
|
|
await seite.waitForTimeout(700);
|
|
|
|
const stellen = await seite.evaluate(() =>
|
|
[...document.querySelectorAll(".kachel")].map((k) => {
|
|
const name = k.querySelector(".kachel__name");
|
|
if (!name) return null;
|
|
const r = name.getBoundingClientRect();
|
|
/* SICHTBAR HEISST: die Zeile liegt im Bild -- nicht die ganze
|
|
Kachel. Der erste Filter verlangte letzteres und liess genau
|
|
EINE uebrig; gemessen wird aber der Punkt unter dem TEXT,
|
|
und der braucht nur sich selbst. */
|
|
if (r.width < 10 || r.top < 8 || r.bottom > window.innerHeight - 8) return null;
|
|
const [tr, tg, tb] = getComputedStyle(name).color.match(/\d+/g).map(Number);
|
|
/* DIE KACHELGRENZEN KOMMEN MIT. Ohne sie wurde der Messbereich
|
|
aus der Textposition geschaetzt (`y - 70`) -- und lag damit
|
|
OBERHALB der Kachel, im Seitenhintergrund. Die Gegenprobe
|
|
lieferte zweimal exakt 153 Punkte; genau diese Gleichheit hat
|
|
den Fehler verraten. Eine Messung, deren Ergebnis sich nicht
|
|
aendert, wenn man das Gemessene entfernt, misst etwas
|
|
anderes. */
|
|
const kb = k.getBoundingClientRect();
|
|
return {
|
|
name: name.textContent.trim(), text: [tr, tg, tb],
|
|
x: Math.round(r.left + r.width / 2), y: Math.round(r.top + r.height / 2),
|
|
kachel: {
|
|
links: Math.round(kb.left), oben: Math.round(kb.top),
|
|
breite: Math.round(kb.width), hoehe: Math.round(kb.height),
|
|
},
|
|
};
|
|
}).filter(Boolean));
|
|
|
|
/* DIE ANZAHL GEHÖRT IN DIE BEDINGUNG: Ohne sichtbare Kacheln wäre
|
|
„alle lesbar" auch dann grün, wenn keine einzige gemessen wurde. */
|
|
ok(stellen.length >= 4, `${stellen.length} Kacheln im Bild gemessen`);
|
|
|
|
/* DER DRITTE AUSGANG. Ohne diesen Sprung stuerzte der Abschnitt ab
|
|
(`werte[0].k` auf einem leeren Feld) -- und ein Absturz sagt
|
|
weniger als ein Fehlschlag: Er verschluckt die Zeilen danach und
|
|
sieht aus wie ein Problem der Pruefung statt der Sache. */
|
|
if (!stellen.length) {
|
|
ok(false, "ohne sichtbare Kachel laesst sich kein Kontrast messen");
|
|
} else {
|
|
|
|
const bild = pngLesen(await seite.screenshot({ type: "png" }));
|
|
const werte = stellen.map((s) => {
|
|
/* Mehrere Punkte je Kachel -- der Untergrund ist nicht
|
|
gleichmäßig (Nebel, Vignette, Sterne). Der SCHLECHTESTE zählt:
|
|
An der hellsten Stelle steht der Text am schwersten. */
|
|
let schlecht = Infinity;
|
|
for (const dx of [-30, -15, 0, 15, 30]) {
|
|
for (const dy of [-4, 0, 4]) {
|
|
const k = kontrast(s.text, pixel(bild, (s.x + dx) * 2, (s.y + dy) * 2));
|
|
if (k < schlecht) schlecht = k;
|
|
}
|
|
}
|
|
return { name: s.name, k: schlecht };
|
|
}).sort((a, b) => a.k - b.k);
|
|
|
|
ok(stellen.length > 0 && werte[0].k >= 4.5,
|
|
`schlechtester Textkontrast ${werte[0].k.toFixed(2)}:1 auf „${werte[0].name}" (Grenze 4,5)`);
|
|
melde(` die drei schwächsten: ${werte.slice(0, 3)
|
|
.map((w) => `${w.name} ${w.k.toFixed(1)}:1`).join(" · ")}`);
|
|
|
|
|
|
}
|
|
|
|
await seite.screenshot({ path: "server/pruef-kachel-universum.png", fullPage: false });
|
|
melde(" -- Bildschirmfoto: server/pruef-kachel-universum.png");
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
melde("");
|
|
melde(`${geprueft} Pruefungen, ${fehler} Fehler`
|
|
+ (offen ? `, ${offen} nicht nachsehbar` : ""));
|
|
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
|
process.exit(fehler ? 1 : 0);
|