8 Commits

Author SHA1 Message Date
richard 431ee295ae Full Xbox/PlayStation controller support: glyphs, remapping, vibration toggle
Extends the earlier Xbox-only ability glyphs into full brand-aware
controller support, following standard practice (position-based
button mapping, safe defaults for unrecognised pads, no per-device
special-casing):

- Controls detects PlayStation pads (DualShock/DualSense) from
  Input.get_joy_name() via a name-substring heuristic, checking for
  "xbox" first so a Bluetooth-connected Xbox pad (reports as "Xbox
  Wireless Controller") can't be misread as PlayStation through the
  generic "wireless controller" hint. Unrecognised/generic pads read
  as Xbox-style, matching the browser's Standard Gamepad Mapping.
- Ability-bar keycaps and the options menu now show ✕/○/□/△ for a
  detected PlayStation pad instead of A/B/X/Y — no index remapping
  needed, since PlayStation's face buttons sit in the exact same
  four positions as Xbox's.
- The options menu's Controls section gains a gamepad rebind button
  next to each ability's keyboard one (movement stays on the D-pad/
  stick, not individually remapped, matching convention). Clicking it
  captures the next joypad button press, mirroring the existing
  keyboard-rebind flow exactly (Esc cancels either). Gamepad rebinds
  persist to controls.cfg alongside keyboard ones and survive a
  reload.
- Added a player-facing "Controller Vibration" toggle (Settings...
  Gameplay section) — Controls.vibrate() now checks it alongside the
  existing DP debug flag, so either can silence rumble.

Verified headlessly end-to-end: default bindings show correct
brand-agnostic glyphs (kick=X, dash=A, ...), rebinding kick to a
different button through the real options-menu flow changes only
that action's glyph, leaves its keyboard binding and other abilities'
gamepad bindings untouched, and survives a reload from disk. Sanity-
checked the PlayStation name heuristic against realistic controller
name strings from Xbox, PlayStation (Windows/macOS/Bluetooth
variants), and other brands.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXBNaEBmrYDSMcv4NgUWtV
2026-09-10 19:33:02 +03:00
richard a34989c49e Show Xbox-style A/B/X/Y glyphs on the ability bar for a gamepad
Controls now tracks whether the player's last deliberate press was a
gamepad button/stick or a keyboard key/mouse click (ignoring plain
mouse motion, since the mouse still drives the camera regardless of
which device moves the bull). The HUD's ability-bar keycaps read from
that: keyboard letters normally, Xbox-style face-button glyphs while
a gamepad is in use, swapping live the moment the player switches
devices.

Godot's joypad button indices are already Xbox-position-normalized
(by SDL natively, by the browser's Standard Gamepad Mapping on web),
so the glyph mapping (button 0/1/2/3 -> A/B/X/Y) reads correctly for
any controller brand with no per-device special-casing.

Verified headlessly: labels start as the bound keyboard letters
(F/G/H/J), switch to X/A/B/Y on a synthetic joypad button press
(matching project.godot's kick/dash/slam/roll bindings), and switch
back on a keyboard press.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXBNaEBmrYDSMcv4NgUWtV
2026-09-10 19:20:54 +03:00
richard e810d2e75b Fix vibration going dead for the rest of the session after the gate
Siim reported controller vibration stops working entirely after the
first playthrough. The gate's pulsed rumble (vibrate_for) fired a new
~0.3s vibration every 0.4s for 7 seconds — about 17 back-to-back start
calls. On at least one setup that appears to leave the gamepad's
rumble motor stuck reporting "busy", so every later vibrate() call
(hits included) silently does nothing for the rest of that session.

Space pulses out more (fewer, longer gaps: 0.25s on / 0.6s off instead
of 0.3s on / 0.1s off) and explicitly stop_joy_vibration() between
them on native instead of relying on each pulse's own duration to
time out, so a new effect never starts before the last one has
cleanly ended.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXBNaEBmrYDSMcv4NgUWtV
2026-09-10 19:17:47 +03:00
richard d2a59f695e Bind only joypad button 0 for ui_accept confirm
Simplify the confirm binding to just the standard button 0 (Xbox-style
"A" / bottom face button) instead of also covering the Nintendo-layout
right-hand position.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXBNaEBmrYDSMcv4NgUWtV
2026-09-10 19:08:12 +03:00
richard d5c6b749d0 Bind a gamepad confirm button — ui_accept had none by default
Siim reported gamepad menu navigation works but pressing a button
does nothing. Checked Godot's actual built-in ui_accept bindings
directly: only Enter/Kp Enter/Space — no joypad button at all. That's
why D-pad/stick focus movement (ui_left/right/up/down do default to
gamepad input) worked while "confirm" never could, on any controller.

Binds both the Xbox-style bottom face button (A, index 0) and the
right-hand one (B, index 1 — physically labelled "A" on a
Nintendo-layout pad, since gamepad mapping normalizes to Xbox
*position* rather than the printed label), so either common "confirm"
button activates the focused menu/end-screen button.

Verified headlessly end-to-end: a synthetic joypad button 0 or button
1 press on the focused end-screen button now reloads the run, matching
what a real gamepad press should do.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXBNaEBmrYDSMcv4NgUWtV
2026-09-10 19:05:55 +03:00
richard a9440c6a43 Fix gate-opening vibration by pulsing instead of one long effect
Siim reported the hit-vibration works but the lobby gate's rumble
doesn't. Godot's start_joy_vibration() docs call this out directly:
long single effects are unreliable on much gamepad hardware/drivers,
and recommend a short effect repeated instead. Added
Controls.vibrate_for() to do that and pointed the gate at it in place
of its single 7-second call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXBNaEBmrYDSMcv4NgUWtV
2026-09-10 17:56:17 +03:00
richard cd54242de0 Make gamepad vibration work on web export
Input.start_joy_vibration() is a documented engine no-op on Godot's
HTML5 export (never wired to the browser side). Controls.vibrate()
wraps it: native platforms go straight through as before, web drives
the browser's Gamepad Haptic Actuator API directly via JavaScriptBridge.
Gamepad movement/ability input already worked on desktop browsers via
the standard Web Gamepad API, so only vibration needed a fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXBNaEBmrYDSMcv4NgUWtV
2026-09-10 17:35:54 +03:00
richard 09d4a60f43 Reorganize into role-based folders and consolidate art
Move every script and scene into role-based directories (actors/, systems/, ui/,
levels/, props/) and all raw art under art/ (grouped by role, snake_case,
Extract-Textures GLBs), dropping Blender version-suffix noise. Filenames are
snake_case (PascalCase reserved for class_name) to avoid case-insensitive
filesystem aliasing on the web/macOS targets. Routes the bull's hits through the
typed DamageEvent pipeline and wires the bull's abilities to the Fx library.
Updates CLAUDE.md's folder-layout overview to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCyaJPTHCH9EDthpf4A5Sj
2026-09-08 14:56:22 +03:00