Skip to content

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

Draft
Oddity-git wants to merge 38 commits into
MHFU-to-FUComplete-portfrom
android-port
Draft

Oddity-git wants to merge 38 commits into
MHFU-to-FUComplete-portfrom
android-port

Conversation

@Oddity-git

Copy link
Copy Markdown
Owner

Requested by Oddity-git · project thread

This replaces #2. It's the same commits, moved to a branch named android-port.

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. On a wide phone screen the quest HUD stayed in a PSP-shaped box in the middle.

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. With Aspect ratio set to Fill, the quest HUD can sit at the screen's sides ("Interface at the edges", video.hud_edges / MHFU_HUD_EDGES). This is on by default on Android and off on desktop.

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. 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 are passed as arrays, and the keystore password reaches apksigner through the environment. The APK packs the Pokee, Dear ImGui, tiny-AES-c and xxHash licences, has no directory records in the zip, uses --no-crunch, and honours 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. MHFU_NO_DISPLAY_RATE_REQUEST=1 turns the request off.
  • Edges: hud_scale::place_at_edges moves each quest-HUD quad on the left or right of the PSP frame out by the fit gap. Middle pieces stay where they are. The renderer then moves the scissor or widens it.
  • Docs: profile README, RELEASING.md, packaging README, TESTING.md, SOURCE_PROVENANCE.md.

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

Related issues

Replaces #2.

Validation

  • Linux x86-64 cloud container: python3 profiles/mhfu/tests/android_packaging_tests.py (7 pass). Headless test build and ctest pass, including the new edge cases in mhfu_settings_tests. clang-format 21.1.8 reports nothing on the changed lines.
  • Built and signed on the owner's Linux PC from 6123b48: pokeefuc-0.3.1-123-g6123b48-android-arm64.apk, with 300 overlay libraries, arm64-v8a and Android 10+. The signature verifies, it contains no game files, and every import exists on Android 29. An earlier build from this branch ran FUComplete on the owner's phone.

Not verified

  • The edge layout has not been checked on a phone yet. Whether the stamina bar's end cap still drifts at long stamina is open.
  • The Robolectric Java suite was not run.
  • CI: the C++ formatting check and cppcheck fail on files this PR doesn't change (43 files of whole-repo format drift; item_sets.cpp:555, vertex_cache_tests.cpp:73, vertex_hold.hpp:94).

Checklist

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

Generated by Claude Code

claude added 15 commits October 20, 2026 12:20
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
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
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
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

Two checks fail on files this PR doesn't change, the same way they did on #2:

  • C++ formatting check: whole-repo drift against the pinned clang-format 21.1.8 (43 files, including lines in hud_scale.hpp and settings_defaults_tests.cpp outside this PR's changes). The lines this PR adds format clean. There's no fix yet. A repo-wide reformat belongs in its own PR.
  • cppcheck (analyze): the blocking findings are in item_sets.cpp:555, vertex_cache_tests.cpp:73 and vertex_hold.hpp:94, all inherited from the base. The finding this PR caused (hud_scale.hpp:190) was fixed in 6fe0a3e. No fix exists for the others yet.

I don't have the means to re-run jobs from here. I'm keeping the PR watched.


Generated by Claude Code

…ole at the edges

Under Fill on a screen wider than the PSP's, the 2D interface is pulled
toward the centre before the HUD sizes run, so scale_quads compared fitted
positions against rectangles in the game's pixels. Only the map, which is
told by its texture, was still recognised; on a phone's 2.2:1 screen every
other piece kept the game's size. scale_fitted_quads sizes a fitted draw in
the game's pixels and fits again only the vertices that moved.

video.hud_edges moved each quad to its own side, so a window or a line of
text reaching over the middle (the start menu's help box, the item box) was
torn in two. A draw now moves only when all its quads go the same way.

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

Project coverage and 80% diff failed on f4d5f24 (61.39%, 159/259). That run was a push run, so it measured the diff against the commit before the push. That diff is the merge of MHFU-to-FUComplete-port (61411ac: FUComplete's CONFIG.BIN options, key quest marks, the overlay tool), not this PR's own changes.

I ran the same coverage locally with COVERAGE_BASE=origin/MHFU-to-FUComplete-port, which is what the pull_request run uses. This PR's changed lines come to 97.56% (80/82). The only uncovered lines are game_hud.cpp:933 and :941. vulkan_renderer.cpp and menu.cpp aren't built in the headless coverage job.

The merged lines' coverage belongs to #1, which is where they came from. Nothing is ported here, because no fix exists yet.


Generated by Claude Code

…cal-android-build

# Conflicts:
#	profiles/mhfu/host/settings/settings.cpp
"Interface at the edges" judged every quad by its rectangle alone, so the
item box's top edge and a loot notice's letters were also pushed to a side
and tore away from the rest of their window. The quad now has to be one of
the quest HUD pieces (map, vitals, item bar, sharpness, weapon, bowgun) as
well as wholly on one side; a menu or a notice stays in one piece. Turning
the setting off fixed both, which confirmed the edge move as the cause.

Tests cover the item box's top edge, a notice's letters and an item bar.
The piece filter that keeps menus and notices still would also have left the
sliding window and panels drawn inside the area-map slot behind while the map
moved. They are not map marks, so they are allowed by their slot instead;
notices over that slot are still excluded. Tests cover both.
Frame interpolation writes the blended vertices of skinned draws, and their
lighting blocks, into a scratch area of the vertex buffer during replay, after
the game frame's own flush has run. On a cached but non-coherent vertex memory
type, which a phone can choose, nothing made those writes visible to the GPU:
the replays read stale vertices and the models burst into stretched triangles.
The scratch range is now flushed when the present is submitted.

Coherent memory is unaffected; the flush is a no-op there.
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