Brian Duffy

Work

Elfmonn

Released

An AR storytelling app for young children that I joined as one contractor among several and now own outright as its sole developer and CTO.

Summary

Elfmonn is an augmented-reality storytelling app for families with young children. Its character narrates illustrated fairy tales in AR or as screen-off audio, places video calls that greet a child by name, hides objects around a real room to be found, and casts to a television so a family can watch together. It ships on iOS and Android, rated for ages three and up.

I arrived as one of several external contractors, brought in to expand the game offerings. The other teams finished their engagements and moved on. I did not, and over the following two years the app became mine to hold: sole developer since November 2025, and 84 per cent of the application code running today is mine. I am now CTO and a shareholder.

The problem

The app worked. That was never the issue. The issue was that it had been built by three successive external teams across separate engagements, each leaving when their contract ended, and nobody held the whole thing in their head. A product in that state can be maintained but it cannot really be developed, because every change is an archaeology exercise first.

On top of that sat two constraints that shape everything. It is a children’s app, so it lives under the Apple Kids Category and Google’s Designed for Families rules, which restrict things most apps take for granted. And it was built as a seasonal Christmas product that needed to become something a family would open in June.

What I built

Six systems are substantially my own work, including the two largest in the codebase: the storytelling engine, the entire menu and UI shell, the video call system, the in-room AR visit, the build and content tooling, and the diagnostic instrumentation. Others I inherited and extended.

Four pieces are worth pulling out.

Screen-off audio, which Unity does not do. The newest feature lets a story play at bedtime with the phone locked, like a podcast: proper lock-screen controls with scrub and skip, polite ducking when another app makes a sound, and a sleep timer. Unity has no answer for this, because a Unity app is not designed to keep running usefully once it is backgrounded. Getting there meant writing native audio sessions on both platforms – an Android foreground media service and an iOS audio session – behind one managed interface, and forking Unity’s Android activity to keep the engine alive across backgrounding. It is the least visible feature in the app and by some distance the hardest.

Making the scenes reviewable. Unity scenes in this project are stored as binary files under version control, which means a change that rearranges an entire menu is invisible in a diff and impossible to merge. Rather than accept that, I moved the wiring out of the scene and into a catalogue in code, with a tool that rebuilds the scene deterministically from it. A change to the menu is now something you can read, review and reproduce instead of something you have to take on trust.

Instrumentation for faults that will not reproduce in the office. Two problems needed catching in the wild: an app freeze that only happened when resuming a cast to a television, and features silently breaking because the release build’s code stripper removed things it wrongly believed unused. The first is handled by a dual heartbeat that can tell a genuinely blocked main thread from one that is running but not drawing – a distinction that decides where you look next. The second by a self-test that runs in the shipped build and is deliberately written to fail on the broken default rather than merely confirm that something is present.

Keeping it inside the store limits. The app had grown to a size that put its download at risk. That produced a set of build tools – texture compression sweeps, code-size passes, package size reporting, and a check that the seasonal and year-round builds still match – which are now the largest single component of the codebase by line count and ship in nothing.

Technical detail

Unity 6 on URP, targeting Android with IL2CPP on ARM64 and iOS with ARKit, from one codebase with two build variants. 240 first-party C# files and roughly 50,000 lines, plus hand-written Java and Objective-C plugins where Unity has no API.

AR runs on ARFoundation with both ARCore and ARKit providers, used for plane-anchored character placement, object concealment and detection, and portal compositing. Story content is delivered as remote Addressable bundles from a CDN rather than shipped in the binary, with on-device eviction, which is most of why the install stays small. Subscriptions run through RevenueCat with server-side paywall configuration, so pricing and presentation change without an app update. Casting is two entirely separate platform paths behind one interface.

What is not mine

Worth stating plainly, because the ratio is unusual and a reader who assumes otherwise has been misled.

785 of the 1,130 C# files in the project are third-party or Unity sample code. The AR foundation the app is built on is Unity’s own sample project. Two subsystems – a portal effect and an unused peer-to-peer AR layer – are not my work and I do not claim them. Scene composition, lighting and layout cannot be attributed from version control at all, and a large share of it predates me. All art, animation, music, and the narration and illustration for the stories, is licensed or commissioned.