Repository navigation
feat(config): render at 60 fps by default and migrate the old 30 - #441
Merged
Merged
Conversation
The render loop treats daemon.target_fps as a ceiling: the FPS controller never upshifts above it and downshifts on its own after two consecutive budget misses. A shipped default of 30 pinned every fresh install to the Medium tier however much headroom the machine had; a fresh 0.7.1 install on a Mac held 30 fps with frames finishing in about half a millisecond. The default is now 60, the top tier, and slower machines stay protected by the same downshift that already guards a hand-set 60. The default lives in one constant, DEFAULT_TARGET_FPS, read by the serde default. The settings page, the configuration guide, the guide overview, and the low FPS troubleshooting page now name 60 as the default and Full as the stock steady state. Tests that asserted 30, or wrote a value equal to the new default, now pin 60 or pick 45 so each still proves a change. Config files are not touched here: a file saved by an earlier build carries target_fps = 30 explicitly and keeps it until a schema migration moves it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every config write serializes the whole HypercolorConfig, so any install that has ever saved a single setting carries `target_fps = 30` on disk and would never see the new default of 60. Schema 7 exists to move that value: loading a v6 file drops a `daemon.target_fps` of exactly 30, so the current default applies and the next save writes it. Any other value is a deliberate choice and stays, and a v7 file keeps 30 if someone picks it later. Schemas 4 and 5 shipped the same 30 default and now chain through every migration, and the v5 to v6 step stamps schema 6 itself so the chain stays explicit. Tests round-trip a real v6 save, follow a migrated file through its next save to a v7 file holding 60, keep 10, 20, 45, 60, and 120 on a v6 file and 30 on a v7 file, and walk a v4 file through all three migrations at once. The configuration guide says which files move. Bumping the schema is a one-way door for downgrades: once a v7 build saves the file, an older build refuses it as written by a newer hypercolor. The durable store inventory and its packaged declaration move to read 4 through 7 and write 7, and the refusal messages, which read the range from the schema constants, now name 4 through 7. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hand-migration test proves a schema 3 file edited onto schema 4 runs every later in-memory migration. It only carried the retired beat threshold, so nothing pinned that such a file also reaches the v6 to v7 step. The fixture now carries target_fps = 30 as well and expects the current default after the load. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The render_loop check formats the tier through FpsTier's Display impl, which prints the rate and the name, so a stock config reports `tier=60fps (full)` rather than a bare tier name. The example now matches, and the prose says what the field carries. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 17 billable files and costs up to $4.25. Or wait 14 minutes for your next included review. View limit details
Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A fresh 0.7.1 install on an M3 Pro ran its render loop at 30 fps while each frame took about 0.5 ms. The shipped
daemon.target_fpsdefault was 30, and the adaptive FPS controller never upshifts past its configured target, so every machine was capped at 30 no matter how much headroom it had. With this PR the default is 60, and the adaptive tiers keep protecting slower machines: the loop still downshifts fast on consecutive budget misses.💡 What changes
The default render target is 60.
defaults::target_fps()now reads a newDEFAULT_TARGET_FPS = 60incrates/hypercolor-types/src/config/daemon.rs.Schema 7 moves the retired 30 off disk. Every config save serializes the whole
HypercolorConfig, so any install that ever saved one setting carriestarget_fps = 30and would never see the new default. This is the same situation schema 6 solved for the beat threshold (695bb1b71), and it uses the same pattern: loading a schema 6 file whosedaemon.target_fpsis exactly the integer 30 drops the value, so the new default applies and the next save writes it. Any other value is a deliberate choice and stays. A schema 7 file keeps 30 if someone picks it later. Schemas 4 and 5 chain through every step. Git history confirms 30 was the default for the whole life of schemas 4 through 6 (it changed from 60 to 30 on 2026-03-03, and schema 4 arrived on 2026-04-25), so a 30 in those files is the serialized default rather than a choice.The durable store inventory and
packaging/managed/durable-stores.jsonnow read 4 through 7 and write 7. The refusal messages build their range from the constants. The Target FPS description in Settings, the guide, the configuration reference, and the performance troubleshooting page now say 60.Left at 30 on purpose: Servo's
DEFAULT_EFFECT_FPS_CAP(a placeholder before an effect's first frame, which also sets the first-paint stall grace), capture, WebSocket, face, and display caps, the OpenRGB bridge'sdefault_target_fps, and each device'smax_fps. Those have their own defaults and contracts.🧪 Validation
Migration tests in
crates/hypercolor-core/tests/config_tests.rs: schema 4, 5, and 6 files with 30 load as 60; a real v6 save serialized withto_string_prettyround-trips to 60; on a v6 file 10, 20, 45, 60, and 120 stay as written; a v7 file keeps 30; a migrated file reaches disk at schema 7 holding 60; a v4 file chains through the start-key rename, the beat threshold, and the render target; a v6 file keeps a beat threshold of 0.6. With the migration broken on purpose, the four tests that depend on it fail.cargo fmt --all -- --check-D warnings --all-targetson types, core, daemon (daemon withpersistence-test-hooks)cargo test -p hypercolor-typesconfig_tests,config_sources_tests,config_persistence_tests,engine_testsstartup_tests,daemon_integration_tests,durable_store_tests,mcp_tests,api_tests,--test-threads=1hypercolor-uicargo check --lib --lockedzola buildAn independent verifier reviewed the default and migration commits and passed them; its one nit (carry the render target through the schema 3 hand-migration test) is the third commit.
🤖 Generated with Claude Code