Skip to content

feat: diagnostics screen, native trackpad streaming, docs refresh, store screenshots, and CI fixes for 1.0.0 - #130

Merged
emir-hasanbegovic merged 25 commits into
mainfrom
chore/docs-config-comment-refresh
Jul 8, 2026
Merged

feat: diagnostics screen, native trackpad streaming, docs refresh, store screenshots, and CI fixes for 1.0.0#130
emir-hasanbegovic merged 25 commits into
mainfrom
chore/docs-config-comment-refresh

Conversation

@emir-hasanbegovic

@emir-hasanbegovic emir-hasanbegovic commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

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

  • Diagnostics screen, reachable from Settings: live telemetry for every connected controller and host, an input inspector (sticks, buttons, motion, touch) with stick drift/range/circularity health and a rumble tester, a wire-truth panel showing what is actually sent on the wire, network stats with a one-way latency sparkline, a flight recorder, and an opt-in hot-path latency benchmark.
  • Physical trackpads stream natively in USB Direct mode: a DualShock 4 or DualSense trackpad drives the satellite touchpad directly, the phone overlay is offered only for inputs without a trackpad, and touchpad routing is derived from one capability projection end to end.

Accuracy fixes (docs / store)

  • README: SDK 36 to 37, the Android Studio Otter / AGP 9.2 build floor, the exact CMake 3.22.1 pin, and the missing feature bullets (USB Direct mode, native SDL controller decoding, guided setup).
  • CONTRIBUTING and rumble.md: drop the dead docs/wire-format.md links (replaced by the satellite contract) and the renamed GamepadInputProcessor.
  • PRIVACY and play/PERMISSIONS_JUSTIFICATION: document BLUETOOTH_SCAN (declared neverForLocation, used to discover Bluetooth hosts and controllers). Play flags this permission as sensitive, so it needs a written justification.
  • THIRD_PARTY: attribute the Linux kernel input/HID drivers the USB-direct rumble and motion code reuses facts from (see the legal note below), plus the SDL Switch Pro scaling reuse.
  • docs/architecture, design-system, usb-direct-mode-followups: stale symbol names, the 17-style typography table, the navigation table, and the Xbox 360 wireless rumble status.
  • CHANGELOG: the user-facing 1.0.0 entries, now including the diagnostics screen and trackpad streaming above.
  • en-US full_description and 1.0.0 changelog: tighter, more accurate copy. play/README, REVIEWER_NOTES, and SCREENSHOT-STRATEGY updated for the Setup flow.

Store screenshots (re-captured)

  • All 144 shots re-taken against the shipping UI: 8 screens x 6 locales x 3 form factors (phone 1080x1920, 7-inch 1920x1080, 10-inch 2560x1440), dark theme, per the fixtures in play/SCREENSHOT-STRATEGY.md. 02/03 now show the guided Setup flow (the old welcome/wizard screens no longer exist), and 07_help is dropped so each form factor meets Play's 8-screenshot cap (numbering keeps a gap at 07).
  • The hero and slot-detail shots showcase the launch features: a USB Direct DualShock 4 emulating PlayStation with rumble, motion, and pad-sourced touchpad pills, plus the on-screen pad streaming to a live satellite.
  • Caveat: the strategy doc's pipeline section predates this capture run; the doc needs a follow-up refresh (the capture harness drives the real app rather than the fake-seam design it describes).

CI

  • Android CI now runs the four native C++ test suites (159 tests) via :app:nativeTest; they were wired to check but CI never called it, so the native layer had no CI coverage.
  • OWASP Dependency-Check is removed. Investigation history, in order: the 30-minute timeout was killing every full NVD sync since June 29 (raised to 90), then the root cause surfaced: a poisoned June 18 cache entry under a permanent key meant every run restored a dud database, re-synced all 346k NVD records, and threw the rebuilt database away (exact-key hits never re-save). Fixing all that still left ODC as the fifth and weakest layer covering known-vulnerable dependencies: OSV-Scanner and dependency review (exact-coordinate GitHub advisory matching, seconds per run), Grype on release artifacts (NVD + GHSA), and Dependabot already cover it, while ODC's CPE-guess matching never produced a finding here (18 of its last 20 runs timed out; the suppression file stayed empty for its whole life). Security-workflow runs drop from 30-90 minutes to about one.
  • The workflow pin-map comments match the live action SHAs again (they had drifted behind the grouped Dependabot bumps).

Play Store automation

All of it gated on a PLAY_SERVICE_ACCOUNT_JSON secret; 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.yml gains a publish-play job: after the tagged build, the signed AAB rolls out to the production track (completed status) with per-locale release notes from play/metadata changelogs 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_dispatch can 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 the featureGraphic.png layout fastlane Supply expects.
  • play-reviews.yml (weekly): recent reviews with unanswered ones flagged, plus crash/ANR vitals, in the job summary.
  • Android CI now lints play/metadata against Play's limits (text caps, screenshot counts and dimensions, graphic sizes). Its first run caught the bs short 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-committed DishScreenshots harness, one leg per form factor, cropped to Play dimensions and uploaded as artifacts arranged in the play/metadata layout for review.

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.
@emir-hasanbegovic emir-hasanbegovic changed the title docs: refresh docs, config, and store copy for the 1.0.0 launch feat: diagnostics screen, native trackpad streaming, docs refresh, and CI fixes for 1.0.0 Jul 7, 2026
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.
@emir-hasanbegovic emir-hasanbegovic changed the title feat: diagnostics screen, native trackpad streaming, docs refresh, and CI fixes for 1.0.0 feat: diagnostics screen, native trackpad streaming, docs refresh, store screenshots, and CI fixes for 1.0.0 Jul 8, 2026
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.
@emir-hasanbegovic
emir-hasanbegovic merged commit 1e10e85 into main Jul 8, 2026
8 checks passed
@emir-hasanbegovic
emir-hasanbegovic deleted the chore/docs-config-comment-refresh branch July 8, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant