            /* ── turned over, for real ────────────────────────────────
               Round twenty-seven wrote the landscape layout and proved it
               in headless Chrome behind a viewport override. No real device
               had ever been turned. The phone walk still refuses a
               `{ view: [...] }` step — a device's orientation is not the
               step list's to set — so the turn lives here in the tail
               instead, where it can hold a latch and put the phone back
               from a `finally` whatever happens above it. It lands right
               after `background`, which has just asserted the position
               advanced by eight seconds with the app off screen: playback
               is provably live, which is the best arming state there is.

               What only a device can answer, and the override cannot fake:
               screen.orientation.angle (an override without
               `screenOrientation` leaves it at 0, so 90 is a fact rather
               than a viewport), env()'s real insets around the cutout and
               the gesture pill, whether the activity survived the
               configuration change instead of being recreated, and whether
               the audio kept going through it. The turn and the RESIZE are
               separate clauses on purpose: a WebView that never got the
               configuration change otherwise looks exactly like a display
               that never rotated.

               What is NOT asserted, because nobody has measured it yet:
               the inset numbers themselves. MainActivity calls androidx's
               enableEdgeToEdge() and nothing in the generated Kotlin
               touches insets, so env() comes straight out of Chromium's own
               window-inset mapping and whether the hole-punch shows up as a
               non-zero side inset depends on the cutout mode that picks.
               The four numbers are REPORTED; what is asserted is the
               plumbing — that .screen, .mini and .np spend whatever env()
               says, which is the rule the side-inset longhands at the foot
               of shell.css exist to win. Ditto chromePct: the first run is
               what records the truth, and a later round can tighten it.

               The phone is Kyle's. Both settings are read before either is
               written and put back from four places; a device whose
               settings will not read is skipped rather than forced; and
               `--no-rotate` skips the block outright, for a run where he is
               holding the phone. */
            if (ARGS.includes("--no-rotate")) {
                console.log("  --  rotation         skipped: --no-rotate");
            } else {
                /* the phase's own reading, beside PLAYER / STREAM / HERO /
                   CAT / NET — PROBE is deliberately left alone, because
                   sibling rounds append their fields to it. env() cannot be
                   read out of a custom property everywhere, so the ground
                   truth is a throwaway fixed 0x0 div whose padding IS the
                   four env()s, read back computed; the app's own
                   --inset-left/right are read beside it and printed, so a
                   disagreement between the two shows up in the report
                   rather than hiding inside an assertion. */
                const ROT = `(function () {
    var d = document.createElement('div');
    d.style.cssText = 'position:fixed;left:0;top:0;width:0;height:0;visibility:hidden;pointer-events:none;'
        + 'padding-top:env(safe-area-inset-top,0px);padding-right:env(safe-area-inset-right,0px);'
        + 'padding-bottom:env(safe-area-inset-bottom,0px);padding-left:env(safe-area-inset-left,0px)';
    document.documentElement.appendChild(d);
    var cs = getComputedStyle(d);
    var ins = [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft].map(function (v) { return Math.round(parseFloat(v) || 0); });
    d.remove();
    var rs = getComputedStyle(document.documentElement);
    function box(sel) { var x = document.querySelector(sel); return x ? x.getBoundingClientRect() : null; }
    function inside(b) { return !!b && b.height > 0 && b.top >= -1 && b.bottom <= innerHeight + 1 && b.left >= -1 && b.right <= innerWidth + 1; }
    function pad(sel, side) { var x = document.querySelector(sel); return x ? Math.round(parseFloat(getComputedStyle(x)[side]) || 0) : -1; }
    /* the chrome that is actually on screen: a mini bar under a raised
       sheet and a nav under one are translated away on purpose */
    var out = [];
    ['.gbar', '.screen', '.mini:not(.hidden)', '.nav:not(.gone)'].forEach(function (sel) {
        var b = box(sel);
        if (b && b.height > 0 && !inside(b)) out.push(sel);
    });
    /* the witness that an activity recreation would destroy: planted on the
       first reading, gone with the window if a new one replaces it */
    if (!window.__ssTurn) window.__ssTurn = 'turn-' + Date.now() + '-' + Math.round(Math.random() * 1000000);
    var p = window.__ss && __ss.player;
    var n = window.__ss && __ss.nav;
    return {
        token: window.__ssTurn,
        origin: Math.round(performance.timeOrigin),
        w: innerWidth, h: innerHeight,
        vp: innerWidth + 'x' + innerHeight,
        /* the one thing a viewport override cannot move */
        angle: (screen.orientation && typeof screen.orientation.angle === 'number') ? screen.orientation.angle : -1,
        ori: (screen.orientation && screen.orientation.type) || '',
        mq: matchMedia('(orientation: landscape)').matches,
        /* the phone-sized landscape query, read off the one variable only
           it sets: 60px portrait, 48px sideways */
        navH: (rs.getPropertyValue('--nav-h') || '').trim(),
        insets: ins,
        varLeft: Math.round(parseFloat(rs.getPropertyValue('--inset-left')) || 0),
        varRight: Math.round(parseFloat(rs.getPropertyValue('--inset-right')) || 0),
        screenPadL: pad('.screen', 'paddingLeft'),
        screenPadR: pad('.screen', 'paddingRight'),
        npPadL: pad('.sheet.up .np', 'paddingLeft'),
        npPadR: pad('.sheet.up .np', 'paddingRight'),
        /* both edges, because a hole-punch is on ONE of them: sideways the
           cutout is left or right depending on which way the phone turned,
           and a check of the left alone would pass by the inset being 0 */
        miniLeft: Math.round((box('.mini') || { left: -1 }).left),
        miniRight: Math.round(innerWidth - (box('.mini') || { right: innerWidth + 1 }).right),
        chromePct: (function () { var g = box('.gbar'), low = box('.mini') || box('.nav'); return Math.round((((g ? g.height : 0) + (low ? innerHeight - low.top : 0)) / innerHeight) * 100); })(),
        outside: out.join('|'),
        sheet: !!document.querySelector('.sheet.up'),
        npOut: [].filter.call(document.querySelectorAll('.sheet.up .np-frame, .sheet.up .np-meta, .sheet.up .np-scrub, .sheet.up .np-transport, .sheet.up .np-foot'), function (x) { var b = x.getBoundingClientRect(); return b.height > 0 && !inside(b); }).length,
        npCols: (function () { var a = box('.sheet.up .np-frame'), b = box('.sheet.up .np-transport'); if (!a || !b) return ''; if (!a.height) return 'face'; return a.bottom <= b.top + 1 ? 'stacked' : 'beside'; })(),
        tab: n ? n.tab : '',
        depth: (function () { try { return Object.keys(n.stacks).map(function (k) { return k + ':' + n.stacks[k].length; }).join(','); } catch (e) { return ''; } })(),
        status: p ? p.status : '',
        title: p && p.track ? p.track.title : '',
        pos: p ? Math.round(p.position) : -1,
    };
})()`;
                /* 15 s, not cdp.evaluate's 45: if the activity really was
                   recreated the socket is dead, and this block asks three
                   times over. A broken device should cost the walk under a
                   minute, not three. */
                const rotAsk = async (expr) => {
                    const r = await cdp.send("Runtime.evaluate", { expression: expr, awaitPromise: true, returnByValue: true }, 15000);
                    if (r.result && r.result.exceptionDetails) {
                        throw new Error("page threw: " + (r.result.exceptionDetails.exception?.description || r.result.exceptionDetails.text || ""));
                    }
                    return r.result?.result?.value;
                };
                /* a known screen: Home, nothing open, no transition in
                   flight, so `.screen` is the viewport box and not a sheet
                   sliding across it */
                try {
                    await cdp.evaluate(HELPERS);
                    await cdp.evaluate(`__settle()`);
                    await sleep(900);
                } catch { /* the reading below says so */ }

                const was = rotationArm();
                let start = null;
                try { start = await rotAsk(ROT); } catch (e) { start = null; }

                if (!was) {
                    console.log("  --  rotation         skipped: the phone's rotation settings would not read");
                } else if (!start) {
                    /* disarmed, not restored: nothing has been written yet,
                       and putting two settings back that were never changed
                       is still writing to his phone */
                    rotationDisarm();
                    console.log("  --  rotation         skipped: the page did not answer before the turn");
                } else if (start.w > start.h) {
                    rotationDisarm();
                    console.log("  --  rotation         skipped: the phone is already sideways (" + start.vp + ")");
                } else {
                    /* "it kept playing" means nothing unless it was playing:
                       the restart phase's own idiom, and the only call this
                       block makes into the app */
                    if (start.status !== "playing") {
                        try { await cdp.evaluate(`window.__ss && __ss.toggle()`); } catch { /* reported by the clause */ }
                        await sleep(2500);
                        try { start = (await rotAsk(ROT)) || start; } catch { /* keep the earlier reading */ }
                    }
                    let rotErr = "";
                    /* polled, never fixed-waited: a real device turns on its
                       own clock, and reading a slow answer as a failure has
                       cost this project a round twice. A page that stops
                       answering breaks the loop rather than paying 15 s
                       sixteen times over — and that IS the recreated-WebView
                       reading, which the finally below still restores from. */
                    const until = async (wide) => {
                        let last = null;
                        for (let i = 0; i < 16; i++) {
                            await sleep(500);
                            try { last = await rotAsk(ROT); } catch (e) { rotErr = e.message; return last; }
                            if (last && (wide ? last.w > last.h : last.w < last.h)) return last;
                        }
                        return last;
                    };
                    /* `adb()` throws on a non-zero exit, and `pidof` exits 1
                       when the process is GONE — which is the one finding
                       this block most exists to catch. Read tolerantly, so a
                       dead app comes back as `alive: false` in the report
                       rather than as a bare throw that takes every other
                       clause with it; `logcat -c`, which some devices refuse
                       to clear, is the same shape before the turn. */
                    const adbSay = (...a) => { try { return adb(...a).toString(); } catch { return ""; } };
                    try {
                        const pid0 = adbSay("shell", "pidof", "com.soulstream.app").trim();
                        adbSay("logcat", "-c");
                        const put = rotationPut(1);
                        if (put) problems.push("ROTATION could not turn the phone: " + put);
                        let turned = await until(true);
                        if (!rotErr && !(turned && turned.w > turned.h)) {
                            /* a device whose natural orientation is landscape
                               maps 1 the other way; one retry at 3 is cheap
                               insurance against a false finding */
                            rotationPut(3);
                            turned = await until(true);
                        }
                        await sleep(700);
                        if (!rotErr) { try { turned = (await rotAsk(ROT)) || turned; } catch (e) { rotErr = e.message; } }
                        try { await still(path.join(OUT, `${prefix}turned.png`)); } catch (e) { problems.push("ROTATION still: " + e.message); }

                        const sys = rotationSystem();
                        const pid1 = adbSay("shell", "pidof", "com.soulstream.app").trim();
                        const fatals = (adbSay("logcat", "-d", "-s", "AndroidRuntime:E").match(/FATAL EXCEPTION|DidNotStartInTime/g) || []).length;
                        const t = turned || {};
                        const ins = t.insets || [0, 0, 0, 0];
                        const checks = {
                            /* only a device moves this */
                            turned: t.angle === 90 || t.angle === 270,
                            /* and the WebView got the configuration change */
                            resized: t.w > t.h && t.mq === true,
                            /* …down to the phone-sized query, exactly */
                            layout: t.navH === "48px",
                            /* and it is still ANSWERING: a reading that went
                               stale because the socket died is otherwise a
                               good-looking reading of the moment before */
                            answering: !rotErr,
                            alive: !!pid1 && pid1 === pid0 && !fatals,
                            /* the manifest declares orientation and screenSize
                               handled, so the activity must NOT have been
                               recreated: a recreation makes a new window and
                               a new document, and both of these would move */
                            kept: t.token === start.token && t.origin === start.origin,
                            stack: t.tab === start.tab && t.depth === start.depth,
                            /* a track that rolled over during the turn starts
                               the next one at zero: still playing, which is
                               the whole of what this clause claims */
                            playing: t.status === "playing" && (t.pos >= start.pos || t.title !== start.title),
                            unclipped: t.outside === "",
                            /* whatever env() says sideways, the chrome spends
                               it — the plumbing, not a number nobody has
                               measured. The `>= 0` clauses are not belt and
                               braces: pad() answers -1 for an element that is
                               not there, and -1 clears `>= inset - 1` whenever
                               the inset is 0, so a missing .screen passed */
                            plumbing: t.screenPadL >= 0 && t.screenPadR >= 0
                                && t.screenPadL >= ins[3] - 1 && t.miniLeft >= 8 + ins[3] - 1
                                && t.screenPadR >= ins[1] - 1 && t.miniRight >= 8 + ins[1] - 1,
                        };
                        const bad = Object.keys(checks).filter((k) => !checks[k]);
                        const report = {
                            vp: t.vp, angle: t.angle, ori: t.ori, navH: t.navH, chromePct: t.chromePct,
                            insets: ins.join("/"), insetsReal: ins[1] > 0 || ins[3] > 0,
                            vars: t.varLeft + "/" + t.varRight,
                            screenPad: t.screenPadL + "/" + t.screenPadR, mini: t.miniLeft + "/" + t.miniRight,
                            pid: pid1 || null, fatals, sys, err: rotErr, failed: bad.join(","),
                        };
                        if (bad.length) problems.push("EXPECT rotation " + JSON.stringify(report));
                        console.log((bad.length ? " FAIL " : "  ok  ") + "rotation".padEnd(16) + " " + JSON.stringify(report));

                        /* the sheet is the one surface whose portrait shape
                           cannot survive a wide viewport: two columns, every
                           control on screen, and its own side padding */
                        if (!rotErr && t.w > t.h) {
                            try {
                                await cdp.evaluate(`var m = document.querySelector('.mini'); m && m.click(); true`);
                                await sleep(1400);
                                await still(path.join(OUT, `${prefix}turned-sheet.png`));
                                const sh = (await rotAsk(ROT)) || {};
                                const sIns = sh.insets || [0, 0, 0, 0];
                                const sChecks = {
                                    up: sh.sheet === true,
                                    beside: sh.npCols === "beside",
                                    onScreen: sh.npOut === 0,
                                    /* `>= 0` first: pad() answers -1 for an
                                       element that is not there, and -1
                                       clears `>= inset - 1` whenever the
                                       inset is 0 */
                                    plumbing: sh.npPadL >= 0 && sh.npPadR >= 0
                                        && sh.npPadL >= sIns[3] - 1 && sh.npPadR >= sIns[1] - 1,
                                };
                                const sBad = Object.keys(sChecks).filter((k) => !sChecks[k]);
                                const sReport = { vp: sh.vp, npCols: sh.npCols, npOut: sh.npOut, npPad: sh.npPadL + "/" + sh.npPadR, insets: sIns.join("/"), failed: sBad.join(",") };
                                if (sBad.length) problems.push("EXPECT rotation-sheet " + JSON.stringify(sReport));
                                console.log((sBad.length ? " FAIL " : "  ok  ") + "rotation-sheet".padEnd(16) + " " + JSON.stringify(sReport));
                                await cdp.evaluate(`window.__ss && __ss.closeSheet()`);
                                await sleep(800);
                            } catch (e) { problems.push("ROTATION rotation-sheet: " + e.message); }
                        } else {
                            console.log("  --  rotation-sheet   skipped: the phone did not turn");
                        }
                    } catch (e) {
                        /* an adb that falls over mid-block, a still that
                           cannot be taken: reported, never thrown. A throw
                           out of here would take the network block, the
                           lock-screen still and the walk's own summary with
                           it — and the phone is put back either way, because
                           catch runs before finally. */
                        problems.push("ROTATION " + e.message);
                    } finally {
                        /* whatever happened above — a clause that failed, a
                           WebView that stopped answering, a throw out of any
                           of it — the phone goes back first and everything
                           else is reported afterwards. */
                        const back = rotationRestore();
                        if (back !== true) problems.push("ROTATION could not put the phone's rotation settings back: " + back);
                        rotErr = "";
                        let home = await until(false);
                        await sleep(600);
                        if (!rotErr) { try { home = (await rotAsk(ROT)) || home; } catch (e) { rotErr = e.message; } }
                        try { await still(path.join(OUT, `${prefix}turned-back.png`)); } catch { /* the phone may be gone */ }
                        const now = rotationSettings();
                        const h = home || {};
                        const bChecks = {
                            answering: !rotErr,
                            portrait: h.w > 0 && h.w < h.h,
                            angle: h.angle === start.angle,
                            layout: h.navH === start.navH,
                            kept: h.token === start.token && h.origin === start.origin,
                            playing: h.status === "playing",
                            settings: !!now && now.accel === was.accel && now.user === was.user,
                        };
                        const bBad = Object.keys(bChecks).filter((k) => !bChecks[k]);
                        const bReport = { vp: h.vp, angle: h.angle, navH: h.navH, status: h.status, settings: now, wanted: was, err: rotErr, failed: bBad.join(",") };
                        if (bBad.length) problems.push("EXPECT rotation-back " + JSON.stringify(bReport));
                        console.log((bBad.length ? " FAIL " : "  ok  ") + "rotation-back".padEnd(16) + " " + JSON.stringify(bReport));
                    }
                }
            }
