One Swift engine, two phones: how Stelline came to Android
Stelline is a Star Battle-style logic puzzle: place one queen (or two stars) in every row, column and coloured region, never two touching. What makes it different from a puzzle book is that every board is made on the phone, and every board is proven, by an exact solver, to have exactly one solution. The same engine grades each board with a seventeen-technique deduction ladder, and the hints walk you through that ladder one step at a time.
When we started the Android version in September 2026, the question was what to do with that engine. It is about 7 000 lines of Swift: a generator, a solver, the deduction engine, the daily-board contract. The obvious Android route is a Kotlin port. We did not take it, and this note is about why, and what it cost.
The daily board decides it
Every day Stelline offers Today's board: the same 9×9 (one queen) and 10×10 (two stars) to everyone. There is no server. The board is a pure function of the date: a seed derived from the day, up to 96 generations, the first one to reach the target difficulty wins. "Reaching the target difficulty" is the deduction engine's verdict, so the board of the day depends on every line of the generator and the grader.
A Kotlin port would have had to reproduce all of that bit for bit, for ever, or Android and iPhone players would get different boards on the same day. Two implementations of a 7 000-line program that must agree on every integer is a maintenance promise nobody keeps. Compiling the same source for both phones makes the agreement true by construction.
Swift on Android in 2026
The Swift SDK for Android is official now: we build with Swift 6.4. It cross-compiles a SwiftPM package from a Mac to aarch64 and x86_64 Android, with swift-foundation underneath. The engine compiled unchanged the first time we tried, in eight seconds.
Getting it to run took an evening of small things:
- Foundation is big. With full Foundation linked statically, the library was 71 MB, most of it ICU. The engine only needs JSON, dates and files, all of which live in the
FoundationEssentialsmodule; switching the imports and replacing three Foundation-only calls (character-set trimming, percent decoding) with twenty lines of our own brought the library to 12 MB on disk and the whole app to a download of about 6 MB. - Dispatch has to be named. A static link left
_dispatch_main_qunresolved atdlopen; adding-ldispatch -lBlocksRuntimeto the link line fixed it. - 16 KB pages. Google Play requires native libraries to work with 16 KB memory pages. NDK r30 aligns them by default; we pass the linker flag anyway and check every
.soin the build script, and the app runs on the 16 KB emulator image. - The bridge is JSON. Rather than expose Swift types to Kotlin, the engine speaks JSON over a dozen JNI functions: open a session, apply a tap, ask for a hint, save. A live game sits behind an integer handle on the Swift side, so a tap costs one small snapshot, not a re-decode of the board. The saved-game document is the same one the iPhone app writes.
- The UI is native. Kotlin and Jetpack Compose: one
Canvasfor the board with arithmetic hit-testing, the system's back gesture, TalkBack reading every cell, per-app language for the seven languages. Nothing of the SwiftUI app was emulated.
Proving it
Same source is not the same as same output: integer width, a platform's maths library, a compiler flag can all change a board. So the Mac's command-line tool wrote 1 700 golden daily boards (850 days, both modes, every field of the result), and an instrumented test on the phone regenerates each one and compares. It passes on Android 9, 16 and 17 emulators and on a five-year-old phone. We also decided what would happen if a day ever differed: the Android board would still be valid, unique and graded, board links carry the regions themselves, and Android 1.0 has no leaderboard that a different board could distort. The test is a regression guard, not a proof, and that is enough.
What it costs on a phone
On a 2019 flagship chip, a fresh 8×8 takes 32 ms, an 11×11 two-star board 1.3 s, and the two-star daily 0.7 s on a typical day. Budget phones are two to four times slower, so the largest two-star boards can cross the three-second deadline; when they do, a board of the nearest size that is already banked stands in and a toast says so, the same rule as on iPhone. Today's boards are made on their own thread at launch, and tomorrow's ahead.
Would we do it again
Yes. The engine has one home, its nearly 400 tests run on the Mac in a minute, and a change to the grader lands on both phones in one commit. The price was one evening of linker archaeology and a build script that knows where Swift 6.4 puts its products. If your app's value is in a deterministic core, the Swift SDK for Android is ready for it.
Stelline for Android has been on Google Play since 2 October 2026, the same game as on iPhone: USD 1.99 once, no ads, no account, and no internet permission at all. More at stelline.page; questions to hello@andelis.studio.