Skip to content

Studio marks clips captured after a batch that ran while it was closed (part of #545) - #760

Merged
ccoventry merged 23 commits into
devfrom
feat/batch-catch-up-545
Oct 29, 2026
Merged

ccoventry merged 23 commits into
devfrom
feat/batch-catch-up-545

Conversation

@ccoventry

Copy link
Copy Markdown
Owner

Release note: New: If DoD Studio was closed during a capture batch, it marks the finished clips as captured the next time it opens.

Part of #545 (part 2, without reconnecting to a still-running game).

Stacked on #757 (feat/seek-default-434), so this PR gets no CI until #757 merges and it is retargeted to dev. Local results are below.

What it does

  • The batch manifest records its highlights. manifests/session_*.json now also holds each payload highlight's id (take_index.js's streakUid), its source demo, the project file the batch was started from, and the capture mode and AGR rate. All #[serde(default)], so older manifests still load. manifest_file::read_planned_in reads back the ones still at outcome planned.

  • Catch-up on start (studio/src-tauri/src/batch_catch_up.rs, command catch_up_unwatched_batches). Studio checks each planned batch's takes on disk with the same check a watched batch runs at its end (no 1.5 s settle: the batch ended long ago). It writes the capture settings beside each take and rewrites the manifest as unwatched, so each batch is caught up once.

  • A game still running: the batch may still be going, so the check waits until no hl.exe is left (polled every 2 s), then sends a batch_catch_up event. Only the sessions planned at startup are checked, so a batch this Studio starts in the meantime is never caught up by mistake.

  • Marking (studio/src/batch_catch_up.js, createBatchCatchUp factory, Vitest tests). Each captured block's highlights are marked Captured with the same forward-only rule as a watched batch: a verified Rendered is kept, a hand-set one is overruled. The take index gets the takes too, so a later render resolves them. Where the marks go:

    • a batch started from a project file that isn't open: that project file is updated on disk;
    • a batch from the open project, or one with no project: the open queue, which becomes unsaved like any status change.

    A toast says how many clips are on disk and how many highlights were marked.

  • The capture payload sends streak_uids and project_path. project_session.js exposes getSessionPath(), and main.js only wires the factory.

  • The close-during-batch prompt's text now says Studio checks the takes when it next opens. It also no longer says the game stays open: since feat(goldsrc-hooks): the game quits itself at a batch's end when Studio isn't watching (Part of #545) #549 the hook quits the game itself.

Decisions

  • No reconnect to a running game's events pipe. There is a helper that finds the running game (running_game_pids) and its pipe name (events_pipe_name), but resuming live watching needs the batch's progress state: block correlation, markers and the ETA. That state lived in the Studio that closed, and rebuilding it is well over an hour's work. Studio waits for the game to close instead, then catches up.
  • Manifests written before this change have no highlight ids, so they cannot be mapped back to highlights. They are left untouched.
  • A batch with no project file only marks highlights already loaded in the queue. At startup the queue is empty, so in that case only the toast and the take metadata show the result.

What's left on #545

  • Part 2's reconnect to a still-running game (live progress after reopening).
  • Part 4: the dev-build job object.

Local results

  • rustup run 1.98.1 cargo fmt --all --check: clean
  • rustup run 1.98.1 cargo clippy --workspace --all-targets --exclude goldsrc-hooks --exclude web-analyzer --locked -- -D warnings: clean
  • cargo test -p desktop-studio --locked: 121 passed (new: 2 in batch_catch_up, 2 in manifest_file)
  • npm run test:unit: 381 passed (new: 9 in batch_catch_up.test.js; main_js_budget passes)
  • npx playwright test: 228 passed

User test (either install)

  1. git checkout feat/batch-catch-up-545, start Studio (npm run tauri dev from studio/, or a build). A dev build kills the game when Studio closes (part 4), so use an installed-style build (build-and-launch.ps1) for this test.
  2. Load or save a project, select 3 highlights and start a capture batch.
  3. While it runs, close Studio and pick Close DoD Studio (let it finish).
  4. Wait for the game to finish the batch and close itself.
  5. Reopen Studio. A toast says "A capture batch finished while DoD Studio was closed: 3 of 3 clips are on disk. 3 highlight(s) marked Captured. Marked them in the project ."
  6. Load the project: the 3 highlights show Captured. The activity log has a [batch-catch-up] line.
  7. Optional: reopen Studio while the game is still capturing. Nothing happens until the game closes, then the same toast appears.

🤖 Generated with Claude Code

ccoventry and others added 2 commits October 13, 2026 05:39
…nned ones

Each manifest now records the payload's highlight ids, source demos, project
file, capture mode and AGR rate. batch_catch_up checks a still-planned
batch's takes on disk at startup (or once the game closes) and rewrites the
manifest as unwatched.

Part of #545

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The capture payload sends each highlight's id and the open project.
batch_catch_up.js marks the caught-up highlights Captured, in the project
file the batch came from or in the open queue.

Part of #545

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ccoventry

Copy link
Copy Markdown
Owner Author

Merged the updated #757 in (f4cf3c9). native 670, desktop-studio 126 pass; clippy clean.

Live test by Claude, 2026-10-14 night (Brain account, POST, Seek Between Clips on). Release build from this head via build-and-launch.ps1 (needed cargo clean --release of ten packages first: their build-script outputs still pointed at the pre-rename dod-tools path), driven over CDP. Scratch project test760_project.json, m00cat's highlights 1, 5 and 7 on wsod25_grp2_h1_m00cat.

Main path passes. Started the batch; once the hook log showed dodstudio_batch start, closed Studio with the window's close button and chose Close DoD Studio. The game kept capturing and closed itself 56 s later; the manifest stayed planned. On reopening: toast "A capture batch finished while DoD Studio was closed: 3 of 3 clips are on disk. 3 highlight(s) marked Captured. Marked them in the project test760_project.json."; activity log [batch-catch-up] session session_20261014_040655 ran while Studio was closed: 3/3 takes on disk. Reloading the project: rows 1, 5, 7 are Captured, row 2 (not in the batch) still None.

Gap found: closing during the launch handoff loses the batch. On the first try I closed Studio ~12 s after Start, which was before Studio had sent dodstudio_batch start (it waits ~5 s after the pipe answers). The close prompt still said the game "keeps capturing on its own", but the game sat at the main menu with no batch for 10 minutes. Catch-up then reported it honestly ("0/3 takes on disk"). Worth either sending the start before closing, or saying in the prompt that the batch hasn't started yet. Not fixed here.

Side finding: cargo test -p desktop-studio writes [batch-catch-up] session session_a … 1/2 and session_b … 0/1 into the real activity log (log_markdown from the tests).

Base automatically changed from feat/seek-default-434 to dev October 14, 2026 10:17
ccoventry and others added 5 commits October 14, 2026 06:59
… the game

After Start, Studio sends the batch's start to the game ~5 s after the
hook's pipe answers. Closed in that window, the game sat at its main
menu with no batch, while the close prompt said it keeps capturing.

The capture loop now records where the start stands (BatchStart). On
close, a start still on its way is waited for (up to 30 s, with a
countdown and Keep Studio open). Once it is sent the usual prompt shows;
if it never gets there, or the game hasn't launched yet, the prompt says
the batch hasn't started and Cancel batch and close cancels it.

desktop-studio's tests no longer write into the real activity log:
every Scratch points DOD_STUDIO_LOG_DIR at a temp folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ccoventry

Copy link
Copy Markdown
Owner Author

Merged dev in (b3ba95c, no conflicts) and fixed the launch-handoff gap and the test-log side finding (600e720).

The handoff gap

  • The capture loop records where the batch's start stands (native::game_handoff::BatchStart, shared through CaptureManager::batch_start): preparing until the game is spawned, then pending (waiting for the pipe + the 5 s settle) or not_needed (a hook-less batch that starts on the launch line), then sent or failed. The handoff itself is unchanged: same pipe wait, same settle, same lines.
  • New command wait_for_batch_start(timeout_ms) (studio/src-tauri/src/batch_close.rs): returns the state once it's no longer pending, the batch stops, or the timeout passes (capped at 60 s). Polls every 100 ms on a blocking thread.
  • The close prompt (batch_close_prompt.js):
    • start already sent / not_needed: the usual prompt, as before.
    • pending: a wait dialog first, "The game is still starting. Studio is starting the batch in it first, so it can keep capturing after Studio closes… (up to 30 s)", counting down, with Keep Studio open. When the start is sent, the usual prompt follows, so the batch runs and the next start's catch-up marks it.
    • still pending after 30 s, preparing (game not launched yet) or failed: "Capture batch not started": "The batch has not started in the game yet, so the game cannot capture it on its own. Closing DoD Studio cancels the batch and closes the game." Buttons Cancel batch and close / Keep Studio open. Cancel goes through the normal cancel path (kills the game, manifest cancelled, so catch-up won't report 0/N), with a "Cancelling…" dialog for up to 15 s before the window closes.
    • if Studio can't read the state (IPC error) it asks as before and cancels nothing.
  • themed_confirm.js gained hideConfirm, setThemedConfirmMessage and dismissThemedConfirm for the wait dialog. main.js is unchanged (the prompt takes the IPC calls as defaults).

Test log

desktop-studio's tests wrote [batch-catch-up] … into the real log because native's cfg(test) redirect is off when another crate links it. test_support::quiet_activity_log() now sets DOD_STUDIO_LOG_DIR to %TEMP%\dod_studio_test_logs once per test binary, called from every Scratch. Checked: today's real activity log was the same size before and after a full cargo test -p desktop-studio. (The baseline run I did before the fix added one more pair of session_a/session_b lines to today's log.)

Decisions

  • The wait keeps the 5 s settle rather than sending the start early: the settle exists because the pipe opens while the engine is still starting, and closing Studio shouldn't change how the game starts.
  • 30 s for the close wait: the pipe answers early in the game's start-up, so it clears a slow launch; a start past that is treated as not started.
  • A close during patching (preparing) asks the not-started question at once instead of waiting, since the game isn't launched yet.

Local results

  • cargo test -p native -p desktop-studio --locked: native 734 passed (9 ignored), desktop-studio 167 passed (new: 4 in batch_close, 1 in test_support, 1 in game_handoff)
  • rustup run 1.98.1 cargo clippy --workspace --all-targets --exclude goldsrc-hooks --exclude web-analyzer --locked -- -D warnings: clean; cargo fmt --all: clean
  • npm run test:unit: 69 files, 601 passed (batch_close_prompt.test.js now 10 tests)
  • CI=1 npx playwright test: 297 passed

Still needs a live test by you (release build via build-and-launch.ps1, either install)

  1. Start a batch with Seek Between Clips on, and close Studio within ~10 s of Start, before the game is up. Expect the "Closing DoD Studio" wait with a countdown, then the usual "Capture batch running" prompt once the hook log shows dodstudio_batch start. Pick Close DoD Studio: the game should run the batch and close itself; on reopening, catch-up marks the clips (the main path you already passed).
  2. Same, but pick Keep Studio open during the wait: Studio stays open and the batch carries on normally.
  3. Close while the batch is still being prepared (a large batch, right after Start): expect "Capture batch not started" at once; Cancel batch and close should leave no game running and the manifest cancelled (no catch-up toast on the next start).
  4. Hard to reproduce: a game whose pipe never answers within 30 s of the close should get the same not-started prompt.

…thing

The close prompt's wait and question share one dialog. Clicking the title
bar's X again during the wait opened a second wait over the first; when
the first wait ended, its dismiss answered the second, whose own dismiss
then closed the first's question, and Studio stayed open with no answer
given. A second close now answers no at once while the first is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ccoventry

Copy link
Copy Markdown
Owner Author

Correctness review of tonight's commit 600e720 (close during the start handoff, wait_for_batch_start, batch_close.rs, game_handoff::BatchStart, themed_confirm, test log-dir redirect).

Fixed (55bcb15):

  • A second close during the wait left Studio open with nothing answered. The wait and the question share the one themed dialog. Clicking the title bar's X again during the wait opened a second wait over the first. When the first wait ended, its dismissThemedConfirm(false) answered the second wait ("Keep Studio open"), and the second's own dismiss then closed the first's question as a No. Both handlers returned false. confirmCloseDuringBatch now answers no at once, with no dialog, while one is already asking or waiting. New Vitest case (fails on the old code).

Checked and judged fine:

  • wait_for_batch_start is async with spawn_blocking, polls every 100 ms, is capped at 60 s whatever the frontend asks, stops when the batch ends or is cancelled, and answers not_running at once with no batch. The invoke has .catch(); the prompt treats an IPC failure as "sent" (asks as before, cancels nothing).
  • Countdown vs. start: Sent is stored right after the lines are sent (Handoff::JustSent), and the wait reads it on its next poll. A start that lands after the 30 s timeout shows the "not started" question, and Cancel batch and close then cancels a batch that has just begun. That's consistent with what the question says.
  • Preparing is reset at each batch start; Failed/Pending stay only until the batch ends, which is_running covers. The wait dialog has no Escape or backdrop close, and the cancel wait shows no buttons. Both waits are bounded (30 s / 15 s), so a close can't hang.
  • hideConfirm is reset on every themedConfirm call, so later dialogs get their OK button back.
  • The DOD_STUDIO_LOG_DIR redirect: test_support is #[cfg(test)], so the variable is only set in desktop-studio's test binary and never in a real run. activity_log_dir() isn't cached, so the order of tests doesn't matter.

Tests after the fix: npm run test:unit 602 passed; CI=1 npx playwright test themed-confirm running-game 13 passed. Only JS changed, so no Rust rerun. This branch still gets no CI while it is stacked.

🤖 Generated with Claude Code

# Conflicts:
#	studio/src/main.js
@ccoventry

Copy link
Copy Markdown
Owner Author

Merged dev in (dce5864): one main.js import conflict, kept dev's refreshRenderButtons import alongside this PR's createBatchCatchUp. main.js is 868/900. Locally cargo test 1860, JS unit 619, e2e 312, clippy + fmt clean; CI green.

ccoventry and others added 4 commits October 27, 2026 05:57
capture_manager.rs: verify_capture_takes keeps dev's on_checked progress
callback (#952) and still delegates to this branch's
verify_capture_takes_settling, which now takes the callback too; the
catch-up passes a no-op. themed_confirm.js: keeps both hideConfirm (#545)
and destructive (#880).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ch away (#880)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ccoventry

Copy link
Copy Markdown
Owner Author

Merged dev in, and a live test on POST: passes

Merge: two conflicts, both where #885's hold and this PR's batch_start were added as the same new parameter of spawn_capture_engine; both are kept. #885's take-verify test needed the three new manifest fields. fmt, clippy, cargo test -p desktop-studio -p native and npm run test:unit (971) pass locally; CI is green.

Live test (POST-Anniversary install, Brain account, a debug build started outside cargo so the game outlives Studio; 3 highlights of ktps8w3-dyelife_ih_anzio_h2.dem per batch, direct-to-video):

Step Result
Manifest records the highlights streak_uids (3) and project_path written, outcome planned
Close during the batch The prompt shows the new text; Close DoD Studio, then the unsaved-changes prompt; the game keeps capturing and quits itself
Reopen after the game closed Toast: "A capture batch finished while DoD Studio was closed: 3 of 3 clips are on disk. 3 highlight(s) marked Captured. Marked them in the project test760.json." The manifest is now unwatched
Project file The 3 highlights are Captured and takeIndex has the 3 takes; loading the project shows them Captured
[batch-catch-up] line In the activity log
Reopen while the game still captures (step 7) Log says it waits; the toast comes 1 s after hl.exe exits (98 s later)
Old manifests without ids Left alone, no toast

One note: the toast is the default 3 s. At startup, or minutes later when the game closes, that is easy to miss; the log line stays.

PRE not run: this PR changes nothing in the hook or the game side, only Studio.

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