Commit Graph
423 Commits
Author SHA1 Message Date
XxJSONDeruloxX 48fa1df3ba fix(stores): guard sign-in and local fixtures 2026-10-23 17:06:32 -04:00
XxJSONDeruloxX 2770a481c2 refactor(stores): share the Cargo workspace 2026-10-23 16:59:10 -04:00
XxJSONDeruloxX d80e4485ac fix(build): preserve local builds without Rust 2026-10-23 16:52:23 -04:00
XxJSONDeruloxX 9222a1fe4d feat(stores): download Amazon engine on demand 2026-10-23 16:50:19 -04:00
Kurt Himebauch 61482ffee7 perf(ci): reuse verified build inputs
## Summary

Follow-up to #1412, focused on repeat work observed in completed CI
logs.

- Give APK compilation and Android tests the same build-label and
API-key inputs. Previously `generateReleaseBuildConfig` changed between
invocations and caused Kotlin/Java recompilation before tests.
- Continue executing the Android test suite on every CI run: disable
reuse of the test task's results in CI while retaining compilation
caching. Keep the existing heap and process isolation settings.
- Cache the four remote checksum-pinned model/AAR downloads used by
add-on packs. Hash-check every cache hit, atomically replace corrupt
entries only after a verified download, and verify the destination
bytes. Packs are still regenerated from current prepared components and
their checksums remain part of the APK.
- Bound download time and retry connection resets as well as HTTP
errors; keep checksum mismatches fatal.
- Include the pack recipes in runtime source freshness checks.
- Restrict PR report updates to the workflow bot's own marker comment,
addressing the minor extraction review finding from #1412.

## Evidence and validation

The previous successful run explicitly recorded changed
`BuildConfig.java` as the reason for repeated Kotlin/Java compilation
between assembly and tests. The final #1412 normal run spent 38 seconds
building/downloading packs, 5m59s building the APK, and 3m03s in the
Android test step. A forced run also hit a connection reset while
fetching pack inputs and passed on retry.

Focused tests cover cold/warm input reuse, corrupt-cache repair,
changed-pin invalidation, rejected download bytes, actual local HTTP
connection failure/retry, identical build metadata and bot-only comment
selection. Actionlint passed. The clean Linux full suite passed 1,149
tests (18 skipped) before the final Gradle test-execution policy block.
Host, APK/ABI and Android checks are green on the final head, including
the ordinary PR run against the updated main branch and both controlled
branch attempts below.

Validation will compare cold and warm runs on the same commit, confirm
cache-hit hash verification, confirm app compilation remains up-to-date
in the test invocation, and confirm the Android suite actually executes
on both runs. Source-tree, preload, APK/ABI, all-test, signing and
publishing gates remain.

Existing Gradle, CMake and Cargo caching remains. Native compilation was
tens of seconds in the inspected baseline; no speculative native cache
changes or unmeasured speed claim are included. Android test execution
itself was about 2m30s; this PR removes avoidable setup/recompilation
rather than weakening coverage or changing concurrency.

## Follow-up correction

The first CI run failed two artwork-key tests that assumed the
compiled-in API key was empty. Keeping build inputs consistent exposed
that assumption. Tests now check the configured build key and its
trimmed fallback after clearing a user key; existing pure-function
empty-key and precedence coverage remains. No key values are included in
assertion messages. The failed run confirmed BuildConfig generation and
Kotlin/Java compilation were UP-TO-DATE in the test invocation;
subsequent full runs passed. The merged #1412 main run40007656633 passed
signing, pack publishing, preview announcement and catalog updates.

## Gradle cache audit fixes

Completed-run logs showed setup-gradle was read-only on PRs.
Same-repository PRs and branch runs can now persist their own cache
entries; fork PRs remain read-only. GitHub cache ref isolation is
retained. See [Gradle cache
guidance](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md)
and [GitHub cache
scopes](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching).
PR caches consume additional shared quota; no claim of reuse between
different PR refs is made.

COMMIT_TIME now comes from SOURCE_COMMIT, matching the provenance field
and avoiding timestamp changes from temporary PR merge commits. The
source tree and pack validation remain unchanged. This enables a
meaningful same-commit warm check. Per-commit generated Java metadata
may still cause cross-commit recompilation; moving metadata into
packaged data is a separate candidate, not part of this patch. Head
9835c4e3 passed full CI and the same-commit warm rerun below.

## Confirmed final results

Both controlled runs use exactly
9835c4e3e1 and workflow_dispatch, avoiding
PR merge-tree changes between attempts.

| Measurement | First attempt | Warm attempt | Reduction |
| --- | ---: | ---: | ---: |
| Request to required gate green | 13m09s | 7m11s | 5m58s (45%) |
| Gradle APK build step | 6m11s | 2m08s | 4m03s (66%) |
| Android test step | 2m57s | 2m08s | 49s |
| Add-on pack build step | 40s | 31s | 9s |

[First
attempt](https://github.com/Droid-Deck/DroidDeck/actions/runs/40015059221/attempts/1)
and [warm
attempt](https://github.com/Droid-Deck/DroidDeck/actions/runs/40015059221/attempts/2)
both passed. Initial queue delay was 3 seconds in each. This is one
observed pair, not a guaranteed latency or isolated attribution of every
timing difference to cache changes. The first attempt already had some
dependency/build cache reuse.

Warm logs confirm the exact head's Gradle-home cache restored; release
Kotlin and Java compilation were FROM-CACHE during assembly, then
UP-TO-DATE during tests. Assembly reported 22 tasks FROM-CACHE. The
Android test executor actually ran; test results were neither cached nor
skipped. All four remote pack input hits were hash-verified; packs were
regenerated. Runtime inputs and APK native exports/dependencies
verified. The ordinary [PR
workflow](https://github.com/Droid-Deck/DroidDeck/actions/runs/40013127993/attempts/2)
also passed against the updated main merge tree.

A fresh Gradle daemon and "no cached configuration" remain expected on a
new runner: configuration/task-graph persistence is distinct from
build-output caching and currently has no configured encryption key. The
measured Kotlin/Java FROM-CACHE results demonstrate output reuse despite
that message.

This proves same-commit warm reuse. New pushes can still invalidate
compilation through changed source, toolchain, generated provenance or
pack metadata, and cache eviction/ref scope can cause misses.
Cross-commit compilation reuse is not established by this pair. Branch
validation intentionally skips main-only publishing; merged #1412's main
run40007656633 separately passed signing and publication. The user
authorized merging after the final cache-recovery correction passed.

## Final cache-recovery correction

Commit c95e1147 addresses the remaining minor review finding: after a
download passes its SHA check, an unexpected directory at the exact
hash-named cache entry is removed before the verified file is promoted.
Invalid downloads leave that directory untouched. A regression test
covers failed verification, successful repair and subsequent warm reuse.
All five focused download tests passed in a clean Linux export. All
final-head PR checks passed in
[run40018498031](https://github.com/Droid-Deck/DroidDeck/actions/runs/40018498031):
host suite ran 1,164 tests (49 platform skips), APK build and native
validation passed, Android tests executed successfully, and the
source/build validation status is green. Benchmark results above remain
measurements of 9835c4e3.
2026-10-23 16:34:50 -04:00
XxJSONDeruloxX c95e114766 fix(ci): recover directory cache entries 2026-10-23 16:20:42 -04:00
maxjivi05 7eb4717d4c chore: drop the comments added with the Unity TSO fix 2026-10-23 16:16:10 -04:00
MaxsTechReview 0c26a829ec fix: keep FEX's TSO on for Unity games (#1415)
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.
2026-10-23 16:15:59 -04:00
MaxsTechReview 9010d275ab fix(steam): the last change to a game's compatibility tool wins (#1347)
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.
2026-10-23 16:13:28 -04:00
MaxsTechReview 36dc4014b7 fix: x86 Linux games under FEX keep LD_PRELOAD libraries given by name (#1419)
## 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.
2026-10-23 16:00:27 -04:00
maxjivi05 3e85e4a614 chore: drop comments 2026-10-23 15:51:06 -04:00
maxjivi05 a4abb797a7 Merge remote-tracking branch 'origin/main' into fix/steam-compat-pick-wins 2026-10-23 15:51:00 -04:00
maxjivi05 f909c7a69c fix: x86 Linux games under FEX keep LD_PRELOAD libraries given by name
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.
2026-10-23 15:44:35 -04:00
Kurt Himebauch 2d59f03e7b fix: preserve GPU stats while pinning clocks (#1416)
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.
2026-10-23 15:41:12 -04:00
XxJSONDeruloxX 9835c4e3e1 ci: persist PR caches and stabilize build time 2026-10-23 15:39:22 -04:00
MaxsTechReview 8f0bd51fd8 Merge pull request #1401 from Droid-Deck/perf/ntsync-fast-paths
## 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.
2026-10-23 15:10:28 -04:00
XxJSONDeruloxX 6cc289f7d3 test: avoid GPU pin scheduling races 2026-10-23 15:06:27 -04:00
XxJSONDeruloxX 9c66a1ac0a fix: select NOP packets for older Adreno cores 2026-10-23 15:04:50 -04:00
XxJSONDeruloxX 556bd96d5a ci: reuse build metadata and verified pack downloads 2026-10-23 15:01:04 -04:00
XxJSONDeruloxX 4d86839910 fix: preserve GPU stats while pinning clocks 2026-10-23 15:00:11 -04:00
maxjivi05 9438aac0db fix: droiddeck-ntsync leaves one-shot handlers alone and replays deferred signals exactly once
- 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.
2026-10-23 14:55:33 -04:00
coffeemeist3r 5d1e70cd20 Keep FEX's TSO on for Unity games
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)
2026-10-23 13:38:59 -05:00
XxJSONDeruloxX d32b1a13fd ci: verify x86 container build recipe 2026-10-23 14:34:44 -04:00
XxJSONDeruloxX d7bf0b274f ci: consolidate verified component preparation 2026-10-23 14:31:13 -04:00
XxJSONDeruloxX d111b2b3dd ci: reuse preload snapshots verified against main builds 2026-10-23 13:59:04 -04:00
XxJSONDeruloxX 8355c1495b ci: bound apt phases and retry the official Ubuntu mirror 2026-10-23 13:48:55 -04:00
XxJSONDeruloxX fcf4c09c50 test: verify dependency installation fails closed 2026-10-23 13:48:55 -04:00
XxJSONDeruloxX 74d5daaa5d ci: parallelize regression checks and isolate component caches 2026-10-23 13:48:55 -04:00
0x1Nawaf 7c51e6e904 fix: draw the gamescope cursor only when that option is on
The extra plane costs frames on a phone, so compositing stays off unless Performance asks for it.
2026-10-23 13:30:42 -04:00
Kurt Himebauch b76f13f7ac fix: keep the shader pre-caching choice until a session confirms it, and read it from its own VDF section 2026-10-23 13:05:04 -04:00
Kurt Himebauch f88227ed74 feat: toggle Steam shader pre-caching from DroidDeck's Steam settings 2026-10-23 13:05:04 -04:00
maxjivi05 77dd04ec8c fix: droiddeck-ntsync keeps protection when more than four signals arrive and keeps waits intact when a handler waits again
- 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.
2026-10-23 12:15:43 -04:00
ovurrsl 70eec3cc32 fix: a half-built steamrt shim is removed, and its launcher accepts --directory DIR 2026-10-23 12:03:41 -04:00
ovurrsl 7aeb7779bb fix: fall back to Valve's stable Steam client when the beta one keeps failing
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.
2026-10-23 12:03:41 -04:00
maxjivi05 4bcd4e3cca perf: droiddeck-ntsync without signal-mask syscalls, waits that skip the queue, and per-thread device references 2026-10-23 10:48:06 -04:00
Kurt Himebauch b7bf189166 fix: stop the authorship check from flagging people whose names contain an assistant's name 2026-10-23 08:32:52 -04:00
MaxsTechReview ac0909eaa3 perf: let Wine read process memory counters from /proc directly (#1374) 2026-10-23 07:18:56 -04:00
MaxsTechReview ffa40a15d5 perf: run Wine's arm64 SIGUSR2 context switch without a kernel signal (#1371) 2026-10-23 02:12:49 -04:00
The412Banner 449dc301bd fix(steam): keep the applied record for a game the client did not report, and apply nothing after a lost write
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.
2026-10-22 22:29:03 +00:00
The412Banner 663da2948c fix(steam): the last change to a game's compatibility tool wins
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.
2026-10-22 22:12:20 +00:00
MaxsTechReview f27d0b006f perf: Wine's write watches stop costing a signal per page on arm64 (#1333)
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.
2026-10-22 19:53:12 +00:00
MaxsTechReview 9a4a611d38 fix: Steam starts again on phones where PipeWire took every client for a sandboxed app (#1338)
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.
2026-10-22 19:30:35 +00:00
MaxsTechReviewandKurt Himebauch 006db14561 perf: SD card library files resolve through the fast path instead of proot (#1331)
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>
2026-10-22 19:11:29 +00:00
Kurt Himebauch 5461886a82 Pin CI 7zip download to verified package mirror 2026-10-22 13:54:22 -04:00
Kurt Himebauch 8c0fcd8475 Merge current main into PR #1317 2026-10-22 13:11:09 -04:00
Kurt Himebauch e4a7e52f81 Fix nested KWin rendering on Android (#1330)
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.
2026-10-22 13:10:16 -04:00
Kurt Himebauch e8a8de1b22 Merge current main into PR #1317 2026-10-22 12:13:54 -04:00
Kurt Himebauch e1a7d527fd Update the nested KWin builder to 6.8.0 (#1328)
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.
2026-10-22 12:13:03 -04:00
Kurt Himebauch 792a69ebfe Combine authorship validation with the required APK result (#1329)
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.
2026-10-22 12:11:05 -04:00
Kurt Himebauch 3a09e5698b fix: stop Steam's Steam Controller welcome tour from opening on every boot (#1321)
* fix: stop Steam's Steam Controller welcome tour from opening on every boot

* fix steam tour failure not to block helper attach
2026-10-22 11:27:59 -04:00