Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_badge.mjs

4.0 KiB, 1 run

created by r2519314175:255, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// The maker's badge: two claims, two hit areas, and the split between them.
2//
3// The badge is one artwork carrying two separate statements -- the Oxedyne
4// flame, which links to Oxedyne, and the AI-disclosure chip, which for now only
5// says what it is. It is drawn as an <img>, so the page cannot reach inside it
6// to hang a link on a group; the hit areas are laid OVER it at a fixed 50%.
7//
8// That is a guess about someone else's artwork, and a guess that would fail
9// silently: redraw the badge with the marks off-centre and the flame's hit area
10// starts covering the chip, with nothing to say so. So the guess is checked.
11// The SVG keeps Inkscape's labels (`oxedyne`, `made_with_mostly_ai_icon`), and
12// this measures where those two groups actually are.
13import fs from 'node:fs';
14import os from 'node:os';
15import path from 'node:path';
16import { fileURLToPath, pathToFileURL } from 'node:url';
17
18const HERE = path.dirname(fileURLToPath(import.meta.url));
19const SVG = path.join(HERE, '..', 'www', 'assets', 'made_by_oxedyne.svg');
20const PW = process.env.DAIMOND_PW
21 || path.join(os.homedir(), '.red-pw/node_modules/playwright-core/index.mjs');
22const { chromium } = await import(pathToFileURL(PW).href);
23const CHROME = process.env.DAIMOND_CHROME
24 || `${process.env.HOME}/.cache/ms-playwright/chromium-1229/chrome-linux64/chrome`;
25
26let bad = 0;
27const check = (ok, what) => { console.log(`${ok ? 'PASS' : 'FAIL'} ${what}`); if (!ok) bad++; };
28
29/// Where the CSS puts the boundary. Read from the stylesheet rather than
30/// restated, so the two cannot drift apart.
31const css = fs.readFileSync(path.join(HERE, '..', 'www', 'css', 'app.css'), 'utf8');
32const m = css.match(/\.made-by \.mb-hit \{[^}]*width:\s*([\d.]+)%/);
33check(!!m, 'the stylesheet states the hit areas\' width');
34const splitPct = m ? parseFloat(m[1]) : 50;
35
36const svg = fs.readFileSync(SVG, 'utf8');
37for (const label of ['oxedyne', 'made_with_mostly_ai_icon']) {
38 check(svg.includes(`inkscape:label="${label}"`),
39 `the artwork still labels its ${label} group (a re-export that drops it would leave this check blind)`);
40}
41
42const browser = await chromium.launch({ executablePath: CHROME });
43const page = await browser.newPage({ viewport: { width: 900, height: 400 } });
44// Inlined here ONLY to measure it; the app draws it as an <img>.
45await page.setContent(`<body style="margin:0">${svg}</body>`);
46await page.waitForTimeout(200);
47
48const box = await page.evaluate(() => {
49 const root = document.querySelector('svg');
50 // getBBox() reports a group's OWN user space and ignores the transforms
51 // above it, so a nested group comes back in the wrong place entirely --
52 // measured that way the chip appeared to sit left of the flame. The painted
53 // rectangle is what the hit areas have to line up with.
54 const rr = root.getBoundingClientRect();
55 const of = (label) => {
56 const g = [...document.querySelectorAll('g, rect, path')]
57 .find(e => e.getAttribute('inkscape:label') === label);
58 if (!g) return null;
59 const b = g.getBoundingClientRect();
60 return { leftPct: 100 * (b.left - rr.left) / rr.width,
61 rightPct: 100 * (b.right - rr.left) / rr.width };
62 };
63 return { flame: of('oxedyne'), chip: of('made_with_mostly_ai_icon'), vbw: rr.width };
64});
65console.log(JSON.stringify(box, null, 1));
66
67check(!!box.flame && !!box.chip, 'both marks were found in the artwork');
68if (box.flame && box.chip) {
69 // The flame is left of the chip, and the boundary falls in the gap between
70 // them -- so each hit area covers its own mark and only its own mark.
71 check(box.flame.rightPct < box.chip.leftPct,
72 `the flame ends before the chip begins (${box.flame.rightPct.toFixed(1)}% then ${box.chip.leftPct.toFixed(1)}%)`);
73 check(splitPct >= box.flame.rightPct && splitPct <= box.chip.leftPct,
74 `the ${splitPct}% boundary falls in the gap between them (${box.flame.rightPct.toFixed(1)}%..${box.chip.leftPct.toFixed(1)}%)`);
75 const mid = (box.flame.rightPct + box.chip.leftPct) / 2;
76 console.log(` the gap's centre is ${mid.toFixed(1)}%`);
77}
78
79console.log(bad ? `\n${bad} FAILED` : '\nALL PASS');
80await browser.close();
81process.exit(bad ? 1 : 0);