The run of work that took the translated build from "boots" to "playable".
Engine:
- Flat guest mapping replaces the software MMU on aarch64 hosts. A 4 GiB
PROT_NONE reservation lets a 32-bit guest address zero-extend safely, so
tcg_out_qemu_ld/st short-circuit before tcg_out_tlb_read and the prologue
materialises the base into X28. Measured 1.24x (51 vs 41 fps, interleaved
A/B). Note the base must be set BEFORE UC_INIT - Unicorn inits lazily, and
setting it after uc_open silently falls back to softmmu.
- num_get<char> facet implemented, which was the real cause of the crash after
the prologue race; a full race is now playable end to end.
- Thread-stack free list + ReleaseThreadEngine, fixing the arena leak that
showed up as a black screen when entering a race. kMaxGuestThreads 16 -> 64.
- Real ARM32 FMOD now runs in-engine via the Java FMODAudioDevice bridge, with
a per-thread JNIEnv. Two of the three blockers were our own single-image-era
guards.
Host/app:
- Native crash handler: async-signal-safe, decodes the host fault back to a
guest address, writes a report file and nothing else. CrashReportActivity
picks it up on the NEXT launch, zips it, and offers to share. No backend, no
automatic upload.
- Game data ships inside the APK and self-extracts on first launch, so a tester
installs one file and plays. Copy-to-.part-then-rename, with a free-space
check up front.
- EGL context preserved across pause, fixing black textures on resume.
- Navigation bar hidden and re-hidden on focus gain; volume keys reported as
system keys, checked before the loading-state gate.
- x86_64 added to abiFilters: the ARM32 guest runs under tcg/i386 with no
houdini in the path. The flat mapping is aarch64-only, so that host falls
back to the software MMU - commented at the abiFilters line.
Ignore rules added for app/translated/ (611 MB of signed release APK, which
also carries the bundled OBB) and ostream_repro/build/.
Co-Authored-By: Claude <noreply@anthropic.com>
Verified live on a Pixel 6a: the game passes its EULA, loads the prologue
and renders real 3D gameplay, with zero heap exhaustion, zero faults and
zero rejected frees over a full session.
Root causes fixed in this state, each backed by a measurement (details and
the list of refuted theories live in ARM64_TRANSLATION_LAYER.md):
* JNI varargs float->double promotion. C promotes float to double in any
varargs call and every Call*Method form is varargs, so reading one 4-byte
slot yielded the double's always-zero low half. EVERY float argument
passed to Java was silently becoming 0; text was just where it showed.
* GuestHeap ~4x memory overhead. Power-of-two size classes carving the full
class, plus segregated free lists that could never share memory between
sizes. Reworked to exact sizing with O(log n) best-fit reuse and splitting
(deliberately not a linear scan - this allocator already had an O(n) perf
cliff in its history). Peak live now 207MB against the real A9's 199MB,
fragmentation ~2.5MB. Also fixed: realloc reading past the old block on
shrink, a 32-bit overflow in calloc, and drifting payload alignment.
* Unbounded FMOD fake-handle leak into the never-freeing permanent arena,
which is why enlarging that arena had not helped.
* Frame presentation, corrected against A9 ground truth: the real frame has
three default-framebuffer colour clears and ONE present at the end; this
engine had been presenting on each of them.
Load-time acceleration (zlib_accel.cpp): host zlib now serves inflate and
crc32, the latter measured by the block profiler as the single hottest guest
routine at 17.7%. Streams are only taken over when this layer saw their own
inflateInit2_, so unknown streams (libpng's, among others) still run the
original emulated path.
name_lookup_accel.cpp is present but its hook is NOT registered - it crashed
on bad assumptions about guest table lifetime and is kept as a starting
point, with both mistakes recorded in its comments.
Co-Authored-By: Claude <noreply@anthropic.com>