Skip to content

Pack the FUComplete build as its own Android app, PokeeFUC - #2

Closed
Oddity-git wants to merge 15 commits into
claude/project-thread-oq5toffrom
claude/project-thread-yl4vev
Closed

Oddity-git wants to merge 15 commits into
claude/project-thread-oq5toffrom
claude/project-thread-yl4vev

Conversation

@Oddity-git

Copy link
Copy Markdown
Owner

Requested by Mathieu · project thread

Changes

Before: Pokee already had an Android app (SDL activity, build_apk.sh, release_android.sh), but it only ever packed io.github.teamgdb.pokee. An Android build of the FUComplete title (MHFU_TITLE=FUC) would have produced an APK with Pokee's id and name, replacing a Pokee install and its data.

After: an Android build directory configured with -DMHFU_TITLE=FUC packs PokeeFUC: application id io.github.teamgdb.pokeefuc, PokeeFUC on the launcher, pokeefuc-<version>-android-arm64.apk. It installs beside Pokee with its own storage and saves. On Android 11+ the app also asks the system for the display rate the Frame rate setting presents at, as Yakumo's Android app does.

How:

  • build_apk.sh reads MHFU_TITLE from the build's CMakeCache.txt, renames the manifest package with aapt2 link --rename-manifest-package, and packs a copy of res/ with the title's app_name. The Java code keeps its single package; the manifest names the activity in full so it is found under either id. An unknown title is refused. release_android.sh names the APK after the title.
  • Ported from Yakumo's build_apk.sh (same MIT licence and contributors): Java sources and classes passed as arrays, keystore password passed to apksigner through the environment instead of argv, Pokee/Dear ImGui/tiny-AES-c/xxHash licences in the APK, no directory records in the zip, --no-crunch, and ANDROID_BUILD_TOOLS_VERSION / ANDROID_COMPILE_SDK_VERSION.
  • Ported from Yakumo: platform/android_display.* calls ANativeWindow_setFrameRate, looked up at run time so the APK still loads on Android 10. The renderer requests the rate on each new surface and when Frame rate changes. pacing::requested_rate now provides the rate, and wanted_rate uses it before its Vsync cap. MHFU_NO_DISPLAY_RATE_REQUEST=1 turns the request off.
  • Docs: profile README (FUComplete on Android, Frame rate, variable), RELEASING.md (### PokeeFUC with the configure commands), packaging README, TESTING.md, SOURCE_PROVENANCE.md.

This PR is stacked on #1 (the FUComplete build), so its base is that branch.

Related issues

None.

Validation

Linux x86-64 cloud container, commit 931d801:

  • python3 profiles/mhfu/tests/android_packaging_tests.py: 7 tests pass. They run build_apk.sh against stand-in SDK tools and check the package id and label for FU, FUC and a build with no title, that an unknown title is refused, the packed licences, SDK version selection, and that the password stays out of argv. Against the previous build_apk.sh, all 7 fail.
  • Headless test build (-DMHFU_RENDERER=OFF -DMHFU_FFMPEG=OFF) and ctest: 59/59 pass, including the five new mhfu_android_display_* loader scenarios and the new requested_rate checks in mhfu_interpolation_tests.
  • vulkan_renderer.cpp compiled with -fsyntax-only -DMHFU_ANDROID_APP=1 against SDL 3.4.16 and Vulkan headers. The only errors came from the generated shader include, which was stubbed out.
  • clang-format 21.1.8 reports nothing on the new and changed lines.
  • python3 -m unittest discover -s scripts/ci: one failure, test_cppcheck_driver ... exact_source_context. It fails the same way without this change.

Not verified

  • No APK was built. The container cannot reach dl.google.com, so it has no Android SDK or NDK, and no native Android cross-compile was run.
  • The Robolectric Java suite was not run, because its dependencies come from Google's Maven repository. Java code is unchanged; only the manifest's activity name became fully qualified.
  • Not tested on a device or emulator: installing, the renamed id, the launcher name, the frame-rate request, or FUComplete running on Android. Building a real PokeeFUC APK needs the FUComplete corpus generated on a computer first, plus the SDK and NDK.

Checklist

  • I followed CONTRIBUTING.md, including the language, source-provenance, game-data and privacy rules.
  • I updated affected documentation.

Generated by Claude Code

An Android build directory configured with MHFU_TITLE=FUC now packs
PokeeFUC: application id io.github.teamgdb.pokeefuc, PokeeFUC on the
launcher, pokeefuc-<version>-android-arm64.apk. It installs beside Pokee
with its own storage. The Java code keeps one package; build_apk.sh renames
the manifest package with aapt2 and packs a copy of the resources with the
title's name, and the manifest names the activity in full so it is found
under either id. An unknown title is refused.

From Yakumo's packer: sources and classes are passed as arrays, the
keystore password reaches apksigner through the environment, the APK
carries Pokee's, Dear ImGui's, tiny-AES-c's and xxHash's licences, zip
leaves out directory records, aapt2 does not crunch the icons again, and
ANDROID_BUILD_TOOLS_VERSION / ANDROID_COMPILE_SDK_VERSION pick SDK versions.

android_packaging_tests.py runs build_apk.sh against stand-in SDK tools.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
On Android 11 and later the app asks for the surface frame rate the
Frame rate setting presents at (Match display: the display's own), so a
phone that switches between 60, 90 and 120 Hz can show 90 or 120.
ANativeWindow_setFrameRate is looked up at run time, so the APK still
loads on Android 10, where nothing is asked. MHFU_NO_DISPLAY_RATE_REQUEST
leaves the display as it was.

The rate comes from pacing::requested_rate, which wanted_rate now uses
before its Vsync cap. Ported from Yakumo with its loader tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
@Oddity-git Oddity-git self-assigned this Oct 20, 2026

Copy link
Copy Markdown
Owner Author

The four red checks on 931d801 come from the base branch, not from this PR's diff:

  • Tests (macos-14) and Android (arm64-v8a): profiles/mhfu/host/camera/game_camera.cpp:1184 uses std::ostringstream without #include <sstream>. The include is missing on main too (1bfed46). libstdc++ pulls the header in through another one; libc++ does not. Proposed fix: add #include <sstream> to the includes of game_camera.cpp.
  • Python lint (Ruff): profiles/mhfu/tools/add_overlay.py:80 F841, base_text is assigned and never used. That change came with the FUComplete branch. Proposed fix: delete that line.
  • C++ formatting check: 35 files differ under clang-format 21.1.8, and 38 already differ on main. The only file this PR touches that differs is vulkan_renderer.cpp, at lines 7850, 8227, 8259 and 8412, and all of them come from 38ab9ce and fb903a5 on main.

The first two fixes belong on the FUComplete branch (#1), and I've passed them on to whoever is working on it. This PR picks them up once that branch has them. I haven't re-run the checks, because the failures are deterministic.


Generated by Claude Code

They run a shell script, so coverage.py measures nothing in them, and the
coverage pipeline test's fixture repository has no copy to run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3

Copy link
Copy Markdown
Owner Author

Update for 78ef875:

  • Coverage pipeline: this PR broke it, and d95938a fixes it. make python-coverage no longer runs android_packaging_tests.py. That test runs a shell script, so coverage.py had nothing to measure, and the pipeline test's fixture repo has no copy of the file.
  • macOS, Android and Ruff: I merged the base branch, which includes eab845e. That commit adds <sstream> to game_camera.cpp and drops the unused base_text from add_overlay.py.
  • Tests (ubuntu-24.04): scripts/ci/test_cppcheck_driver.py::test_reviewed_exception_is_bound_to_exact_source_context fails the same way on main, without this PR. The test pins a reviewed cppcheck exception to kernel.cpp lines 819–837, and that code has since moved. No fix exists yet. This PR doesn't touch kernel.cpp or the cppcheck driver, so I'm leaving that fix out of this PR.
  • C++ formatting check: the formatting drift comes from main, as I said above.

Generated by Claude Code

The NDK links shared libraries with --no-undefined, so the two fixture
modules failed to link in the Android test build. They leave the
framework's symbols to the loader, as the overlay libraries already do
with -z undefs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
ld64 refuses a bundle with undefined symbols; the fixtures take the
framework's from the test executable when loaded, so they link with
-undefined dynamic_lookup.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
macOS's bash 3.2 takes an empty array for an unset variable under set -u,
so build_apk.sh stopped with 'class_files[@]: unbound variable' when javac
made no classes (the packaging tests' stand-in made none). The script now
counts the sources and classes and says which is missing; the stand-in
javac writes a class.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
The HUD scale replay and overlay swap tests read fixtures from host paths
compiled into them, which do not exist on the emulator. CTest now names
both directories through the environment, the tests prefer that value,
and the Android runner copies any directory a test's environment names
inside the checkout or the build to the device and rewrites the value.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
Both branches taught the overlay test fixtures to leave host symbols to
the loader on Android and macOS; the FUComplete branch's wording is kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
macOS temporary directories sit behind a symlink, and the runner pushes
the resolved directory.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
The coverage build sets only the C++ compiler to Clang, so the KTX2
decoder's zstd C source went to the system cc with Clang-only flags and
the build stopped. The flags now apply per language when that
language's compiler is Clang.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
The FUComplete branch fixed the same two things this branch had: the
Android runner now copies the overlay and HUD fixtures itself, and the
coverage flags apply to C++ only. Its versions are kept, and this
branch's environment-based fixture copying is dropped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3

Copy link
Copy Markdown
Owner Author

The cppcheck job (analyze) is red on files this PR does not touch, so I am leaving it alone here:

  • profiles/mhfu/host/game/item_sets.cpp:555 (containerOutOfBounds): from the FUComplete base branch (Add a FUComplete build of the MHFU profile #1).
  • profiles/mhfu/tests/vertex_cache_tests.cpp:73 (mismatchingContainers) and profiles/mhfu/host/gpu/vertex_hold.hpp:94 (invalidContainer): inherited from main.

No fix for these exists yet to port. Once the base carries one, a merge of the base will pick it up here.


Generated by Claude Code

Under Fill the 2D interface is pulled into a PSP-shaped box in the middle
of the screen, which on a phone leaves wide empty bands beside the clock,
the bars, the map and the item bar. "Interface at the edges"
(video.hud_edges, MHFU_HUD_EDGES) moves each quest HUD quad out by the
gap the fit left: the clock, the bars and the party list to the left, the
map and the item bar to the right. The side is judged from the game's own
rectangle of each quad, so a bar's end cap stays with its bar, and what
sits in the middle stays. Only draws filed by game_task's quest HUD move;
menus and names over hunters keep their place. A draw's scissor moves with
it, or takes in where its quads landed when they went different ways.

It is on by default on Android and off on the desktop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3
cppcheck followed place_at_edges into quad_bounds with an empty copy and
reported an out-of-bounds read the size guard already prevents. A
one-vertex copy keeps the mismatch case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hyrco8tLuU7Jb5hpCUJdH3

Copy link
Copy Markdown
Owner Author

Moved to the android-port branch as #3, with the same commits. I'm closing this one.


Generated by Claude Code

@Oddity-git Oddity-git closed this Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants