Build and run on Android #22

Merged
icub3d merged 1 commit from 21-android-target into main 2026-09-13 17:15:01 +00:00
Owner

The game targets a tablet now. Desktop is where work is written because it iterates
faster; a tablet is where it is confirmed.

Android dlopens a shared object and calls android_main — it never runs a main.
#[bevy_main] generates that entry point and can only generate it where the crate
builds as a cdylib, so the app moves into src/lib.rs and main.rs becomes a
one-line caller. Both platforms assemble the identical App.

Nothing in world/, character/, ui/, or theme/ was touched to get a running APK.
That is the bet in CLAUDE.md § Separation of Simulation and Presentation paying out,
and it is the main reason this was worth doing now rather than after there is art to
redo.

android/build.sh cross-compiles with cargo-ndk, links resources and assets with
aapt2, adds the library, aligns, and signs. NativeActivity needs no Java and so no
Gradle. strip = "symbols" on release is worth 60 MB of the shared object.

The three ADRs are the part worth reviewing

  • [0002] Target Android (Accepted) — the platform decision, with the spike evidence
    and the costs: a second place for every change to be wrong, NativeActivity being the
    weaker host, doubled build times.
  • [0003] Design input for touch first (Accepted) — the one that had to be decided
    now. A touchscreen has no hover; movement.rs currently draws a route to the cursor;
    and #6, #7, #8 are all interaction. Deciding after they land means rewriting them.
  • [0004] Render the mission view without PBR (Proposed, deliberately) — 31 fps
    on an empty scene at native tablet resolution is fact. "Therefore drop PBR" is not
    yet, because the measurement that would distinguish fill-rate from shading cost has
    not been taken. Filed as its own issue.

Verification

cargo fmt --check, cargo clippy --all-targets -- -D warnings, and cargo test (114
passing) are clean; desktop is unaffected.

Ran on a Pixel Tablet (Android 17, Mali-G710, 2560x1600): renders over Vulkan in 546 ms,
loads its RON definitions from the APK, and takes touch input. Frame rate is a steady
31 fps, and four defects the spike surfaced are filed separately rather than fixed here
— blank screen on resume, system insets, forced portrait, and missing font glyphs.

Closes #21

🤖 Generated with Claude Code

https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh

The game targets a tablet now. Desktop is where work is written because it iterates faster; a tablet is where it is confirmed. Android dlopens a shared object and calls `android_main` — it never runs a `main`. `#[bevy_main]` generates that entry point and can only generate it where the crate builds as a `cdylib`, so the app moves into `src/lib.rs` and `main.rs` becomes a one-line caller. Both platforms assemble the identical `App`. Nothing in `world/`, `character/`, `ui/`, or `theme/` was touched to get a running APK. That is the bet in `CLAUDE.md` § Separation of Simulation and Presentation paying out, and it is the main reason this was worth doing now rather than after there is art to redo. `android/build.sh` cross-compiles with `cargo-ndk`, links resources and assets with `aapt2`, adds the library, aligns, and signs. `NativeActivity` needs no Java and so no Gradle. `strip = "symbols"` on release is worth 60 MB of the shared object. ## The three ADRs are the part worth reviewing - **[0002] Target Android** (Accepted) — the platform decision, with the spike evidence and the costs: a second place for every change to be wrong, `NativeActivity` being the weaker host, doubled build times. - **[0003] Design input for touch first** (Accepted) — the one that had to be decided now. A touchscreen has no hover; `movement.rs` currently draws a route to the cursor; and #6, #7, #8 are all interaction. Deciding after they land means rewriting them. - **[0004] Render the mission view without PBR** (**Proposed**, deliberately) — 31 fps on an empty scene at native tablet resolution is fact. "Therefore drop PBR" is not yet, because the measurement that would distinguish fill-rate from shading cost has not been taken. Filed as its own issue. ## Verification `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test` (114 passing) are clean; desktop is unaffected. Ran on a Pixel Tablet (Android 17, Mali-G710, 2560x1600): renders over Vulkan in 546 ms, loads its RON definitions from the APK, and takes touch input. Frame rate is a steady 31 fps, and four defects the spike surfaced are filed separately rather than fixed here — blank screen on resume, system insets, forced portrait, and missing font glyphs. Closes #21 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh
The game targets a tablet now, and desktop is where the work is written rather
than where it ends. Both entry points assemble the identical `App`, so the
difference is only which one the platform calls.

Android dlopens a shared object and calls `android_main`; it never runs a
`main`. `#[bevy_main]` generates that entry point, and can only generate it
where the crate builds as a `cdylib` — so the app moves into `src/lib.rs` and
`main.rs` becomes a one-line caller. Nothing in `world/`, `character/`, `ui/`,
or `theme/` was touched to get there, which is the architecture bet in
`CLAUDE.md` § Separation of Simulation and Presentation paying out.

`android/build.sh` cross-compiles with `cargo-ndk`, links resources and assets
with `aapt2`, adds the library, aligns, and signs. It hosts the game in
`NativeActivity`, which needs no Java and therefore no Gradle. GameActivity is
the better long-term host but wants the `androidx.games` AAR and with it the
whole Android Gradle Plugin tree — a build-system decision to make on purpose,
not a prerequisite for finding out whether the game runs.

`strip = "symbols"` on release is not tidiness. It is 60MB of the shared
object, which the APK would otherwise carry to the device.

Three ADRs record what this costs. 0002 makes Android a target. 0003 decides
input is designed for touch first — a touchscreen has no hover, the movement
preview is currently built on one, and #6 through #8 are all interaction, so
the model has to be settled before they land rather than after. 0004 is
Proposed rather than Accepted: the mission view measured 31 fps on an empty
scene at native tablet resolution, which is a real problem, but the case for
dropping PBR rests on a measurement not yet taken.

Verified on a Pixel Tablet (Android 17, Mali-G710): renders over Vulkan, loads
its RON definitions from the APK, and takes touch input.

Closes #21

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh
icub3d merged commit 9aea89bcf3 into main 2026-09-13 17:15:01 +00:00
icub3d deleted branch 21-android-target 2026-09-13 17:15:02 +00:00
Sign in to join this conversation.
No description provided.