Files
fn_registry/dev/issues/0072g-gamedev-android-build.md

7.3 KiB

id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
id title status type domain scope priority depends blocks related created updated tags
0072g gamedev — Android build (NDK + touch input + virtual gamepad + WalletConnect) pendiente feature
gamedev
multi-app media
0072b
0072c
2026-05-10 2026-05-17
gamedev
android
mobile

Objetivo

Compilar el runtime gamedev a APK Android, ejecutarlo en device fisico, gestionar touch input + virtual gamepad overlay, integrar wallet crypto via WalletConnect deep links, y mantener APK base ≤ 20 MB.

Toolchain

Requisitos en host:

  • Android SDK (cmdline-tools)
  • Android NDK r27+ (LTS)
  • JDK 17
  • Gradle 8+
  • adb para deploy a device

Funcion bash: bash/functions/infra/setup_android_ndk.sh que descarga e instala SDK+NDK en ~/android-sdk/. Idempotente.

Estructura del proyecto Android

SDL3 ya provee plantilla Android. Adaptar:

cpp/apps/<app>/android/
  build.gradle              # Top-level
  settings.gradle
  app/
    build.gradle            # App module (NDK config, ABI filters)
    AndroidManifest.xml
    src/main/
      java/com/.../MainActivity.java   # Hereda SDL3 main activity
      jni/                              # NDK wrapper
        Android.mk / CMakeLists.txt
      res/
        drawable/icon.png
        values/strings.xml

CMake cross-compile

# Ya en cpp/apps/<app>/CMakeLists.txt, sin cambios.
# Build invocado desde Android Studio o gradle:
# ./gradlew assembleRelease

ABIs target:

  • arm64-v8a — obligatorio (todos los devices modernos)
  • armeabi-v7a — opcional (devices viejos)
  • x86_64 — solo para emulador

Cada ABI ≈ 5-7 MB del APK base. Distribuir como App Bundle (AAB) en Play Store, que entrega solo la ABI del device.

Touch input

cpp/functions/gamedev/input_unified (de 0072b) ya tiene array touches[8]. Aqui lo poblamos.

SDL3 provee:

SDL_FINGERDOWN, SDL_FINGERUP, SDL_FINGERMOTION

input_unified mapea a InputState.touches. Cada touch tiene id, x, y (normalizado 0..1), pressure.

Virtual gamepad

Funcion nueva: virtual_gamepad_cpp_gamedev (impure):

struct VGamepadCfg {
    bool show_dpad;       // Lado izquierdo
    bool show_buttons;    // Lado derecho (A, B, X, Y)
    float dpad_radius;    // En pixels
    float button_radius;
    Color color_active;
    Color color_idle;
};

void vgamepad_render(const VGamepadCfg& cfg, InputState& out_input,
                     const InputState& touch_input);

Logica:

  • Detecta touches dentro del area del dpad → setea out_input.left/right/up/down.
  • Detecta touches en area de botones → setea out_input.action_a/b/x/y.
  • Render: dibuja con sprite_batch los iconos del dpad y botones.

Estilo: semi-transparente, configurable color/tamaño. Sprites por defecto en cpp/assets/vgamepad/ (dpad.png, btn_a.png, ...).

Auto-hide opcional cuando hay gamepad fisico conectado (Bluetooth):

if (SDL_GetGamepads(NULL) > 0) cfg.show_dpad = cfg.show_buttons = false;

Safe area / notch

Funcion: safe_area_cpp_gamedev (pure):

struct SafeArea { int top, bottom, left, right; };  // En pixels
SafeArea safe_area_get(SDL_Window* w);

SDL3: SDL_GetWindowSafeArea(SDL_Window*, SDL_Rect*). Render UI dentro de ese rect.

Permisos

AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- WalletConnect deep link: -->
<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="<app_name>" />
</intent-filter>

NO pedir permisos de:

  • Storage (escribir solo en getFilesDir(), no necesita permiso).
  • Camera/Mic — solo si el juego los usa.

Crypto wallet (WalletConnect)

En mobile no hay window.ethereum. Usuario abre el juego, pulsa "Connect Wallet" → SDK genera URI WalletConnect → abre app de wallet (MetaMask Mobile, Trust, Rainbow) via deep link → wallet firma → callback con resultado.

Plan: empotrar WalletConnect Sign SDK version C (¿existe?). Si no:

  1. Implementar protocolo WalletConnect v2 minimo en C++ (WebSocket + JSON-RPC + AES-256-GCM). ~1000 LoC.
  2. Funcion: walletconnect_session_cpp_crypto, walletconnect_request_cpp_crypto.

Alternativa pragmatica: webview embebida con bridge JS (mismo patron que 0072e). Android tiene WebView, iOS tiene WKWebView. Pesa mas pero reusa codigo.

Decision pendiente — sub-issue dedicado al protocolo si se complica.

Pipeline de build

bash/functions/pipelines/build_android_cpp_pipelines.sh:

#!/usr/bin/env bash
set -euo pipefail
APP="$1"
cd "cpp/apps/$APP/android"
./gradlew assembleRelease
APK="app/build/outputs/apk/release/app-release.apk"
echo "APK: $(stat -c%s "$APK") bytes"

Para deploy a device:

adb install -r app/build/outputs/apk/release/app-release.apk
adb shell am start -n com.fn.<app>/.MainActivity

Firma del APK

Generar keystore una vez:

keytool -genkey -v -keystore release.keystore -keyalg RSA -keysize 2048 \
    -validity 10000 -alias <app_name>

Guardar en ~/keystores/ (gitignored) y referenciarlo en gradle.properties:

RELEASE_STORE_FILE=/home/lucas/keystores/<app>.keystore
RELEASE_STORE_PASSWORD=...
RELEASE_KEY_ALIAS=...
RELEASE_KEY_PASSWORD=...

Tamaño

Componente Tamaño aprox
arm64-v8a .so (runtime) 4-6 MB
SDL3 lib 1-2 MB
Java glue 50 KB
Resources (icons) 50 KB
Assets bundle depende del juego

APK base objetivo: ≤ 20 MB (sin assets pesados).

Logging

adb logcat | grep <app> para ver printf y crashes. SDL3 redirige stdout/stderr a logcat por default.

Funcion: bash/functions/infra/android_logcat_filter.sh <app_name> que tail -f con filtros utiles.

e2e_checks

e2e_checks:
  - id: build_apk
    cmd: "bash bash/functions/pipelines/build_android_cpp_pipelines.sh <app>"
    timeout_s: 600
  - id: apk_size
    cmd: "test $(stat -c%s cpp/apps/<app>/android/app/build/outputs/apk/release/app-release.apk) -lt 20971520"
  - id: apk_install_emulator
    cmd: "bash bash/functions/infra/android_emulator_smoke.sh <app>"
    severity: warning  # Emulador puede no estar disponible
    timeout_s: 300

Tests en device

Smoke test manual al principio. Automatizar despues con adb shell input tap + adb shell screencap + comparacion de imagenes (si vale la pena).

Criterio de exito

  • engine_demo corre en Android device (Pixel/Samsung de prueba).
  • Touch input + virtual gamepad funcionan.
  • Audio sin glitches.
  • Safe area respetada (notch invisible no tapa UI).
  • APK release ≤ 20 MB.
  • WalletConnect: firma de mensaje funciona con MetaMask Mobile (basico, profundizar en sub-issue dedicado si hace falta).
  • Pipeline reproducible desde Linux WSL + Windows.

Riesgos

  1. WalletConnect en C++ puro — protocolo no trivial. Plan B: webview.
  2. NDK API levels — minSdk 24 (Android 7). Devices < 7 fuera.
  3. App Bundle requerido en Play StorebundleRelease en lugar de assembleRelease para production.
  4. Apple-style audio latency — Android variable. miniaudio AAudio backend ayuda en API 26+.

No-objetivos

  • Push notifications.
  • Google Play Billing — out of scope, prioridad crypto payments.
  • AdMob / ads.
  • iOS (en 0072h).