the phone walk refuses one, because a real device's orientation is not the
harness's to set.

The device turns itself over instead, in a block of the phone walk's own,
after `background` and before the network — because a step list cannot do
either of the two things this needs. It reads
`settings get system accelerometer_rotation` and `user_rotation` FIRST and
latches the pair; only then does it put auto-rotate off and `user_rotation`
to 1 (a retry at 3 for a device whose natural orientation is landscape), and
it polls for the turn rather than waiting a fixed time. Then it asserts what
only a device can answer: `screen.orientation.angle` is 90 or 270 — an
`Emulation.setDeviceMetricsOverride` without `screenOrientation` leaves the
angle at 0, so the mock physically cannot fake this, and it is the one clean
"a real phone did this" clause — that the WebView RESIZED as well (a separate
clause, because a WebView that never got the configuration change looks
identical to a display that never rotated), that `--nav-h` computed to 48px
so the phone-sized landscape query really fired, that the activity was NOT
recreated (the same window token and the same `performance.timeOrigin`, the
same pid, no `AndroidRuntime` fatal — the manifest declares `orientation` and
`screenSize` handled, so a recreation here would be the bug), that the tab
and every stack are where they were, that the track is still playing and past
where it was, that nothing is clipped, and that the glass bar, the mini bar
and the screen spend whatever `env()` says. Then the player sheet sideways:
two columns, every control on screen, its own side padding. Then back.

The four safe-area insets are printed, not asserted. Nobody had ever measured
them on this phone: `MainActivity` calls androidx's `enableEdgeToEdge()`,
`themes.xml` sets no cutout attribute 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 run records the numbers — `insets` as
top/right/bottom/left, `insetsReal`, the app's own `--inset-left/right` beside
them, and `chromePct` — and a later round can assert them once they are known.
Asserting a number nobody has measured is how a round fails on a correct
build.

Putting the phone back is the part that is written carefully, because the
phone is Kyle's. The saved pair is a module-level latch, and the restore is
reached from four places: the rotation block's own `finally`, main's
`finally`, `process.on("exit")` and SIGINT/SIGTERM/SIGBREAK. It has to be all
four — a failed clause only pushes a problem and walks on, but `still()` and
the probe call are wrapped in nothing, so a WebView that stops answering
rejects after 45 s and throws clean out of the loop, and Ctrl-C runs no `exit`
handler at all. The latch clears itself before it writes, so only the first
caller acts and a restore that fails hands the latch back for the next one.
The rotation goes back before auto-rotate does, so a phone that had the sensor
in charge gets it back last. A device whose two settings will not read is
skipped with a line rather than forced — `rotation skipped: the phone's
rotation settings would not read` — as is one that is already sideways, and
`--no-rotate` skips the block outright for a run where he is holding the
phone. Nothing else on the device is touched: no download, no room, no
message, no sign-in, nothing written to the config or the library. The one
case no handler covers is a hard kill of the machine, after which auto-rotate
is a Quick Settings tile away.

The stills are `phone-turned.png`, `phone-turned-sheet.png` and
`phone-turned-back.png`.
