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>