Skip to content

Practice CoD: stop after a confirmed VICTORY or DEFEAT end screen (QORCAP_AUTOCLOSE, practice lane) - #151

Merged
ConWan30 merged 1 commit into
mainfrom
feat/cod-autoclose
Nov 11, 2026
Merged

ConWan30 merged 1 commit into
mainfrom
feat/cod-autoclose

Conversation

@ConWan30

Copy link
Copy Markdown
Owner

Draft. Not for merge without Con's word. Practice lanes only.

What it does. With QORCAP_AUTOCLOSE=1 and QORCAP_LANE=practice (the practice LIVE launcher sets both) on a Call of Duty match, QorCap stops on its own after a confirmed VICTORY or DEFEAT end screen. It waits a tail first (default 12 s) and then stops through the same path as Enter.

What QORCAP_AUTOCLOSE did before. It was read only by the launcher .ps1, which closes the console window 10 s after a clean exit. Neither qorcap.exe nor the sidecar read it, and it was not specific to Madden or CoD. Before this PR, CoD stopped by itself only when the full CoD final posted: the end word plus a readable Qortrola30 row with K/D and owner-row consensus. If the row didn't read, you had to press Enter.

False-trigger guard (sidecar/qorcap_cod_endstop.py). It watches reads the CoD frame loop already makes. It doesn't read pixels itself and posts nothing to /events.

  • A frame counts only when CodEndScreenReader returns a calibrated, confirmed victory or defeat.
  • Any other frame ends the run, so a one-frame flicker cannot confirm. That covers killcam, gameplay, lobby, a mid-match Tab board without the word, a blurred or cut word, and the other word.
  • Timestamps must be strictly later, with no gap over 3 s (the reader's own agreement gap).
  • If Qortrola30's end row was read on at least one frame of the run, the bar is 8 reads over at least 3 s (QORCAP_AUTOCLOSE_FRAMES). If the row was not read, the bar is twice that: 16 reads over at least 6 s.
  • Arming: the run must start at least 120 s after the first frame (QORCAP_AUTOCLOSE_ARM_S). A leftover end screen in the lobby cannot stop a new match.
  • Once confirmed, the result stays fixed for the rest of the match.

Stop. After confirmation, capture runs QORCAP_AUTOCLOSE_TAIL_S (default 12, clamped 10..15). Then the main loop stops the same way as Enter: /shutdown, seal, countersign, bundle, receipt.

  • The stop reason is an existing value: final if a CoD final posted, otherwise post_final_tail. No new value goes into the receipt.
  • Enter/Q and capture errors still stop at once.
  • The tail only adds time. An older final stop that would fire earlier waits for it.
  • The live tap keeps sending through the tail. It ends the way it does today: QorCap stops sending, and QorStream's UDP idle timer ends the ingest. There is no separate end signal.

Info only.

  • One stop note: line goes into qorcap.log. /shutdown accepts an optional printable-ASCII note of at most 240 bytes; it is logged and not stored. The line contains none of the needles stop_reason_from_log looks for.
  • cod_end_stop.json is written beside the bundle: result, reader, run count and span, frame seq, capture t_ms, config. It follows the same rule as post_final_tail.json: not bundle.json, not the receipt, not read by verification.
  • I did not use cod_session.json, because attach_session copies its sync tags onto bundle.json.

Files. sidecar/qorcap_cod_endstop.py (new), sidecar/qorcap_cod.py (+12: optional read hook; an exception in the hook is swallowed), sidecar/qorcap_match.py (wiring), src/api.rs (/shutdown note logged), tests, tools/assert_pytest_skips.py (new file in both suites; box-only file excluded; floors re-collected to 905 linux / 887 windows against qortime 0204e6d).
Untouched: receipt.v0, contracts/, schema/, the capture encode, the recording path. REAL MATCH stays on v0.3.38. The keyframe fix (Fix 2) stays on hold.

Tests (local Linux).

  • test_qorcap_cod_endstop.py, 25 tests:
    • Negatives: killcam, Tab, flicker, word switch, gap, repeated timestamp, pre-arm, another player's row.
    • Positives: VICTORY and DEFEAT, with and without the row.
    • Tail timing with an injected clock, the stop-reason wiring, the shutdown body, the log note and sidecar file, the runner hook.
    • Committed 720p fixtures: VICTORY splash then board confirms; mid-match and transition frames don't; board alternating with gameplay doesn't.
  • test_qorcap_cod_endstop_box.py runs only on the build box (excluded from CI) over 370 earlier sampled frames. No mid-match sample read as an end word; each of the 18 end-word samples confirmed its own word when held.
  • Linux sidecar suite: 894 passed, 1 allowlisted skip. The 10 failures are all test_qorcap_submit.py because fastapi isn't installed on the box; CI installs it. Skip gate passes.
  • cargo test: lib 206, main 1, contracts 2, haptic_replay 1, redteam 2, all pass. No new clippy warnings.

Not measured. I have no committed killcam or "Final Killcam" frames, so the killcam negative is modeled ("no confirmed word"), not measured on a real killcam. Real-match recall at the sidecar's sample rate (about 2–4 Hz) is unmeasured; a slow PC could take longer to reach 8 or 16 reads. In the sampled frames, "BEST PLAY" follows the end header, and a 12 s tail may end during it.

CI artifact. The CI on main uploads qorcap-<sha7>-windows-x64 for this PR, which contains #150 plus this change.

Off unless QORCAP_AUTOCLOSE=1, QORCAP_LANE=practice and the game set
is Call of Duty (the practice LIVE launcher sets both variables).
Before this change QORCAP_AUTOCLOSE only closed the launcher window
10 s after a clean exit; qorcap.exe and the sidecar did not read it.
CoD already stopped on its own only when the full final posted (end
word plus a readable Qortrola30 row and owner-row consensus).

New sidecar/qorcap_cod_endstop.py watches the end-word and owner-row
reads the CoD frame loop already makes. Guard:
- only a calibrated, confirmed victory/defeat read counts; any other
  frame (killcam, gameplay, lobby, Tab board without the word, the
  other word) ends the run, so a one-frame flicker cannot confirm;
- strictly later capture timestamps, gap <= 3 s;
- 8 reads over >= 3 s when Qortrola30's end row was read on one frame
  of the run, otherwise 16 reads over >= 6 s;
- the run must start >= 120 s after the first frame seen (arming).
After confirmation capture runs a tail (QORCAP_AUTOCLOSE_TAIL_S,
default 12, clamped 10..15), then the existing stop path runs: the
same /shutdown, seal, countersign, bundle and receipt as Enter. The
stop reason is an existing value (final if a final posted, otherwise
post_final_tail). Enter/Q and capture errors still stop at once; the
tail only adds time to the older final stop.

Info only: one "stop note:" line in qorcap.log (the /shutdown body
may carry a printable-ASCII note, at most 240 bytes, that is logged
and not stored) and cod_end_stop.json beside the bundle. Neither is
bundle.json, the receipt, or a verification input.

Tests: state machine with fake reads and an injected clock (killcam,
Tab, flicker, word switch, gap, pre-arm negatives; VICTORY and DEFEAT
positives; tail timing), the stop wiring, the runner hook, committed
720p fixtures, and a box-only frame test kept out of both CI suites.
Sidecar suite floors re-collected: 905 linux / 887 windows.

Untouched: receipt.v0, contracts/, schema/, the capture encode and
the recording path. REAL MATCH stays on v0.3.38.
@ConWan30
ConWan30 marked this pull request as ready for review November 11, 2026 17:37
@ConWan30
ConWan30 merged commit 8c5181d into main Nov 11, 2026
2 of 3 checks passed
ConWan30 added a commit that referenced this pull request Nov 11, 2026
…#152)

The linux job failed on main and on #150/#151 after about 63 min in
`sudo apt-get update && sudo apt-get install -y ffmpeg`. The logs show
no prompt and no dpkg lock: on the ubuntu-26.04 image the runner could
not resolve azure.archive.ubuntu.com, archive.ubuntu.com or
security.ubuntu.com ("Temporary failure resolving"). apt waited 20 s
per package per mirror for ~90 packages, then exited 100.
packages.microsoft.com and github.com resolved.

The ffmpeg step now downloads a pinned BtbN/FFmpeg-Builds static build
(autobuild-2026-10-31-12-59, n8.1.3 linux64 gpl, month-end builds are
kept long-term), checks its sha256, and puts ffmpeg/ffprobe on PATH
via GITHUB_PATH. If the download or checksum fails, apt runs with
DEBIAN_FRONTEND=noninteractive, Acquire retries/timeouts, a dpkg lock
timeout and an outer `timeout`. The step has timeout-minutes 15.

Job timeouts: linux 40, contracts 20, windows 45 min.

Only .github/workflows/ci.yml changes. release.yml and product code
are untouched.
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.

1 participant