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.

Chuck the Elf on the sofa in his cottage, waving

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 4+ on the App Store and 3+ on Google Play.

I arrived as one of several external contractors, brought in to expand the game offerings. The other teams finished their engagements and moved on, and I stayed. I have been the sole developer since November 2025, and the application code is now all mine. I am now CTO and a shareholder.

The problem

The app had been built across separate engagements by three external teams, each of which moved on when its contract ended. Before I could develop it further, I had to learn the whole codebase well enough to change any part of it safely.

Two constraints shape everything else. It is an app for young children, so it is built to Google Play’s Families policy, which restricts things most apps take for granted. And it began 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. The rest I inherited, and over successive overhauls it has become mine as well.

Four of those are worth describing in more detail.

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 does not provide this, because a Unity app is not designed to keep running usefully once it is in the background. 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 to build.

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 are not included in the app itself.

Technical detail

Unity 6 on URP, targeting Android with IL2CPP on ARM64 and iOS with ARKit, from one codebase with two build variants, 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

This is worth stating plainly, because the proportion is unusual.

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.