No root · No account · No backend · No analytics · Sideload only
At a glance · Screens · Why GameCore · Install · Latest release · Plugins · Features · Security · Architecture · History
Important
The rule: if GameCore cannot measure it, it says so. A missing reading is shown as unavailable, with the reason — never as a zero, an estimate, or a green checkmark.
| Platform | Android 8.0+ (API 26) · arm64-v8a / armeabi-v7a |
| Built with | Kotlin 2.0.21 · Jetpack Compose · Material 3 · Hilt · Room + SQLCipher |
| Codebase | 731 Kotlin source files · roughly 184,000 lines (app + plugin SDK) |
| Tests | 3,181 JVM tests · 271 test classes · no emulator required |
| Plugins | 21 official .gcplugin packages · 13 bundled in the app |
| Network | One optional, payload-free TCP latency probe · no backend |
| License | MIT |
Live readings, per-game profiles, HUD layouts and theme controls — drawn from what the device actually reports.
Home — live device readings and setup status |
Games — per-game profiles that apply and unwind |
HUD — overlay modules and layouts you place yourself |
Settings — theme, accent, overlays and monitoring |
|
Most “game boosters” show a number and hope. GameCore’s central type is |
Every profile field is nullable. |
|
A |
No account, no backend, no analytics, no crash reporting. The only outbound connection is an optional, payload-free TCP latency probe you can switch off. |
GameCore is a signed sideloaded APK. There is no Play Store listing and no account to create.
|
Get the signed APK from the latest release. |
Allow installs from your file manager when Android asks, then tap the APK. Android 8.0 or newer. |
Shizuku, usage access and overlay access are optional. Each feature says what it needs before it asks. |
From a computer with adb:
adb install -r GameCore.apkNote
Shizuku is optional. Without it, the app still runs; capabilities that need ADB-level authority say so and point at the setup screen instead of pretending to work.
This release is about the two things a hard session leaves behind: a display you cannot read, and a stutter you cannot explain. Both now have an answer that says how sure it is.
|
Every display-size change shows a fifteen-second countdown with Keep and Revert. Silence, or a process that dies before the answer, puts the previous size back. A size left unconfirmed across a restart is recovered at launch, and a notification explains what happened — the undo never depends on reaching the screen that set it. |
A display-recovery screen in Settings restores the panel’s own size, shows whether the elevated shell is reachable, and says plainly when nothing was recorded to put back. It is also where the launch notification leads. |
|
Session reports name what was happening around each spike — thermal throttling, a capped refresh rate, battery saver, device load, high temperature, a profile that did not fully apply — and call a spike nothing explains unexplained instead of picking a cause. |
Every session keeps a timeline of profile applies, thermal and refresh changes, power-saver transitions, network spikes and frame-time spikes. The whole report can be rendered as a PNG receipt and shared through the same file provider as the session card. |
|
Backups carry an optional device snapshot, and a restore preview names the refresh rates, display sizes and Android version differences that may not mean the same thing on this device. Files written before this release still restore and read as unknown origin; the device section is optional and additive. |
The profile editor estimates a game’s battery cost from its finished sessions: the median percent per hour, how many sessions that rests on, and the playtime left at the current level when the device is not charging. When there is not enough measured history, it says how many more sessions it needs rather than inventing a figure. |
|
Session reports compare frame-time spikes against two measured lanes — TCP handshakes to the router and to a known host beyond it — and answer beyond this device, device to router, network but not localised, or not supported by the network record. No game server is contacted or claimed; the probe is labelled as a handshake, not a ping, everywhere it appears. |
|
21 official packages today · 13 bundled · one .gcplugin each
A plugin is a .gcplugin zip archive of JSON and image assets that GameCore reads. It can restyle the app, add a readout or expose a setting; it never runs inside GameCore and never gets a shell. The plugin directory can grow without widening what any one install can do.
|
|
| Themes & interface packs | Tools & readouts |
|---|---|
| Liquid Glass · True Glitch · Nebula · Nothing OS · Personal OS · Anime · Cyberpunk · Comic · Cybercity · Cybercore · Cybersigilism · Chromecore · Retro Futurism · Robot Core · RedMagic | Advisor · Automation History · Benchmark Suite · Dashboard Composer · Graphics Tuning · Sensitivity Lab |
The full set lives in dist/. Bundled copies are in app/src/main/assets/plugins/. Start with the format, the SDK, and the threat model.
| Area | What it covers |
|---|---|
| Overlay | Floating button, quick sheet, full control panel, dock, stats pill, crosshair, HUD builder |
| Profiles | Per-game refresh rate, brightness, display size, orientation, volume, DND, overlays, HUD, colour and session tracking |
| Monitoring | Per-core CPU, memory, battery, thermal, GPU, display, touch sampling, network and storage |
| Sessions | Encrypted history, graphs, timeline, frame-time attribution, network verdicts, shareable cards and receipts |
| Aim Lab | First-person 3D arena, seven training modes, weapon editor, records and exports |
| Plugins | Data-only themes, widgets, screens, settings and overlay modules |
The rest of this section is the long version: every overlay, profile field, reading and session rule, with the edge cases named rather than hidden.
Follow system, Dark, Light or AMOLED black, with a fixed palette of eight accents or a custom colour of your own. The choice applies to the whole app and to the overlay windows the service draws over your game, so the two never disagree.
- A draggable gaming button that snaps to any of four corners — or to the nearest edge on a free drag — remembers its place separately for portrait and landscape, survives rotation, and says what it knows without being opened: dimmed after a few seconds idle, with a small orange or red dot when the device is hot or critical.
- Tapping it opens a quick sheet on the screen edge nearest the button: up to six toggles you pin yourself, brightness and media volume, the game's own icon and the session clock. It is narrow on purpose, so the game stays visible beside it.
- Behind More — or an optional double tap on the button — is the full panel, in Display, Overlays, Capture and Session tabs: brightness and media-volume sliders, saturation, contrast and hue, screenshot, screen recording, Do Not Disturb, orientation lock, flashlight, colour presets, display shape, refresh rate, a shortcut back into the game, and a hold-to-confirm End session. Switching the panel's layout to split-across-both-edges keeps the earlier design, with the game visible between the two halves.
- A configurable performance pill, as a compact single line or a detailed card: pick which stats it shows and set its position, size, opacity, corner radius, text size and update interval. It samples only while something is looking at it — and not at all once the screen is off.
- A crosshair overlay with eleven designs — cross, dot, ring, ring-and-dot, cross-in-ring, T, X, chevron, corner brackets, box, or a PNG you import — and independent control of size, thickness, centre gap, rotation, opacity, colour and screen position. The drawn designs use Compose primitives, so they stay crisp at any size and ship no bitmaps.
- A visual HUD builder: drag widgets onto a live preview, choose from 18 stats, set each widget's text size, opacity, colour, label and background, and save layouts that a game profile can raise by name.
- A separate, lighter floating dock: a small draggable strip that snaps to a screen edge and carries one-tap shortcuts to controls you already have. Which buttons it shows, and their order, is yours to set and is saved per game, alongside the full panel rather than in place of it.
One profile per package, holding target refresh rate, brightness, display size, orientation lock, screen timeout, media volume, Do Not Disturb, which overlays to raise, a HUD layout, a crosshair preset, a colour preset, a performance mode, whether to free memory on launch, and whether to track the session — plus the three 3.5 smart features: thermal auto-downshift, the network check, and holding full performance under battery saver. All three are off unless you turn them on for that game.
Every adjustable field is nullable, and null means leave it alone — not "use a default". A profile that sets only brightness records what brightness was, changes it, and puts exactly that back when the game exits. Nothing else is touched, so nothing else can be restored to a value the device never had.
Automatic detection applies a profile when its game comes to the foreground and unwinds it when the game leaves. You can also launch a game straight from its profile screen.
Free RAM on launch is the one setting whose effect lands on your other apps, so it is off
by default, per game, and it runs last — after the overlay is up and the profile is applied,
because those are the things you are waiting for. GameCore never closes itself, the game, your
launcher, your keyboard, anything holding a foreground service (music, a recording, a
download, a navigation route), any part of the system, or anything on your never-close list in
Settings. With Shizuku it enumerates what is actually running, closes through the elevated
shell and re-reads the list to confirm; without it, it asks Android through
killBackgroundProcesses(), which reports nothing about what it did — so the summary says
"asked Android to close" rather than claiming a count it cannot verify. The freed figure is
two ActivityManager readings with the pass between them, and it is allowed to say it could
not measure, or that available memory did not rise.
Per-core CPU usage and frequency from /proc/stat and sysfs, memory from /proc/meminfo
and ActivityManager, battery level, charging source, health, temperature, voltage and
instantaneous current, thermal status, GPU load where the device exposes a /sys node for it,
display mode and rotation, the touch sampling rate where the device will report it, network type, latency and
throughput, and free storage — with live graphs, a configurable sampling interval, and
sampling that stops the moment nothing is looking at it.
Battery drain is reported as percent per hour rather than raw points lost, and only after five minutes of discharging, so a two-minute session cannot extrapolate to a headline figure.
Every tracked session is written to an encrypted Room database: the game, start and end time, duration, battery delta, average and peak CPU load, average and peak memory use, average and peak temperature, the refresh rate it ran at, the network it used, what its latency probes did, and the colour preset and values the display was held at. A session recorded before 1.1 reads back as no colour reading rather than as a neutral one — an absence, not an invented zero.
The session report draws those as graphs. The history screen filters, sorts, deletes and aggregates them. A session interrupted by the app's process being killed is repaired on the next launch and marked as ended by process death, rather than being silently dropped or left running forever.
What the connection did, across the whole session. The latency probe answers "how is it right now" from a burst of handshakes. A tracked session also keeps what happened across all of them: how many completed, how many did not, how many came back at least twice the session's own running average and at least 40 ms above it, the worst reading, the average spread between consecutive probes, and the longest unbroken run of failures. The report turns that into one word — Stable, Spiky, Unreliable, No reply, Not measured — and one sentence naming the figures behind it. The two are not symmetric on purpose: "stable" is withheld until at least three probes have gone out, while a single handshake that did not complete is reported at once. Two quiet probes are not evidence of a good connection; one refused handshake is evidence of a bad moment. A spike is measured against the session's own average rather than a fixed threshold, because 180 ms is an event on a connection that has been sitting at 30 ms and unremarkable on one that has been sitting at 190 ms. None of it is called packet loss.
A card you can share. The report has a share action that draws a 1080×1260 PNG on the
device — game, date, duration, average frame rate, average heat, battery drain — and hands it
to the standard share sheet as a one-shot read grant through the app's own non-exported
FileProvider, never as a file path. The card keeps the rules of the screen it came from,
with less room to explain them: a reading the device could not take is an em dash and never a
zero, an interrupted session's duration is marked as a lower bound, a per-hour battery figure
is only printed when the session was long enough to support one (below that the tile becomes
the points actually lost, which is a subtraction of two real readings), and the sample count
travels with the averages — four samples and four hundred are different claims, and a card is
read alone in a chat window with nothing beside it to qualify it. The game's own label is the
one string on it GameCore did not write, so it is stripped of control characters, collapsed
and clamped before being drawn, and it contributes nothing to the filename.
What each of your games is holding in cache, and the one part of it GameCore is willing to
delete. Every cache cleaner on a store shows one number and one button; the number is usually
the whole data directory and the button usually runs pm clear, which is why the reviews of
those apps are full of people who lost a save. So each game gets three figures instead of one:
- Cache — what the platform reports for the package, the same number as that app's own storage page in Settings.
- Clearable — the part of it GameCore can actually reach: the shared-storage cache directory, and nothing else. A separate reading rather than a fraction of the first, because Android 11 and below will not break the total down, and guessing the split would be guessing how much a button is about to free.
- Untouched — the rest. Saves, logins, settings, and the assets a game downloaded after install. Nothing GameCore does touches it, and it is on screen so that "freed 900 MB" can never be read as having come out of it.
The list is your games — an app that declares itself one, or an app you made a profile for, which is better evidence than the manifest — ordered largest cache first, with rows whose size could not be read placed last rather than treated as empty.
A tap reads the cache figure, deletes Android/data/<package>/cache, waits for the platform's
accounting to catch up, and reads the figure again. What it reports is the difference between
those two readings: not the size that was there beforehand, and not the exit code of the
delete, since rm -rf on an empty directory and on two gigabytes exit identically. The figure
is allowed to come out at nothing — the reachable part may already have been empty, or the game
may have refilled it in the second it took to look, and both are ordinary.
The copy of the cache inside the game's private data directory is out of reach: a shell running as the shell user cannot open another app's data directory, and GameCore holds no root path to one. That part is reported as remaining, next to a button for Android's own storage page for the app, which needs nothing granted and reaches more than this does. Measuring needs usage access, deleting needs Shizuku, and when either is missing the screen says which rather than offering an action that would achieve nothing.
Only rates the panel actually advertises are offered — the list comes from
Display.getSupportedModes(), not from a hardcoded 60/90/120.
A change is reported as applied only after it has been read back and confirmed. This
matters for a specific, verified platform behaviour: on several MediaTek and PowerVR
devices the standard preferredDisplayModeId / Surface.setFrameRate() calls are accepted
without error and then ignored. An app that reports success because the setter did not
throw is lying to its user on every one of those devices. GameCore instead reports
"accepted the request but stayed at 60 Hz" and offers the Shizuku path, which does work
there.
Frame rate gets the same treatment. Reliable per-frame FPS is generally not available through public APIs without root or instrumentation, and what is available varies by OEM and GPU. GameCore detects whether a usable signal exists on the device it is running on, and shows "Not available on this device" when it does not — rather than estimating.
| ✅ GameCore does | 🚫 GameCore refuses |
|---|---|
| Measure before it changes, then restore the reading it found. | Fabricate a reading, use a fallback constant, or dress an estimate as a measurement. |
| Say “Unavailable”, “Requires Shizuku” or “Not supported by this device” with the reason. | Claim a system call worked because the setter did not throw. |
| Close only what a profile asked for, with a never-close list. | Root, inject code, read another process’s memory or interfere with anti-cheat. |
| Delete one game’s shared-storage cache after measuring it. | Run “clean everything”, pm clear, or touch a save, login or downloaded asset. |
| Draw share cards and receipts on the device. | Upload sessions, analytics or crash reports. |
| Make one optional, payload-free TCP handshake. | Invent a packet-loss percentage or contact a game server. |
Most "game booster" apps are theatre. They show a RAM figure, animate a progress bar, kill some processes Android immediately restarts, and report success. GameCore does have a switch that closes background apps when a game starts — off by default, per game, and it tells you what it measured afterwards rather than what it hoped for, including when the measurement failed or came out negative. The difference is not the capability. It is that the number is a reading and not a flourish. Its central type is
sealed interface Observed<out T> {
data class Value<T>(val value: T, val source: DataSource, val precision: Precision)
data class Restricted(val reason: RestrictionReason, val unlockedBy: AccessLevel?, ...)
data class Failed(val detail: String, val cause: String?)
}A reading is either a value that names where it came from and how exact it is, or an absence that names its reason. There is no default, no fallback constant and no estimate dressed up as a measurement. The consequence is visible in the app: some cards say "Not available on this device" or "Requires Shizuku" instead of showing a number. That is the feature, not a gap.
- No root, no injection. No reading or writing another process's memory, and no anti-cheat interference.
- No fabricated statistics. No claiming a system call worked when it did not.
- No timer that closes apps. Nothing is closed without a profile asking for it — never your launcher, your keyboard, the game itself, anything holding a foreground service, or anything on your never-close list.
- No thermal limit override, and no protected system files.
- No “clean everything”. The storage screen deletes one named directory belonging to one game you tapped, and never a save, a login or a downloaded asset.
- No packet-loss percentage. A refused TCP handshake is not a dropped packet.
- Nothing you record is uploaded. The shareable session card is drawn on the device and handed to your own share sheet.
The one thing that does write a game's own files is the config editor, and it stays inside that
game's shared Android/data/<package>/files directory — the config file you opened and edited,
its original backed up before the first change, each write shown to you line by line and committed
whole or not at all. Never its code, never a save in the private storage a shell running as shell
could not reach anyway, never a path that a .. could climb out of, and never without Shizuku and
a one-time notice you accept.
The full set of shell commands the app can construct lives in one file
(core/shizuku/ShellCommand.kt) and a unit test enumerates every one of them and asserts
that cmd, su, setprop, force-stop, pm clear, pm trim-caches, install and
friends are unreachable at any privilege level. Most of what that file can build reads or
writes something about this device — a settings key, the display size, GameCore's own
permissions. A few reach further, and each is bounded to a form the test pins literally so
that a second use cannot be added without it failing: am kill --user current <package>
closes one game's background processes; rm -rf /storage/emulated/<user>/Android/data/<package>/cache
clears one game's shared cache; taskset -ap <mask> <pid> pins one game's process to a set of
cores; input tap and input swipe send one synthetic tap or press-and-hold to a per-game
screen point the user placed, for the volume-button trigger — no text, no key event, and no other
input subcommand is expressible; the charge-bypass toggle writes a single digit to one validated
/sys/class/power_supply/<supply>/<node> — the one command in the app that runs sh -c, and it
passes the value (0 or 1) and the path as positional arguments ($1, $2) rather than splicing
either into the script text, after a test -w probe and a cat read of that same node; the GPU HUD
reads one validated GPU-load node under /sys with cat; and the config editor's ls, stat,
cp, mv and rm -f read and rewrite files inside one game's own Android/data/<package>/files —
never its cache, never its obb, never the private storage a shell-uid process cannot reach, and
never through a path a .. could climb out of. Every argument that originates outside the app's own
code is validated against a form before a command exists; every command but that one charge write
reaches exec as an argument vector; and that write runs a fixed script whose only inputs are its
two positional arguments, so there is nowhere a glob is expanded or a word is split.
A display colour screen, with the same values reachable from inside a game.
- Eleven values. Red, green and blue gain; gamma as one slider or three; saturation, contrast, hue rotation and a brightness offset. Every slider shows its number, and tapping the number opens a keypad for an exact entry — held to the field's own range, so a hue turns ±180° while a gain stops at ±100%, and an out-of-range entry is refused rather than quietly clamped to something you did not type.
- Presets you own. Seven ship — Vibrant, Warm, Cool, Night, Protanopia, Deuteranopia, Tritanopia — and they are ordinary saved rows, not a fixed menu: open one, move a slider, save it under a new name, rename it, delete it. There is no cap on how many you keep.
- Per game. A profile can carry a colour preset. It is applied when the game comes to the front and put back when it leaves, from the reading taken before the change — the same restore rule every other profile field follows.
- In the overlay panel. Saturation, contrast and hue sit directly under the existing volume and brightness sliders, a Colour tile joins the action grid, and a long press on it drops your saved presets in as chips. Everything applies as the slider moves, with no confirm step.
- In the session report. Which preset, and which values, were active while the session ran.
And the reason the feature is built the way it is: Android exposes no per-channel colour
matrix to an app. ColorDisplayManager's matrix and SurfaceControl.setDisplayColorTransform
are hidden platform API that neither WRITE_SECURE_SETTINGS nor a shell running as uid 2000 can
reach. So GameCore projects what you asked for onto the sinks that do exist on the device it is
running on — night-display warmth, the display colour mode, the daltonizer,
reduce-bright-colours, colour inversion — and the screen lists every write it would make, names
each value this device has no sink for, and says why. A gamma slider that moved and quietly did
nothing would have been easier to build, and would have been exactly the lie this project exists
to avoid.
Colour correction needs WRITE_SECURE_SETTINGS, which Android never grants an app on its own —
Shizuku or a wireless-ADB pairing is the only way to hold it. Without it the screen says so and
points at the setup, rather than presenting sliders that write nothing.
Note
Shizuku is optional. It unlocks the device-wide capabilities listed below. Without it, GameCore says which ones are unavailable and points at setup instead of faking a result.
Shizuku (or a wireless-ADB pairing) raises the app to ADB-level authority, which is what most
devices require for a device-wide refresh-rate change, for animation scales, for the display's
colour keys, for reading and reshaping the display's own size, for deleting a game's
shared-storage cache, for reading and editing a game's own configuration files, and for a handful
of dumpsys reads Android does not expose to ordinary apps.
What the app can run with that authority is a closed, enumerated set:
| Program | Used for |
|---|---|
id |
confirming the shell's own uid before trusting it |
cat |
/proc/stat, /proc/meminfo, a battery charge-control node, and a GPU-load /sys node where the device exposes one |
dumpsys |
thermalservice, battery, display, SurfaceFlinger --latency, activity, gfxinfo |
settings get/put |
nineteen specific keys, each with its own value range |
getprop |
three ro.* chipset properties |
pm grant |
three permissions, to this app only |
appops set |
two app-ops, to this app only |
wm size |
reading the display's size, setting a per-game override, and clearing it |
taskset -ap |
reading one game process's CPU affinity, and pinning it to chosen cores |
input tap / input swipe |
tapping, or press-holding, one saved per-game screen point — the volume-button point trigger's one action |
am kill |
closing one named background app, when a profile asks to free memory |
rm -rf |
one game's shared-storage cache directory, on a tap in Game storage |
ls / stat |
listing and sizing files inside one game's own config directory |
cp / mv |
staging an edited config to a temp file and committing it over the original atomically |
rm -f |
removing one staged temp, or one config file you deleted in the editor |
test -w |
probing whether a device's battery charge-control node exists and is writable, before offering the toggle |
sh -c |
writing a single digit (0/1) to that charge-control node — the one shell command in the app, with the value and path passed as positional arguments ($1, $2) rather than spliced into the script |
The one shell command in that list runs a fixed script and receives the value and the path as
positional arguments ($1, $2), never spliced into the script text, so there is no place for a
value or a path to break out of the echo or its redirection. Every other command reaches exec
as an argument vector, with no shell at all. Every
argument that originates outside the app's own code — a package name from a stored profile,
a value from a slider — is validated against a form before a command is built, and a
rejected argument produces no command at all rather than a malformed one. pm grant and
appops set are checked against the compiled application id, so neither can be aimed at
another app.
Tip
Nothing is requested until the feature that needs it is switched on. Every permission below is also explained in the app’s own Permissions screen, with a button to the exact system page that grants it.
| Permission | What stops working without it |
|---|---|
SYSTEM_ALERT_WINDOW |
the floating button, pill, crosshair and HUD |
PACKAGE_USAGE_STATS |
automatic game detection and apply-on-launch, and cache sizes |
QUERY_ALL_PACKAGES |
listing installed games so you can make a profile |
KILL_BACKGROUND_PROCESSES |
"free RAM on launch" on a device without Shizuku |
WRITE_SETTINGS |
brightness, screen timeout and orientation lock |
ACCESS_NOTIFICATION_POLICY |
the Do Not Disturb toggle |
MODIFY_AUDIO_SETTINGS |
the volume slider |
WRITE_SECURE_SETTINGS |
colour correction (granted through Shizuku or adb only) |
ACCESS_NETWORK_STATE, INTERNET |
network type, latency and throughput |
POST_NOTIFICATIONS |
any foreground service, since each must post one |
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS |
detection and tracking surviving Doze |
FOREGROUND_SERVICE_* |
the app's foreground services |
Shizuku API_V23 |
the elevated capabilities, if you use Shizuku at all |
Each foreground service declares its real Android 14 foreground-service type rather than one
generic value: dataSync for session tracking, mediaProjection for screen recording with
its own separate consent flow, and specialUse — with a manifest justification for each — for
the overlay, game-detection, performance-monitoring and shake-shortcut services, which is the
closest the platform offers. Nothing runs when it is not needed — each service stops itself.
| Layer | Guarantee |
|---|---|
| APK | Release builds go through R8 with obfuscation and resource shrinking. |
| Database | Room is encrypted with SQLCipher; the key lives in the Android Keystore. |
| Preferences | EncryptedSharedPreferences; android:allowBackup="false". |
| Intents | Only the launcher activity is reachable by another ordinary app; no exported receivers, no deep links. |
| Files | A non-exported FileProvider grants one URI at a time; no storage permission. |
| Network | Cleartext is blocked; the only outbound connection is the optional latency probe. |
| Root | Detected, never required and never exploited. |
- Release builds go through R8 with obfuscation and resource shrinking, so a decompiled APK does not hand over the optimization engine's internals or the shell command structure.
- The Room database is encrypted with SQLCipher, and its key is generated in and held by
the Android Keystore — not stored in a file the app could leak. Preferences go through
EncryptedSharedPreferences. android:allowBackup="false". The session database and encrypted preferences never leave the device through a backup transport.- The only intent filter an app can invoke is the launcher activity's. The three others in
the manifest are system-dispatcher actions on services the system binds, never another app: the
notification listener that reads the active media session, the Quick Settings tile, and the
optional accessibility service behind the in-game volume-key trigger. Each is guarded by a
BIND_*permission the system alone holds and no<uses-permission>requests, so being exported makes it reachable bysystem_serverand nothing else. No exported receivers and no deep links, and every activity but the launcher isexported="false". Two content providers exist and neither is a way in: the AndroidXFileProviderthat hands out screenshots, recordings, exports and session cards is not exported and grants a read on one URI at a time, and the Shizuku startup provider must be exported for Shizuku's own server process to bind it, so it is guarded byINTERACT_ACROSS_USERS_FULL— a signature-level permission no ordinary app holds. - The files that leave the device leave by your hand. Screenshots, recordings, session
exports and session cards are written inside the app's own storage and reach another app only
through the share sheet, as a content URI with a one-shot read grant rather than a path, which
is why GameCore asks for no storage permission on any API level. A card's name is a timestamp
and nothing else — a game's own label has no business in a path — at most four are kept before
the older ones are deleted, and the backup rules exclude the whole of
files/from both cloud backup and device transfer. - No cleartext traffic, enforced by a network security config. The only outbound connection the app ever makes is the TCP latency probe, to a host you can change or switch off in Settings.
- All external text is sanitized before storage and again before render. A game's
android:labelis another developer's string and an imported HUD layout is a file from anywhere, so both pass a filter that strips bidi overrides, zero-width characters, control characters and combining-mark stacks, and truncates on a grapheme boundary rather than mid-surrogate. There is a unit test per hostile input, with each invisible character built from its code point so the test file stays reviewable. - No security-relevant behaviour branches on
BuildConfig.DEBUGor on a preference, and no shell output, session data or device identifier is logged in a release build. - Root is detected, not required and not exploited. If the sandbox is not intact, GameCore says so plainly rather than continuing to assume its own guarantees hold.
flowchart LR
UI["Compose UI<br/>Screens · State · ViewModel"] --> Domain["Domain<br/>profiles · sessions · display"]
Domain --> Repo["Repositories"]
Repo --> DB[("Encrypted Room<br/>SQLCipher")]
Domain --> Platform["Android APIs<br/>readers · controllers"]
Domain --> Shell["Shizuku shell<br/>optional · enumerated"]
Plugin[".gcplugin<br/>data only"] --> Host["Plugin host"]
Host --> UI
Kotlin only, Jetpack Compose with Material 3, MVVM, Hilt, Coroutines and StateFlow. No
Java, no XML layouts — the only XML is Android resources and the manifest. 701 Kotlin source
files in the app and 30 in the plugin SDK, about 184,000 lines together.
Package tree
app/
├── core/
│ ├── common/ Observed<T>, AccessLevel, Formatters, TextSanitizer
│ ├── model/ the domain types — profiles, HUD, colour, sessions, readings
│ ├── permissions/ the catalog and live permission state
│ ├── system/ readers and controllers over the platform APIs
│ ├── overlay/ window geometry, drag/snap arithmetic, the overlay hosts
│ └── shizuku/ the elevated shell, its command set, root detection
├── data/
│ ├── database/ Room + SQLCipher, entities, DAOs, mappers
│ ├── preferences/ EncryptedSharedPreferences, the Keystore-held DB key
│ └── repository/ the only thing the domain layer talks to
├── domain/
│ ├── color/ the colour values, and which sink each one can reach
│ ├── display/ refresh rate and display size, applied and read back
│ ├── gaming/ game detection, profile apply and unwind, session tracking
│ ├── memory/ which apps a reclaim pass may close, and what it measured
│ ├── monitoring/ sampling, aggregation, thermal, battery and latency trends
│ ├── optimization/ OptimizationManager over Standard / Shizuku optimizers
│ ├── overlay/ what is on screen and why
│ └── storage/ per-game cache measurement, and the one delete it offers
├── service/ six foreground services, the media listener, and the quick-trigger tile and accessibility services
└── ui/ one package per screen, each a Screen + State + ViewModel
The UI never executes a shell command or touches a system API directly; it reads state and
calls into the domain layer. OptimizationManager chooses between StandardAndroidOptimizer
and ShizukuOptimizer based on what DeviceCapabilityChecker found, and every operation
comes back as one of available, requires Shizuku, requires a permission,
unsupported, applied or failed — there is no boolean that means "probably".
| Task | Command |
|---|---|
| Debug APK | ./gradlew :app:assembleDebug |
| JVM unit tests | ./gradlew :app:testDebugUnitTest |
| Signed release | ./gradlew :app:assembleRelease |
| Lint | ./gradlew :app:lintDebug |
git clone https://github.com/Dreamucxe/GameCore.git
cd GameCore
./gradlew :app:assembleDebug # installable debug APK
./gradlew :app:testDebugUnitTest # JVM unit tests, no device needed
./gradlew :app:assembleRelease # R8 + shrinking; signed if you have a key
./gradlew :app:lintDebug # zero warnings is the standard hereKotlin 2.0.21 · AGP 8.5.2 · Gradle 8.9 · JDK 17 · compileSdk 34 · minSdk 26.
assembleRelease produces a signed APK when a keystore.properties is present at the
project root:
storeFile=release.keystore
storePassword=…
keyAlias=…
keyPassword=…Without it the build still succeeds and emits an unsigned APK, so a contributor with no signing key is not blocked — but an unsigned APK will not install on a device.
The unit tests are deliberately written against the pure, Android-free seams: the geometry, the formatters, the sanitizer, the command builder, the aggregators, the state reducers, the per-session latency fold, and every word the shareable card is allowed to print. 271 test classes, 3,181 tests. No mocking framework, no Robolectric, no emulator — the suite runs on any JDK.
| Data | Where it lives | Leaves the device? |
|---|---|---|
| Profiles, HUD layouts, sessions, samples | Encrypted Room database on the device | No |
| Screenshots, recordings, exports, session cards | App-private storage | Only when you hand one to your own share sheet |
| Latency probe | One TCP handshake to a host you choose | Only that handshake; no payload |
There is no backend, no account system, no cloud sync, no analytics, no crash reporting and no advertising SDK. Everything GameCore records — profiles, HUD layouts, sessions, samples — lives in its own encrypted database on the device, and none of it leaves on its own. The session card is the one artefact meant to leave, and it leaves the way a screenshot does: drawn on the device, then handed to whichever app you choose from your own share sheet. GameCore has nowhere of its own to send it.
One thing touches the network, and it is honest about it on the screen it belongs to:
- The latency probe. The single network call GameCore's own code ever makes — a payload-free TCP handshake to a host you choose, timed and closed the instant it connects. It exists because you asked for a ping figure, and it can be switched off. No payload, ever, in either direction. The 3.5 network check reuses this same probe rather than adding one of its own; everything else it reports — the transport, the Wi-Fi band, the link speed — it reads from Android on the device.
Older releases, preserved. Expand any version for the full notes.
3.17.0 — Tilt training for phones without a gyroscope
The gyro challenge no longer needs a gyroscope. A phone without one can run it as Tilt training — a real measurement, read from the accelerometer and labelled as tilt.
- Bank to steer, tip to aim. Tilt training reads the phone's lean against gravity: bank the phone left or right in the plane of the screen to steer, and tip the top edge away from you to aim up. A phone with a gyroscope is unchanged — the gyroscope is still the input.
- Its records stay apart from the gyro's. An accelerometer cannot sense turning on the spot — gravity does not change when the phone rotates about itself — so a tilt run is stored under its own mode and is never ranked against a gyro record.
- No sensor, no pretend run. A device with neither a gyroscope nor an accelerometer gets a plain explanation in place of a Start button, rather than a run that could not move.
3.16.0 — Themes can be made of glass
A theme can ask for glass now. A plugin stays data — it never touches the canvas — but it can now ask the host for a material, and the host draws every frame of it.
- Themes can be made of glass. The theme schema is now 4, and a
glassblock asks for a material rather than a flat colour: a translucent surface, a specular rim along the top edge, a highlight that drifts slowly down the face, and an iOS-style spring that settles a panel when the material changes. A theme that declares schema 3 and carries the block is refused, not downgraded — a glass theme whose transparency silently became a flat fill is not the design its author wrote. - The blur belongs to Android 12. From API 31 the backdrop behind a panel is blurred as well. Below that the host still draws the translucency, the rim and the gloss against a sharp backdrop, and the Control Center says so — "Sharp — this Android version cannot blur one" — rather than calling it frosted.
- One slider. Settings → Glass Control Center holds a single control: how much of the material the app is wearing, scaling the transparency, the gloss and the blur radius together. The preview above it is drawn from the exact material the slider points at, through the app's own renderer, so you can judge it before you let go. The row appears only while a theme with a material is in force.
- Animation settings still win. Settings → Animations → Off, or the system's remove-animations switch, and the material is drawn at rest — the translucency, the rim and the gloss stay, the highlight stops and the panels stop settling. A plugin asks for motion; it can never take it.
Ships with Liquid Glass GameCore (dist/liquidglass/liquidglass.gcplugin) — rewritten to ask
for the material it is named after, version 1.2.0, and it needs GameCore 3.16 or newer. It is a
downloadable package rather than a bundled theme: drop the .gcplugin file into GameCore's
plugin installer. All twenty official plugin packages ship with the release that way, and nine
of them are also bundled in the app.
3.15.0 — Five launcher icons, and a plugin can choose one
GameCore has five launcher icons, and a plugin can choose one of them.
- The picker. Settings → App icon shows the set: Core, Mono, Glitch, Neon and Redline. Tapping one changes the icon your launcher shows — a real switch, not a shortcut to the app, because each icon is its own launcher component and exactly one is enabled at a time.
- Plugins can ask. A plugin with the
change_iconpermission ships anicon.jsonnaming a slot; Settings shows which plugin is setting the icon and offers a one-tap Undo back to your own choice. A plugin that is disabled, uninstalled or suspended loses the icon on the next launch. - Plugin artwork, honestly. Android has no API that lets an app install a bitmap as its launcher icon, so a plugin's own image cannot become the icon — and this release does not pretend it can. Artwork is offered as a pinned home-screen shortcut instead: you confirm it in the launcher, and GameCore's own icon is left alone. The picker says so in as many words.
3.14.0 — Themes can move
Theme plugins can move now. A plugin stays data — it never gets to touch the canvas — but it can now ask the host to distort what it draws, and the host owns every frame of that.
- Themes can move. A schema-3 theme may carry a
glitchblock. The app's content is recorded into a layer and re-drawn as displaced horizontal bands: the frame tears, text and panels together, with one-pixel fracture lines, ghost copies at the band edges and translucent block noise. It is a live re-draw, not a colour filter, and it applies to the whole app. - It reads the phone. With
hardwareReactiveon, a device above 85% CPU tears about twice as often, and one the platform reports as thermally throttled adds a vertical frame-drop on the first frame of a burst. Off, the effect keeps a fixed rhythm and reads no sensors at all. - Animation settings still win. Settings → Animations → Off, or the system's remove-animations switch, suppresses every theme effect. A plugin asks for motion; it can never take it.
- Off by default. Nothing changes unless you install and select a theme that asks for it. Every existing theme renders exactly as it did.
Ships with True Glitch (dist/trueglitch/trueglitch.gcplugin) — cyan and red on near-black,
monospaced, torn.
3.13.0 — One tap, the full panel
The floating button stops making you tap twice.
- One tap, the full panel. Tapping the game button opens the control panel straight away — every tab, every control — instead of the small quick sheet. The sheet has not gone anywhere: a double tap on the button opens it, with the same toggles, sliders and session clock.
- Or keep the sheet first. Settings → Overlay → Quick sheet has a switch. Off, a tap opens the sheet first and the panel stays behind its "More" button, exactly as it used to behave.
3.12.0 — The Theme Engine reaches every screen
The Theme Engine stops being something you visit and becomes the colour the app wears.
- App-wide colour. A switch on the calibration screen hands the app's accent to the engine. On, every screen is tinted by what the device is doing right now — the same ice-blue, violet and molten crimson you see on the canvas — and a theme plugin keeps everything else it does but stops choosing the palette. Off, a plugin's colours win exactly as they always have, which is the default.
- The engine runs the whole time. It starts with the app rather than with its settings screen, so its colour is live everywhere and the first recolour is a transition instead of a jump. Calibrating still drives it live; leaving the screen no longer stops it.
- Say who chooses. The same card prints the accent in force and whether it came from your settings, the engine, or nothing at all yet — no guessing which layer won.
3.11.0 — A live backdrop from what the device is doing
A live backdrop painted from what your device is actually doing — and a calibration screen where every figure it uses is on the table.
- Theme Engine. A canvas in Settings → Theme Engine draws a soft ice-blue drifting at half speed when the device is cool and quiet, turns Aurora Violet once CPU, memory or temperature cross their thresholds, and goes molten crimson at speed when it is hot or saturated. The colour and the speed both follow the readings as they change.
- See the vector. The calibration screen prints the mode in force, its exact
rgb(r, g, b)value and its fluid velocity beside the three readings it was drawn from — CPU, memory allocation and core temperature, each labelled with what it is. - Calibrate against your own thresholds. Flat sliders stand in for each reading so you can watch the backdrop answer without loading the device, and the thresholds that decide each mode are adjustable.
- Save points. Keep a calibration you like under a name and recall it later. Save points are stored on the device and survive a restart.
The engine is built into GameCore, in its own package rather than in the screens that use it. It
is not a .gcplugin: those are data GameCore reads, and this one draws, so it ships with the app.
3.8.1 — Eight official plugins and the plugin host
Eight plugins GameCore built itself — and the host features that let them do real work, all still under the rule that a plugin is data GameCore reads, never code it runs.
- Eight official plugins. A first set you can install from a file: a Nothing-OS-style theme,
an anime-inspired pack, a sensitivity workspace, an automation-history view, a personal
dashboard, a local advisor, a benchmark readout and a dashboard composer. Each ships bundled and
can also be sideloaded as a
.gcpluginfile. - Plugin settings you can change. A plugin can offer its own options — switches, choices and numbers — and GameCore stores and applies them for you. The controls live in GameCore; the plugin still runs nothing.
- Your automation history, shown by a plugin. A plugin you allow can display the record of automations GameCore has already run for you. It reads that history only to show it — it is handed none of your other data and runs no code of its own.
- Live readouts on plugin screens. Plugin screens and dashboard widgets can show figures GameCore already measures — frame rate, temperature and the like — each drawn by GameCore from data you have allowed.
3.8 — Data-only plugins arrive
Plugins arrive — and arrive as data, not code. A plugin is a file GameCore reads to restyle itself and add small readouts; it never runs inside GameCore and never gets a shell, so the directory can grow without widening what any one install can do.
- Install a plugin from a file. Add a plugin from a
.gcpluginpackage, see exactly what it asks for before anything is on, and turn it on yourself. A plugin is data GameCore interprets with code it already ships — it carries no scripts, no WebView and no native libraries. - Permission by permission. Every plugin lists exactly what it wants and you grant each item separately; a new plugin arrives switched off with nothing granted, and anything that touches a device setting needs a second opt-in.
- Plugin themes. A plugin can supply colours, corner rounding and UI scale. Unreadable colour pairs are replaced automatically, and the Plugins screen always draws in GameCore's own theme — so the way back is never styled by the plugin you are turning off.
- On-screen plugin modules. A plugin can add a small readout that sits over your game beside the Game Pill — frame rate, temperature and the like. It cannot cover the screen, cannot take touches from the game, and appears only once you grant that plugin the overlay permission; up to three at a time.
- One switch to stop them all. A single toggle on the Plugins screen stops every plugin at once, whatever each plugin's own switch says.
- Order the list your way. Sort installed plugins by name, by date installed, or by version
number — highest first, with
1.10correctly above1.9. The order only changes how the list is drawn; it changes nothing about which plugins run.
3.7.2 — Dock customisation and mid-session revert
The floating dock's buttons become yours to arrange, the active profile can be put back one field at a time without ending play, and an in-game trigger can be placed by touch — plus the fix for a release-blocking bug where the trigger-placement overlay swallowed every touch except the game's own pill.
- Dock customisation. Rearrange the floating dock's buttons and choose which of them appear, saved per game — so the dock a game raises holds exactly the controls that game needs.
- Quick actions on the dock. Put shortcuts to GameCore's existing controls onto the dock for one-tap access without opening the full panel; a quick action runs through the same handler the panel's own tile does and invents no new capability.
- Revert settings mid-session. A card on Home lists exactly what the active profile changed on your device and puts any of it back without ending play — the same reading-paired restore a profile does on exit, offered one field at a time while the game runs.
- In-game point-trigger placement. Place a volume-key tap target by touching the spot on screen, from inside the game and without opening the app. It is saved per game, and the tap it later fires is the one synthetic input the app can send (see below), gated on Shizuku.
3.7.0 — Floating dock, screen extraction and touch tools
A second overlay surface and four stand-alone tools, each holding the app's one rule: what it cannot measure or do, it says plainly rather than faking.
- Floating Dock. A small draggable dock over your game with its own quick control panel; it snaps to the screen edge, is turned on from Settings, and sits alongside the existing floating button rather than replacing it.
- Screen Extraction. Capture the current screen, crop a region, then save or share the result — drawn and cropped on the device and handed out through the same one-shot share grant every other artefact uses.
- Touch Sampling Monitor. A live read of the device's real touch input rate. Where the hardware will not report it, it reads Unavailable rather than a plausible number — the rule every reading in the app follows.
- Volume Button Point Trigger. Map a volume key to tap a saved screen point for a game — single, double or hold. It needs the optional accessibility service, so the key reaches GameCore from inside another app, and Shizuku, so the tap can be injected; without either, the control says so rather than pretending to work.
- What's New. Shows the features a release introduced the first time you open the app after updating, and lets you browse every past update any time from Settings.
3.6.1 — Network stability, performance HUD and custom overlays
Three leaf screens, each turned on or off from Settings, and each holding the app's one rule: a figure it cannot measure is shown as "Unavailable", with the reason, never as a zero.
- Network Stability Mode. A live read of the connection you play on — transport, signal, estimated link speed, device-wide download and upload, and whether the system calls it metered — with a stability window built from TCP-handshake bursts. It reports ping (median handshake time) and jitter (the variation between consecutive probes, the honest stand-in for the packet-loss figure it will not fake), and how many probes in the window completed. Where Android allows it, it also keeps the device from switching networks mid-game.
- Advanced Performance HUD. A customisable heads-up display over FPS, CPU, GPU where the device exposes a load node, RAM, temperature, battery, ping and session time, as a compact line or an expanded card. Any stat the device will not report reads "Unavailable" rather than a made-up value.
- Custom Overlay Modules. Turn individual overlay modules on and off, drag them to position, resize the ones that support it, and choose exactly which information each shows — saved as an overlay layout per game that a profile can raise by name.
3.6 — Instant Replay
The feature people ask for by name — keep the last few seconds of play, and decide to save them after they have already happened.
- Instant Replay. A rolling video buffer that always holds the most recent stretch of the game — 15, 30, 60 or 120 seconds, 30 by default — so a moment you did not know you would want is already recorded by the time you reach for it. When you save, the buffered window is written out as a single clip and nothing outside it is; the buffer itself lives in the app's own private cache and is trimmed back to the length you chose as it rolls, so it never grows without bound. It is off until you turn it on, set per game like every other profile field, and the first time you enable it a one-time notice states the three things worth knowing first: the buffer records the whole time the game is open, everything stays on this device, and this version is video only — it holds no microphone permission and captures no audio, on purpose, because a "record audio" switch that produced a silent track on half the games it was used with is exactly the kind of control this app refuses to ship. It reuses the same screen-capture consent the recorder already asks for, so it adds no new permission. An in-game chip shows when it is armed, and a save control sits in the overlay while the game runs.
3.5.4 — Config editor and per-game resolution override
Two features about reaching past the settings a game exposes — to the files behind them, and to the resolution it renders at.
- A config editor. Browse a game's own configuration files and edit one in place, with the whole change shown as a line-by-line diff before a single byte is written — nothing is saved until you confirm exactly what changed. The write is whole-file and atomic, so a config is never left half-applied. The first time you edit a game's config its original is backed up automatically; you can keep named checkpoints and restore any of them later, and if the game has been updated since that backup the editor says so, because an edit made against a config the update already replaced is an edit against a stale file. It reaches those files through the same elevated shell the rest of the app uses, so it needs Shizuku — without it the screen says so and points at the setup — and a path outside the selected game's own config is refused rather than opened. A one-time notice explains the feature before the first edit, and can be shown again.
- Resolution override. A per-game render resolution, set as a scale of the display's native
size and applied through
wm sizewhen the game comes to the front — then cleared and the display put back exactly as it was when the game leaves, the same restore rule every other profile field follows. Lowering it can lift frame rate on a GPU-bound game at the cost of sharpness; it is per game, off unless you set it, and it asks once for confirmation the first time because it reshapes the whole display while the game runs. An in-game status chip shows when an override is active, so a changed resolution is never a mystery. Needs Shizuku, like every display-size write.
Also since 3.5, and until now in no release:
- A pinned magnifier. A loupe overlay that shows the centre of the screen enlarged — a crop-and-scale of a single capture frame, parked in a corner with its geometry chosen so it can never fall over the region it magnifies and mirror itself. There is no detection, no tracking and no aim logic: it enlarges pixels and nothing else. It uses the same screen-capture permission the other overlays do.
- Panel macros. A named, ordered set of overlay actions you already have, fired in sequence by one tap. A macro invents no new capability — every step runs through the same handler the panel's own tiles use — so all it adds is the order and the count. They live as an editable, reorderable config list, not a database table.
- Config backup and restore. Export every configuration store — profiles, presets, overlay layouts, macros — to one file you can keep or move to another install, and restore it later. It touches configuration only: your recorded sessions and Aim Lab runs are never written into the file and a restore can never overwrite them, and everything a restored file carries is validated before it reaches a store.
- Profile transfer. Share one or more game profiles as a single file, presets and all, and import them elsewhere — recreating what is missing and matching-then-remapping what already exists, so an import never silently duplicates a preset or clobbers one you had.
- Profile suggestions. A starting profile derived only from what a game was actually measured doing, with a plain sentence behind every field it fills. A dimension the sessions did not measure enough to speak to is left alone; a game with thin or contradictory history gets no suggestion at all rather than a guess dressed as advice. It only drafts — nothing is saved or applied until you open it in the editor.
- Per-sample export. Alongside the one-row-per-session export, write one row per sample — the raw time series a graph is drawn from — as CSV or JSON. It keeps the same honesty rule the aggregate export follows: blank, never zero, for a reading a sample never took.
3.5 — Setup wizard, thermal auto-downshift and network check
Four features about the app setting itself up, and then looking after the session on its own.
- A first-run setup wizard, and a Setup health screen. A new install now walks through what GameCore needs — which features you want, and only the permissions those features actually use — instead of leaving you on an empty Home screen to work it out by trial. Nothing is granted on your behalf and no step is required: skip all of it and the app still runs, minus whatever you skipped. Afterwards, Settings › Setup health lists every item with its real state — Ready, Not set up, or Unavailable with the reason Android gave — a Fix button that opens the exact system page, and a re-check when you come back from it. Upgrading never re-runs the wizard over your existing profiles; it offers a dismissible Home card instead, and only when something genuinely needs attention.
- Thermal auto-downshift. Off by default, per profile. When the device holds a temperature you choose, or the thermal status Android reports crosses the floor you set, the refresh rate steps down — and steps back up once it has stayed cool long enough. The hysteresis, the sustain window in each direction and the minimum time between changes are all yours to set, so it cannot sit there oscillating. The session records how many times it stepped down and the lowest rate it reached.
- Network check. Off by default, per profile. Before a launch it can warn you about the connection you are about to play on; during a session it can alert you when the connection turns poor — high latency, jitter, or probes that stop coming back at all. It still refuses to call any of that packet loss, because a TCP handshake that times out is not a dropped packet. It reads the transport, the Wi-Fi band and the link speed — never the network name, never the BSSID, and it asks for no location permission, which is what Android would demand before handing those over. Anything Android will not report shows as Unavailable with the reason.
- Keep full performance under battery saver. Off by default, per profile. Battery saver caps the refresh rate and the governor on most builds; with this on, a profile holds full performance for that game anyway, puts the saver's own behaviour back when the game exits, and notices — and records — when the system switched the saver back on behind it. No new access of any kind: it goes through the same elevated shell the refresh-rate setting already uses.
Also since 3.2, and until now in no release:
- A redesigned in-game overlay. The floating button has real states — dimmed when idle, an orange or red dot when the shared thermal classifier says hot or critical — three sizes and four corner snaps, and it keeps its place per orientation. Tapping it opens a quick sheet: a narrow panel on the screen edge nearest the button, holding up to six toggles you pin yourself, the brightness and volume sliders and the session clock, so the things you reach for mid-game no longer need the full panel and most of the game stays visible. The full panel is still there, behind More or an optional double tap, now divided into Display, Overlays, Capture and Session tabs with a hold-to-confirm End session (the older split-across-both-edges layout keeps its previous design). The stats pill comes as a compact single line or a detailed card, and it stops sampling the moment nothing is looking at it — including when the screen goes off, where it used to keep ticking.
- Themes. Follow system, Dark, Light or AMOLED black — a true
#000000background that switches OLED pixels off rather than dimming them — with eight accent colours or one you pick yourself, applied across the app and the overlay windows alike.
3.2 — Aim Lab fire modes and on-screen controls
Two additions to the Aim Lab, both about the weapon and the way you hold the phone.
- Fire modes. A weapon now discharges in one of three ways — single, a fixed burst, or full auto — and the training loop honours it: a burst walks up the recoil pattern at the real fire-rate cadence rather than landing all at once, and the magazine and reload gate every mode the same. The weapon editor picks the mode and, for burst, the round count; a built-in Burst Carbine ships so the mode is there to try without building one. Older saved weapons read back as auto — exactly what they already did — so nothing changes under you.
- On-screen controls on every mode. The saved control layouts (HUDs) you build in the editor can now be drawn over the arena during a run from any mode's setup screen, not just some — pick one, or leave it on "None" for a clean view. The choice is per run and never forced.
Also from 3.1: the Aim Lab runs in landscape with a real horizontal field-of-view setting, the gyro axes remap correctly per screen rotation, and control layouts are stored per orientation.
3.0 — The offline 3D Aim Lab
An offline Aim Lab — a first-person 3D training arena, built inside GameCore and held to the same rule as everything else: nothing it reports is invented.
- A real first-person 3D room, not a flat target board. You stand inside an enclosed arena — checkered floor, grey walls and ceiling, light depth fog — with a low-poly weapon held at the bottom-right and glossy spheres floating at varied distances and angles. The crosshair is fixed dead centre; you look by dragging, and a shot is the ray straight out of the crosshair, hit-tested against the spheres in 3D. Rendered with OpenGL ES 2.0 and nothing else: no game engine, no third-party 3D library, no model or texture files — the room, the spheres and every weapon are built procedurally in code, and the shaders are plain source strings.
- Seven modes, all in the 3D view. Flick, Tracking, Reaction, Gyro, Recoil, Movement and a Free Practice sandbox. Aim error is measured in degrees, not pixels, so a result means the same on any screen. Recoil kicks the camera along the weapon's pattern and scores how well you pull it back; Movement walks you through the room and scatters shots taken on the move; Gyro turns the view from the real gyroscope. A phone with no gyroscope but an accelerometer runs the gyro challenge as Tilt training — bank the phone left or right in the plane of the screen to steer and tip the top edge away from you to aim up — read from the accelerometer, and filed under its own mode so a tilt run is never ranked against a gyro record.
- Your gear drives it. The weapon editor, sensitivity lab, control/HUD editor, statistics, personal records, session history and JSON/CSV export/import all carry over — and a weapon's fire rate, magazine, reload, spread and movement penalty are now actually felt in a run.
- Old training data is kept, and kept separate. Sessions recorded by the previous flat trainer stay in your history, labelled as legacy, and are never ranked against the new 3D scores, because the two are measured differently. The database upgrade is additive — no history is dropped.
- It stays out of the way when you leave. The 3D view renders only while a run is on screen; the moment you leave or background the app, rendering stops, the motion sensor is released and the run is cancelled. If a device cannot run OpenGL ES 2.0 the arena shows a plain "3D view unavailable" panel instead of crashing.
The Aim Lab is off by default behind a master switch, and turning it off is a real disable, not a hidden screen.
2.0 — A second panel shape and in-game controls
Five additions, all of them about reaching a setting without putting the game down.
- A second shape for the control panel. It can now split into two plates pinned to opposite screen edges — readouts on one, controls on the other, the game still visible between them, full screen height — instead of one plate below the button. Centred stays the default, because a panel you have already learned should not rearrange itself on an update. A Layout tile inside the panel switches between the two on one tap, so trying the other shape costs nothing and neither does going back.
- Refresh rate from the panel. Every rate this display reports, as chips under the tile grid, plus the way back off a pinned one. The tile lights only from a change GameCore made and confirmed: the platform is entitled to drop a pinned panel to 60 Hz on a static screen, so a low reading is not evidence the pin failed and a high one is not evidence it holds. A rate that was written but could not be read back says so rather than claiming it took.
- Crosshair quick-select. Hold the crosshair tile for the active crosshair's design and colour without opening a screen. It offers the built-in colours plus the ones you already mixed somewhere with more room — no HSV square, because that is a two-handed control and this window is open over a game.
- An eleventh crosshair design, and more colours. A box: a square outline, for framing a target rather than marking a point.
- CPU core affinity presets, marked experimental. A per-profile choice of which cores a game's main process may run on — performance cores only, or all cores minus one efficiency core. The app says what this is worth in the same words every time: it may reduce stutter by controlling which cores the game runs on, it does not make the device faster, and depending on the game it can do more harm than good. Needs Shizuku. The mask the process was found on is recorded before anything changes and written back when the game exits.
1.3 — Game storage, connection history and a shareable card
Three additions, and four reported bugs fixed.
- Game storage. A screen that measures what every game is holding in cache, and clears one game's shared-storage cache on a tap. It reads the size, deletes, waits, reads again, and reports the difference between the two readings rather than an exit code — so a clear that freed nothing says nothing was freed, instead of claiming a number it did not measure. It never touches a save, a login or a downloaded asset. Details.
- What the connection did. Every session now carries a latency log: probes that completed, probes that did not, the worst reply, jitter, the longest run of failures, and one of five verdicts with the figures behind it in a sentence. No packet-loss percentage anywhere, because a refused TCP handshake is not a dropped packet. Details.
- A card you can share. A finished session renders to a 1080×1260 image and goes to your own share sheet. A reading this device could not take is a dash on that card and never a zero, an interrupted recording says its length is a lower bound, and the sample count travels with the averages. Drawn on the device; nothing is uploaded. Details.
The bugs, all four found in use rather than in a test: a crosshair that drew the dot preset whatever you picked — and the HUD, which had the identical bug, because a layout you arranged is not "any layout"; a profile edited after leaving a game that did nothing until GameCore was restarted; the resolution card stuck on "loading" after quitting a game; and a Do Not Disturb popup that put DND back on after you had turned it off by hand. The last one is now impossible by construction: GameCore restores a device setting only when it made the write and nothing outside GameCore has changed it since.
MIT — see LICENSE.
Built with Kotlin and Jetpack Compose. No trackers. No telemetry. Just readings.




