feat(capture): lock decision #1 and prove Xvfb+Chromium frame capture

Decision #1: watch the real Discord screen share by running a real web client
under a virtual display (Xvfb) and capturing rendered frames — not bot/selfbot
protocol receive. PLAN.md records all locked decisions and the step list.

PoC (poc/capture_xvfb.py + test_page.html) launches system Chrome non-headless
under Xvfb via Playwright and captures 6 changing, non-blank frames -> de-risks
the display+capture chain on the .9 host.
This commit is contained in:
claude-owner
2026-08-09 20:52:57 +09:00
parent 4eeddc4b1f
commit 7d20a40a84
4 changed files with 155 additions and 0 deletions

29
poc/test_page.html Normal file
View File

@@ -0,0 +1,29 @@
<!doctype html>
<html>
<head><meta charset="utf-8"><title>capture test</title>
<style>html,body{margin:0;background:#101418}</style></head>
<body>
<canvas id="c" width="1280" height="720"></canvas>
<script>
// An always-changing scene, so captured frames must differ frame-to-frame and
// be non-blank. This stands in for "a Discord screen share is playing".
const c = document.getElementById('c'), x = c.getContext('2d');
let t = 0;
function draw() {
t++;
x.fillStyle = '#101418'; x.fillRect(0, 0, 1280, 720);
// moving box
const px = (t * 7) % 1180;
x.fillStyle = `hsl(${(t*3)%360} 80% 55%)`;
x.fillRect(px, 300 + 120 * Math.sin(t / 15), 100, 100);
// big live counter text
x.fillStyle = '#e8eef2'; x.font = 'bold 90px monospace';
x.fillText('FRAME ' + t, 60, 140);
x.font = '28px monospace';
x.fillText(new Date().toISOString(), 60, 200);
requestAnimationFrame(draw);
}
draw();
</script>
</body>
</html>