Skip to content

Tolerate a recording whose screen never changed at all - #13

Merged
eiliya-luzia merged 1 commit into
The-Wordlab:mainfrom
omegasoft7:fix/tolerate-fully-static-recording
Sep 17, 2026
Merged

eiliya-luzia merged 1 commit into
The-Wordlab:mainfrom
omegasoft7:fix/tolerate-fully-static-recording

Conversation

@omegasoft7

Copy link
Copy Markdown
Contributor

The bug

aua record stop exits 3 with recording_coverage_failed whenever the screen stayed static for the entire recording window — even though a valid MP4 was written and the encoder never faltered.

Reproduced on Pixel_6_Pro_API_34 (API 34, arm64, macOS host) with v0.27.5:

$ aua record start && sleep 5 && aua record stop rec.mp4
{"ok":false,"action":"record-stop","recording":{
  "segments":[{"media_duration_s":0.0,"status":"finalized","exported":true}],
  "requested_duration_s":8.02,"media_duration_s":0.0,"duration_check":"failed",
  "coverage_shortfall_s":8.02,
  "gaps":[{"reason":"static_screen_no_frames","shortfall_s":8.33}]}}
exit 3

The file on disk was fine: 37,320 bytes, ISO Media, MP4 v2, ffprobe → nb_frames=1. The same sequence with any on-screen activity passed (media_duration_s: 16.677, duration_check: "passed").

Why

android_recording.py already excuses stillness per segment, and says so in its own comment (the rule added in v0.20.0, "a static stretch costs media seconds without costing any footage"). It appends a static_screen_no_frames gap and does not set failed.

The aggregate guard on the next lines then overrode it:

failed = failed or bool(dark) or (requested > _COVERAGE_TOLERANCE_S and media_total <= 0)

screenrecord emits a frame when the screen changes, so a window whose screen never changed once encodes a single frame — and one frame is exactly 0.0 seconds of media, not merely a short one. "Captured nothing at all" and "nothing happened" turn out to be the same number. The result was inverted against the intent: a partly idle window passed, a wholly idle one failed.

The change

Zero media now fails only where no static_screen_no_frames gap accounts for it:

stillness = any(gap["reason"] == "static_screen_no_frames" for gap in gaps)
captured_nothing = requested > _COVERAGE_TOLERANCE_S and media_total <= 0 and not stillness
failed = failed or bool(dark) or captured_nothing

any(...) rather than "all gaps are stillness": gaps also receives encoder_startup, segment_rotation and recording_ended_before_stop. Since the supervisor rotates screenrecord at its 180s limit, an all-gaps test would fix the 8-second case but keep failing the same idle screen at 400 seconds. test_a_static_window_that_rotated_segments_also_passes pins that.

The safety net is untouched. Everything a dead recorder actually produces is judged before this line — no segments, no finish event, encoder_failed, a missing or unreadable segment file, a non-zero recorder exit, and dark stretches where the encoder was not running. A recording with zero finalized segments never reaches coverage at all; stop() raises recording_no_playable_segments.

Note on an existing test

This replaces test_capturing_nothing_at_all_still_fails. Its premise was the bug: _timeline(media_s=0.0) is a fully idle window, so the test asserted the behaviour reported here. Rather than loosen it, it is replaced by five tests whose premises hold — two for the bug (plain and rotated), one that stillness is still reported rather than hidden, and two regression guards: zero media that stillness does not explain, and a recorder that produced no segment at all. Please sanity-check that call, since it is the one judgement in this PR that removes an existing assertion.

docs/recording.md had one stale sentence describing this same rule; it is corrected here.

Verification

$ pytest tests/test_recording_coverage_is_judged_in_one_direction.py -n0
15 passed

# with only the source change reverted, tests kept:
2 failed, 13 passed
FAILED ...::test_a_wholly_static_window_is_zero_media_and_still_passes
FAILED ...::test_a_static_window_that_rotated_segments_also_passes

Full recording area (12 files): 156 passed, 2 skipped in 15.29s.
ruff check . → All checks passed!

The full suite is not green on this host before the change either (22 failed on clean main vs 26 failed with the change, sets differing in both directions under -n auto; the 6 that failed only in the patched run all pass serially on both trees). The 20 collection errors are ModuleNotFoundError: No module named 'jsonschema', an optional dev dependency missing from this environment. No recording module is involved in any of it.

No device run was used to verify the fix itself — the emulator was used only to reproduce the original failure. The post-fix check is at package level through the editable install, replaying the reported numbers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@eiliya-luzia
eiliya-luzia merged commit 099cace into The-Wordlab:main Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants