Skip to content

Tilt a game's world by tilting the device - #41

Draft
MHunterG wants to merge 1 commit into
mainfrom
feature/tilt-controls
Draft

MHunterG wants to merge 1 commit into
mainfrom
feature/tilt-controls

Conversation

@MHunterG

Copy link
Copy Markdown
Contributor

A game played by tilting (LocoRoco tilts its world with L and R) can now be tilted with the motion sensors of a gamepad or of the phone itself. It is opt-in per game (GameProfile::tilt) and off by default for the player. First user: TeamGDB/Purun (LocoRoco 2).

What changed

  • GameProfile::tilt (TiltControls): the buttons that tilt left and right, whether the game eases its tilt towards the held side (proportional), and a note for the menu. Without it, nothing opens a sensor and the menu has no Tilt controls section.
  • host/input/tilt.{hpp,cpp}: plain arithmetic, no SDL.
    • Roll is asin(-up.x) from the accelerometer, so it does not depend on the device's pitch: a flat gamepad and an upright phone or Deck read the same. It is fused with the gyroscope's rate through a complementary filter.
    • Dead zone with 1.5° hysteresis, re-centring and inversion.
    • Modes:
      • Angle: the roll from neutral.
      • Angle + level horizon: the same, plus the picture is turned against the roll.
      • Rate: turning faster than 20°/s adds up into a tilt that stays and fades over about 3 s, so a slow return to a comfortable hold keeps the tilt.
    • With proportional, a roll between the dead zone and full tilt presses the button for that share of the frames.
  • host/input/motion.{hpp,cpp}: sensors through SDL3.
    • The gamepad's own first (SDL_SetGamepadSensorEnabled, gyro and accel, enabled only while tilt is on). Otherwise the device's (SDL_OpenSensor), turned from the natural orientation into the screen's.
    • The log names what was found ([tilt] DualSense…: gyroscope and accelerometer (… Hz), [tilt] device sensors: …), or why there is nothing. Under Steam Input it points at Steam Input hiding the Deck's gyroscope.
  • Renderer:
    • Tilt bits are ORed into the pad after the gamepad and touch, unless the player presses a tilt button (a tilt never becomes both buttons, which is LocoRoco's jump).
    • R3 alone re-centres, and so does closing the menu.
    • Angle + level horizon adds a pass: the game frame is copied into a sampled image per swapchain image, then drawn turned and zoomed to cover its rectangle. The zoom is max(cos a + (H/W) sin a, cos a + (W/H) sin a), 1:1 when level, capped at input.tilt_level_limit (default 10°). The overlay and ImGui draw after it and stay upright. Android pre-rotation targets are handled.
  • Settings:
    • input.tilt, input.tilt_mode (angle|level|rate), input.tilt_full (12°), input.tilt_dead_zone (4°), input.tilt_invert, input.tilt_level_limit (10°).
    • Variables <prefix>_TILT, _TILT_MODE, _TILT_FULL, _TILT_DEAD_ZONE, and <prefix>_TRACE_TILT (also under TRACE_PAD).
    • The menu's Controls > Tilt controls shows the source and a live "Tilted now" readout, and has a Re-centre button. "Restore control defaults" covers the new settings.
  • Input script: tilt ROLL [PITCH] and gyro X Y Z steps. The virtual pad gets a gyroscope and accelerometer only when a script uses them, or with INPUT_LIVE.
  • tests/tilt_tests.cpp (portablekit_tilt_tests) and docs/PROFILE_GUIDE.md.

This branch is the cherry-pick onto main. Purun uses the same commit on purun/tilt-controls (on e73a866, which merges cleanly with purun/confirm-cross). The only conflict with main was the test block in CMakeLists.txt.

How it was verified

  • macOS 27 on M1 (MoltenVK), Purun at feature/gyro-tilt with this commit on e73a866:
    • portablekit_tilt_tests and portablekit_settings_tests pass.
    • Bounded windowed runs (NO_AUDIO, own data dir) driven through INPUT_LIVE with synthetic tilt and gyro steps, in LocoRoco 2's Franzea 1. [tilt]/[pad] traces and window captures showed:
      • 3° presses nothing, 7° pulses R (12 presses in about 35 frames) and the world leans part way, 15° holds R, and -15° holds L.
      • R3 re-centres.
      • Rate mode: a quick turn tilts, a slow return keeps it (61%), and it fades in about 3 s.
      • The menu section and the live readout render and save to settings.ini.
      • Level horizon: turned the right way (+10° moves the right edge up), no black border pixels at 0°, 5°, 10° and 15° (capped at 10°), and the menu stays upright over the turned picture.
      • A Mac with no pad logs [tilt] this device has no accelerometer or gyroscope of its own.
  • On this main-based branch: a -fsyntax-only compile of every changed translation unit with the Purun build's flags. Not a full build or run of main.

Not verified

  • No real sensor hardware: DualShock 4, DualSense, Switch Pro, the Steam Deck through SDL's HIDAPI driver, and phones. The axis conventions follow SDL's documentation. The Android device-sensor path and its orientation mapping are untested; the emulator was not tried, because Purun has no Android build yet.
  • Linux, Windows and Android builds.
  • The Rate mode thresholds and the level-horizon feel need someone holding a device.

🤖 Generated with Claude Code

A game played by tilting (LocoRoco tilts with L and R) can now be tilted
with the motion sensors of a gamepad or of the phone itself. It is off
by default and only for a game whose profile says how it tilts.

- GameProfile::tilt names the buttons that tilt left and right, and
  whether the game eases its tilt towards the held side; then a small
  roll presses the button for part of the frames and tilts part way.
- input/tilt: the roll from the accelerometer, fused with the
  gyroscope's rate; the dead zone with hysteresis, re-centring,
  inversion; three modes: Angle, Angle + level horizon, and Rate, where
  turning the device tilts and the tilt stays when the turning stops.
  Unit tested with synthetic readings (portablekit_tilt_tests).
- input/motion: the sensors through SDL, a gamepad's first, else the
  device's own turned with the screen. The log says which were found,
  and why none were: Steam Input's virtual pad hides a Steam Deck's.
- A tilt button the player presses takes over from the sensors, so a
  tilt never becomes both buttons at once. R3 alone re-centres, and so
  do the start and closing the menu.
- Angle + level horizon turns the finished game picture against the
  device's roll and zooms it just enough to cover its rectangle at that
  angle (1:1 when level), capped at 10 degrees by default. The overlay
  and the interface are drawn after it and stay upright.
- Controls > Tilt controls in the menu, settings.ini keys input.tilt*,
  and <prefix>_TILT, _TILT_MODE, _TILT_FULL, _TILT_DEAD_ZONE.
- The input script's virtual pad gets a gyroscope and an accelerometer
  when a script uses the new tilt and gyro steps, so all of it can be
  driven without hardware.

Co-Authored-By: Claude Opus 5.5 (1M context) <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.

1 participant