Releases: andrewnakas/skate3-android
Release list
Skate 3 Android v0.1.19
Three handhelds built on the same Qualcomm chip — an AYN Thor, a Retroid Pocket 6 and an AYN Odin2 Portal — had never once reached the skatepark on any build of this port. They sat on a menu forever. They work now, confirmed by testers on the Retroid Pocket 6 and the AYN Thor.
What was wrong
Nothing to do with those devices' graphics or audio. The game's executable was being assembled incorrectly the moment it loaded, before a single frame was drawn.
Applying the title update means shifting blocks of the game's code and data to new positions in memory. Ninety-two of those shifts land on a region that overlaps where the block already sits. The code used a copy that is only correct when the two regions do not touch, and Android picks its copying routine to suit the processor — so the same app assembled a different executable depending on the phone.
Picture moving a sentence three words to the right, one letter at a time, working left to right. By the fourth word you are copying letters you already moved, so you repeat the first three words over and over instead of moving the sentence. That happened to a 32 KB block that only needed to move 480 bytes.
One of the ruined blocks holds the table the game uses to find its audio decoders. Audio setup failed, and the world never loaded. The other ruined blocks are where the crashes and the strange buffer sizes in earlier reports came from. One cause, not four.
The fix
The copy now handles overlap correctly. It is verified in both directions: it repairs the broken devices, and on a phone that already worked the assembled executable is byte-for-byte what it was before.
The loader also logs a fingerprint of the executable after unpacking and again after the update is applied, so a diagnostic report now states outright whether that phone assembled the game correctly.
Also fixed
- Reads from the game folder no longer stop early. Android storage may legitimately return part of a read; a partial read used to be handed to the game as though it were complete.
- Choosing a skater's name no longer crashes. The on-screen keyboard could be dismissed before the engine had finished connecting it to the game's request.
- Loading the game's web module no longer erases the engine's memory bookkeeping for the main executable.
sha256 cd9eacda621828a9e9bfe93b80749253bd33a59af14d64ab3e615ccfbcee900f
Skate 3 Android v0.1.18
This is a diagnostic build, not a general update. It is here for the three QCS8550 handhelds that never load the world — the AYN Thor, the Retroid Pocket 6 and the AYN Odin2 Portal. It arms a write watch over the game's static data, which has been tested on exactly one phone; if your game already works on v0.1.17, stay on v0.1.17.
Three handhelds on the QCS8550 chip — an AYN Thor, a Retroid Pocket 6 and an AYN Odin2 Portal — stop on a frontend screen and never load the world. This build does not claim to have fixed that. It ships the instrument that will name the cause from a single report, plus two real bugs found along the way and fixed.
If you are on one of those devices, please run this build once and send a diagnostic report. It will contain either the identity of whatever writes over the game's static data, or proof that the data was already wrong when loading finished. Either one ends the investigation.
What changed
The game's static data is now guarded and checked. Right after the executable and its title update finish loading — the one moment the data is known good — the engine keeps a copy, logs a hash of it, and makes those pages genuinely read-only. The first write into any of them is caught, recorded with the address of the writer, the game's own call chain and the bytes it was about to overwrite, and then allowed through so nothing breaks. Every thirty seconds the live data is compared against the copy and any difference is printed. When the audio table is found damaged, the whole neighbourhood is restored from the copy rather than the two entries the previous build could put back.
File reads no longer stop early. A read from the game folder used to be a single system call whose result was handed to the game as-is. Android serves an app's external files through a layer where a read may legitimately come back short or be interrupted, and the game — written for a console where that never happened — would go on to parse zeros. Reads and writes now continue until they are complete, and anything unusual is logged with the file and offset. Verified by deliberately breaking reads on a working phone: without the fix the game hangs while loading, with it the same conditions are only a few log lines and a normal boot.
Choosing a skater's name no longer crashes. A Retroid Pocket 6 that got past the frontend crashed the moment the game asked for a name. The on-screen keyboard could be dismissed before the engine had finished connecting it to the game's request, and the engine then wrote into a dialog that no longer existed. Dialogs are now created and connected in one step, on the thread that draws them.
Loading the game's web module no longer erases the engine's memory bookkeeping for the main executable, which happened a few minutes into every session.
The diagnostic report says more: which filesystem serves the game folder, the open-file limit, and what is in the user folder. Every failing device so far was on a first boot with no save; the development phone always had one.
Verified on the development phone
896 pages protected, none of them written, ten consecutive integrity checks clean across three minutes of play, audio resolving normally, and no measurable cost to frame pacing.
sha256 f119e561ebeb2bbfddb4f79a6f0ef9527351d20db33e17f0c7c76f83ea5cbc20
Skate 3 Android v0.1.17
A diagnostic step. v0.1.16's table repair works — on a Retroid Pocket 6 the
codec tags were restored and audio decoders that had been failing began to
resolve — but those devices still do not reach the world, and this release
closes the last gap in what a report can check.
No game content is included and none is bundled in the APK. Setup is
unchanged: install, pick your disc image, download the title update, press Play.
What v0.1.16 established
The repair fired and did its job. What it exposed underneath is a parser
producing negative buffer sizes:
buffer setup asked to zero -12 bytes (size=FFFFFFF4) ... from lr=82B7F970
and, moments later, the game's own assert firing with another negative size on a
neighbouring buffer. Reading the guest code, the caller validates two fields of
that struct for negative values and errors out — and never checks the one that
arrives negative.
A negative size read as unsigned by a copy is exactly what writes a sentence
over static data, which is the corruption the table repair has been papering
over. The failing devices' executables are now confirmed byte-identical to
the one this build was compiled from, by full-file hash. So the bad values are
being parsed out of data, not code.
What this release adds
- Every game file is hashed in full, including the 430 MB of
.musaudio
streams. Those were sampled at one megabyte on the reasoning that they were
being identified rather than verified. That was wrong: the identical
reasoning had already hidden a disputed region ofdefault.xexby 41,744
bytes, and the audio streams are the input feeding the parser that is
producing the garbage. A report takes a few seconds longer and answers the
question instead of assuming it. - The buffer guard reports its neighbouring arguments, so the shape of the
bad parse is identifiable rather than just its symptom.
If it fails for you
Launch it, let it reach the point where it stops or crashes, then save a
diagnostic report from the setup screen. Crackling audio before a crash is a
useful detail to mention — it is consistent with buffers built from these sizes.
Performance
Unchanged since v0.1.14. Leave skate3_diagnostics off for normal play.
Skate 3 Android v0.1.16
This one has a mitigation for the two devices that stop on a frontend screen and
never load the world, and it names what is actually wrong on them.
No game content is included and none is bundled in the APK. Setup is
unchanged: install, pick your disc image, download the title update, press Play.
What v0.1.15 found
The checks added in v0.1.15 came back from a Retroid Pocket 6 and ruled out
everything easy. Its default.xex matches the one this build was compiled
from. The title update applied successfully. And its codec tag table reads:
their table[0..7] = ['d-fo','rwar','d an','d fe','ed-b','ack ','elem','ents']
a working device = ['P6L0','PFN0','Pack','etPl','ayer','....','Pack','etPl']
Read as one string, theirs is "d-forward and feed-back elements" - filter
terminology, so audio library text, sitting exactly where the codec tags belong.
The 'rwar' that earlier reports showed was never a four-character code; it is
the letters inside the word "forward".
So the image is right when it loads and wrong by the time audio starts.
Something overwrites static image data at run time, the codec lookup then misses
forever, and the frontend never advances - while the renderer keeps drawing that
screen at a locked 60 fps, which is why it reads as a hang.
What this release does about it
When the lookup misses, the table is checked against the two tags the build was
compiled with, and put back if a sentence has landed on them. The lookup is
retried every audio quantum, so the next retry resolves.
This is a mitigation and not a fix, and it is worth being clear about that.
The write that put a sentence there covered at least thirty-two bytes; this
restores eight. Whatever else it landed on is still wrong, and it will happen
again. What it buys is a device that gets past the frontend and can report what
else is broken. skate3_audio_repair_format_table=false turns it off to see the
failure unrepaired.
On a device where the table is intact this code never runs.
Also
Reports identify their own build - app version, engine version and build tag in
the first five lines. Six builds in one day previously all called themselves
0.1.14 (15).
Performance
Unchanged since v0.1.14, including the honest part: those fixes free roughly a
fifth of the render thread and do not remove the hitch when new map areas
stream in. That hitch is the game setting up a zone's objects, which the
Xbox 360 hitched on too. Leave skate3_diagnostics off for normal play.
Skate 3 Android v0.1.15
A diagnostic release. Same engine work as v0.1.14, plus the instruments needed
to tell two devices apart — and a version number that finally distinguishes one
tester build from another.
No game content is included and none is bundled in the APK. You supply your
own Skate 3 Xbox 360 disc image. Setup is unchanged from v0.1.14: install, pick
your disc image, download the title update, press Play.
Why this exists
Two devices with the same Snapdragon — an AYN Thor and a Retroid Pocket 6 — stop
on a frontend screen and never reach the world. v0.1.14's heartbeat showed what
was happening, and it is not a hang or a crash:
alive frame=27000 items=0 draws=0 draws_2d=89491 draws_since_last=27000
Exactly 27,000 draws every thirty seconds for eight minutes — a static screen
rendered at a locked 60 fps, forever, with the world never loading. Upstream of
that, an audio codec tag read from a static table comes back 'rwar' where a
working device reads 'PFN0' from the same address, through the same code, with
an identical and healthy format registry.
Same instructions, different bytes underneath them. This release adds the checks
that say why.
What it adds
- The player's
default.xexis checked against the one the build was compiled
from. The recompiled code is generated from one specific executable and
reads all of its static data — tables, strings, constants — out of the copy
you supply. The title update has been hash-verified since it was added; the
base executable never was. It logs a warning naming the consequence and does
not refuse to run. - The title update patch result is logged at warning level. Every failure
path was already an error, but success was invisible at the shipped log level,
so its absence proved nothing about whether a device ran patched code. - The codec tag table is dumped beside any audio failure. A healthy device
reads['P6L0','PFN0','Pack','etPl','ayer',...]— entry 2 onward is the
string "PacketPlayer" — so one overwritten entry is easy to tell from a
wholly different region. - Reports identify their own build. The Build section now names the engine
version, and the app version is bumped so no two tester builds report the same
number. Six builds in one day all called themselves0.1.14 (15), two testers
ended up on different ones, and telling them apart meant grepping for the
presence of individual log lines. That was avoidable. - Game executables are hashed whole rather than by their first megabyte.
The prefix missed the disputed region by 41,744 bytes and reported a match
that had never been checked.
If the game stops on a screen for you
Launch it, let it reach the point where it stops, then save a diagnostic report
from the setup screen. Every answer above lands in the first minute — there is
no need to wait it out.
Performance
Unchanged from v0.1.14. Those notes stand, including the honest part: the four
fixes free roughly a fifth of the render thread and do not remove the hitch
when new map areas stream in. That remaining hitch is the game setting up a
zone's objects, which the Xbox 360 hitched on too.
skate3_diagnostics is worth leaving off for normal play — its per-frame
accounting runs whether or not a line is printed.
Skate 3 Android v0.1.14
This release has no download. Its APK was removed during an upload that was
interrupted, and the exact binary was not kept. Use
v0.1.15
instead - it contains everything below plus the image and build-identity checks.
Skate 3 running as native ARM64 code on Android. This is not an emulator: the
Xbox 360 executable was translated ahead of time into C++ and compiled for the
phone, so the skating, physics and career mode are the retail game's own code
running natively. During gameplay the Xbox GPU is not emulated either, a native
renderer reads the game's scene state and draws it through Vulkan directly.
No game content is included and none is bundled in the APK. You supply your
own Skate 3 Xbox 360 disc image.
What you need
- An arm64 Android phone, Android 9 or newer, with Vulkan. Built and measured on
a Galaxy S23 FE. - Your own Skate 3 Xbox 360 disc image, on the phone or a USB drive.
- About 7 GB free. Roughly 6 GB is extracted from the image and stays on the phone.
Setting it up
- Install the APK and open Skate 3 Android.
- Choose Install from a disc image and pick your image. It is read where it
lies and extracted, which takes a while. - Choose Download title update. The game cannot boot without Title Update 3
and it is not shipped here. You can also pick your own copy of the package
instead. Either way the engine verifies both patch payloads by hash and
refuses anything that does not match. - Press Play.
New in this release
This one is mostly about where the time actually goes, and it began by proving
the previous assumption wrong.
The render thread was spinning, not waiting. The plan said the hitches when
new map areas stream in were disc reads or lock contention. A scheduler trace
said otherwise: the two file threads slept for 31 and 48 seconds of a 50 second
window, and the render thread never blocked for longer than 13 ms. Inside an
80 ms hitch it was running for 73 of those milliseconds. It was spinning
inside the game's own frame-end call, waiting for the emulated command
processor - which the same trace caught runnable with no core for 7.8 of those
50 seconds.
Four changes give that thread its core back, each verified in a profile:
- The frame cap spun for 2 ms at the end of every wait, which was 14.4% of the
render thread, an eighth of every frame, held on a performance core doing
nothing. It sleeps to the deadline now and spins 300 us. Measured after: 1.6%.
Tunable asskate3_guest_fps_cap_spin_usif a device wakes threads slowly. - The command processor's own idle poll yielded 500 times before blocking, worth
another 14.4% of that thread. Low-RAM devices already dropped it to 32 for
exactly this reason; every Android device does now. Having more busy threads
than fast cores is not a low-memory property. - The draw hook matched shader names with
strstrin five separate places, each
reading 119 bytes of guest memory a byte at a time first, and none of the
memos survived a full session. That was 4.9% of the render thread and 14%
inside the hitches, its single largest entry. Three of those probes look for
water and keep searching until they find some, so across most of the map,
where there is none, they never stopped. - Two smaller ones on the command processor: a register-table lookup made only
to decide whether to print a message the log level discards, and a per-packet
call into the trace writer that exists to return immediately when no trace is
open.
Being honest about the result: together these free roughly a fifth of the
render thread, and they do not remove the streaming hitch. After them the
hitch has no hot spot left - just dozens of guest functions each running once at
50 to 140 times their normal rate, which is the game setting up a zone's
objects. That is real work; the Xbox 360 hitched there too. Steady state is
unaffected either way: 95% of frames land on time.
The AYN Thor crash is found. Testers on that device described a hang. It is
not a hang - it is a crash, four to seven minutes in, every time, and the
Android tombstones in a recent report are five copies of the same one. An audio
buffer setup calls memset(buffer, 0, size) and that size arrives as -12.
PowerPC memset reads the count as unsigned and computes a 16-byte block count of
size >> 4; the crash dump's r0 is 0x0FFFFFFF, which is exactly
0xFFFFFFF4 >> 4. It then writes through 4.29 GB of address space until it
reaches memory that is reserved but not committed, and dies there. The
0x70000000 address in every Thor report going back months was never
meaningful - it is simply how far the memset got, and every theory built on it
was chasing a number with no content. A negative byte count is now clamped and
the caller logged.
Reports say more. A thirty-second heartbeat logs at warning level, so a
normal log can finally distinguish a frozen game from one that is running but
not drawing the world - the Thor's living world was updating sixty times a
second while a tester watched a still screen, and nothing in the log could say
so. The audio trap dumps the registered codec formats on both failing paths, and
the diagnostic report now carries sizes and hashes of your disc files, the
locale and the engine's startup arguments.
Tuning
The engine reads files/user/android_args.txt at startup, one setting per line.
The android_args/ folder in the repository has profiles to start from.
streaming.txt is the one for hitch work; v0114-ab-old.txt reverts this
release's timing changes so a single build can be measured both ways.
skate3_diagnostics is worth leaving off for normal play. Its per-frame
accounting runs whether or not a line is printed, so anything measured with it
on is a floor rather than what the game actually does.
Skate 3 Android v0.1.11
Skate 3 running as native ARM64 code on Android. This is not an emulator: the
Xbox 360 executable was translated ahead of time into C++ and compiled for the
phone, so the skating, physics and career mode are the retail game's own code
running natively. During gameplay the Xbox GPU is not emulated either, a native
renderer reads the game's scene state and draws it through Vulkan directly.
No game content is included and none is bundled in the APK. You supply your
own Skate 3 Xbox 360 disc image.
What you need
- An arm64 Android phone, Android 9 or newer, with Vulkan. Built and measured on
a Galaxy S23 FE. - Your own Skate 3 Xbox 360 disc image, on the phone or a USB drive.
- About 7 GB free. Roughly 6 GB is extracted from the image and stays on the phone.
Setting it up
- Install the APK and open Skate 3 Android.
- Choose Install from a disc image and pick your image. It is read where it
lies and extracted, which takes a while. - Choose Download title update. The game cannot boot without Title Update 3
and it is not shipped here. You can also pick your own copy of the package
instead. Either way the engine verifies both patch payloads by hash and
refuses anything that does not match. - Press Play.
Controls
On-screen controls appear when no controller is attached and hide themselves
when one is. Bluetooth and USB controllers work through SDL.
Tuning
The engine reads files/user/android_args.txt at startup, one setting per line.
The android_args/ folder in the repository has profiles to start from, and
its README explains which of the iOS settings deliberately did not carry over.
diagnostics.txt turns on the frame pacing lines, and scripts/perf.sh reads
them back.
Defaults are inherited from a 4 GB iPhone and are conservative for a recent
phone. quality.txt spends that headroom on shadows, ambient occlusion and
antialiasing. Change one setting at a time.
New in this release
Mali GPUs are no longer turned away. A Galaxy S20 FE reported the app
opening and closing again with no message, which read as a crash. It was not a
crash: the engine was refusing the GPU. An Arm Mali-G77 reports no
vertexPipelineStoresAndAtomics, and the device was rejected before anything
was drawn.
That requirement was never as hard as the check enforcing it. The emulated GPU
path already handles the feature being missing - it routes vertex memexport
through compute shaders, which is what that path is for - and the only thing it
cannot serve is a draw that exports from a vertex shader. The device is
accepted now, and a draw that genuinely needs it fails where it happens instead
of at startup. On a GPU that has the feature nothing changes at all, because
the check passes there regardless.
This is the third time a requirement belonging to the emulated pipeline this
build replaces has turned away hardware that could otherwise run: geometry
shaders and non-solid fill were the first two.
If you are on a Mali device, this is worth trying - but it has only been proven
not to break the GPUs that already worked. Whether Skate 3 runs to the end of a
session on Mali is untested, and a report either way is useful.
Devices that could not create their own storage folder now work. A tester
on LineageOS could open the file picker and select a disc image, and nothing
ever arrived - the launcher was quietly showing "free space unknown", which is
what this app prints when the folder it extracts into does not exist and could
not be created. Android/data is restricted ground on recent Android versions.
When that happens the game is kept in the app's own private storage instead,
which always works. The setup screen says so, and says the trade-off: a file
manager cannot see it, and uninstalling takes the game with it. If the folder
works, nothing changes - and whichever location already holds an install is
always preferred, so an existing 6 GB copy is never orphaned.
Settings you change now survive the session. Every graphics row was being
put back at the next launch - resolution, V-Sync, antialiasing, shadows,
ambient occlusion, bloom, sun shafts, draw distance, the frame cap and the
texture budget. The tuned values this build ships are applied first and the
menu writes over them, rather than the other way round, so what you pick is
what you get next time.
The app no longer dies when the file picker opens. Two people reported it
closing at the exact moment the picker appeared, on LineageOS and on stock
Samsung, and it was neither of those: it was a deadlock in this app. The
engine's installer asked the game activity to show the picker and waited for an
answer, on the one thread SDL runs the whole game on. Opening the picker pauses
that activity and Android destroys its rendering surface - and the event that
releases the surface is handled on the same thread that was sitting there
waiting. The renderer went on using a window Android had already freed.
Files are chosen on the launcher screen now, before the engine starts, where
there is no surface to lose and no thread to block. There is also a button to
pick your own title update file, which went through the same broken path.
"Save a diagnostic report". If something goes wrong, this gathers what is
needed to work out why into one file you can attach to a message: the build
version, the ROM fingerprint, the SoC, ABIs, per-core clocks, RAM, what is in
the game folder, free space, the engine's log and its crash report, and the
system log - which survives the crashed process, so a report gathered after a
crash contains the crash. All of that normally lives under Android/data,
which recent Android versions will not let a file manager open, so there was
previously no way for anyone to hand it over.
The game's threads are placed on the fast CPU cores. Phones split their
cores into clusters at different clock ceilings, and the threads the frame
depends on were landing wherever the scheduler happened to put them. The
frame-critical threads now go to the faster cores and streaming and decoding go
to the slower ones, out of the frame's way. The cluster layout is read from the
device at startup rather than assumed, because it varies: a Galaxy S23 FE has
three clusters - four cores at 1.79 GHz, three at 2.50 and a single 2.99 GHz
core - and an earlier version of this that assumed the usual two put every
frame-critical thread on that one prime core and left the 2.5 GHz cores idle.
Measured after the fix on that phone: a locked 60.1 fps in gameplay, 16.66 ms
at the 95th percentile, no frame over 20 ms.
A profile for devices under 4 GB of RAM, selected automatically from the
memory the device reports, so nothing larger is affected. It drops two mip
levels from the biggest textures rather than one - sixteen times fewer bytes,
and sixteen times less CPU spent expanding them on GPUs that cannot sample the
compressed format at all - halves the draw distance, lets the texture and mesh
stores go below the old 256 MB floor, bounds a lock spin that previously
hammered a single cache line up to 65,280 times without yielding, and builds
the guest's static-world draw packets every other frame. That last one is safe
because the native renderer already discards them; they were simply the guest
render thread's largest per-item cost.
This is aimed at low-end phones and tablets generally. On the worst case to
hand - a Galaxy Tab A7 Lite, eight in-order Cortex-A53s and 2.9 GB of RAM - it
reaches gameplay but is still slow, and the honest summary is that the
remaining limit there is the emulated game code itself.
Fixed since the first builds
Three separate reasons the app closed or refused to install, all found by
people testing on hardware the author does not own.
It was built for one phone's CPU. The recompiled game code carried ARMv8.3
instructions, which fault on anything older - a Cortex-A78, A76 or A55, which
is most Android hardware. The app closed the instant guest code ran, which
looked like the setup buttons failing because each one starts the game. It now
targets the common ARMv8 baseline and picks its atomics at run time, so recent
phones keep the fast path and older ones still work.
It demanded GPU features it does not use. Geometry shaders were required
by the emulated pipeline this build replaces. Adreno has them; PowerVR and
many Mali parts do not, and those devices exited during graphics setup.
The title update could not be downloaded from inside the installer. The
engine's wizard shelled out to curl, which Android does not ship, so it failed
on every device and reported it as a connection problem. It now downloads
through the app.
It could not open a disc image from a USB drive. The file picker returns an
open descriptor; naming it by path and re-opening it fails for anything under
system-only storage. It now reads the descriptor directly.
Also fixed: the app's own folder is created wherever it is needed rather than
only when starting the game, which is what made the title update download fail
with a missing-file error and free space read as 0.0 GB.
Low-memory devices
android_args/tiny.txt in the repository puts every memory and distance
setting at its floor, for devices with around 3 GB of RAM. It quarters the
memory the largest textures take, quarters how much of the world is drawn, and
turns off every effect that carries its own full-screen target.
It cannot lower the 3D scene's resolution: the render scales only multiply
upward from 1, so there is no fractional setting to give. And it cannot help a
device whose CPU is the limit - the emulated game code is what costs the frame,
and no setting reduces how much the game simulates.
Known rough edges
- Several on-screen buttons draw a question mark instead of a label. The touch
overlay is missing glyphs for the d-pad and Back/Start. - The runtime logs a thread priority permission denial at startup. Android
refuses ...
Skate 3 Android v0.1.10
Skate 3 running as native ARM64 code on Android. This is not an emulator: the
Xbox 360 executable was translated ahead of time into C++ and compiled for the
phone, so the skating, physics and career mode are the retail game's own code
running natively. During gameplay the Xbox GPU is not emulated either, a native
renderer reads the game's scene state and draws it through Vulkan directly.
No game content is included and none is bundled in the APK. You supply your
own Skate 3 Xbox 360 disc image.
What you need
- An arm64 Android phone, Android 9 or newer, with Vulkan. Built and measured on
a Galaxy S23 FE. - Your own Skate 3 Xbox 360 disc image, on the phone or a USB drive.
- About 7 GB free. Roughly 6 GB is extracted from the image and stays on the phone.
Setting it up
- Install the APK and open Skate 3 Android.
- Choose Install from a disc image and pick your image. It is read where it
lies and extracted, which takes a while. - Choose Download title update. The game cannot boot without Title Update 3
and it is not shipped here. You can also pick your own copy of the package
instead. Either way the engine verifies both patch payloads by hash and
refuses anything that does not match. - Press Play.
Controls
On-screen controls appear when no controller is attached and hide themselves
when one is. Bluetooth and USB controllers work through SDL.
Tuning
The engine reads files/user/android_args.txt at startup, one setting per line.
The android_args/ folder in the repository has profiles to start from, and
its README explains which of the iOS settings deliberately did not carry over.
diagnostics.txt turns on the frame pacing lines, and scripts/perf.sh reads
them back.
Defaults are inherited from a 4 GB iPhone and are conservative for a recent
phone. quality.txt spends that headroom on shadows, ambient occlusion and
antialiasing. Change one setting at a time.
New in this release
Devices that could not create their own storage folder now work. A tester
on LineageOS could open the file picker and select a disc image, and nothing
ever arrived - the launcher was quietly showing "free space unknown", which is
what this app prints when the folder it extracts into does not exist and could
not be created. Android/data is restricted ground on recent Android versions.
When that happens the game is kept in the app's own private storage instead,
which always works. The setup screen says so, and says the trade-off: a file
manager cannot see it, and uninstalling takes the game with it. If the folder
works, nothing changes - and whichever location already holds an install is
always preferred, so an existing 6 GB copy is never orphaned.
Settings you change now survive the session. Every graphics row was being
put back at the next launch - resolution, V-Sync, antialiasing, shadows,
ambient occlusion, bloom, sun shafts, draw distance, the frame cap and the
texture budget. The tuned values this build ships are applied first and the
menu writes over them, rather than the other way round, so what you pick is
what you get next time.
The app no longer dies when the file picker opens. Two people reported it
closing at the exact moment the picker appeared, on LineageOS and on stock
Samsung, and it was neither of those: it was a deadlock in this app. The
engine's installer asked the game activity to show the picker and waited for an
answer, on the one thread SDL runs the whole game on. Opening the picker pauses
that activity and Android destroys its rendering surface - and the event that
releases the surface is handled on the same thread that was sitting there
waiting. The renderer went on using a window Android had already freed.
Files are chosen on the launcher screen now, before the engine starts, where
there is no surface to lose and no thread to block. There is also a button to
pick your own title update file, which went through the same broken path.
"Save a diagnostic report". If something goes wrong, this gathers what is
needed to work out why into one file you can attach to a message: the build
version, the ROM fingerprint, the SoC, ABIs, per-core clocks, RAM, what is in
the game folder, free space, the engine's log and its crash report, and the
system log - which survives the crashed process, so a report gathered after a
crash contains the crash. All of that normally lives under Android/data,
which recent Android versions will not let a file manager open, so there was
previously no way for anyone to hand it over.
The game's threads are placed on the fast CPU cores. Phones split their
cores into clusters at different clock ceilings, and the threads the frame
depends on were landing wherever the scheduler happened to put them. The
frame-critical threads now go to the faster cores and streaming and decoding go
to the slower ones, out of the frame's way. The cluster layout is read from the
device at startup rather than assumed, because it varies: a Galaxy S23 FE has
three clusters - four cores at 1.79 GHz, three at 2.50 and a single 2.99 GHz
core - and an earlier version of this that assumed the usual two put every
frame-critical thread on that one prime core and left the 2.5 GHz cores idle.
Measured after the fix on that phone: a locked 60.1 fps in gameplay, 16.66 ms
at the 95th percentile, no frame over 20 ms.
A profile for devices under 4 GB of RAM, selected automatically from the
memory the device reports, so nothing larger is affected. It drops two mip
levels from the biggest textures rather than one - sixteen times fewer bytes,
and sixteen times less CPU spent expanding them on GPUs that cannot sample the
compressed format at all - halves the draw distance, lets the texture and mesh
stores go below the old 256 MB floor, bounds a lock spin that previously
hammered a single cache line up to 65,280 times without yielding, and builds
the guest's static-world draw packets every other frame. That last one is safe
because the native renderer already discards them; they were simply the guest
render thread's largest per-item cost.
This is aimed at low-end phones and tablets generally. On the worst case to
hand - a Galaxy Tab A7 Lite, eight in-order Cortex-A53s and 2.9 GB of RAM - it
reaches gameplay but is still slow, and the honest summary is that the
remaining limit there is the emulated game code itself.
Fixed since the first builds
Three separate reasons the app closed or refused to install, all found by
people testing on hardware the author does not own.
It was built for one phone's CPU. The recompiled game code carried ARMv8.3
instructions, which fault on anything older - a Cortex-A78, A76 or A55, which
is most Android hardware. The app closed the instant guest code ran, which
looked like the setup buttons failing because each one starts the game. It now
targets the common ARMv8 baseline and picks its atomics at run time, so recent
phones keep the fast path and older ones still work.
It demanded GPU features it does not use. Geometry shaders were required
by the emulated pipeline this build replaces. Adreno has them; PowerVR and
many Mali parts do not, and those devices exited during graphics setup.
The title update could not be downloaded from inside the installer. The
engine's wizard shelled out to curl, which Android does not ship, so it failed
on every device and reported it as a connection problem. It now downloads
through the app.
It could not open a disc image from a USB drive. The file picker returns an
open descriptor; naming it by path and re-opening it fails for anything under
system-only storage. It now reads the descriptor directly.
Also fixed: the app's own folder is created wherever it is needed rather than
only when starting the game, which is what made the title update download fail
with a missing-file error and free space read as 0.0 GB.
Low-memory devices
android_args/tiny.txt in the repository puts every memory and distance
setting at its floor, for devices with around 3 GB of RAM. It quarters the
memory the largest textures take, quarters how much of the world is drawn, and
turns off every effect that carries its own full-screen target.
It cannot lower the 3D scene's resolution: the render scales only multiply
upward from 1, so there is no fractional setting to give. And it cannot help a
device whose CPU is the limit - the emulated game code is what costs the frame,
and no setting reduces how much the game simulates.
Known rough edges
- Several on-screen buttons draw a question mark instead of a label. The touch
overlay is missing glyphs for the d-pad and Back/Start. - The runtime logs a thread priority permission denial at startup. Android
refuses the real-time scheduler to apps; it is harmless. - It looks for a controller mapping database in a system path that does not
exist on Android, and says so once. - Custom map packs work. Drop a pack folder, the data file and its header
together, into the app's own folder alongsidegame. Confirmed on a phone
with two packs installed. - Suspending and resuming has been reported as freezing on at least one device.
It has not reproduced here across repeated background and resume cycles, so
if it happens to you the details are worth reporting.
Building it yourself
scripts/build_native.sh builds the native library from the engine tree, then
scripts/build_apk.sh packages it. The native build takes hours and needs your
own game files, since the recompiler consumes them. See the repository README.
Credits
Built on the Skate 3 native recompilation and the rexglue SDK, which is derived
from Xenia's Xbox 360 research. The Android shell, platform work and tuning in
this release are new; the iOS port shares the same engine.
Skate 3 Android v0.1.9
Skate 3 running as native ARM64 code on Android. This is not an emulator: the
Xbox 360 executable was translated ahead of time into C++ and compiled for the
phone, so the skating, physics and career mode are the retail game's own code
running natively. During gameplay the Xbox GPU is not emulated either, a native
renderer reads the game's scene state and draws it through Vulkan directly.
No game content is included and none is bundled in the APK. You supply your
own Skate 3 Xbox 360 disc image.
What you need
- An arm64 Android phone, Android 9 or newer, with Vulkan. Built and measured on
a Galaxy S23 FE. - Your own Skate 3 Xbox 360 disc image, on the phone or a USB drive.
- About 7 GB free. Roughly 6 GB is extracted from the image and stays on the phone.
Setting it up
- Install the APK and open Skate 3 Android.
- Choose Install from a disc image and pick your image. It is read where it
lies and extracted, which takes a while. - Choose Download title update. The game cannot boot without Title Update 3
and it is not shipped here. You can also pick your own copy of the package
instead. Either way the engine verifies both patch payloads by hash and
refuses anything that does not match. - Press Play.
Controls
On-screen controls appear when no controller is attached and hide themselves
when one is. Bluetooth and USB controllers work through SDL.
Tuning
The engine reads files/user/android_args.txt at startup, one setting per line.
The android_args/ folder in the repository has profiles to start from, and
its README explains which of the iOS settings deliberately did not carry over.
diagnostics.txt turns on the frame pacing lines, and scripts/perf.sh reads
them back.
Defaults are inherited from a 4 GB iPhone and are conservative for a recent
phone. quality.txt spends that headroom on shadows, ambient occlusion and
antialiasing. Change one setting at a time.
New in this release
Settings you change now survive the session. Every graphics row was being
put back at the next launch - resolution, V-Sync, antialiasing, shadows,
ambient occlusion, bloom, sun shafts, draw distance, the frame cap and the
texture budget. The tuned values this build ships are applied first and the
menu writes over them, rather than the other way round, so what you pick is
what you get next time.
The app no longer dies when the file picker opens. Two people reported it
closing at the exact moment the picker appeared, on LineageOS and on stock
Samsung, and it was neither of those: it was a deadlock in this app. The
engine's installer asked the game activity to show the picker and waited for an
answer, on the one thread SDL runs the whole game on. Opening the picker pauses
that activity and Android destroys its rendering surface - and the event that
releases the surface is handled on the same thread that was sitting there
waiting. The renderer went on using a window Android had already freed.
Files are chosen on the launcher screen now, before the engine starts, where
there is no surface to lose and no thread to block. There is also a button to
pick your own title update file, which went through the same broken path.
"Save a diagnostic report". If something goes wrong, this gathers what is
needed to work out why into one file you can attach to a message: the build
version, the ROM fingerprint, the SoC, ABIs, per-core clocks, RAM, what is in
the game folder, free space, the engine's log and its crash report, and the
system log - which survives the crashed process, so a report gathered after a
crash contains the crash. All of that normally lives under Android/data,
which recent Android versions will not let a file manager open, so there was
previously no way for anyone to hand it over.
The game's threads are placed on the fast CPU cores. Phones split their
cores into clusters at different clock ceilings, and the threads the frame
depends on were landing wherever the scheduler happened to put them. The
frame-critical threads now go to the faster cores and streaming and decoding go
to the slower ones, out of the frame's way. The cluster layout is read from the
device at startup rather than assumed, because it varies: a Galaxy S23 FE has
three clusters - four cores at 1.79 GHz, three at 2.50 and a single 2.99 GHz
core - and an earlier version of this that assumed the usual two put every
frame-critical thread on that one prime core and left the 2.5 GHz cores idle.
Measured after the fix on that phone: a locked 60.1 fps in gameplay, 16.66 ms
at the 95th percentile, no frame over 20 ms.
A profile for devices under 4 GB of RAM, selected automatically from the
memory the device reports, so nothing larger is affected. It drops two mip
levels from the biggest textures rather than one - sixteen times fewer bytes,
and sixteen times less CPU spent expanding them on GPUs that cannot sample the
compressed format at all - halves the draw distance, lets the texture and mesh
stores go below the old 256 MB floor, bounds a lock spin that previously
hammered a single cache line up to 65,280 times without yielding, and builds
the guest's static-world draw packets every other frame. That last one is safe
because the native renderer already discards them; they were simply the guest
render thread's largest per-item cost.
This is aimed at low-end phones and tablets generally. On the worst case to
hand - a Galaxy Tab A7 Lite, eight in-order Cortex-A53s and 2.9 GB of RAM - it
reaches gameplay but is still slow, and the honest summary is that the
remaining limit there is the emulated game code itself.
Fixed since the first builds
Three separate reasons the app closed or refused to install, all found by
people testing on hardware the author does not own.
It was built for one phone's CPU. The recompiled game code carried ARMv8.3
instructions, which fault on anything older - a Cortex-A78, A76 or A55, which
is most Android hardware. The app closed the instant guest code ran, which
looked like the setup buttons failing because each one starts the game. It now
targets the common ARMv8 baseline and picks its atomics at run time, so recent
phones keep the fast path and older ones still work.
It demanded GPU features it does not use. Geometry shaders were required
by the emulated pipeline this build replaces. Adreno has them; PowerVR and
many Mali parts do not, and those devices exited during graphics setup.
The title update could not be downloaded from inside the installer. The
engine's wizard shelled out to curl, which Android does not ship, so it failed
on every device and reported it as a connection problem. It now downloads
through the app.
It could not open a disc image from a USB drive. The file picker returns an
open descriptor; naming it by path and re-opening it fails for anything under
system-only storage. It now reads the descriptor directly.
Also fixed: the app's own folder is created wherever it is needed rather than
only when starting the game, which is what made the title update download fail
with a missing-file error and free space read as 0.0 GB.
Low-memory devices
android_args/tiny.txt in the repository puts every memory and distance
setting at its floor, for devices with around 3 GB of RAM. It quarters the
memory the largest textures take, quarters how much of the world is drawn, and
turns off every effect that carries its own full-screen target.
It cannot lower the 3D scene's resolution: the render scales only multiply
upward from 1, so there is no fractional setting to give. And it cannot help a
device whose CPU is the limit - the emulated game code is what costs the frame,
and no setting reduces how much the game simulates.
Known rough edges
- Several on-screen buttons draw a question mark instead of a label. The touch
overlay is missing glyphs for the d-pad and Back/Start. - The runtime logs a thread priority permission denial at startup. Android
refuses the real-time scheduler to apps; it is harmless. - It looks for a controller mapping database in a system path that does not
exist on Android, and says so once. - Custom map packs work. Drop a pack folder, the data file and its header
together, into the app's own folder alongsidegame. Confirmed on a phone
with two packs installed. - Suspending and resuming has been reported as freezing on at least one device.
It has not reproduced here across repeated background and resume cycles, so
if it happens to you the details are worth reporting.
Building it yourself
scripts/build_native.sh builds the native library from the engine tree, then
scripts/build_apk.sh packages it. The native build takes hours and needs your
own game files, since the recompiler consumes them. See the repository README.
Credits
Built on the Skate 3 native recompilation and the rexglue SDK, which is derived
from Xenia's Xbox 360 research. The Android shell, platform work and tuning in
this release are new; the iOS port shares the same engine.