Skip to content

Test OBS capture: one-click check and "highest steady FPS" search for this PC (#646) - #1067

Merged
ccoventry merged 11 commits into
devfrom
feat/obs-capture-benchmark-646
Oct 29, 2026
Merged

ccoventry merged 11 commits into
devfrom
feat/obs-capture-benchmark-646

Conversation

@ccoventry

@ccoventry ccoventry commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Release note: New: "Test OBS capture" and "Find my highest steady FPS" beside the OBS settings record a test clip of a real match and say whether your PC keeps up at your OBS Capture FPS and OBS Quality, and which setting to change.

Closes #646

Carries #1066's commits (fix/obs-encoder-speed-1060): the test is meaningless on the old encoder settings. Once #1066 merges, this PR's diff shrinks to its own changes.

What the panel does

  • Test OBS capture records 1:32-2:30 of a demo that now ships with Studio (test-fixtures/dodstudio_benchmark_anzio.dem, see below) as a normal one-clip OBS batch at the current settings. On one of your own demos it records 20 s (60 s with "1-minute clips") around the highlight with the most kills. Then it judges three things and says Sustainable / Borderline / Not sustainable with one setting to change:
    • OBS's own counters while the clip records (GetStats, own connection, 250 ms): frames skipped (encoder lag) and missed (rendering lag).
    • Frames repeated in the file because the game drew no new one in time: FFmpeg signalstats YDIF < 0.15 on a 640-wide copy ([R&D] OBS mode: what hasn't been verified live yet #645's method).
    • Sound: a track exists and isn't silence (volumedetect).
    • The game's own frame rate from the hook: a new ds_debug_record_frame_times cvar, switched on and off around the clip by two scheduled commands; average, 1% and 0.1% low, and how many frames took longer than one OBS frame.
  • Find my highest steady FPS runs the same test at 60, 120, 144, 165, 180, 200, 240, stops on a clear failure (or two shaky rates in a row), reports the highest steady rate and why the next one failed, and recommends one ladder step below it on the bundled demo ("Steady up to 200 fps at OBS Quality High on this demo. Recommended: 180 fps (one step lower, for busier maps)." plus a "Why lower" line with the anzio vs harrington numbers). When only 60 held it recommends 60 and says it's at the edge. On your own demo it recommends the highest steady rate itself. "Use N fps" uses the recommended rate; the table still lists every rate tried. OBS goes back to the user's OBS Capture FPS afterwards.
  • Every step is named on the panel's line; reading the clip has a real frame count bar; the batch itself shows on the footer as usual. The clip is deleted after.
  • Test batches use an obs_test_ session id: their takes come back as obs_test_takes_verified, so they never mark highlights captured, never replace Last Batch and never start Finish clips; the end toasts and OS notifications are skipped.

The bundled demo

  • test-fixtures/dodstudio_benchmark_anzio.dem, 6.5 MB: the first 100 s of ktps4-thunder-anzio-axis.dem, recorded by the repo owner, m00cat ("ChillRx (m00cat)"), a 6v6 KTPS4 vs Thunder match on dod_anzio, no lost packets. Player names in it are public (CLAUDE.md).
  • Cut with cargo run --release -p analysis --example trim_demo -- ktps4-thunder-anzio-axis.dem 100 dodstudio_benchmark_anzio.dem, which reproduces the tested file byte for byte (SHA-256 d57e6667def0a38fd0bb81311c54ee57aeed363784c4e045e0fcf7f746dea453). Only the end can be cut (one full entity snapshot, at the start).
  • Picked from 79 stock-map POV demos by grenades, shots, deaths and players in view: 8-12 grenades and 12-16 deaths a minute against ci_fixture.dem's 1 and 3.8, and 15-21% slower for the game to draw (timedemo, PRE and POST).
  • BUNDLED_WINDOW = 92-150 s (viewdemo clock): 1:32-1:52 has 11 grenades and 6 kills, 2:10-2:30 6 grenades and 6 kills; m00cat's own kills at 1:46 and 1:57 anchor the clip.
  • The user OK'd adding this demo to the repo and the bundle resource line in the otherwise locked studio/src-tauri/tauri.conf.json (obs-test/dodstudio_benchmark_anzio.dem, +6.5 MB to the install). .gitignore gets the matching !/test-fixtures/... exception. ci_fixture.dem stays a test fixture but is no longer bundled.
  • Checks: desktop-studio's the_test_demo_is_found_from_the_repository and new the_bundled_window_gives_a_clip (the demo's highlights fill the window); hl-demo-auditor's new the_benchmark_demo_passes_the_integrity_check (and its folder test now expects both fixtures).

Thresholds

Steady: at most 0.5% skipped or missed and 1% repeated (clean runs measured 0-0.4% and 0.07-0.37%, #645/#1060). Not steady: 5% or more, the live warning's share (lagging runs measured 32-98%).

Overlap with #1063

Both read GetStats. This PR adds ObsClient::stats / record_active near obs_version and keeps its own small BenchCounters in obs/bench.rs, so neither depends on the other; obs/mod.rs gets one pub mod line each (adjacent lines: a trivial conflict for whichever merges second). Once both are in, bench.rs could use #1063's FrameCounters instead.

Test done

After the safety margin (JS only): JS unit 933 pass (new: recommendedRate step / edge / own demo, the summary lines); Playwright full suite 413 pass (the ladder recommends 120 when 144 held, "Use 165 fps" when 180 held and the table keeps all 6 rates, own demo recommends 165 when 165 held), incl. app-boot.

Local, after the demo swap: cargo test --workspace --locked all pass; clippy 1.98.1 workspace (--all-targets -D warnings), fmt; JS unit 931 pass; Playwright full suite 412 pass (incl. app-boot and obs-capture-test.spec.js). Earlier: native obs:: 55, goldsrc-hooks frame_times 3 (i686), clippy goldsrc-hooks i686.

Live, POST, Brain via game_lock, OBS 33.0.0, 2560x1440, RTX 3070 Ti, my own Studio build with its own settings copy (user's settings.json unchanged), game behind other windows:

Run Result
Ladder, High, new anzio demo (2026-10-27) 60-200 Sustainable (0% skipped/missed, 0-0.07% repeated; game 1% low 245-266 fps above 144); 240 Not sustainable: 11.4% skipped by the encoder. 10 min 50 s for 7 rates (~90 s each)
Control, same session: wsod25_grp5_h1_m00cat (dod_harrington), 165 High Not sustainable, 6.7% skipped (6.3-6.4% before): conditions unchanged
Ladder, High, old ci_fixture.dem 60-200 Sustainable; 240 Not sustainable: 35.9% skipped
Ladder, Standard, old ci_fixture.dem 60-200 Sustainable; 240 Borderline: 3.4% skipped
wsod25_grp5_h1_m00cat (real match), High 120 Borderline (the demo's own lag: 0% of game frames late), 144 Sustainable, 165 Not sustainable, 200 62.9% skipped
same, Standard 180 highest steady, 200 Not sustainable (14.1% skipped)
PRE install, 120 High Sustainable, game 240 fps avg / 223 1% low: the hook recorder works on both builds

The anzio demo does not bring the result down to a harrington match's: on this PC it holds 200 fps at High like the bot demo did, where the WSOD25 harrington stretch fails at 165. OBS's encoder load follows what's on screen, and harrington's detail costs it more than anzio's fights. The panel's note now says a map with more detail loads the encoder more and to test one of your own demos for other maps.

fps_max (#1058's 2x rule), old bundled demo, High, repeated / game 1% low:

OBS fps fps_max = rate +10% 2x (today) 1000
120 2.2% / 119 0.34% / 131 0.06% / 222 0.06% / 233
144 2.6% / 142 0.56% / 156 0.14% / 233 0.19% / 231
165 1.4% / 163 0.25% / 178 0.21% / 231 0% / 240

2x holds: it matches uncapped within noise and beats rate and +10%; above ~120 it is uncapped in practice (the game draws 230-480 fps). No change to the rule.

The repeat count was changed after the first live clips: a plain "YDIF under 0.15" counted 23% of frames at 120 fps with the game at 240 fps, all in still stretches of 8-157 frames (the player standing still). It now counts only runs of up to 100 ms between moving frames: 0 such frames at fps_max 240, 34 (1.9% of moving frames) at fps_max 120, which matches #645's 1.5-2.1% against HLAE takes.

Decisions

  • The bundled demo is m00cat's anzio cut (user's choice, 2026-10-27), replacing ci_fixture.dem as the bundled resource.
  • The window covers both busy stretches (58 s), so a bundled test records about a minute instead of 20 s; the tooltips say ~1.5 min per rate, and "1-minute clips" only lengthens a clip of your own demo.
  • Safety margin (user's choice, 2026-10-27): the bundled anzio demo stays, and the ladder recommends one step below its highest steady rate, because the demo is lighter to encode than detailed maps (200 at High here, where a harrington match fails at 165). recommendedRate in obs_capture_test_logic.js.
  • No margin on your own demo: the test records that demo's stretch with the most kills, the busiest part of a map you actually capture, so the highest steady rate already is the worst case for it; a step lower would give up frames for nothing. The result line says the number is "on your demo (its busiest stretch)", so it isn't read as a promise for other maps. (The alternative, a step lower there too, is a one-line change in recommendedRate.)
  • The clip on your own demo is the stretch of a highlight with the most kills that fits, with equal roll on both sides.
  • The frame-time recorder lives in this PR: one hook module, three lines in commands.rs, the name in Commands.txt and console_commands_data.js.

For review

  • The ladder's result lines (ladderBest, ladderBestEdge, ladderBestOwn, LADDER_MARGIN_WHY in strings.js) and the margin on the bundled demo only.
  • The panel's copy changed (DEMO_NOTE, the two button tooltips, LONG_TITLE).
  • "1-minute clips" now matters only for your own demo: keep it, or drop it.

🤖 Generated with Claude Code

ccoventry and others added 2 commits October 27, 2026 01:27
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pture test (#646)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ccoventry and others added 7 commits October 27, 2026 01:45
…x); fps_max comparison hook; name registered since #555

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… still pictures

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…other demo…", clearer repeat wording

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated studio/src/console_commands_data.js (both sides' ds_ names).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bot demo (#646)

Bundles the first 100 s of m00cat's ktps4-thunder-anzio-axis.dem
(test-fixtures/dodstudio_benchmark_anzio.dem, cut with trim_demo, 6.5 MB)
in place of ci_fixture.dem, and records 1:32-2:30, its two busy stretches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…maps load the encoder more (#646)

The bundled window is 58 s, so a test records about a minute, not 20 s:
the tooltips' times follow, and the 1-minute option only lengthens a clip
of the user's own demo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the bundled demo (#646)

The bundled anzio stretch is lighter to encode than detailed maps (200 fps at
High on an RTX 3070 Ti, where a dod_harrington match fails at 165), so Find my
highest steady FPS recommends one ladder step lower and Use N fps uses that.
On the player's own demo the busiest stretch is already the worst case: no margin.

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 at OBS Quality Max: passes

Merge (a7ab92a): #1066 is on dev now, so this PR's diff is its own. Two conflicts:

fmt, clippy (1.98.1), cargo test -p native -p desktop-studio -p hl-demo-auditor, the hook DLL (i686 release), npm run test:unit (1001) and Playwright obs-capture-test + app-boot (11) pass locally.

Live (POST-Anniversary, Brain under game_lock, game behind windows, OBS 33.0.0, 2560x1440, the hook DLL from this branch, debug build with its own settings copy; the user's own pick, OBS Quality Max, not tested before):

Test OBS capture, 120 fps, bundled demo: "Sustainable. 0% skipped, 0% missed, 0.25% of the moving frames repeated. The game itself: 240 fps average, 1% low 236 fps, 0.1% low 199 fps. Sound: present, -0.8 dB. Nothing to change." No end toasts, Last Batch untouched, the take deleted after.

Find my highest steady FPS, Max (7 min 30 s, stopped at the first clear failure):

OBS fps Verdict Skipped Missed Repeated Game avg (1% low)
60 Sustainable 0% 0% 0.16% 120 (119)
120 Sustainable 0% 0% 0.21% 240 (225)
144 Sustainable 0% 0% 0.45% 285 (232)
165 Sustainable 0.43% 0% 0.72% 321 (230)
180 Not sustainable 6.0% 0% 6.9% 343 (236)

Result: "Steady up to 165 fps at OBS Quality Max on this demo. Recommended: 144 fps (one step lower, for busier maps)." plus the Why-lower line. OBS Capture FPS went back to 120 after the ladder; Use 144 fps set it to 144 and saved it.

Max tops out lower than High (200 in the PR's run), as expected from its I444 path.

Small thing seen: for about a second the step line read "Reading the clip: frame 0 of 0" before the frame count arrived.

… no "frame 0 of 0" while the clip's length is read

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

OBS mode: a capture benchmark — can this PC keep up with these settings?

1 participant