diff --git a/clients/android/app/src/main/kotlin/io/unom/punktfunk/HostConnect.kt b/clients/android/app/src/main/kotlin/io/unom/punktfunk/HostConnect.kt index 100a8e27..fb5985be 100644 --- a/clients/android/app/src/main/kotlin/io/unom/punktfunk/HostConnect.kt +++ b/clients/android/app/src/main/kotlin/io/unom/punktfunk/HostConnect.kt @@ -84,6 +84,9 @@ suspend fun connectToHost( // The host's approval-list / trust-store label for this device — the same // Build.MODEL convention the pairing dialogs use for nativePair. Build.MODEL ?: "Android", + // Tier-A pad audio: ask for the 0xD1 plane only when a setting would render it, so a + // user with it off does not make the host provision endpoints it will never feed. + settings.padHaptics || settings.padSpeaker, ) } } diff --git a/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/NativeBridge.kt b/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/NativeBridge.kt index 909ae473..1caf8e83 100644 --- a/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/NativeBridge.kt +++ b/clients/android/kit/src/main/kotlin/io/unom/punktfunk/kit/NativeBridge.kt @@ -69,6 +69,10 @@ object NativeBridge { * list and trust store show for it, same convention as [nativePair]'s `name`. `null`/blank ⇒ * the host falls back to a fingerprint-derived "device abcd1234" label. */ deviceName: String?, + /** Advertise `CLIENT_CAP_PAD_AUDIO` — the SESSION-level negotiation for the 0xD1 per-pad + * DualSense plane. Without it the host never sets `HOST_CAP_PAD_AUDIO` and emits nothing, + * so a captured pad's own render capabilities would have nothing to gate. */ + padAudioOk: Boolean, ): Long /** 64-hex SHA-256 of the cert the host presented on [handle]; valid after a successful connect. */ diff --git a/clients/android/native/src/session/connect.rs b/clients/android/native/src/session/connect.rs index 2b65edfb..982400f7 100644 --- a/clients/android/native/src/session/connect.rs +++ b/clients/android/native/src/session/connect.rs @@ -145,6 +145,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'lo timeout_ms: jint, launch: JString<'local>, device_name: JString<'local>, + pad_audio_ok: jboolean, ) -> jlong { let host: String = match env.get_string(&host) { Ok(s) => s.into(), @@ -268,7 +269,16 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeConnect<'lo // CLIENT_CAP_PHASE_LOCK is honest: the async decode loop's presenter feeds // report_phase (advisory in v1 — the host arms on report receipt — but the Hello // should say what the client does). - punktfunk_core::quic::CLIENT_CAP_PHASE_LOCK, + // CLIENT_CAP_PAD_AUDIO is the SESSION-level negotiation, separate from the per-pad + // arrival bits: without it the host never sets HOST_CAP_PAD_AUDIO and never emits 0xD1, + // so declaring a pad's render caps later would have nothing to gate. Gated on the + // settings so a user with pad audio off does not make the host provision endpoints. + punktfunk_core::quic::CLIENT_CAP_PHASE_LOCK + | if pad_audio_ok != 0 { + punktfunk_core::quic::CLIENT_CAP_PAD_AUDIO + } else { + 0 + }, // Slice-progressive delivery, by decoder truth (Kotlin probes FEATURE_PartialFrame on // every decoder this device would use; `debug.punktfunk.force_parts` overrides for the // on-glass experiment): AU prefixes then arrive as `Frame::part` pieces and the decode