GENERATED VIEW · PRIVATE ARCHIVE · DO NOT SERVE
RobCo Industries · Archive Museum

audit-u9-harness.mjs



RELEASE

planning/2.8.5/mockups/FO3/audit-u9-harness.mjs

sha256 18684ab37650d516 · 11029 bytes · original held in the private archive

// AUDIT U9 — independent Opus stage-3 harness. Not committed logic; lives under gitignored planning/. import { chromium } from 'playwright'; import { fileURLToPath } from 'url'; import path from 'path'; const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..'); const INDEX = 'file://' + path.join(ROOT, 'index.html').split(path.sep).join('/'); const OUT = path.join(ROOT, 'planning', 'mockups', 'fo3'); function saveFO3({ la = 'OK', ra = 'OK', ll = 'OK', rl = 'OK', hd = 'OK', hpCur = 100, rads = 0 } = {}) { return { activeContext: 'FO3', campaigns: { FO3: { lvl: 12, hpCur, hpMax: 100, rads, caps: 350, la, ra, ll, rl, hd, s_s: 6, s_p: 7, s_e: 5, s_c: 4, s_i: 8, s_a: 6, s_l: 5, inventory: Array.from({ length: 24 }, (_, i) => ({ name: 'Test Item ' + i, qty: 15, wgt: 1, val: 10, type: i % 2 ? 'weapon' : 'aid' })), collectibles: [], perks: [], status: [{ name: 'Well Rested', ticks: 4, type: 'BUFF' }], }, }, }; } async function boot(browser, save, viewport) { const ctx = await browser.newContext({ viewport, hasTouch: true, isMobile: true }); await ctx.addInitScript(s => localStorage.setItem('robco_v8', JSON.stringify(s)), save); const page = await ctx.newPage(); await page.goto(INDEX); await page.waitForTimeout(2200); return { ctx, page }; } async function gotoStatus(page) { await page.evaluate(() => window.selectSubsystem && window.selectSubsystem('operator')); await page.waitForTimeout(200); await page.evaluate(() => { const b = [...document.querySelectorAll('#fo3SubtabRail button')].find(x => x.textContent.trim() === 'STATUS'); if (b) b.click(); }); await page.waitForTimeout(300); } // geometry of each limb box vs its figure limb, plus computed crippled state async function limbGeo(page) { return page.evaluate(() => { const fig = document.querySelector('.vaultboy-fig'); const figRect = fig.getBoundingClientRect(); const figCx = figRect.left + figRect.width / 2; const ids = ['la', 'ra', 'll', 'rl', 'hd']; const out = {}; for (const id of ids) { const btn = document.getElementById('btn_l_' + id); const grp = document.querySelector('.vaultboy-fig [data-limb="' + id + '"]'); const br = btn ? btn.getBoundingClientRect() : null; const lr = grp ? grp.getBoundingClientRect() : null; out[id] = { btnSide: br ? (br.left + br.width / 2 < figCx ? 'left' : 'right') : null, limbSide: lr ? (lr.left + lr.width / 2 < figCx ? 'left' : 'right') : null, limbCrippled: grp ? grp.classList.contains('crippled') : null, btnText: btn ? btn.textContent.replace(/\s+/g, ' ').trim() : null, }; } return out; }); } // Sweep every element inside the FO3 glass for non-green computed colour. async function redSweep(page, label) { return page.evaluate(lbl => { const RED = []; const AMBER = []; const root = document.querySelector('.glass-frame') || document.body; const isVisible = el => { const r = el.getBoundingClientRect(); if (r.width === 0 || r.height === 0) return false; const cs = getComputedStyle(el); if (cs.display === 'none' || cs.visibility === 'hidden' || parseFloat(cs.opacity) === 0) return false; return true; }; const parse = c => { const m = c.match(/rgba?\(([^)]+)\)/); if (!m) return null; const p = m[1].split(',').map(s => parseFloat(s)); return { r: p[0], g: p[1], b: p[2], a: p[3] === undefined ? 1 : p[3] }; }; const els = root.querySelectorAll('*'); for (const el of els) { if (!isVisible(el)) continue; const cs = getComputedStyle(el); // only leaf-ish text nodes matter for "colour"; but check color + borderColor + boxShadow const txt = (el.childNodes.length && [...el.childNodes].some(n => n.nodeType === 3 && n.textContent.trim())); const col = parse(cs.color); if (txt && col && col.a > 0.1) { // red-ish: r dominant, g and b low if (col.r > 150 && col.g < 120 && col.b < 120 && col.r - col.g > 60) { RED.push({ kind: 'text-color', tag: el.tagName, id: el.id, cls: el.className.toString().slice(0, 40), color: cs.color, text: (el.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 30) }); } else if (col.r > 170 && col.g > 110 && col.g < 200 && col.b < 90 && col.r - col.b > 90) { AMBER.push({ kind: 'text-color', tag: el.tagName, id: el.id, cls: el.className.toString().slice(0, 40), color: cs.color, text: (el.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 30) }); } } // border const bc = parse(cs.borderColor); if (bc && bc.a > 0.15 && (cs.borderTopWidth !== '0px')) { if (bc.r > 150 && bc.g < 120 && bc.b < 120 && bc.r - bc.g > 60) { RED.push({ kind: 'border', tag: el.tagName, id: el.id, cls: el.className.toString().slice(0, 40), color: cs.borderColor }); } } // box-shadow red (vignette) if (cs.boxShadow && cs.boxShadow !== 'none' && /rgb/.test(cs.boxShadow)) { const bs = parse(cs.boxShadow); if (bs && bs.r > 150 && bs.g < 120 && bs.b < 120 && bs.r - bs.g > 60) { RED.push({ kind: 'box-shadow', tag: el.tagName, id: el.id, cls: el.className.toString().slice(0, 40), color: cs.boxShadow.slice(0, 60) }); } } } return { label: lbl, RED, AMBER }; }, label); } (async () => { const browser = await chromium.launch(); const report = []; // ---- TEST 1: mirror — LEFT-only crippled ---- { const { ctx, page } = await boot(browser, saveFO3({ la: 'CRIPPLED', ll: 'CRIPPLED', hpCur: 60, rads: 100 }), { width: 780, height: 360 }); await gotoStatus(page); const geo = await limbGeo(page); await page.screenshot({ path: path.join(OUT, 'audit-u9-mirror-left-crippled.png') }); report.push({ test: 'LEFT-only crippled (la,ll)', geo }); await ctx.close(); } // ---- TEST 2: mirror — RIGHT-only crippled ---- { const { ctx, page } = await boot(browser, saveFO3({ ra: 'CRIPPLED', rl: 'CRIPPLED', hpCur: 60, rads: 100 }), { width: 780, height: 360 }); await gotoStatus(page); const geo = await limbGeo(page); await page.screenshot({ path: path.join(OUT, 'audit-u9-mirror-right-crippled.png') }); report.push({ test: 'RIGHT-only crippled (ra,rl)', geo }); await ctx.close(); } // ---- TEST 3: DATA correctness — click L.ARM box, read state.la ---- { const { ctx, page } = await boot(browser, saveFO3({ hpCur: 60, rads: 100 }), { width: 780, height: 360 }); await gotoStatus(page); const rd = () => page.evaluate(() => { const s = (typeof state !== 'undefined') ? state : (window.state || {}); return { la: s.la, ra: s.ra, ll: s.ll, rl: s.rl }; }); const before = await rd(); // click the L.ARM box await page.evaluate(() => document.getElementById('btn_l_la').click()); await page.waitForTimeout(150); const after = await rd(); const geo = await limbGeo(page); await page.screenshot({ path: path.join(OUT, 'audit-u9-tap-larm.png') }); report.push({ test: 'TAP L.ARM box → state', before, after, laLimbCrippled: geo.la.limbCrippled, raLimbCrippled: geo.ra.limbCrippled, laLimbSide: geo.la.limbSide, laBtnSide: geo.la.btnSide }); await ctx.close(); } // ---- TEST 4: RED SWEEP — critical HP + high rad + no radaway, all 6 boards ---- { const { ctx, page } = await boot(browser, saveFO3({ la: 'CRIPPLED', hpCur: 18, rads: 850 }), { width: 780, height: 360 }); const boards = ['operator', 'operations', 'ops']; // will enumerate subsystems below // enumerate subsystems + subtabs const subs = await page.evaluate(() => [...document.querySelectorAll('.nav-cluster .navkey, #bezelNav button, .navkey')].map(b => b.getAttribute('data-subsystem') || b.textContent.trim()).filter(Boolean)); report.push({ test: 'subsystems-found', subs }); // Sweep STATUS specifically await gotoStatus(page); await page.screenshot({ path: path.join(OUT, 'audit-u9-red-status.png') }); const sweepStatus = await redSweep(page, 'STATUS (critical HP, high rad)'); report.push(sweepStatus); // body class check const bodyClass = await page.evaluate(() => document.body.className); report.push({ test: 'body-class', bodyClass }); // sweep each FO3 subtab under operator const subtabs = await page.evaluate(() => [...document.querySelectorAll('#fo3SubtabRail button')].map(b => b.textContent.trim())); report.push({ test: 'operator-subtabs', subtabs }); for (const st of subtabs) { await page.evaluate(name => { const b = [...document.querySelectorAll('#fo3SubtabRail button')].find(x => x.textContent.trim() === name); if (b) b.click(); }, st); await page.waitForTimeout(250); const sweep = await redSweep(page, 'operator/' + st); report.push(sweep); await page.screenshot({ path: path.join(OUT, 'audit-u9-board-operator-' + st.replace(/[^a-z0-9]/gi, '') + '.png') }); } await ctx.close(); } // ---- TEST 5: other subsystems (operations/comms/etc) red sweep ---- { const { ctx, page } = await boot(browser, saveFO3({ la: 'CRIPPLED', hpCur: 18, rads: 850 }), { width: 780, height: 360 }); const subsystems = await page.evaluate(() => { const set = new Set(); document.querySelectorAll('[data-subsystem]').forEach(b => set.add(b.getAttribute('data-subsystem'))); return [...set]; }); report.push({ test: 'all-subsystems', subsystems }); for (const s of subsystems) { await page.evaluate(sub => window.selectSubsystem && window.selectSubsystem(sub), s); await page.waitForTimeout(300); const sweep = await redSweep(page, 'subsystem:' + s); report.push(sweep); await page.screenshot({ path: path.join(OUT, 'audit-u9-subsystem-' + s + '.png') }); } await ctx.close(); } // ---- TEST 6: NV untouched at 360/412/desktop ---- for (const vp of [{ width: 360, height: 800, k: '360' }, { width: 412, height: 915, k: '412' }, { width: 1440, height: 900, k: 'desktop' }]) { const nvSave = { activeContext: 'FNV', campaigns: { FNV: { lvl: 10, hpCur: 18, hpMax: 100, rads: 200, caps: 100, s_s: 5, s_p: 5, s_e: 5, s_c: 5, s_i: 5, s_a: 5, s_l: 5, inventory: [{ name: 'Item', qty: 1, wgt: 1, val: 1, type: 'aid' }], status: [{ name: 'Rested', ticks: 2, type: 'BUFF' }] } } }; const { ctx, page } = await boot(browser, nvSave, { width: vp.width, height: vp.height }); await page.screenshot({ path: path.join(OUT, 'audit-u9-nv-' + vp.k + '.png') }); // NV should KEEP red delete buttons — sweep and expect some red present const sweep = await redSweep(page, 'NV-' + vp.k); report.push({ test: 'NV-' + vp.k + ' redCount', redCount: sweep.RED.length, sample: sweep.RED.slice(0, 5) }); await ctx.close(); } await browser.close(); console.log(JSON.stringify(report, null, 2)); })();
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).