Repository navigation
Fix/destselect touch version - #1
Merged
Merged
Conversation
…ot-plug auto-hide, layout editor
- touchhud.{h,cpp}: per-frame context publishing (Hidden/OnFoot/Climbing) + JNI input bridge
- HudBridge/HudController/TouchHudView: overlay with fades, multi-touch, safe areas,
layout editor (long-press), opacity/low-contrast, haptics, JSON persistence
- Physical gamepad hot-plug hides/shows HUD automatically (real-gamepad source check)
- Stable committed keystore for debug+release (no more signature churn between CI runs)
… bypasses view dispatch)
…n, door NIS trigger logging
The real PSX WORLDPTS door scripts (shadowing the compiled reconstructions at runtime) use top-level opcode 0x3E with a humanoid-hash operand to walk the player into the doorway via NisControl. The recomp only knew 0x3E as a DoorFunc sub-command, so the interpreter hit the unknown-opcode path and blocked forever at the same scriptPtr — door never opened (stuck NIS, actor reload loop). Implement it as AttachToDoor with the door taken from g_codeSnipThing (set by SetCodeSnip when the door collision triggers the NIS).
…, title tap = Start - AndroidInput: first-finger touchscreen tracking feeds androidbridge touch state - glesDisplay (Android): IsMouseButtonDown/GetMousePosition return touch state, so PlatformInput/ActionInput/the engine's own menu code handle touch natively (entry hover, click-to-activate, sliders, scrollbars - full PC mouse parity) - touchhud: tap on the 'press any button' title screen pulses Start - HudController: FLAG_NOT_TOUCHABLE pass-through in Menu/Hidden contexts (taps reach the engine); gameplay contexts keep the overlay controls
…Input touch handling, glesDisplay mouse stubs)
… (IsMouseButtonDown)
…via Confirm() — no mouse emulation
…DEX_MASK (0xff00) instead of ACTION_MASK (0xff)
…t taps already activate, prevents double Confirm
…se isn't ignored on touch-only devices
…engine, movie skip button - Multi-touch slots in androidbridge (4 pointers) driven by AndroidInput - touchcontrols module: contextual controls (OnFoot full / Climbing reduced), fade transitions, virtual gamepad via androidbridge (same path as physical pad) - Render via ScreenDraw + TextManager labels in Display::EndFrame (on top) - Movie cutscene skip button (Start pulse) rendered+processed in PlayMovie loop
…orrect render-target state), center labels via TextAlign_Center/SCREEN_SCALE
Root cause of the invisible 'Pular' / A / B / X / Y labels: ScreenDraw::DrawFilledCircle takes (centerX, centerY, radiusX, radiusY, u0, v0, u1, v1, segments, r, g, b, a). The previous call passed (x, y, r, r, 255, 255, 255, alpha) — the trailing 4 args landed in the u0/v0/u1/v1 UV slots, leaving the color at its default (255,255,255,255) (opaque white). White text on an opaque white circle was camouflaged, so the labels looked like they were rendered behind the button. Fixes: - Add TouchColor struct + dark translucent palette (kFill, kRing, kKnob, kSkipFill, kSkipRing, kText) plus FadeAlpha() helper that scales the alpha channel by the current HUD fade. - Add FillCircle()/RingCircle() wrappers that pass all 13 positional args explicitly, so the color actually reaches the (r,g,b,a) tail. - Render() and RenderMovieSkip() now draw a dark filled circle + light ring + white label on top, matching the PPSSPP/AetherSX2 overlay look. - Render() reorganized into two passes: pass 1 draws every visible control's background (filled circle + ring + joystick knob); pass 2 draws the labels. Guarantees consistent ordering regardless of how the text backend's texture changes trigger pending-batch flushes. Missing HUD on Destination Select (and other menus): - touchcontrols::Update() only set s_visible for OnFoot / Climbing. GameState::LocationMenu -> HudContext::Menu -> HUD hidden. - IsControlInteractive(ci, climbing) refactored to take HudContext; Menu context now exposes joystick (d-pad nav) + A (confirm) + B (back) + Start (pause/title-confirm). X/Y/LB/RB are hidden. - Update() now includes HudContext::Menu in wantVisible, so the contextual HUD appears on the title screen and destination select.
Root causes found from user screenshots + log: 1. Character moved by itself / buttons dropped while held: LoadTouchPoints() returns a COMPACTED array (only active points), so indices shift whenever another finger lifts. Tracking joystick/buttons by array index made the joystick grab the wrong finger after a lift (e.g. the A button finger -> character walks alone) and made held buttons release spontaneously. Track pointers by their stable Android pointer id (TouchPoint.id) instead. 2. Skip button looked like an empty 'ball' during cutscenes: during PlayMovie the FE fonts are usually not loaded, so the text-only 'Pular' label never rendered, leaving a bare circle. The button now draws a font-independent '>>' fast-forward glyph (two batched triangles) inside the circle, with 'Pular' as an optional caption under it when a font is available (MeasureString > 0 guard). 3. Skip button shown on non-skippable movies: PlayMovie(skippable=0) (logos, credits) rendered a fake skip button. Add SetMovieSkippable()/EndMovieSkip(); the button only shows and feeds input for skippable movies. 4. Virtual Start stuck down after a skip: DoSkip() posted Start down and never released it, leaking into the next state (instant pause / skipping the following movie). EndMovieSkip() releases it after the movie loop. A finger that already skipped is ignored until it lifts (no re-trigger while held). 5. Start button showed a broken '?' glyph: the '≡' label is not in the PSX bitmap font. Replace it with a 3-bar hamburger glyph drawn as batched rects (no font dependency). 6. Layout: Y button was clipped at the right edge (0.965 -> 0.950); L1 moved from mid-left screen to the action cluster (0.845, 0.455) above the diamond so combat inputs sit together; Start slightly larger (0.042 -> 0.048); skip button lowered (0.05 -> 0.075) so the circle is not cut off by the top edge.
The engine was treating the finger's touch-as-mouse position as 'desktop input', which cleared gamepadActive every frame the user dragged a finger — including on the virtual joystick itself. The virtual gamepad and the phantom 'keyboard/mouse' fought each other: analog controls froze, prompts flipped to keyboard glyphs, buttons got scrambled by the control-map remap, and menu drags switched to cursor mode and gated pad navigation off. - inputaction: on Android there is no desktop to switch to — the virtual gamepad is the primary device; touch-as-mouse no longer counts as keyboard/mouse input - fecustommenumgr: mouse hover/cursor mode permanently off on touch-first devices (taps via ProcessTouchTaps, nav via pad) - androidbridge: virtual pad connected from boot so analog mode and gamepad prompts are active from the first frame - main loop: touch controls feed the pad BEFORE ServiceInput so touch hits land in the same frame, not one frame later - AndroidInput: require full AINPUT_SOURCE_TOUCHSCREEN source so pointer-class devices (BT mouse/stylus) are not treated as fingers
Root cause of the intermittent 'analog stuck + all buttons dead': FindTouchSlot() searched the COMPACTED LoadTouchPoints() array, so when slots had holes a MOVE wrote a finger into the WRONG bridge slot, duplicating its pointer id; the leftover copy stayed active forever (zombie). Zombies held s_joyPointer (joystick frozen at its last deflection = character moving alone) and filled all 4 slots until new touches were silently dropped (every button dead). - androidbridge: new real-slot helpers FindTouchSlotById / AcquireTouchSlot (self-heals stale same-id entries) / ClearAllTouchPoints; TouchPoint gains a per-acquire 'seq' so a recycled Android pointer id can never keep holding a control. - AndroidInput: DOWN/MOVE/UP now address real slots; MOVE self-heals missed DOWNs; ACTION_CANCEL clears the WHOLE gesture (it aborts all pointers, not one). - AndroidShell: reset touch state on PAUSE/STOP/LOST_FOCUS/TERM_WINDOW (Android never delivers the missing UPs when fingers are taken away). - touchcontrols: match controls by (pointer id, seq); dedup duplicated ids defensively; movie-skip pointer also seq-guarded.
It was defined inside 'namespace {' (internal linkage) so AndroidShell.cpp
could not resolve it. Now declared up front and defined at file scope.
The PSX original ran every texture through SetTransparentTim (LOADERS.CPP:460) at load time, which rewrote the magenta key colour (0x7C1F) in the CLUTs to 0x0000 so the GPU skipped those texels in every draw mode. That step was never ported, so raw magenta leaked to the screen wherever textures use magenta as the transparency key: - semi-transparent world geometry (windows, banners, decals, pickup sprites): the 3D shader only discarded magenta on opaque draws (uUseZeroTexelKey==0); blended draws used zero-texel keying only - decoded VRAM pages used by BackG sprites (PsxToRGBA keyed only 0x0000) - CELL images / TIM images / bitmap font palettes (PsxAbgr1555ToRgba8888 set alpha=255 for every colour != 0) Fix: discard/key 0x7C1F (either STP bit) unconditionally in all four VRAM/CLUT shaders (GLES + desktop GL, colour + shadow depth passes), in PsxToRGBA (world.cpp) and in PsxAbgr1555ToRgba8888 (psxcolor_helpers.h), reproducing the hardware behaviour.
The Game Over screen (GameState::EndGameLoop, 'Press A to continue') had
no touch path: the HUD context was Hidden (no virtual buttons) and the
touch-as-mouse bridge was disabled on Android, so touch-only players
were stuck and needed a physical gamepad.
- ComputeContext: EndGameLoop now maps to HudContext::Menu, so the
virtual joystick/A/B/Start set is available on the Game Over screen.
- PublishFrame: generalizes the title 'tap = Start' pulse into a
'tap = the button this screen waits for' pulse:
* TitleLoop -> Start (unchanged)
* EndGameLoop -> A (Game Over continue)
* Intro legal splash -> A (AnyJustPressed skip; PC skips with
any mouse button, touch had nothing)
The confirm pulse is gated on the fade state so taps during the
Game Over fade-out don't queue dead input.
- PublishFrame drains the pending position-carrying tap whenever no
custom menu is active: previously it stayed queued and resurfaced as
a phantom click in the next menu to open (taps on the Game Over
screen clicking a Location menu entry right after the fade, HUD taps
during gameplay clicking the pause menu opened later).
- game.h: public IsGameOverFading()/IsIntroSplashActive() queries.
Connecting a Bluetooth/USB pad now takes over as THE input: the on-screen touch HUD (joystick, action buttons, movie-skip button) hides, and its virtual gamepad input path is suspended so palm brushes on the glass can't fight the real pad. When the last pad disconnects, the HUD comes right back. Detection (reliable connect AND disconnect): - GameActivity now implements InputDevice.InputDeviceListener, registered in onCreate (rescan in onResume, unregistered in onDestroy). Every add/remove/change rescans all devices for real gamepads (SOURCE_GAMEPAD / SOURCE_JOYSTICK with actual stick axes; virtual devices, keyboards and DPAD-only TV remotes are excluded) and pushes the result through HudBridge.nativeSetPhysicalGamepad -> JNI -> androidbridge::SetPhysicalPadConnected (new atomic flag). - Native self-confirm: gamepad-SOURCE key/motion events in AndroidInput.cpp also set the flag (DPAD-source remote navigation is excluded on purpose - remotes can't play analog, the HUD must stay). Engine consumption: - touchcontrols::Update(): wantVisible is gated on !IsPhysicalPadConnected(); ResetAll() now runs on EVERY visibility transition so fingers holding the joystick/buttons release immediately when the pad connects (instead of driving the game through the fade-out), and touch processing is fully skipped while hidden. - UpdateMovieSkip()/RenderMovieSkip(): the on-screen skip button is part of the HUD - hidden and inert while a pad is connected (Start on the pad skips the movie). - touchhud::PublishFrame(): the tap-to-continue pulses (title 'press start', Game Over 'press A', legal splash skip) are suspended while a pad is connected; the real pad's own Start/A drives those screens. Direct menu taps stay enabled with a pad connected (mixed input is convenient and harmless). [TouchDiag] log now includes physPad=. The new flag is completely independent from PostGamepadConnected: the engine's gamepad MODE still stays on (touch-first boot default), only the HUD visibility follows the physical pad.
The device listener API lives on android.hardware.input.InputManager (instance obtained via getSystemService(INPUT_SERVICE)), not on android.view.InputDevice — the first CI run failed to find InputDevice.InputDeviceListener / registerInputDeviceListener. Same registration pattern as the (disabled) HudController: register in onCreate with a null handler (callbacks on the main-thread Looper), rescan in onResume, unregister in onDestroy. Java now syntax/semantics-checked locally with ecj against minimal Android API stubs (scripts/javastub) before pushing.
Xiaomi/MIUI exposes fingerprint readers (uinput-fpc, uinput-goodix, uinput-synaptics, uinput-elan, uinput-vfs, uinput-atrus) as input devices whose sources mask lies: SOURCE_GAMEPAD|SOURCE_JOYSTICK are set while isVirtual() returns false (documented in Godot #47656, libgdx #5596, Unity forums, GameMaker on Redmi). The pad-presence scan saw one of these at boot on every Xiaomi/Redmi/POCO phone, so IsPhysicalPadConnected() stuck true and the contextual touch HUD never appeared. Fix, engine + Java working together: - GameActivity: skip devices named uinput-* in the physical-pad scan (the filter the whole industry converged on); push the ghost device ids into the engine alongside the presence flag. - androidbridge: lock-free ghost-id blacklist (single Java writer, ids-then-count release ordering; input thread reads a consistent prefix). - AndroidInput: swallow events from ghost device ids entirely - they can no longer confirm pad presence nor inject phantom buttons/axes (fingerprint swipe gestures would otherwise drive the virtual stick). - HudBridge/touchhud: nativeSetGhostPadDeviceIds JNI bridge. Real Bluetooth/USB pads are unaffected; TVs remotes/keyboards still don't count; virtual pads (scrcpy-style streaming) still confirm presence via events, which is correct.
…ame Over/title
The Level Results screen ("Press [A] to continue", GameState::EndLevelLoop)
had no touch path: it fell into ComputeContext()'s default -> HudContext::
Hidden, so the virtual buttons never showed and touch-only players were
stuck the same way they were on the Game Over screen before.
- touchhud ComputeContext: EndLevelLoop -> HudContext::Menu. The tally
already accepts exactly what the navigation set provides - A held/
pressed = fast-forward the count (ACTION_MENU_CONFIRM, hdTally::Update)
and A/Start = confirm the "done" prompt (hdTally::DoDoneStuff) - and
this state renders through MenuDraw -> MenuRender -> HUD::Display, so
the on-screen controls draw automatically.
- touchhud PublishFrame: tap-anywhere = A pulse on EndLevelLoop, matching
the Game Over / legal-splash behaviour (tap during the count fast-
forwards it, tap on the prompt leaves the screen).
- game.cpp gsEndGameLoopState: call touchcontrols::Render() on Android.
The Game Over screen renders on its own path (no HUD::Display), so the
virtual A/Start buttons were ACTIVE but INVISIBLE; touch players only
had the blind tap-anywhere pulse.
- game.cpp gsTitleLoopState (title menu active): same - the title menu
renders via its own path, draw the touch controls explicitly like the
pause menu does via CustomMenuRender.
Full state audit (all 27 GameStates + hdTally/hdDestSelect/movie loops):
every other screen already has a touch path - custom menus take direct
taps, PlayMovie has the skip button, press-start/Game Over/splash have
tap pulses, loads and transitions take no input. ErrorLoop is a latent
(non-touch) dead-end, unreachable through normal play.
Tapping an adjustable row (Frame Rate, Anti-Aliasing, Language, volume sliders, ...) ran Confirm(), which is a no-op for most List bindings - the value could only be changed with the virtual D-pad. Adjustable rows now render their value flanked by '<' and '>' arrows. The arrows' screen positions are measured during RenderCurrentPage() (TextManager::MeasureString) and recorded as tap zones that ProcessTouchTaps() consumes: tapping '<' steps the value down, '>' steps it up (same Adjust() path as MENU_LEFT/RIGHT, including the staged commit flow for Resolution/ScreenMode/MSAA). Tapping elsewhere on the row keeps the previous select+confirm behaviour. Applies to every adjustable row across Display, Sound, Controller and Cheats pages (List, Slider and Toggle entries). Desktop/other builds render and behave exactly as before.
Custom menu pages that direct touch already covers (title, options, settings, cheats, confirms, pause, location) no longer show the on-screen joystick/A/B/Start overlay - taps and < value > steppers drive everything, so the pad was pure visual clutter. The pad stays on pages that taps cannot fully operate: Key Bindings, Mods and the save-slot tables (scrolling + non-generic row layouts need virtual D-pad navigation), plus Changelog on updater builds. Non-interactive Menu-context states (press-start, Game Over, Level Results, transitions) also hide the pad: the existing tap-anywhere Start/A pulses cover them, and a physical pad is unaffected.
Anti-Aliasing in Display Settings always reverted to Off on Android because glesDisplay::SetMSAA was a no-op and GetMSAA() always returned 0; Display::SetMSAA() reads the value back, so every commit reset the setting (and saved 0 to the config). The EGL config cannot change while the GL context lives (that would destroy every GL object), so AA is now done through an offscreen multisampled framebuffer: each frame renders into a window-sized multisampled FBO (RGBA8 + depth24/stencil8 renderbuffers) and is resolved onto the window framebuffer with a blit right before the swap. Sample count changes apply instantly from the settings menu; the request is clamped to GL_MAX_SAMPLES with graceful fallback (halve samples, then off). GetMSAA() reports the count actually applied so the menu and the saved config stay honest. Also tags the app as a game (android:appCategory="game" + android:isGame) so launchers, Game Mode and Play Games recognise it.
…ings Anti-Aliasing was one of the three staged bindings (Resolution, Screen Mode, MSAA): the arrows only staged a pending value and it took a separate confirm tap on the value text to commit. Touch users never discovered the confirm step - they tapped the arrow, saw the new value, and watched it snap back to Off as soon as they moved to another row or backed out of the page, because every staging cleanup path (MoveCursor/SetPage/row-tap/mouse-leave) discards pending values. The staging made sense when changing AA could not take effect at runtime, but the GLES backend now switches its multisample framebuffer on the fly (safely clamped to what the device supports), so MSAA is applied the moment the arrow is tapped - exactly like Frame Rate and V-Sync. The displayed value is the read-back effective sample count, so the row always shows what is really active, and ApplyValue persists it to the config immediately. Resolution and Screen Mode keep their staged flow (those changes can blank the display and still deserve a confirm step). Desktop is unaffected beyond sharing the menu code: glDisplay::SetMSAA only toggles GL_MULTISAMPLE.
Three fixes for the fresh-install experience (data cleared, no save): 1. Destination select (Level N / grade / dragon grid) is now tappable: the page is fully custom-drawn and its single Confirm entry is invisible, so the generic hit-test left a thin uncued strip in the middle of the panel - touch players read the screen as dead and got stuck (no pad shown either). A tap anywhere on the panel now selects; the bottom prompt row works as drawn (right half, where the 'B Back' prompt sits, backs out to the hub). 2. The contextual HUD never appeared on a fresh save's first hub visit. gsOpenFEState called Activate(MenuPage_Frontend) and then handed off through FE -> OpenLocationMenu -> QueueLevelLoad -> PrePlay -> Play without ever deactivating the menu; nothing in that chain Invokes it (its only Invoke sites are Intro/TitleLoop/Menu/LocationMenu). The stale 'active' flag made ComputeContext report the menu overlay, and since the menu-hiding rework that means Hidden - no HUD, no virtual joystick, hub unplayable until a pause/resume cycle. (Continues never hit this: the pending-load branch skips the Activate.) The dead Activate is removed, and ComputeContext now ignores an active menu in states that never Invoke one, so no future leak can hide the pad during gameplay. 3. Version 1.0.2 (versionCode 2) in gradle + GAME_VERSION.
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.
No description provided.