This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Nature Quest is an offline scavenger hunt for families and kids. You pick a place (park, forest, garden, urban walk), how many things to find (5, 8 or 12) and the age of the youngest player. Then an open-source AI model running on the phone writes a hunt list for that place, season and age: "a red leaf", "something with a spiral shape", "bark with moss on it". The app reads the list aloud and asks everyone to put the phone away.
When someone finds something, they tap it on the list and take one photo. The same model checks it, answers with a short, encouraging message (plus a friendly hint if it is not a match), and reads that aloud too. When the hunt ends, there is a bronze, silver or gold medal and the time spent outside.
The theme was "Touch Grass", so every design decision started from one question: how do I make the screen the shortest part of the experience?
- Setup is one screen with everything pre-selected, so starting a hunt is one tap.
- The list and every piece of feedback are read aloud, so nobody needs to look at the phone.
- Big tap targets and a high-contrast palette, because the phone is used outdoors in the sun, for a few seconds at a time.
- One photo per find. No camera roll, no feed, nothing to scroll.
It is for families and kids on a walk, with an adult, in English and Spanish. The first screen always says: hunt with an adult, only look and take photos, never pick or touch anything.
| Setup | The hunt, written on the phone | Medal and time outside |
|---|---|---|
![]() | ![]() | ![]() |
Demo
There is no web demo, because the whole point is that it runs on a phone.
- Install it: Nature Quest 1.0.1 on GitHub Releases (signed APK, about 55 MB, Android 12+, 64-bit ARM). On first launch the app offers to download the AI model (2.59 GB, Wi-Fi recommended). That is the only time it uses the internet.
Taking it outside
WIP
Code
RZEROSTERN / hacktoberfest-week2
Second submission for hacktoberfest. The theme: Touch Grass
Nature Quest
An offline nature scavenger hunt for families and kids. The AI runs on the phone; the fun happens outside.
Nature Quest is an Android app. You pick a place (park, forest, garden, urban walk), how many things to find, and the age of the youngest player. An open-source AI model running on the phone writes a hunt list ("a red leaf" "something with a spiral shape", "bark with moss on it"), reads it aloud, and invites everyone to put the phone away When someone finds something they take one photo; the same on-device model checks it, answers with a short, encouraging message (also read aloud) and gives a friendly hint if it is not a match. At the end there is a bronze, silver or gold medal and the time spent outside.
The phone is a tool for a few seconds at a time. The screen is…
MIT licensed (plus a Beerware clause). The repo has 154 unit tests, the architecture in the README, and docs/DECISIONS.md, a log of 32 technical decisions with the reasons behind each, written as I went. docs/BENCHMARKS.md has every number quoted below.
How I Built It
The open pieces. The model is Gemma 4 E2B (open weights, Apache-2.0) in LiteRT-LM's .litertlm format. It runs through LiteRT-LM (Apache-2.0), Google's on-device runtime, with no network and no server. The rest is a normal Android stack: Kotlin, Jetpack Compose, Hilt, CameraX and Android's own text-to-speech. Nothing closed runs in the app. (I built it with Claude Code, which is not open source. It was my pair programmer, not part of the product.)
The shape of it. The app talks to the model through one interface, InferenceEngine, so ViewModels and use cases never touch LiteRT-LM and tests use a fake. The model answers in strict JSON, which I parse with kotlinx.serialization. If the JSON is bad I retry once, then fall back to something safe: "I'm not sure, try another photo", or a built-in hunt list. Prompts are versioned text files, not strings in Kotlin.
I benchmarked before I built. The first real step was a debug-only screen that loaded the model and timed it on my Pixel 10. That changed the design:
- The CPU backend was about twice as fast as the GPU: a hunt list took 5.7 s on average on CPU and 11.9 s on GPU, and the first GPU load took 25 s against 3 s. Google's published numbers for a flagship phone did not carry over to mine.
- Decoding is slow (7 to 23 tokens per second), so every answer is kept short. "Twelve words at most" in a prompt is a performance feature.
- The phone slows about 1.6x after 25 minutes of back-to-back runs, even though the OS reported no thermal throttling. So I picked settings that hit my targets in the slow state: 140 visual tokens and a 640 px photo. A photo check takes 4.5 to 5 s on a cool phone and 5.7 to 6.5 s on a warm one.
- Peak memory is 2.2 to 2.6 GB, so the app loads the model for each use and releases it right after. A warm reload is about half a second, and memory drops to about 240 MB.
Safety cannot live in the prompt. The prompt asks for safe items. The model still produced "a coiled snake", "orange fungus" and "a bird's nest hidden in a hollow", and later "a cloud shaped like a deer". For a kids' app that is not acceptable, so every item now passes a deterministic validator in code (English and Spanish word lists: no picking or touching, no animals, insects or mushrooms, nothing near water or roads, no climbing, nothing to eat, no photos of people). Rejected items are regenerated, and if the model still falls short, a built-in safe list fills the gap. The model's feedback is checked too, because it is read aloud to children.
Bugs the phone found that tests did not.
- The same settings produced the identical hunt three times in a row. LiteRT-LM's sampler seed defaults to 0, which makes sampling deterministic. Each request now gets a random seed.
- A regex that passed every JVM test crashed on Android, because Android's regex engine rejects a bare
}that Java accepts. - Cancelling the Kotlin Flow does not stop native inference. You have to call
cancelProcess()yourself. - CameraX's in-memory JPEG already carries its EXIF rotation, so rotating it again turns the photo sideways.
- A Compose cleanup effect unbound the camera I had just bound. It read state when disposed instead of when created.
Privacy and delivery. Photos are processed in memory. A small copy of each found photo stays in private cache only for the summary and is deleted when you close it, unless you tap "Save to gallery". The only code that opens a network connection is the model downloader: it resumes interrupted downloads, pins an exact model revision, and verifies the SHA-256 before using the file. I tested that on the phone by killing the app at 17% and resuming. The manifest asks for CAMERA, INTERNET (download only) and network state.
Limits, stated plainly. I could not measure how accurate the photo check is: I did not have a set of real photos to score the model against, so treat its verdicts as fun and encouraging, not as a judge. I tested on one phone. Reading aloud needs a voice installed for the phone's language.
Why Does Open Innovation Matter?
Four things an open model made possible that a closed API would not:
- It works with no signal on the trail. After a one-time download, there is no server to reach. The people this is for are standing in a field.
- Kids' photos never leave the phone. With a hosted model, "check this photo" means sending a child's picture to someone else's server. Here it is a function call on the device, and the only network code in the app is the downloader. Because the code is open, anyone can check that claim.
- It costs nothing per hunt. No API bill, so a family, a school or a scout group can use it as much as they like. The cost is the phone's time: about 5 to 12 seconds per hunt list and 5 to 7 per photo.
- It is swappable and pinned. The model is a file plus one entry in
ModelSpec(URL, size, SHA-256), behind an interface. The app behaves identically next year, because the exact revision is pinned and checksummed instead of changing under me. And I could turn the knobs a hosted API hides: backend (the CPU-versus-GPU finding above), image resolution and memory.
The honest counterpoint: a server GPU would be faster, and probably more accurate. I traded speed and a 2.6 GB first download for privacy and offline use. For a hunt in a forest, I think that is the right trade, but it is a trade.
My Agent Session
I built Nature Quest with Claude Code, from an empty repository to two signed releases. Here is the whole conversation: my prompts, Claude's replies and every tool call, from the benchmark spike to the release. Claude's private reasoning is not included, long tool outputs are truncated, and anything personal (emails, local paths, device serial numbers) is scrubbed.


