Hello everyone!
The game is running on Android. The beta app is still rough around the edges, but the login flow works end-to-end, overlays render, touch + keyboard behave themselves, and the camera drags with one finger like you'd expect on a phone. You can grab the APK from the homepage — iOS is not far behind, but this post is about getting the Android build to feel like a real client instead of a proof of concept.
What's in this update
Two big things and a stack of small ones. The big things are a new platform abstraction layer under the game client, and a from-scratch rendering + input path for Android. The small things are the usual parade of fixes you end up with when you port a 20-year-old codebase onto a phone for the first time.
If you just want to know what's new as a player: you can play on your phone now, cold start is way faster on desktop too, and a few longstanding bugs got caught along the way. Full list is at the bottom.
Mobile App (Beta)
You can download and test the Android beta from the homepage. It's playable today, but please treat it like a beta — report anything weird in the Helpdesk or on Discord and I'll look at it.
A few things worth knowing up front:
The client is a native Android app, not an emulator wrapper. It runs the real game code. The APK is around 28MB and installs in a few seconds. First launch takes a bit longer than subsequent launches because map and animation archives stream on-demand the first time you need them — after that, everything's cached locally.
Input-wise, a single-finger drag rotates the camera, a tap is a click, and the soft keyboard comes up automatically when you focus a text field. You can background the app mid-login and come back to it without being kicked — the network layer listens for connectivity changes and reconnects gracefully.
If the app feels hot, drop the graphics settings from the in-game menu. Phones don't have cooling fans, and you can absolutely push a modern phone over 60fps doing GPU-accelerated pixel blits all day. You probably don't want to.
The Platform Abstraction Layer
This is the technical section — feel free to skip it if you don't care about the plumbing.
The game client was never meant to run anywhere but desktop Java. It uses AWT for windows, Swing for a handful of dialogs, Java2D for drawing, ImageIO for loading images, Desktop.browse() for URLs, and raw java.net.Socket for networking. Android doesn't ship any of that. Early experiments made it clear that scattering if (isAndroid) branches through the codebase would make a mess that neither client would survive.
So instead, I pulled every platform-specific call behind a set of interfaces — sockets, clipboard, image loading, URL launching, and a few others. The desktop backend is the old AWT/Swing path, unchanged in behavior. The Android backend uses the actual Android primitives: BitmapFactory for image decode, Intent.ACTION_VIEW for URLs, ConnectivityManager for network state, and so on. Everything is injected through Guice. The game code itself doesn't know which platform it's on.
The APK ships the game JAR as a dependency rather than duplicating the sources, so there's one source of truth for both builds. A small set of AWT/Swing stubs lets the game classes load under Android's runtime — the stubs either no-op or delegate into the abstraction layer. That let me avoid rewriting the thousands of call sites that reach for java.awt.* somewhere in a call chain.
Rendering on Android
Getting pixels on screen was its own adventure. The desktop client draws into a Java2D buffer and blits to a window. Android has no Java2D — what it does have is SurfaceView and OpenGL ES.
The Android client starts in a headless mode that renders the game frame into an offscreen pixel buffer, then blits that buffer to the surface. Initial versions used a straight CPU path, which was fine for the login screen. For the full 3D scene I wrote a small OpenGL ES 2.0 renderer that takes the same pixel buffer and uploads it as a texture, which the GPU then draws in one call. That got the frame rate where it needed to be on older devices.
Overlay plugins (XP drops, HP bars, the usual stack) were trickier, because they call real Graphics2D methods — lines, arcs, text, the whole API. Rewriting every overlay was not happening. Instead, I wrote a small software rasterizer that implements the Graphics2D calls we actually use and writes straight into the same pixel buffer the game renders into. It's not as fast as native Java2D, but overlays are cheap and you never notice.
Touch, Keyboard, and the IME
Touch is straightforward: motion events synthesize mouse events, and the game's existing MouseHandler doesn't know the difference. The fiddly part was the keyboard.
Android's input method framework is fine when you're building a normal text-editor app. The game is not a normal text-editor app — it has in-game text fields that need to accept input without covering the entire screen with a full-height extract editor, it has a login screen where you need the keyboard to show up automatically, and it has to tolerate the player backgrounding the app mid-login and coming back. Samsung's IME in particular tries very hard to "help" by opening a fullscreen UI over your app; that had to be explicitly suppressed.
Along the way I also found and fixed a handful of genuine bugs in the text path — punctuation was being dropped by an over-eager filter, a null-source AWTEvent was blocking key dispatch entirely, and backspace on an empty field was throwing. None of that was noticeable on desktop; all of it hit the first time I typed on a phone.
Startup Performance (for everyone)
A side effect of doing mobile work was caring about startup time, which translates into a desktop win as well. Two big changes:
Archives now stream on demand from the cache server (SwiftFUP) instead of being bulk-downloaded at first launch. Old behavior was a several-hundred-megabyte cache download before anything showed on screen. New behavior is: draw the login screen as soon as possible, then stream map, animation, and music archives as the game needs them. The login screen appears instantly now, and the large archives fetch in parallel while you're typing your password.
A splash drawable shows before the game thread even boots, so the classic second of black screen on startup is gone.
On desktop you'll notice both of these too — cold launch to the login screen went from several seconds to roughly under one on a reasonable connection.
Changelog
- The Android beta client is now downloadable from the homepage. Log in, play, report issues in Helpdesk or Discord.
- Cold-start time has been significantly reduced on both desktop and Android — archives now stream on demand instead of bulk-downloading at first launch.
- Client version checking has been moved to a single
/api/launcherendpoint. The oldversion.txtfile is gone. - Client downloads now use
cache.lakesidegamers.comdirectly, bypassing an intermittent Cloudflare issue that caused the occasional 403 on the APK. - The in-game settings panel no longer crashes on Android if you tap a desktop-only option like Fullscreen.
- A null-source AWT event bug that could silently block all key dispatch has been fixed. This wasn't reachable on desktop but affected every Android keystroke.
- The loading bar no longer corrupts the login screen — it renders in its own overlay surface.
- A stale "Loading update list 60%" string on login tap has been cleared.
- Multi-touch pinch and the Android back button are wired up.
- The app now reconnects cleanly when the phone sleeps and wakes — the network layer listens for connectivity changes.
- Overlay plugins (XP drops, HP bars, etc.) render correctly on Android via a software Graphics2D path.
- An admin-side
/admin/player/kickendpoint has been added on the server for faster moderation. - A new dev tool, MapImageDumper, is now part of the build, and the WorldMapPlayers plugin has been fixed.
So what's next? The Android app needs a week or two of real-player feedback before I'm comfortable calling it stable. After that, iOS uses the same abstraction layer, so the heavy lifting is already done — the iOS backend is mostly implementing the same handful of interfaces with UIKit / CoreGraphics equivalents. Expect an iOS beta announcement once Android shakes out.
All the best, Austen