Skip to content

feat(android): v1.8.2 — multi-touch virtual controller + app icon - #160

Merged
doublegate merged 8 commits into
mainfrom
feat/v1.8.2-virtual-controller
Jun 20, 2026
Merged

doublegate merged 8 commits into
mainfrom
feat/v1.8.2-virtual-controller

Conversation

@doublegate

Copy link
Copy Markdown
Owner

v1.8.2 "Android" — input & the virtual controller

The input increment on the v1.8.0 platform line. Verified end-to-end on a Samsung Galaxy Z Fold 7.

Added

  • Multi-touch virtual NES controller (VirtualController.kt): one Canvas draws an NES-001 pad styled per the RustyNES icon (nes_controller() geometry + palette, pressed-state lighting) and collects input via one pointer loop that tracks every pointer by id and recomputes the full Buttons mask from the whole set — so 3+ simultaneous presses (D-pad + B + A), diagonals, and slide all work. Touch + hardware-gamepad masks are OR'd. The art and hit regions derive from the measured size, so they resize + remap in lockstep across the Z Fold 7 cover/inner displays. Light haptics (system Vibrator, EFFECT_CLICK) on each new press.
  • The RustyNES app icon for the launcher (adaptive 62%-inset foreground so the squircle keeps the corner peripherals + legacy mipmaps), replacing the placeholder.

Changed

  • Icon wordmark refresh (make_icon.py): "Rusty" now matches "NES" size (baseline kept, nudged right ~1/8 char, overlapping the cog). Refreshes assets/RustyNES_Icon/, the Android mipmaps, and the README logo — desktop icon updates too.
  • PLAY_BUILD flag (default false): the freemium engages only in the Play AAB; sideload/GitHub/dev builds are full-featured (Play launch held to v1.8.6).

Also: refreshed README image assets (+ images/archive/ -v1.5 originals). Version bumped 1.8.1 → 1.8.2; the byte-identical core keeps AccuracyCoin 100% (139/139).

🤖 Generated with Claude Code

doublegate and others added 7 commits June 20, 2026 00:21
… PLAY_BUILD

- Multi-touch virtual controller (VirtualController.kt): a single Canvas both draws
  an NES-001 pad styled per assets/RustyNES_Icon/rustynes.svg (nes_controller()
  geometry + palette: grey body, charcoal face plate, black cross D-pad, grey
  Select/Start pills, red racetrack logo, dark-red concave A/B, red labels, with
  pressed-state lighting) AND collects input through one awaitPointerEventScope
  loop that recomputes the full Buttons mask from ALL pressed pointers each event —
  giving simultaneous presses, D-pad diagonals (overlapping corner regions) and
  slide. Replaces the per-button detectTapGestures overlay that only registered one
  input at a time. Art + hit regions both derive from the measured size, so they
  resize + remap in lockstep across the Z Fold 7's cover/inner displays.
- EmulatorHandle: OR a touchMask (virtual controller) with a keyMask (hardware
  gamepad) into the single late-latched P1 mask so the two input sources don't
  clobber each other. Verified on-device: the pad renders, the D-pad lights on
  press, and the press drives SMB.
- App icon: real RustyNES icon (from assets/RustyNES_Icon/icon-1024.png) as the
  launcher icon — adaptive (foreground inset 72% over a #1C1F24 background) +
  legacy density mipmaps; drops the placeholder "RN" monogram vector.
- PLAY_BUILD flag (default false): gate the freemium (8-min timer + persistence
  locks + Billing) so sideload/dev builds are full-featured; only the Play AAB
  (v1.8.6) flips it on. Per the deferred-Play-launch plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…version bump

- Light haptics on a new button press (LocalHapticFeedback) in the virtual
  controller.
- App-icon fix: the adaptive foreground was inset 72%, so the launcher squircle
  (Samsung One UI) still clipped the icon's corner peripherals. Re-inset to 62%
  so the whole RustyNES icon (gear + controller + the four corner peripherals)
  sits inside the mask, with dark-background margin. Verified on the Z Fold 7.
- Version bump 1.8.1 -> 1.8.2 (workspace + crate pins; Android versionCode
  10802 / versionName 1.8.2); CHANGELOG [1.8.2]; README badge + row + BibTeX;
  STATUS header. README/STATUS also note the deferred Play launch (v1.8.6) and
  the PLAY_BUILD full-featured interim builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…right

Modify the parametric generator (make_icon.py) and regenerate the icon set:
- "Rusty" cap height 27 -> 35 (now equal to "NES"), with its baseline kept at the
  previous bottom (184 + 27/2 = 197.5 -> center 180), so it grows upward and
  overlaps the cog behind it (intentional).
- "Rusty" nudged right by 1/8 of a character (one char = the 35px em, so +4.375).

Regenerated assets/RustyNES_Icon/ (rustynes.svg, rustynes.ico, icon-{16..1024}.png,
preview.png) via the committed PressStart2P.ttf, and re-derived the Android v1.8.x
launcher mipmaps from the new icon-1024.png (legacy full + adaptive 62%-inset
foreground so the squircle keeps the corner peripherals). Verified on the Z Fold 7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Haptics: Compose's LocalHapticFeedback(TextHandleMove) tick wasn't felt on the
  Z Fold 7 (too subtle / gated by system touch-vibration). Switch the virtual
  controller to the system Vibrator (VibratorManager.defaultVibrator on API 31+,
  else VIBRATOR_SERVICE) firing a short EFFECT_CLICK (oneShot fallback < API 29)
  on each new button press; add the VIBRATE permission.
- Replace images/RustyNES_Logo-Icon.png (the README header logo) with the
  regenerated icon-512.png (new equal-size "Rusty"/"NES" wordmark).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New versions of the root README assets (Arch-Blueprint 1 & 2, Banner-Logo,
Emu-Compare); the previous versions are preserved under images/archive/ with a
-v1.5 suffix. (RustyNES_Logo-Icon.png was already refreshed to the new icon-512.)
The pointer loop recomputed the mask from event.changes each event, which could
miss held pointers when a new finger went down (so pressing a 3rd button dropped
the first two — e.g. SMB run+jump while moving). Now track every active pointer by
PointerId in a map and recompute the mask from the full set, so arbitrarily many
simultaneous presses (D-pad + B + A …) all register.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 20, 2026 04:59

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates RustyNES to version 1.8.2, introducing a multi-touch virtual NES controller, refreshing the app icon, and gating freemium features behind a new PLAY_BUILD flag. The review feedback highlights several critical improvements for the Android implementation: wrapping the touch input loop in a try-finally block to prevent stuck inputs upon gesture cancellation, synchronizing input mask updates to avoid race conditions between touch and hardware inputs, eliminating object allocations (like listOf, to pairs, and Paint instances) in the hot drawing path to prevent GC stuttering, and using the Float overload of hypot to avoid unnecessary double conversions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread android/app/src/main/java/com/doublegate/rustynes/MainActivity.kt
Comment thread android/app/src/main/java/com/doublegate/rustynes/VirtualController.kt Outdated
Comment thread android/app/src/main/java/com/doublegate/rustynes/VirtualController.kt Outdated
Comment thread android/app/src/main/java/com/doublegate/rustynes/VirtualController.kt Outdated
Comment thread android/app/src/main/java/com/doublegate/rustynes/VirtualController.kt Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR ships the Android v1.8.2 release updates: it replaces the previous single-touch Compose button overlay with a multi-touch, Canvas-rendered virtual NES controller, introduces the real RustyNES launcher icon, gates freemium/Billing behavior behind a PLAY_BUILD flag, and bumps release/version documentation across the repo.

Changes:

  • Add a new multi-touch VirtualController Canvas that both draws the controller and computes a full pressed-buttons mask across all active pointers (with haptics).
  • Gate Play Billing + freemium behavior behind BuildConfig.PLAY_BUILD (default false) so sideload/dev builds are full-featured.
  • Update icons, version numbers, and release documentation (README / STATUS / CHANGELOG + workspace versions).

Reviewed changes

Copilot reviewed 13 out of 50 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Bumps displayed version and expands v1.8.2 release notes in the version table and citation.
docs/STATUS.md Updates the status header summary to v1.8.2 with the new input/controller + icon + PLAY_BUILD notes.
CHANGELOG.md Adds the 1.8.2 changelog entry documenting the virtual controller, icon, and PLAY_BUILD gating.
Cargo.toml Bumps workspace/crate versions to 1.8.2.
Cargo.lock Updates locked crate versions to 1.8.2.
assets/RustyNES_Icon/rustynes.svg Updates the icon wordmark geometry (“Rusty” resized/shifted).
assets/RustyNES_Icon/make_icon.py Adjusts wordmark sizing/position parameters used to generate icon assets.
android/app/src/main/res/values/ic_launcher_background.xml Changes launcher icon background color to match the new icon styling.
android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml Switches adaptive icon foreground reference to the mipmap foreground asset.
android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml Same as above for the round adaptive icon.
android/app/src/main/res/drawable/ic_launcher_foreground.xml Removes the old placeholder “RN” vector foreground.
android/app/src/main/java/com/doublegate/rustynes/VirtualController.kt Adds the new multi-touch virtual controller Canvas (rendering + hit testing + haptics).
android/app/src/main/java/com/doublegate/rustynes/MainActivity.kt Integrates VirtualController, ORs touch+hardware input, and gates freemium behavior behind PLAY_BUILD.
android/app/src/main/AndroidManifest.xml Adds VIBRATE permission for controller haptics.
android/app/build.gradle.kts Bumps Android app version to 1.8.2 and introduces BuildConfig.PLAY_BUILD=false default.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…llocs

Adopt the Gemini/Copilot review findings directly on this branch:
- try/finally around the pointer loop so a cancelled gesture (parent intercept,
  focus loss, disposal) always clears the touch mask — no stuck buttons [HIGH].
- synchronize EmulatorHandle.setTouchMask/onKey so the touch + key masks can't
  clobber each other under concurrent updates [HIGH].
- drop per-frame allocations on the 60fps draw path: inline the SELECT/START and
  A/B draw loops (no listOf/Pair), and remember the label Paint instead of
  allocating it each draw.
- use the Float hypot overload in hitTest (no Double round-trip).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doublegate
doublegate merged commit 0aed939 into main Jun 20, 2026
22 checks passed
@doublegate
doublegate deleted the feat/v1.8.2-virtual-controller branch June 20, 2026 06:38
doublegate added a commit that referenced this pull request Jun 20, 2026
…llocs

Adopt the Gemini/Copilot findings from the v1.8.2 review (the code now lives in
this v1.8.3 branch):
- try/finally around the pointer loop so a cancelled gesture (parent intercept,
  focus loss, disposal) always clears the touch mask — no stuck buttons [HIGH].
- synchronize EmulatorHandle.setTouchMask/onKey so the touch + key masks can't
  clobber each other under concurrent updates [HIGH].
- drop per-frame allocations on the 60fps draw path: inline the SELECT/START and
  A/B draw loops (no listOf/Pair), and remember the label Paint instead of
  allocating it each draw.
- use the Float hypot overload in hitTest (no Double round-trip).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants