feat: diagnostics screen, native trackpad streaming, docs refresh, store screenshots, and CI fixes for 1.0.0 - #130
Merged
Conversation
Bring the documentation, Play Store metadata, and config comments back in line with the current code, and reword a few rough spots so they read better. README: SDK 37, the Android Studio Otter / AGP 9.2 floor, the exact CMake 3.22.1 pin, and feature bullets for USB Direct mode, native SDL controller decoding, and guided setup. Drop the overstated telemetry line. CONTRIBUTING: fix the dead docs/wire-format.md link, the renamed hot-path symbol, and the Studio floor. THIRD_PARTY: attribute the Linux kernel input/HID drivers that the USB-direct rumble and motion code reuses facts from, and note the SDL Switch Pro scaling reuse. PRIVACY and the Play permission notes: document BLUETOOTH_SCAN (neverForLocation) to match the manifest, and bump the privacy date. CHANGELOG: add the user-facing 1.0.0 features (guided setup, Bluetooth host pairing, wider controller support, tri-zone cards). docs/architecture, rumble, design-system, usb-direct-mode-followups: fix stale symbol names, the 17-style typography table, the navigation table, and the Xbox 360 wireless rumble status. play: correct the visual-asset status (screenshots and feature graphics are committed, icon still pending), refresh the reviewer steps and screenshot catalogue for the Setup flow, and reword the en-US listing copy. CI: sync the workflow pin-map comments with the live action pins.
…enchmark A Diagnostics entry under Settings lists every connected controller (transport, poll rate, gyro rate, state) and host (link state, virtual controller availability, active count, epoch), plus a latency-profiling toggle. The toggle arms native instrumentation for the USB-direct hot path (URB reap to sendto) and the heartbeat round trip; off by default, each mark is a single relaxed atomic load, and turning it on shows a warning first. The persisted flag re-arms on launch. Debug and the new benchmark build type (release-grade native, debug signing) also register an adb broadcast driver that logs percentile snapshots for the device-side half of the latency budget; methodology in tools/bench/README.md.
The changelog now leads with any-controller-or-phone, names the free Satellite app and the Bluetooth host targets, and mentions the guided setup; the full description drops the awkward calques and points at the protocol docs instead of the retired wire-format page.
…uting from one projection Touch is now pad-first. The USB-direct parsers decode the DS4/DualSense two-finger touch surface and click (newest bundled frame, wire-normalized int16), and a per-device gate streams it as MSG_TOUCHPAD: edges go out immediately, moves coalesce to the motion cadence, and the final state is re-sent twice with the same event time so a lift lost on plain UDP heals. The phone-screen overlay is offered only for inputs with no trackpad of their own (and the on-screen pad); a trackpad-bearing pad on a framework path, where its touches are a system mouse the app must not hijack, carries neither. The descriptor's touchpadMode is no longer stored per slot binding: like the motion caps bit it is pulled at descriptor-build time from a single projection (TouchpadRouting.wireMode via CapabilityComposer), which gates the persisted per-satellite pick by the slot's touch source, the type's advertised modes, and the host's mouse grant. Quick-binds, USB-claim auto-binds, and slot migrations now carry the saved routing instead of silently declaring off while the dashboard claimed otherwise, and a pick change converges every bound slot through refreshCapsIfChanged. The dashboard pill, the overlay launcher, and the screen-rate readout all read the same projection per slot, so the UI can no longer promise a routing the satellite does not have; a pad-sourced slot hides the overlay so two producers never fight over one stream. The configure and setup screens sanitize the draft pick against the candidate path (replacing the hardcoded PlayStation-id check) and gate the Mouse segment on availability like the Pad segment, so a pick a host cannot grant can no longer wedge the reconcile loop on a permanent wants-vs-granted mismatch. Gone: the dead per-slot mode setters, the never-wired display default (TouchpadModeComposer and its assume-everything mode set), the unread HostFeatureSet.touchpadModes field, the unused touchpad-mode nav argument, and the duplicate helper that kept wire_encoders_test.cpp from compiling.
…nd trip The heartbeat measures a full round trip; the number a player actually wants is the one-way path, so the row now shows half the RTT p50 under a "Network latency" label, tilde-prefixed like the other approximated readouts since the split assumes a symmetric path.
…st latency window The latency warning fired on every screen open when profiling was already on: the switch listener was wired before the async store collector's first emission flipped it. The switch is now seeded synchronously before the listener exists, and the listener ignores transitions that match the store, so only a real user toggle confirms (cancel reverts silently, off never asks). The dialog buttons sat flush because the button bar reuses the filled and outlined chips, whose visible bounds cover the padding that spaces Material's transparent text buttons; dialog variants now carry a start margin, which fixes every dialog in the app. Arming the bench also clears the sample window (the persisted flag made percentiles blend every idle stretch since process start), and the latency card plus the bench README now say to read the number while playing: on an idle link, Wi-Fi power save parks the radio between the 2 s heartbeats, so the ping measures the doze schedule, not the latency games see.
… panel, flight recorder The Diagnostics screen grows the rest of the triangle: is the controller okay, is the network okay, and what happened. Tapping a controller card opens a live inspector showing exactly what the wire carries: stick crosshair plots, trigger bars, pressed buttons, gyro/accel values (and their factory scale conversion), and a touchpad surface with per-finger trails, fed by a native snapshot of the same DeviceState the send path publishes. Stick tests capture resting drift (with a suggested deadzone), reach envelope, and circularity error from a sweep; a rumble row drives the slot's actuator through the same routing rules the satellite path uses. The latency card adds polling jitter (URB inter-arrival percentiles, stream pauses excluded) and a recent-RTT sparkline; the connections section shows the Wi-Fi link (RSSI, band with a 2.4 GHz callout, link speed) and per-slot wire truth: declared type and touchpad routing against the satellite's applied and streaming state. An EVENTS section records connection and controller lifecycle transitions (attach, claim failures, replug, link state moves) into a bounded ring with a copy button; lines stay English on purpose as bug-report material. Everything new keeps the off-costs-nothing rule: the inspector's motion/touch mirror is armed only while that screen is open (one relaxed atomic load per report when off, the bench markers' budget), jitter rides the existing latency toggle, all polling is screen lifecycle bound, and the flight recorder only observes connection-state flows the UI already collects, never the input path.
…-positive pixels Pushing a stick down showed the dot moving up on every controller: the wire carries sticks in the XUSB orientation (+Y up; the DS4 and framework paths invert their down-positive sources to match, the Switch Pro's raw Y is already up-positive), but the plot mapped values straight onto screen pixels, which grow downward. The wire-to-screen flip now lives in two pure fraction mappers pinned by tests; the touch plot stays direct because MSG_TOUCHPAD is down-positive like the screen.
The controller cards stop being whole-card tappable; each carries an Inspect input button instead, and both it and the RTT sparkline are inflated from layout XML rather than constructed in code, per the project rule that views live in XML.
The :app:nativeTest task (gamepad input, wire encoders, USB parsers, HID descriptor; 159 tests) was wired to check, but CI invokes tasks individually and never called it, so the native layer had no CI coverage at all.
A cold-cache run downloads the full NVD corpus (about 346k records, roughly an hour at the throttled API rate), so the 30-minute limit cancelled every run before it finished. A cancelled job never saves the NVD cache, which kept every following run cold: the gate has not produced a verdict since June 29. One completed run repopulates the cache and brings later runs back to delta speed.
…ming Both landed on this branch after the 1.0.0 changelog entries were written, so the Unreleased section missed them.
gamepad_input.h declares formatDeviceStateJson (added with the diagnostics inspector) using size_t without including a header that defines it; MSYS2 g++ and NDK clang provide it transitively, ubuntu's gcc does not, so the native tests' first CI run failed to compile. usb_parsers.h had the same latent gap, covered only by its gamepad_input.h include.
144 shots (8 screens x 6 locales x 3 form factors), replacing the 2026-05-28 set that still showed the deleted welcome carousel and setup wizard. 02/03 now show the guided Setup flow's input and connection steps; 07_help is dropped so each form factor meets Play's 8-screenshot cap, leaving a numbering gap at 07. Fixture data per play/SCREENSHOT-STRATEGY.md: Gaming PC live plus one saved satellite, virtual pad at 91% and a USB Direct DualShock 4 at 78% emulating PlayStation (rumble, motion, pad-sourced touchpad), two saved Bluetooth hosts on the connections screen, dark theme. Captured on Pixel-9-class (1080x2424 API 36) and Pixel-Tablet-class (2560x1600 API 35, rebooted at 1920x1080 density 240 for the 7-inch set) emulators, center-cropped to 1080x1920 / 1920x1080 / 2560x1440.
Catches listing content the Play Console would reject at upload time: text over the per-field caps, screenshot counts outside 2 to 8, sides outside the allowed range or off 16:9, wrongly sized feature graphics or icons. Missing optional assets warn instead of failing. Zero dependencies; PNG sizes come from the IHDR chunk. Its first local run caught the bs short description at 81 chars.
A publish-play job after the build uploads the bundle with per-locale release notes from play/metadata changelogs and the R8 mapping, as a draft on the internal track by default (workflow_dispatch can pick the track and status). No promotion or staged-rollout automation by design. The job no-ops until PLAY_SERVICE_ACCOUNT_JSON exists and skips unsigned bundles; the app record and its first build must still be created manually in the Play Console before the API can publish.
Play Listing Sync (dispatch, validate-only by default) pushes the committed per-locale text, screenshots, feature graphics, and icon via fastlane supply, flattening the named feature-graphic files into the featureGraphic.png layout Supply expects. Play Reviews Digest (weekly schedule) writes recent reviews, flagging unanswered ones, plus crash and ANR vitals to the job summary. Both no-op gracefully until PLAY_SERVICE_ACCOUNT_JSON exists and the app is published.
The instrumented test that produced the committed 1.0.0 screenshot set. It drives the real app with the SCREENSHOT-STRATEGY fixtures: no production seams, no Hilt test components. Persisted state seeds through real store APIs reached via activity injection fields; runtime state (a Live satellite session, a USB Direct DualShock 4) pushes into the root MutableStateFlows by reflection; the two dashboard shots freeze the ViewModel state so reconnect churn and scan spinners cannot land in a frame. Captures go to filesDir/screengrab/<testLocale>/ for the runner to pull.
One emulator leg per form factor (phone, 7-inch, 10-inch) forced to the strategy doc's resolutions and densities via wm overrides, looping the six listing locales through the DishScreenshots harness, cropping to Play's dimensions, and uploading artifacts pre-arranged in the play/metadata layout for human review before committing.
android-emulator-runner executes every script line as its own shell, so the inline for-loop died at its first line. The sequence now lives in scripts/ci_capture_screenshots.sh invoked as one command, and gates on the instrumentation summary because am instrument -w exits 0 even when tests fail.
Tagged releases now default to the production track with a completed rollout instead of an internal draft, and the same supply call syncs the whole store listing (text, screenshots, feature graphics) after a metadata lint. workflow_dispatch keeps the track and status overridable for cautious runs. Requires the service account to hold the Release-to-production permission.
An exact-key cache hit never re-saves, so the permanent key pinned a 121 MB June 18 dud forever: every run restored it, found no usable database inside, did a full 346k-record NVD sync, and threw the rebuilt database away. The key now carries the ISO week so the first completing run each week saves a fresh database; later runs restore a seven-day-max copy that validForHours accepts without syncing, and restore-keys still seed delta updates across week or dependency-hash boundaries. The stale entry itself was deleted from the cache.
Redundant with the four other layers that cover known-vulnerable dependencies: OSV-Scanner and dependency review on every PR (GitHub advisory data with exact Maven coordinates), Grype against the release artifacts (NVD + GHSA), and Dependabot updates. ODC was the most expensive and least reliable of the five: its NVD mirror sync burned 30 to 90 runner-minutes per run, 18 of its last 20 runs died at a timeout, and its CPE-guess matching never produced a single finding (the suppression file stayed empty for its whole life). Drops the gradle plugin, the workflow job, the suppression file, and the NVD_API_KEY plumbing.
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.
What this does
The last branch before tagging 1.0.0. Refreshes the docs, Play metadata, and config comments to match the code; adds the Diagnostics screen and native USB Direct trackpad streaming; re-captures the full Play screenshot set for the shipping UI; and closes two CI gaps found while auditing the launch (the native C++ tests never ran in CI, and the OWASP Dependency-Check job could never finish a full NVD sync).
Highlights
Features
Accuracy fixes (docs / store)
docs/wire-format.mdlinks (replaced by the satellite contract) and the renamedGamepadInputProcessor.BLUETOOTH_SCAN(declaredneverForLocation, used to discover Bluetooth hosts and controllers). Play flags this permission as sensitive, so it needs a written justification.Store screenshots (re-captured)
02/03now show the guided Setup flow (the old welcome/wizard screens no longer exist), and07_helpis dropped so each form factor meets Play's 8-screenshot cap (numbering keeps a gap at 07).CI
:app:nativeTest; they were wired tocheckbut CI never called it, so the native layer had no CI coverage.Play Store automation
All of it gated on a
PLAY_SERVICE_ACCOUNT_JSONsecret; the app record and its first build must still be created manually in the Play Console before any API publishing works. Track promotion and staged rollouts are deliberately NOT automated: a tagged release goes straight to production at 100%.release.ymlgains apublish-playjob: after the tagged build, the signed AAB rolls out to the production track (completed status) with per-locale release notes fromplay/metadatachangelogs and the R8 mapping (native symbols already ride inside the AAB), and the same call syncs the full store listing (text, screenshots, feature graphics) after a metadata lint.workflow_dispatchcan pick a different track or a draft status for cautious runs.play-listing.yml(dispatch, validate-only by default): syncs listing text, screenshots, feature graphics, and the icon from the repo to the console, flattening the named feature-graphic files into thefeatureGraphic.pnglayout fastlane Supply expects.play-reviews.yml(weekly): recent reviews with unanswered ones flagged, plus crash/ANR vitals, in the job summary.play/metadataagainst Play's limits (text caps, screenshot counts and dimensions, graphic sizes). Its first run caught thebsshort description at 81 chars, one over Play's cap; fixed in this PR.store-screenshots.yml(dispatch, plus a self-test on edits to its own file): re-captures the full screenshot set on CI emulators using the now-committedDishScreenshotsharness, one leg per form factor, cropped to Play dimensions and uploaded as artifacts arranged in theplay/metadatalayout for review.