Daily's World is written twice. Once for iOS, once for Android, with each platform's own tools and nothing else. No cross platform game engine, no abstraction layer between our code and the hardware.
A choice that was not obvious
Between an all in one 3D engine and two native codebases there is a world of difference. The engine gives you an editor, a physics system, an asset pipeline, and a build for every platform in a single command. Native gives you nothing: every element has to be written, optimised, then written again for the other system.
We took the long road, deliberately.
The weight we did not want to carry
A full game engine ships whole, whether you use 10 per cent or 90 per cent of what it offers. That weight is paid at install, and above all at every launch, while the engine initialises before the player sees anything.
For a very large game the trade is excellent: the engine is amply repaid by everything it makes possible. For a small 3D game you open for a minute or two a day, it stops making sense. Daily's World is played in short sessions. Launch time is not a technical detail there, it is the first thing the player feels.
The same effect, two answers
The scratch cards behave identically on both platforms. The player rubs, the opaque layer disappears, the result shows. Underneath, the two implementations have almost nothing in common, and that is deliberate.
iOS: SceneKit and Metal
On iOS there is one graphics API, Metal, and it is available on every device we target. The work goes to the GPU without debate. Erasing the mask is a compute kernel that walks the whole texture and sets transparency to zero.
scratch_erase_allMetal Shader Language
/// Reveal total : alpha = 0 sur toute la texture du masque.
kernel void scratch_erase_all(
texture2d<float, access::read_write> mask [[texture(0)]],
uint2 gid [[thread_position_in_grid]]
) {
if (gid.x >= mask.get_width() || gid.y >= mask.get_height()) return;
// On conserve le RGB : mettre la couleur à zéro en même temps que l'alpha
// créerait des franges sombres au filtrage bilinéaire sur les bords révélés.
float4 c = mask.read(gid);
c.a = 0.0;
mask.write(c, gid);
}
The 3D rendering runs on SceneKit, the interface on SwiftUI, with a strict MVVM architecture. When an effect asks for more than SceneKit offers, we drop straight down to Metal.
Android: Jetpack Compose and Filament
On Android the same approach would have been a mistake. Filament's material system is built for rendering, not for arbitrary compute on a texture. Clearing the mask on the GPU would have meant a render to texture pass, extra plumbing, and behaviour to validate separately on OpenGL ES and on Vulkan across a very fragmented fleet of devices. For one 512 pixel square, cleared once per ticket.
clearScratchMaskAllKotlin
/**
* Efface l'intégralité du Scratch-Mask en un coup (alpha = 0 partout), appelé au verify.
* Le contenu visible (gradient + texte) disparaît instantanément, révélant le Background.
* Appelé uniquement après initialisation du moteur : les early returns couvrent le cas limite.
*/
fun clearScratchMaskAll() {
val bmp = scratchMaskBitmap ?: return
val texture = scratchTicketTextures[ScratchTicketSurface.MASK] ?: return
val eng = engine ?: return
bmp.eraseColor(AndroidColor.TRANSPARENT)
TextureHelper.setBitmap(eng, texture, 0 /* mip level */, bmp)
}
That reveal happens once, when the ticket is checked, so the gap with a GPU version is invisible. Ten times faster would not have made the game better, only harder to maintain across two graphics APIs.
Because we own every layer, we can move any part of the game from the processor to the GPU the day it matters.
The interface is in Jetpack Compose, the rendering in Filament.
What the choice says
A cross platform engine would have made that call for us, and hidden it. The reveal would have worked, and we would not know what it costs, nor be able to change it.
Android has nothing to do with iOS: memory handling, screen life cycles, the spread of devices, the way each system deals with rendering surfaces. Writing native does not mean rewriting everything identically. It means deciding separately, where the problem actually is, knowing the price of each option.
That is what we wanted to keep: our hands on every element of Daily's World, at any time, on either system.
What it costs, what it buys
It costs twice the work, two areas of expertise to maintain, and no way to fix a bug only once.
In exchange, the app carries only what it needs, it opens fast, and every behaviour in the game belongs to us. When Apple or Google ship something new, we do not wait for a middle layer to support it.
We use audience-measurement cookies from Meta to understand where our
visitors come from and improve our advertising campaigns. They are only
set with your consent, and you can change your mind at any time.
Learn more