Built Native

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.

Daily hesitates between two doors labeled Normal Mode and Hard Mode, and reaches for the flaming Hard Mode entrance

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.

A Vimp in a fancy cape strains under a huge wooden crate overflowing with gears, wrenches, and machinery

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.

Two Vimps stand side by side, each turning outward to press a button on their own casino slot machine

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_all Metal 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.

clearScratchMaskAll Kotlin
/**
 * 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.

The same two Vimps side by side at their slot machines, now surrounded by bags of coins and prizes

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.

A Vimp coding at night at a desk with two monitors, a coffee mug, and the moon through the window

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.