Skip to content

Dev voice feedback (R2) design

Date: 2026-10-06. Status: approved by Chris (brainstorm, 2026-10-06).

In Dev builds only, pressing R2 (right trigger) on a controller opens the in-game feedback panel with its text box focused and the system keyboard up, so a tester (Toby) can dictate playtest feedback instead of typing it. Works on iPhone/iPad and Apple TV, and reuses the existing feedback path (FeedbackPanel → GameplayTelemetry.send_feedback → EventQueue → D1).

  • A controller cannot invoke Siri. Dictation comes from the system keyboard: the mic key on iOS, and on tvOS the keyboard screen driven by the Siri Remote’s mic. Chris accepted that Toby picks up the Siri Remote for dictation on Apple TV.
  • tvOS apps cannot record the microphone and SFSpeechRecognizer is not on tvOS [Likely], so a custom speech path is not an option there.
  • The tvOS fork has no virtual keyboard today. ~/Claude/godot-tvos/godot/platform/tvos/ display_server_tvos.mm carries a TODO: tvOS virtual keyboard support; the shared virtual_keyboard_show messages a keyboardView that is never created on tvOS (nil, no-op). FeedbackPanel.text_entry_available() hides the text box on tvOS for this reason.
  • FEATURE_VIRTUAL_KEYBOARD already reports true on tvOS (shared display_server_apple_embedded.mm), so it cannot distinguish an old template from a new one.
  • R2 (JOY_AXIS_TRIGGER_RIGHT) is unbound. R1 = warp, L1 = decoy.
  • R2 binding — input/input_router.gd: edge-triggered read of JOY_AXIS_TRIGGER_RIGHT > 0.5 on any connected pad emits a new feedback_requested signal. Gated on BuildConfig.dev_tools(). One emission per press (re-arms when the axis drops back below the threshold).
  • Routing — main.gd connects it to the existing _open_feedback_direct() (the touch speech-bubble path), so every “can we pause now” guard applies and the game resumes when the panel closes. It passes a voice flag through pause_menu.open_feedback_then_resume(voice).
  • Voice open — with voice, FeedbackPanel opens on kind OTHER, focuses the text box, places the caret at the end and calls grab_focus() so the system keyboard comes straight up.
  • Scope — R2 only acts where pause acts (in a run). No R2 in shop/title menus in v1. Amended 2026-10-07 (Chris: “no matter what else is on the screen”, e.g. the death screen): R2 now opens main’s own top-layer overlay from ANY screen instead of routing through the pause menu; input underneath is switched off while it is up (InputFreeze) and a live run is frozen. See docs/architecture/dev-tools.md § Dev R2.
  • Diagnostics off — same as the touch button: focus the Diagnostics toggle, no panel.
  • tvOS gate — text_entry_available() becomes: not tvOS, or OS.has_feature("tvos_text_input"). A build on an old template keeps the quick-notes-only panel instead of a dead text box.
  • Marker — the feedback body carries voice: true for R2-opened notes, so bh-feedback-triage can tell them apart. (Check the Worker’s /events feedback handling keeps unknown body keys; if it drops them, add the column/key there.)
  • No Debug Settings row — R2 is always on in Dev, so the console-parity rule does not apply.
  • R2 edge-detect fires once per press, re-arms on release.
  • R2 does nothing when dev_tools() is false.
  • voice opens the panel on OTHER with the text box focused.
  • text_entry_available() follows the tvos_text_input feature on tvOS.
  • voice: true reaches the feedback body.

2. Engine fork (~/Claude/godot-tvos/godot, branch tvos-render-1080p)

Section titled “2. Engine fork (~/Claude/godot-tvos/godot, branch tvos-render-1080p)”
  • DisplayServerTVOS overrides virtual_keyboard_show / virtual_keyboard_hide / is_keyboard_active.
  • Show — present a UIAlertController with one UITextField prefilled with the existing text, plus Done / Cancel, on the root view controller. Focusing the field brings up the full-screen tvOS keyboard (dictation via the Siri Remote mic).
  • Done — replace the Godot field’s contents via the same key-event injection iOS uses (DisplayServerAppleEmbedded::key): one BACKSPACE press/release per UTF-32 char of the existing text (caret is at the end, set by GDScript), then each character of the new text.
  • Cancel — send nothing; field unchanged.
  • Feature — OS_TVOS::_check_internal_feature_support("tvos_text_input") returns true.
  • Ship — commit on the local fork branch, rebuild + repackage the tvOS template (bh-appstore-release §0.55), verify strings <binary> | grep tvos_text_input before install.
  • Testing — no unit tests on the fork side; verified on-device (see Done).
  1. Game side (§1) merged to main. Safe alone: iOS gets R2 + dictation immediately; tvOS keeps quick notes until the feature gate flips.
  2. Fork change + template repackage (§2).
  3. One Dev install on the Apple TV.
  • What’s new line in control/src/whats_new.js (build: null), BUILD bump via bump-build.sh after checking live branches for higher BUILDs.
  • Store builds unaffected (R2 is dev_tools()-gated); the fork change rides the next store tvOS template but is inert unless a text field opens.
  • [Likely] With the alert up, the pad’s A/B may also leak into Godot. Fix if seen: swallow pad input while is_keyboard_active() is true.
  • [Guessing] The Siri Remote dictation affordance may need Dictation enabled in the Apple TV’s Settings. Device setting, not code.
  • iPhone: R2 → panel → keyboard → dictated note lands in D1 with voice: true.
  • Apple TV: the same via the alert + Siri Remote mic.
  • scripts/gate.sh green.