Files
SoulStream/handoff/rounds/ins-r42-helpers.txt
T
KeelrLeek 9f0601027c android: the handoff — the pending rounds, the pipeline, and what is unverified
Everything written this session that has not been through the device: rounds 43
to 48 (each patch adversarially verified, 43's transient-notice expectation
fixed), a partial round 49, the pipeline scripts, the Android Auto design and
the icon comparison mock. Also the two harness changes that were never A/B'd —
adaptive waits, which may only shorten a declared wait and never lengthen it,
and a ship that runs the mock walk and the deploy at the same time. The A/B and
its 255-step baseline are in handoff/tools; main stays at what the phone has
actually walked.
2026-09-14 07:57:41 -05:00

109 lines
5.4 KiB
Plaintext

/* ── the phone, turned over: the two settings that do it ───────────────
* `settings get system accelerometer_rotation` and `user_rotation` are the
* only two things on the device this walk ever writes, and both are READ
* before either is written. The saved pair is latched at module scope on
* purpose: the step loop's `still()` and its PROBE call are wrapped in
* nothing at all, so a WebView that stops answering rejects after cdp.mjs's
* timeout and throws clean out of the walk into main's `finally`. That is
* the exact path that would otherwise leave Kyle's phone locked sideways,
* which is why the restore is a latch reachable from four places — the
* rotation block's own finally, main's finally, process 'exit' and a
* signal — rather than a step in a list. */
let rotationSaved = null;
/** Both rotation settings as the device has them now, or null when either
* will not read as something we recognise. null is the SKIP signal: the
* block never writes what it could not read first. `user_rotation` comes
* back as the literal "null" on a phone where it has never been set. */
function rotationSettings() {
try {
const accel = adb("shell", "settings", "get", "system", "accelerometer_rotation").toString().trim();
const user = adb("shell", "settings", "get", "system", "user_rotation").toString().trim();
if (!/^\d+$/.test(accel)) return null;
if (!/^\d+$/.test(user) && user !== "null") return null;
return { accel, user };
} catch { return null; }
}
/** Read once and latch. Returns the pair, or null when the device would not
* say — in which case nothing has been written and there is nothing to put
* back. */
function rotationArm() {
const was = rotationSettings();
if (was) rotationSaved = was;
return was;
}
/** Drop the latch WITHOUT writing anything. For the skip paths: the pair has
* been read but nothing has been put, and restoring there would write two
* settings onto Kyle's phone that this walk never changed. */
function rotationDisarm() { rotationSaved = null; }
/** Hold the phone at one rotation. Auto-rotate goes off FIRST: with the
* sensor still in charge it takes `user_rotation` straight back and the
* turn never happens. Returns null, or the message it failed with. */
function rotationPut(n) {
try {
adb("shell", "settings", "put", "system", "accelerometer_rotation", "0");
adb("shell", "settings", "put", "system", "user_rotation", String(n));
return null;
} catch (e) { return e.message; }
}
/** Put both back, once. The latch is cleared BEFORE anything is written, so
* the first caller is the only one that acts and the three behind it are
* no-ops; a restore that fails puts the latch back so the next caller can
* try again. The rotation goes first and auto-rotate last — a phone whose
* auto-rotate was on must hand control back to the sensor only once the
* angle underneath it is the one it started at. Synchronous, because
* `adb()` is spawnSync and nothing asynchronous survives an 'exit'
* handler. Returns true, null when there was nothing to restore, or the
* message it failed with. */
function rotationRestore() {
const was = rotationSaved;
rotationSaved = null;
if (!was) return null;
try {
if (was.user === "null") {
/* never set on this phone: delete it rather than inventing a 0 */
try { adb("shell", "settings", "delete", "system", "user_rotation"); }
catch { adb("shell", "settings", "put", "system", "user_rotation", "0"); }
} else {
adb("shell", "settings", "put", "system", "user_rotation", was.user);
}
adb("shell", "settings", "put", "system", "accelerometer_rotation", was.accel);
return true;
} catch (e) { rotationSaved = was; return e.message; }
}
/** The system's own view of the turn, for the report only. The two settings
* read back, plus the display's current size and rotation out of `dumpsys
* window displays` — whose spelling moves between Android versions, so a
* parse failure comes back empty and is printed, never asserted. */
function rotationSystem() {
const out = { setAccel: "", setUser: "", dispCur: "", dispRot: "" };
try {
out.setAccel = adb("shell", "settings", "get", "system", "accelerometer_rotation").toString().trim();
out.setUser = adb("shell", "settings", "get", "system", "user_rotation").toString().trim();
} catch { /* empty is the reading */ }
try {
const d = adb("shell", "dumpsys", "window", "displays").toString();
out.dispCur = (d.match(/\bcur=(\d+x\d+)/) || [])[1] || "";
out.dispRot = (d.match(/mCurrentRotation=(?:ROTATION_)?(\w+)/) || d.match(/\brotation=(\d+)/) || [])[1] || "";
} catch { /* empty is the reading */ }
return out;
}
/* 'exit' does not run on a signal and a signal does not run 'exit', so
both. Ctrl-C in the middle of the turn is precisely the case that would
otherwise leave the phone sideways until Kyle found the Quick Settings
tile. Registered only for the phone walk — the mock never writes a
setting, and a SIGINT handler on the mock run would change how Ctrl-C
behaves there for nothing. */
if (PHONE) {
process.on("exit", () => { rotationRestore(); });
for (const sig of ["SIGINT", "SIGTERM", "SIGBREAK"]) {
process.on(sig, () => { rotationRestore(); process.exit(130); });
}
}