The default FEX preset (Performance + TSO) is fine for Unity. But when
the Default profile is switched to a preset
that turns TSO off (Performance, Extreme, Extreme-gn, Denuvo), Unity
players break. DroidDeck already keeps full TSO
for .NET games (`coreclr.dll`) whatever the preset; this gives Unity
players the same safety net for TSO.
Tested on a Pixel 10 Pro Fold (PowerVR DXT-48-1536), shared preset
Performance (TSO off):
| Game | Build | TSO off | TSO on |
| --- | --- | --- | --- |
| Megabonk | Unity 2023.2 IL2CPP, native Linux, "Linux x86 (FEX)" |
froze on about half of launches (every thread parked on a futex) | 5 of
5 starts, played run stayed responsive |
| Crab Game | Unity IL2CPP, Windows, Proton arm64 | 9 of 10 starts; 1
access violation in `UnityPlayer.dll` (read of a garbage pointer) | 10
of 10 starts |
Measured cost of TSO on Megabonk: none (+2% fps with TSO on, within
noise).
What changes:
- `droiddeck-game-env`: `unity_memory_ordering` sets `FEX_TSOENABLED=1`
when `UnityPlayer.dll`, `UnityPlayer.so` or
`GameAssembly.so` sits next to the executable (or one folder below). A
FEX preset chosen for the game still wins
(fixes sit between the shared preset and the per-game entry, as for
.NET).
- `droiddeck-fex`: the FEX tool now applies the environment half of
these game-file fixes, so native Linux Unity builds
get it too. Launch arguments some fixes add stay Proton-only.
- Docs paragraph in `docs/development/game-environment.md`; tests in
`test_game_environment.py` and
`test_droiddeck_fex.py`.
Device check of the change itself: with the session's
`droiddeck-game-env` patched, shared TSO off and no per-game
entry, Crab Game started with `FEX_TSOENABLED=1`.
Related to #1386, but not a fix for its Risk of Rain 2 crash: that game
is Unity Mono, which with the default preset
already has TSO on; the report points at multiblock instead (as the .NET
fix turns off). Both games tested here are
IL2CPP, so this PR doesn't change multiblock. #1386's other points
(`steam_appid.txt`, `device.txt`) aren't covered.
Tests (WSL, on main `16bedf36` with the patch): `test_game_environment`
+ `test_droiddeck_fex` 119 OK. Full `tools/tests`: 1114 tests, one
failure,
`test_appimage_run…test_a_failed_program_shows_the_log_it_wrote`, which
fails the same way without the patch.
Refs #1248
## Problem
Users report that changing a game's compatibility tool in Steam's game
properties doesn't stick: it goes back to DroidDeck's default. #1248 was
closed by #1250, but its reporter still saw it after clearing the game's
DroidDeck profile.
A game's tool can be chosen in two places, DroidDeck's per-game Proton
(`games.json`, written by `ProtonDefault.requestGame`) and Steam.
`plan_mapping` treats every `games.json` entry as an override that
always wins, so the live helper (`droiddeck-steam-compat`, on
`CompatManager.NotifyStateChanged`) sets it back within a second, and
`steam-compatibility` does again at every session start. A `null` entry
("use the default", also written by a profile reset) is forced as the
default, so resetting the profile made it worse.
Reproduced with the real `plan_mapping` (game 220, Steam set to
`droiddeck-fex`):
| `games.json` entry | before |
|---|---|
| none | kept |
| `null` (profile reset / "Default") | reverted to default |
| a DroidDeck Proton | reverted to it |
## Change
Whichever side changed a game's tool last wins:
- A DroidDeck choice is applied once (as now). Once applied, a different
pick in Steam stands:
- a pick of one of DroidDeck's Protons becomes the DroidDeck choice too
(`games.json` updated, so the app shows it);
- any other pick (FEX, a Steam Linux Runtime, clearing the forced tool)
removes the game from `games.json`.
- `null` is applied once and then dropped, so the game follows the
default like one DroidDeck never managed.
- What was applied is kept in `state.json` as `games_applied`. Choices
an earlier build already applied count as applied (when `state.json` is
from an earlier build), so upgrading respects the Steam picks users
already made.
- `games.json` is rewritten only if the app didn't replace it after it
was read, so a newer app choice is never overwritten.
- An x86-only Valve Proton picked in Steam is still replaced
(`unrunnable`), unchanged.
Both paths share the logic: `register_default` (session start, reads
config.vdf; with the client stopped, an app missing from the mapping
counts as cleared) and the live helper's `reconcile`.
## Tests
- `test_steam_compat_live.py` + `test_game_environment.py`: 109 pass
locally (103 existing + 6 new). The 6 new tests fail on the old scripts.
- Steam pick after an app choice is kept live, and a new app choice
applies again
- Steam pick of a DroidDeck Proton becomes the app's choice
- "use the default" applies once, then Steam picks stand
- upgrade: choices an earlier build applied give way to Steam picks
- an app choice written during a sync is not overwritten
- session start keeps a Steam pick from config.vdf
Not device-tested.
## Side note: `test_authorship` writes into the global git hooks
`InstalledHooksTest.setUp` runs
`tools/release/install_authorship_hooks.py` in a temp repo, but the
installer reads `core.hooksPath` from the **global** config. On a
machine with a global hooks path, running the suite replaced the user's
global `commit-msg`/`prepare-commit-msg`/`pre-push` with hooks pointing
into the deleted temp repo (every later commit failed), and the saved
originals were deleted with the temp repo. The test should isolate
`HOME`/`GIT_CONFIG_GLOBAL`. Not changed in this PR.
## Summary
`droiddeck-fex` kept an `LD_PRELOAD` entry only if `os.path.exists()`
found it. A bare soname that the loader resolves through
`LD_LIBRARY_PATH` doesn't exist relative to the working directory, so it
was dropped before the x86 game started. MelonLoader's bootstrap in
Drova is one example.
Names without a `/` are now passed through. Paths that don't exist and
arm64 libraries are still dropped, as before.
## Proof (REDMAGIC tablet, Stardew Valley on x86 Linux (FEX), launch
options `LD_PRELOAD=libz.so.1 %command%`)
| | before | after |
|---|---|---|
| `LD_PRELOAD` in the FEX game process | missing | `libz.so.1` |
| `libz.so` mappings in the game process | 4 (the arm64 host's) | 10
(the x86 one loaded too) |
| Game reaches its title screen | yes | yes |
## Tests
`tools/tests/test_droiddeck_fex.py` now has a bare soname in the
guest-environment case. It fails on main and passes here; all 51 tests
pass.
## Credits
Reported with the fix idea on the DroidDeck Discord by whatsupbros.
droiddeck-fex dropped every LD_PRELOAD entry that was not an existing path, so a bare soname the loader finds through LD_LIBRARY_PATH (MelonLoader's bootstrap in Drova, for one) never reached the game. Names without a slash are now kept; missing paths and arm64 libraries are still dropped.
Enabling GPU clock pinning froze Thor's kernel utilization counters,
leaving the HUD stuck at an arbitrary value (10–11% in the reproduced
sessions). KGSL_PROP_PWRCTRL disables both the governor and its
utilization sampling.
Use a dedicated KGSL context with a maximum-power constraint, renewed by
a small NOP submission every 25 ms. Keep devfreq running, bound the
queue to one submission, and release the context, mapped command buffer
and GPU allocation on stop or failure. Preserve recovery of legacy
global pins and report native setup failures instead of claiming
success.
Validation:
- AYN Thor: reproduced frozen counters with the old pin. The replacement
native implementation, run as a standalone ARM64 harness alongside the
installed Steam/Bulletstorm session, kept 680 MHz while utilization
changed from 17% in Steam to about 4% loading and 44% at the animated
title. A final-source smoke run also returned changing 60–66% samples at
680 MHz.
- Native lifecycle regression passed on Linux and as an ARM64 executable
on Thor: setup failure cleanup, renewal, bounded queue, runtime failure,
restart, and type-3/type-7 NOP selection for older/newer Adreno cores.
- Android native build and Kotlin compilation passed locally (NDK
27.0.12077973).
- CI build, Android tests, native regressions, and signing passed.
Installed the release-signed PR APK on Thor with app data preserved.
With GPU pin enabled, the normal Steam/Bulletstorm session held 680 MHz
while the HUD changed from 3% loading to 53% in the animated game scene
at 60 FPS; raw gpubusy samples also continued changing. This checks
utilization reporting, not long-term gameplay stability.
The earlier session exit was recorded by Android as LOW_MEMORY; this PR
addresses the utilization freeze.
## Summary
droiddeck-ntsync (the userspace stand-in for the Linux ntsync driver,
Wine sync = ntsync) was slower than droiddeck-esync on every cheap
operation. On the tablet a `SetEvent` cost 0.94 µs against esync's 0.21
µs, and a wait on a signaled event 0.45 µs against 0.08 µs. Four things
made it slow:
- **Signal masking:** every operation blocked and restored the signal
mask, so each call made 2 `rt_sigprocmask` syscalls, and a blocking wait
made 4. The masking itself is needed: if Wine's suspend signal stopped a
thread while it held an object lock, a GC thread that suspended it and
then touched the same object would deadlock.
- **Queueing for objects that were already free:** a wait linked itself
onto every object and unlinked again even when the first object could
simply be taken.
- **Wakes nobody needed:** a futex wake syscall went out for every
handoff, even when the waiter was still spinning and would have seen the
change anyway.
- **One shared counter:** every call took and dropped a reference on a
single process-wide device counter, so threads working on unrelated
objects bounced one cache line between cores.
### Changes
- **Signals are deferred instead of masked:** handlers installed by
Wine's `ntdll.so` run behind a small trampoline. If such a signal
arrives inside a critical section (a per-thread depth count), it is
saved with its `siginfo` and sent again with `rt_tgsigqueueinfo`
(falling back to `tgkill`) as soon as the section ends.
- `sigaction` queries still return Wine's own handler.
- Synchronous signals (SEGV, BUS, ILL, FPE, TRAP, SYS) are never
wrapped, as before.
- Handlers installed by anything other than `ntdll.so` are left alone.
- **Wait-any fast path:** an object that is signaled and has nobody
queued on it is taken directly, in index order. A wait whose timeout has
already passed returns `ETIMEDOUT` without building a queue.
- **Short spin before sleeping:** a waiter spins for up to 4 µs, bounded
by time rather than by iteration count, before it sleeps on the futex.
- **Wake only sleeping waiters:** the waiter publishes a `sleeping`
flag, and the waker only calls `FUTEX_WAKE` when it is set. Both sides
use seq_cst ordering, so a wake can't be lost. The queue layout changed,
so `NS_VERSION` is now 2.
- **Per-thread device reference:** a thread takes one reference on the
device the first time it uses it and keeps it until it exits or moves to
another device, instead of two atomics on the shared counter per call.
- **More than four signals in one section:** a fifth distinct signal is
held pending with the thread's signal mask, then released once the
section ends. Its handler never runs while an object lock is held.
- **Replay:** deferred signals are replayed with every signal blocked,
so each one is sent exactly once. A held signal is released at the same
signal-nesting level that held it. Handlers installed with
`SA_RESETHAND` are not wrapped, so a one-shot handler keeps its kernel
behaviour.
- **Waits from a handler during another wait's sleep:** they get their
own queue and, for a second device, a reference for that call only. The
outer wait keeps its queue and can't pick up the inner wait's result.
### ntsync becomes the default
- **New installs:** start on ntsync.
- **Existing installs:** move to ntsync once, on the first start of this
version, whatever sync they were on before (a one-time `ntsyncDefaultV1`
marker). Anything picked after that is kept, including esync, fsync and
wineserver.
- **Wording:** the ntsync switch in Performance no longer says
"experimental" or "for testing", in all 33 languages.
- **Unchanged:** per-game sync choices on a game's settings page still
win over the app-wide choice. Proton builds without ntsync still fall
back to droiddeck-esync, then droiddeck-fsync.
- **Tests:** `SyncBackendTest` covers a fresh install, each earlier
choice moving to ntsync once, and a later choice surviving the next
start. All 2,160 unit tests pass.
## Performance (REDMAGIC tablet)
Windows microbenchmark (`syncbench.exe`, x86-64 under FEX), µs per
operation:
| | esync | ntsync before | ntsync after |
|---|---|---|---|
| SetEvent | 0.57 / 0.70 | 0.51 / 0.65 | 0.38 / 0.35 |
| wait on signaled event | 0.13 / 0.13 | 0.44 / 0.43 | 0.27 / 0.20 |
| wait, timeout 0 | 0.27 / 0.25 | 0.52 / 0.51 | 0.19 / 0.18 |
| semaphore release + wait | 0.72 / 0.68 | 0.66 / 0.63 | 0.12 / 0.16 |
| mutex wait + release | 0.77 / 0.73 | 0.66 / 0.64 | 0.12 / 0.12 |
| wait on 8 objects | 0.10 / 0.09 | 0.59 / 0.57 | 0.15 / 0.15 |
| alertable wait | 0.06 / 0.05 | 0.43 / 0.42 | 0.07 / 0.07 |
| event ping-pong, 2 threads | 25.6 / 24.5 | 24.4 / 29.3 | 1.97 / 1.66 |
| 4 threads on one semaphore | 9.4 / 4.6 | 20.3 / 19.1 | 7.3 / 7.6 |
The backend each run actually used was checked from its file
descriptors:
- **esync:** wineserver held eventfds.
- **ntsync:** wineserver held the `droiddeck-ntsync` device and its
timerfd objects.
Native arm64 benchmark against the device directly, µs (before / after):
- set: 0.31–0.36 → 0.06–0.08
- signaled wait: 0.37–0.45 → 0.04–0.08
- semaphore pair: 0.56–0.58 → 0.06–0.07
- mutex pair: 0.56–0.61 → 0.04
- ping-pong: 15–30 → 1.7–2.5
Removing the shared device counter on its own takes 4 threads working on
their own objects from 0.21 to 0.04 µs per operation (x86-64 host).
Games, default Steam launch, The Forest and PEAK at the same scene each
run, 20 s pacing. Columns are esync, ntsync before and ntsync after;
each cell gives two runs.
| | esync | ntsync before | ntsync after |
|---|---|---|---|
| The Forest Low, fps | 64.5 / 61.9 | 70.1 / 74.8 | 72.1 / 72.1 |
| The Forest Low, CPU | 489 / 511% | 434 / 419% | 436 / 426% |
| The Forest High, fps | 58.3 / 57.8 | 72.0 / 69.8 | 72.5 / 69.9 |
| The Forest High, CPU | 506 / 504% | 417 / 429% | 425 / 435% |
| PEAK, fps | 50.4 / 48.6 | — | 47.0 |
| RDR2, launch to signed in / game process | — | — | 29.2 / 42.0 s and
28.3 / 41.1 s |
Two more games, esync against ntsync after, three runs each, default
settings:
| | esync | ntsync after |
|---|---|---|
| Among Us, menu fps (59 cap) | 59.0 / 58.9 / 59.3 | 59.0 / 59.0 / 58.7
|
| Among Us, launch to main menu | 6.65 / 6.84 / 6.85 s | 6.39 / 6.52 /
6.64 s |
| Among Us, CPU | 123 / 132 / 133% | 133 / 133 / 124% |
| Mortal Kombat X attract fight, fps | 56.7 / 54.1 / 56.3 | 61.1 / 56.9
/ 56.2 |
| Mortal Kombat X, 1% low | 20.0 / 30.3 / 30.3 | 30.3 / 30.3 / 30.3 |
| Mortal Kombat X, launch to window | 7.75 / 7.79 / 7.75 s | 7.81 / 7.71
/ 7.63 s |
Mortal Kombat X's CPU moves between 140% and 275% in both modes because
the attract mode changes fighters and stage, so it isn't compared.
The Forest's 1% low was 41.7 fps in every run. PEAK is GPU-bound and
stays in run-to-run noise. Its second ntsync run stopped at the title
menu because the rig's menu input didn't register; the game was still
running at 87 fps, and the same rig miss happened on esync before. RDR2
signs in and reaches the game on the new code.
## Tests
- **`tools/tests/test_ntsync_preload.py` (new):** builds the preload and
checks the emulation against the semantics documented for the kernel
driver:
- semaphore counts and `EOVERFLOW`;
- mutex owners, recursion, `EPERM`, kill/abandon with `EOWNERDEAD` on
read and on the next wait;
- auto and manual events, set/reset/pulse previous state;
- wait-any index order, duplicates, the alert object, `EINVAL` limits,
timeouts;
- wait-all atomicity, duplicates and alert;
- wakes across threads and across processes (inherited fds and fds sent
with `SCM_RIGHTS`);
- 20,000-round ping-pong and 4-thread semaphore stress with infinite
timeouts;
- two signal cases. In one, a handler that re-enters the same object is
fired 2,000 times at a busy thread. In the other, a handler parks the
thread while another thread uses the same object, as Wine's suspend does
under a GC.
- eight real-time signals fired in bursts at a thread busy on one
object, each handler touching that object, so more signals arrive inside
a section than can be deferred;
- a handler that waits on the same device and on a second device while
its thread sleeps in another wait;
- a one-shot (`SA_RESETHAND`) handler installed and fired 300 times at a
busy thread, every one of which must run.
- **The signal tests catch a broken implementation:** with signal
protection removed, the program hangs and the test fails. Before the
last fix, the burst test hung and the outer wait returned the nested
wait's result.
- **The suite passes:** 8 of 8 runs natively on arm64 under proot on the
tablet, and 40 of 40 on the host.
- **On the final build:** The Forest Low ran at 77.7 fps, PEAK at 47.5
fps, and Among Us and two Mortal Kombat X runs played through.
- **The new paths stay out of the way in games:** a build that logged
them saw about 1,400 Wine signals land during ntsync sleeps over two
Mortal Kombat X runs and one The Forest run. None of those handlers
waited on ntsync and no signal was held back, so games run the same code
as before the last fix. The Forest Low ran at 71.6 fps and PEAK at 47.2
fps on the final build.
- **Existing preload tests still pass:** fsync, openat2, usr2 (also with
`BL_SYNC=1`), tracer, dircache, noexec, binfmt, faultreport and esync
packs.
## Credits
- [Linux](https://github.com/torvalds/linux)
`Documentation/userspace-api/ntsync.rst` (GPL-2.0): the object, wait and
error semantics the new test checks. Also added to the README credits.
- Handlers installed with SA_RESETHAND are no longer wrapped, so a deferred one-shot signal can't fall through to the default action.
- Deferred signals are replayed with every signal blocked, so a signal landing mid-replay can't send an entry twice.
- Taking a deferral slot and holding a signal are safe when one signal interrupts another, and a held signal is released only at the nesting level that held it, so a handler's return can't block it again.
A FEX preset that turns TSO off (Performance, Extreme, Extreme-gn, Denuvo)
breaks Unity players under FEX:
- Megabonk (Unity 2023.2, IL2CPP, native Linux build, "Linux x86 (FEX)")
froze on about half of its launches, every thread parked on a futex.
With TSO on: 5 of 5 starts, and a played run stayed responsive.
- Crab Game (Windows IL2CPP build under Proton arm64) crashed on 1 of 5
starts with an access violation in UnityPlayer.dll on a garbage pointer.
With TSO on: 5 of 5 starts.
A Unity player (UnityPlayer.dll, UnityPlayer.so or GameAssembly.so beside
the executable) now keeps FEX_TSOENABLED=1 over the shared preset, the way
.NET games already keep full TSO. The FEX tool applies the environment half
of the game-file fixes too, so native Linux builds get it as well. A FEX
preset chosen for the game itself still wins.
Related: #1386 (its Mono crash happens with TSO already on)
- A fifth distinct signal inside a critical section is now held pending with the thread's signal mask and released once the section ends, instead of running its handler while an object lock is held.
- A wait from a signal handler during another wait's sleep uses its own queue and, for a second device, a reference for that call only, so the outer wait keeps its queue and can't return the inner wait's result.
The native arm64 client follows steamdeck_publicbeta, so a Valve beta
regression reaches every user at once. If the repaired beta client is still
started twice without being shown, or the beta manifest carries no arm64
client, droiddeck-steam-install now installs steamdeck_stable, keeps it, and
logs why (listing the manifest's blocks in the second case). The session asks
the installer for the channel, so -clientbeta and package/beta stay in sync.
BL_STEAM_CHANNEL or ~/.config/droiddeck/steam-channel (beta|stable) pins it.
Valve's 2026-10-15 beta moved the x86 client into a Steam Runtime container.
In case the arm64 client follows, its session now exports
STEAM_STEAMRT_RECURSION_GUARD=1 and, only if steamrtarm64 carries pv-runtime
or a steam-runtime-check-requirements probe, starts it through the same
steamrt shim the x86-64 client uses (now droiddeck-steamrt-shim) and logs it.
Review: a game the client briefly refused lost its games_applied record, so its
unchanged request later looked new and overwrote the Steam pick; it now keeps
what was applied before. When the app rewrote games.json during both tries,
the stale second read was applied; that pass now applies no per-game choice
and keeps the record.
A game's tool can be chosen in DroidDeck (per-game Proton) and in Steam's
game properties. When they disagreed, DroidDeck always put its own choice
back: the live helper within a second of the change, and again at every
session start. Resetting the game's DroidDeck profile made it worse - it
saved "use the default" (null), which was then forced onto the game.
A DroidDeck choice is now applied once, and the client's later pick stands:
a pick of one of DroidDeck's Protons becomes the DroidDeck choice too, any
other pick (FEX, a Steam Linux Runtime, clearing the forced tool) ends it.
"Use the default" and a profile reset are applied once and then dropped, so
the game follows the default like one DroidDeck never touched. What was
applied is kept in state.json (games_applied); choices an earlier build
already applied count as applied. games.json is rewritten only if the app
did not replace it meanwhile. An x86-only Proton picked in Steam is still
replaced, since it cannot start on the phone.
Wine asks for userfaultfd with the i386 syscall number on arm64, so it
never gets one and falls back to mprotect and a SIGSEGV per first write,
which under proot is a ptrace stop each time. libblsession hands it the
real userfaultfd and, where Android refuses the PAGEMAP_SCAN ioctl, answers
it from /proc/self/pagemap and UFFDIO_WRITEPROTECT.
PipeWire's access module checks each client's /proc/<pid>/root for a
Flatpak marker. Where that lookup fails under proot (seen on a nubia
NX809J), PipeWire treats the client as sandboxed and holds it until the
session manager grants permissions, and WirePlumber, itself held, never
does. gamescope then waits forever for its capture stream and never
starts Steam: a black screen until the session is closed.
The session's PipeWire now gives both of its sockets unrestricted access
and WirePlumber keeps those clients at full permissions, so nothing
depends on that lookup.
perf: files in an SD card library resolve through the fast path instead of proot
A library bound at several guest names (droiddeck-sd, bannerlator-sd and
case variants) made every symlink and /proc/self/fd readlink under it fall
back to proot. The fast path now orders its binds the way proot does and
gives each host path the name proot would, and lstat("link/.") follows
the link as the kernel does.
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
Select the explicitly configured Android render node for nested KWin 6.8, apply OpenGL settings before Plasma starts, and pin the original signed Mesa 26.2.4 package that passed Thor rendering. Remove build-directory runtime search paths and fail closed on ELF inspection errors.
Native ARM64 build passed. Thor validated a visible nested Wayland terminal, active OpenGL ES/Zink on Turnip, 1280x720 output, and keyboard input. Mesa pin regression tests, launcher and ELF failure controls, shell syntax, and human authorship checks passed. Published desktop pin remains r2; full latest-head CI is pending and maintainer authorized this merge after device validation.
Match the native builder and desktop package override to Arch Linux ARM KWin 6.8.0, pin the official source hash, and port the nested-scaling patch. Check package/source version consistency in both build jobs.
Official source checksum, patch application, live package closure, and shell syntax verified. The published desktop remains pinned to r2; candidate 6.8 device/rendering validation remains pending before any package publication.
Combine trusted source and squash-metadata validation with the exact-head APK compilation result. Preserve automatic trailer cleanup in the installed hooks.
Validated with 10 authorship/hook tests and 9 combined-gate tests.
Install commit and push hooks that remove bot co-author trailers, preserve human credits, and reject bot primary authors. Add a trusted default-branch CI checker and publish its required status on the exact proposed commit. Validate main pushes as well. Ten regression tests pass, including real Git hook tests and the --no-verify case.
* feat: tools/device-smoke.sh checks a build on a real device over plain adb
Installs and launches the app, changes the density and the window size, cycles a foldable's device state, then reads the newest debug session (first frame, last frame rate, memory page size, process exits). It restores the display and the device state it changed. No python needed; complements tools/droiddeck-scenario, which drives games.
* fix: device-smoke.sh restores only what it changed and checks more carefully
Restore the device state by cancelling the override the script made, or
by putting back the override that was already pinned (a test lab pins
one), instead of pinning the committed state. Restore density and size
only after the script changed them. Keep adb's exit status, count only
crashes of the tested app and fail when the crash buffer cannot be read,
compare the top activity after the density change, and exit 2 when an
option value is missing.
* fix: check wm density/size exit statuses and keep restore flags on failure
* fix: verify smoke-test display changes and restoration
* fix: reject false smoke-test launches and quote remote paths
* Run final display cleanup before reporting smoke results
---------
Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com>
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
The == STEP lines in session.log now read HH:MM:SS.mmm, so the setup
steps before Steam starts can be timed; with whole seconds most of them
showed the same time.
Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com>
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
* fix(gamescope): avoid retaining pointer cursor SHM commits
* test(gamescope): verify cursor release and pending window control
* test(gamescope): keep live probe inert during test discovery
* test: match Gamescope process name in cursor probe
* test: clarify cursor-role probe serial behavior
---------
Co-authored-by: siyuan.qsy <siyuan.qsy@alibaba-inc.com>
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
* feat: itch.io store, library and installs for DRM-free Windows builds (#1064)
* feat: add experimental SDK-free VITURE Pro 2 tracking
* docs: record full-app Pro 2 hardware validation
* docs: record successful Pro 2 user game test
* fix: translate Pro 2 tracking notice for all supported locales
* fix: respect model capabilities for glasses controls
* fix: handle fold configuration during glasses probe
---------
Co-authored-by: kingokksa <47888451+kingokksa@users.noreply.github.com>
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
* Run FEX-tool games on the chosen game cores
Performance > Game cores reached only the Proton launchers: games started
through "Linux x86 (FEX)" ran on every core, including the slowest cluster.
* Test that SteamVR FEX launches keep the game cores