Cut idle CPU, binary size and bundled art on Windows - #2
Conversation
start.sh is the only documented way in, and it is a bash script that probes ../../_Build/linux/install and shells out to ss -- none of which applies on Windows, which is where this branch's real-machine pass is. Add RUNNING.md: per-OS prerequisites, the emulator build step that has to come first for Launch to work at all, and the three ways to start it (tauri dev, a release binary, a bundle). Records the traps that cost time here: the debug binary has devUrl baked in and dies standalone, Vite's 1421 is strictPort so a busy port fails the run outright, and discover_emulator's walk-up is what makes a dev checkout work unconfigured. Verified on Windows: npm ci, a release build, and the resulting binary all run as written.
save() wrote audio_output_device= and audio_input_device= into [GlobalConfiguration] unconditionally, so every save injected two keys into the Kyty.ini the Qt launcher shares -- and broke load_then_save_real_file_is_byte_identical, which exists to catch exactly that. bvh_stub_enabled three lines up already documents the rule these missed: a launcher-tauri addition the Qt launcher knows nothing about is written only when it is set. Empty is the default and read_from leaves String::new() for an absent key, so absent and empty already mean the same thing on load; emit the key only when a device is picked, and remove it when one is cleared. Adds a round-trip test covering both directions -- the existing one only covered the empty case, which left nothing guarding that a selected device still persists.
Measured on Windows with the window open and the user doing nothing, the launcher sat at ~17% of one core across its eight processes and never went quiet -- ~13% of that inside WebView2's GPU process. The hero Ken Burns pan, the per-tile flux ring and shimmer, and the in-game pulse dot all loop forever, composited against 34 backdrop-filter rules, and nothing anywhere paused on hide, blur, or a running game. A control build confirmed WebView2 does not throttle this on its own: minimized, it still burned 13.5% of a core. lib/idle.ts sets data-idle on the document root when the window is hidden, minimized, unfocused, or a game is running; CSS parks the looping animations and drops live backdrop-filter to a flat fill. Minimized now measures 0.2%. Only the infinite animations are parked. A blanket rule also froze the one-shot entrance animations, and those reveal their elements -- Home builds its tiles at opacity 0 and animates up with forwards/both, so pausing them left the game icons invisible. Both window signals are read from Tauri rather than inferred. Seeding "focused" as true and waiting for a blur missed a launcher that starts in the background, and minimize fires no reliable blur and leaves document.hidden false; either left it animating at ~11-14% of a core. Alongside that: - webview2_tuning.rs asks WebView2 for MemoryUsageTargetLevel::Low while a game runs, the Windows counterpart to webkit_tuning.rs. Bound to that transition only, never to focus, since Low is documented to drop caches and swap. Not using TrySuspendAsync: it requires the controller to be invisible and throws ERROR_INVALID_STATE otherwise, so it would mean blanking the window first. This path is untested at runtime -- it needs a real game launch. - A release profile (lto, codegen-units = 1, opt-level = "s", panic = "abort", strip): 27.3MB -> 19.0MB. Nothing uses catch_unwind, and cargo ignores panic for test targets, so cargo test --release still passes. - lib/contextMenu.ts suppresses the native right-click menu, which offers page navigation and "save image as" in a gamepad UI. Editable fields keep theirs so paste still works in Settings, and dev builds keep Inspect. - emulator.rs's Write import is gated to cfg(unix), where the only user of it lives; the build is warning-free again.
public/art was 12.97MB of PNG, most of it six 1024x1024 cover placeholders at ~1.5MB each. Re-encoded with libwebp: 12.97MB -> 2.61MB, and the shipped dist drops to 3.47MB. Lossy (quality 82) for the photographic art, measured against the originals rather than assumed -- SSIM 0.993/0.994 for the ambients and 0.976/0.980 for the covers. The covers carry the lowest figure and are the least exposed to it: heroArt.ts only ever resolves them with needsBlur, so they are never drawn sharp. Lossless for the two pieces of UI art with alpha, kyty_mark and controller_diagram, which are drawn at full fidelity -- both re-encode bit-exact (SSIM inf) and still come out smaller. grain.png stays PNG. It is a tileable noise texture, exactly the content lossy encoding turns into visible tiling seams, and it is 45KB. Not touched, and unreferenced by any source file: trophy_*.png (674KB) and ambient_empty.png (562KB). Trophies.tsx renders each game's own trophy icons through convertFileSrc, never these. They are 1.2MB of the remaining 2.61MB if they turn out to be dead rather than planned.
`npm run i18n:coverage` died on every run here with "SyntaxError: Unexpected token 'export'", so the guard added in 24a26b8 to stop locale gaps regressing silently has never actually run on this platform. The script strips types by regex before evaluating a catalog as a data: URL module, and the DeepPartial strip ends in `:\s*T;\n`. Git checks these files out CRLF on Windows (core.autocrlf), so that never matched `T;\r\n`, the `export type` survived the strip, and the whole module failed to parse. It reports coverage fine on Linux, which is why it went unnoticed. Normalise to LF on read, before any of the strips run.
main.tsx wraps the initial createRoot().render in try/catch, which only covers the first synchronous render. Anything throwing after mount unmounts the tree and leaves an empty document -- and on a launcher driven by a gamepad, with no devtools in a release build, there is no way out of that except killing the process. Adds an error boundary around App. It is deliberately dependency-free below the failure point: no useT(), no theme tokens, explicit colours. If the thing that broke is i18n or the theme, translating the error screen is exactly what cannot be relied on. The stack is shown rather than hidden -- every bug report this project gets asks for one, and a user cannot copy it out of a dead window.
Both .icon-button and .pill-button.primary painted an outline AND a `0 0 0 2px` box-shadow ring on focus, so every focused icon button and the Play button came up wearing two concentric rings a couple of pixels apart. Most obvious on a modal's close button and on Home's Play, the largest primary in the app. Keeps the outline in both cases and drops the duplicate. The outline follows border-radius on its own, so the inset copy on .primary bought nothing; that button's drop shadow stays, since it is the lift off the page rather than a focus indicator. Focus stays clearly visible either way -- this UI is navigated by pad, where an invisible focus ring has been a real bug before.
The community feed carries a per-OS breakdown next to its cross-platform aggregate, and parse() read only the aggregate -- zero references to "platforms". The two disagree often enough to matter: a title reported InGame on Linux can be DoesntBoot on Windows, and the launcher showed the Linux-influenced figure to everyone. KytyPS5#177 raises exactly this ("be aware of the game compatibility across platforms ... probably not, especially on macOS"). Prefer this platform's own entry, fall back to the aggregate when the feed has no breakdown for it. The entry also carries the report count and the emulator build the reports were filed against, both already in the payload and both previously dropped: one report against a build from months ago is a very different claim from twelve on the current one. `platformSpecific` says which of the two is being shown, so the UI can label it rather than leaving the user to guess. A locally-edited database has no "platforms" key at all and falls straight through to the top level, which is what it should do -- a local edit is an opinion, not a report, and is never marked platform-specific or given a count. Tests cover all three: per-platform winning over the aggregate, an entry without a breakdown falling back without claiming to be platform-specific, and a local edit staying neutral.
Nearly every issue on the tracker is a [GAME STATUS] or [GAME BUG] report, and they are all typed out by hand while the launcher sits on most of what the form asks for. Adds a Report status action that opens the upstream form with the game's title, serial, the emulator build it just probed, and the host OS already filled in -- the build field being the one a hand-written report most often gets stale. It opens the form and nothing more. A confirm step comes first, because leaving the app for an external site should not happen on a single button press, and the user completes the parts only they know and submits it themselves. Modal rather than a native confirm(): the OS dialog's buttons live outside FocusNav's DOM, so a controller-only session could not answer it -- the same reason the saved-data confirm next to it is written out longhand. Field names are the template's own `id:`s and GitHub ignores ones it does not recognise, so a rename upstream degrades to a blank form. `opener:allow-open-url` had to be granted for this: the capability listed only allow-open-path, never opener:default, so openUrl was denied and the button would have silently done nothing. Scoped to the issue tracker rather than opened up to any URL. Alongside it, in the same pane: - The compatibility badge now shows its platform, report count and tested build on hover or pad focus, rather than a permanent line of fine print under it. `.ps-focused` reveals it as well as `:hover` -- a hover-only tooltip would hide it from the input this app is designed around. - A note field under the local status dropdown. compatibility_set_comment has existed since the port with nothing calling it, so a user editing their own database could set a status and never record why. - Modal takes a `dividers` prop. The folder and image browsers scroll a list under their header and keep the hairlines; a short confirm has nothing to scroll, so there the two rules were pure chrome.
|
|
Four commits from the Windows pass.
public/artBefore/after are the same tree, with and without the change — not an estimate.
launcher-tauri: document starting the launcher off Linuxstart.shis bash, probes_Build/linux/installand shells out toss.RUNNING.mdadds per-OS prerequisites, the emulator build that has to comefirst, and the three ways to start it.
config: keep audio device keys out of an unset Kyty.iniaudio_output_device=andaudio_input_device=were writtenunconditionally, so every save injected two keys into the
Kyty.inisharedwith the Qt launcher, and
load_then_save_real_file_is_byte_identicalfailed. Now written only when a device is set — the rule
bvh_stub_enabledalready follows three lines above. Adds a round-trip test for both
directions.
launcher-tauri: stop spending CPU while nobody is watchingThe hero Ken Burns pan, per-tile flux ring and shimmer, and the pulse dot
loop forever against 34
backdrop-filterrules, with nothing pausing onhide, blur or a running game.
lib/idle.tssetsdata-idlewhen the windowis hidden, minimized, unfocused or a game is running; CSS parks the looping
animations and flattens live
backdrop-filter.Only the infinite animations are parked — a blanket rule also froze the
one-shot entrance animations, which is what reveals the tiles, so Home's
icons stayed invisible. Focus and minimize are both read from Tauri rather
than inferred, since minimize fires no reliable
blurand leavesdocument.hiddenfalse.Also here:
webview2_tuning.rsasks WebView2 forMemoryUsageTargetLevel::Lowwhile a game runs, a release profile(
lto,codegen-units = 1,opt-level = "s",panic = "abort",strip),right-click suppression outside editable fields, and
emulator.rs'sWriteimport gated to
cfg(unix).launcher-tauri: re-encode the bundled art as WebPSix 1024×1024 cover placeholders at ~1.5 MB each were most of the 13 MB.
Lossy quality 82 for the photographic art — SSIM 0.993/0.994 for the
ambients, 0.976/0.980 for the covers, which are only ever drawn blurred.
Lossless and bit-exact for
kyty_markandcontroller_diagram, which aredrawn sharp.
grain.pngstays PNG — tileable noise is what lossy encodingturns into visible seams.
Not verified
MemoryUsageTargetLevelnever executed — it needs a real game launch.has real icon art.
cargo test84/84 andcargo test --release84/84, up from 82 passed /1 failed.
tsc --noEmitclean, release build warning-free.