Grassling

本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。

Grassling

Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass Submission 🌿 Grassling: An AI Pet That Actually Wants You to Leave Your Screen This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass.

Hacktoberfest 开源 AI 挑战赛第一周:Touch Grass 参赛作品 🌿 Grassling:一款真正希望你离开屏幕的 AI 宠物。这是 Hacktoberfest 开源 AI 挑战赛第一周“Touch Grass”的参赛作品。

What I Built I built Grassling, a small Android virtual pet with one unusual goal: it wants you to put your phone down and go outside. I discovered this Hacktoberfest challenge a little later than I would have liked, so Grassling was built under a pretty tight time constraint. That forced me to focus on the core idea first: get a real Android app working, track movement, build a simple game loop, and run a local language model directly on the phone.

我构建了 Grassling,这是一个小型 Android 虚拟宠物,它有一个不同寻常的目标:它希望你放下手机,走到户外。我发现这个 Hacktoberfest 挑战赛的时间比我预想的要晚,因此 Grassling 是在非常紧张的时间限制下构建的。这迫使我首先专注于核心理念:让一个真正的 Android 应用程序运行起来,追踪运动,构建一个简单的游戏循环,并在手机上直接运行本地语言模型。

There are still rough edges, especially around dialogue quality and polish, but the core system works and gave me a chance to experiment with something I had wanted to try for a while: on-device AI on Android. The idea started from the “Touch Grass” theme itself. Instead of building another AI application designed to keep someone chatting with a screen, I wanted the AI to actively encourage the opposite behavior.

虽然仍有一些粗糙之处,特别是在对话质量和润色方面,但核心系统运行良好,并让我有机会尝试我一直想做的事情:在 Android 上实现端侧 AI。这个想法源于“Touch Grass”(接触自然/走出户外)这一主题本身。我不想构建另一个旨在让用户沉迷于屏幕聊天的 AI 应用,而是希望 AI 能积极鼓励相反的行为。

Grassling has an energy level that slowly increases over time. As that energy rises, its mood changes: Sleeping, Normal, Anxious, Exploding with rage. The main way to calm it down is simple: walk. The application tracks the user’s steps using Android’s step counter. Walking reduces Grassling’s energy, while completing outdoor quests reduces it even further.

Grassling 有一个随时间缓慢增加的能量值。随着能量上升,它的情绪会发生变化:睡眠、正常、焦虑、愤怒爆发。让它平静下来的主要方法很简单:走路。该应用程序使用 Android 的计步器来追踪用户的步数。走路可以降低 Grassling 的能量,而完成户外任务则能进一步降低能量。

Some current quests are things like: Walk 750 steps, Take a 10-minute walk, Find something blue outside, Find three different plants, Spend five minutes outdoors, Walk 500 steps without using your phone. There is intentionally not much to do by staring at the app. You open it, check on Grassling, accept a quest… …and then the useful thing to do is leave.

目前的一些任务包括:走 750 步、散步 10 分钟、在户外寻找蓝色的东西、寻找三种不同的植物、在户外待五分钟、不看手机走 500 步。刻意设计让用户盯着应用看时没什么可做的。你打开它,查看 Grassling,接受一个任务……然后,你应该做的有意义的事情就是离开。

Where the AI comes in Grassling runs Gemma 3 270M locally on the Android device. Gemma gives the character its personality. It generates short dialogue based on Grassling’s current state and reacts when the user accepts a quest. An anxious Grassling should not sound like a sleeping Grassling, and an extremely energetic Grassling should feel noticeably more desperate to get outside.

AI 的作用:Grassling 在 Android 设备上本地运行 Gemma 3 270M 模型。Gemma 为角色赋予了人格。它根据 Grassling 的当前状态生成简短的对话,并在用户接受任务时做出反应。焦虑的 Grassling 不应该听起来像是在睡觉,而精力极其充沛的 Grassling 应该让人明显感觉到它渴望去户外。

One important detail is that Gemma currently does not generate the quests themselves. The quests come from a predefined pool and are randomly selected. I deliberately kept the game rules deterministic while using the model for the part where generative AI is actually useful: personality and variation. That separation also makes the application much easier to reason about. The AI can say something weird. It cannot randomly give itself 500 energy points.

一个重要的细节是,Gemma 目前并不负责生成任务本身。任务来自预定义的池,并被随机选择。我特意保持了游戏规则的确定性,同时将模型用于生成式 AI 真正有用的部分:个性和变化。这种分离也使得应用程序更容易理解。AI 可以说一些奇怪的话,但它不能随意给自己增加 500 点能量。

Because I was working with limited time, this separation also helped a lot. I could focus on making the game loop reliable without depending on the language model for critical application behavior.

由于我的时间有限,这种分离也提供了很大帮助。我可以专注于使游戏循环变得可靠,而不必依赖语言模型来实现关键的应用程序行为。

Code The project is open source: 👉 https://github.com/gbmao/Grassling The Android application is primarily built with: Kotlin, Jetpack Compose, Android step-counter sensors, Foreground Services, WorkManager, Kotlin Coroutines / Flow, C++17, JNI, llama.cpp, Gemma 3 270M IT Q8_0 / GGUF. The application supports arm64-v8a and x86_64.

代码:该项目是开源的:👉 https://github.com/gbmao/Grassling。该 Android 应用程序主要使用以下技术构建:Kotlin、Jetpack Compose、Android 计步器传感器、前台服务、WorkManager、Kotlin 协程/Flow、C++17、JNI、llama.cpp、Gemma 3 270M IT Q8_0 / GGUF。该应用程序支持 arm64-v8a 和 x86_64 架构。

One architectural decision I particularly liked was keeping the game logic separate from the language model. Conceptually, the flow looks like this: Android UI ├── Step Counter └── Energy / Mood ├── Quest System └── Local AI └── JNI └── llama.cpp └── Gemma. Gemma receives information about the current state. It generates dialogue. But it does not control the state itself. That means energy calculation, walking progress, quest completion, and persistence remain deterministic Kotlin code.

我特别喜欢的一个架构决策是将游戏逻辑与语言模型分开。从概念上讲,流程如下:Android UI ├── 计步器 └── 能量/情绪 ├── 任务系统 └── 本地 AI └── JNI └── llama.cpp └── Gemma。Gemma 接收有关当前状态的信息并生成对话,但它本身并不控制状态。这意味着能量计算、步行进度、任务完成和持久化仍然由确定性的 Kotlin 代码处理。

How I Built It The application started with the basic virtual-pet loop. Grassling has an energy value from 0 to 100. The value increases automatically over time, reaching maximum energy in roughly 24 hours if the user does nothing. Walking decreases it. Currently, every 11 new steps remove one energy point, while completing a quest provides an additional energy reduction.

构建过程:该应用程序始于基本的虚拟宠物循环。Grassling 的能量值在 0 到 100 之间。如果用户什么都不做,该值会随时间自动增加,大约 24 小时达到最大能量。走路会减少能量。目前,每走 11 步会减少一点能量,而完成任务则会提供额外的能量减免。

From there, I added Android’s TYPE_STEP_COUNTER sensor and a foreground service so the app could keep tracking walking without requiring the main screen to remain open. Then came the part I was most curious about: running the language model directly on Android. Since I discovered the challenge late, I did not have much time to slowly prototype each part. A lot of the project became a process of: get the smallest version working, test it on a real phone, find what breaks, fix it, move to the next part. That made the project a little rough around the edges, but it was also a very practical way to learn.

在此基础上,我添加了 Android 的 TYPE_STEP_COUNTER 传感器和一个前台服务,这样应用程序就可以在不保持主屏幕开启的情况下持续追踪步行。接下来是我最感兴趣的部分:直接在 Android 上运行语言模型。由于我发现这个挑战赛的时间较晚,我没有太多时间去慢慢原型化每个部分。项目的很大一部分变成了一个过程:让最小版本运行起来,在真机上测试,找出故障,修复它,然后进入下一部分。这使得项目在细节上有些粗糙,但这也是一种非常实用的学习方式。

Gemma + llama.cpp I used a quantized Gemma 3 270M IT Q8_0 model in GGUF format. Instead of calling a cloud endpoint, Grassling talks to llama.cpp through JNI: Kotlin ↓ JNI ↓ C++ ↓ llama.cpp ↓ Gemma GGUF. The model is not included inside the APK. On first launch, the app downloads it using WorkManager into the application’s private storage. The current model is around 278 MiB. After downloading it, Grassling validates: expected file size, GGUF header, SHA-256 checksum. Only after validation is the model loaded. Once that initial download has completed, inference itself happens locally. No OpenAI API. No Gemini API. No server running the model somewhere else. The phone is actually doing the inference.

Gemma + llama.cpp:我使用了 GGUF 格式的量化 Gemma 3 270M IT Q8_0 模型。Grassling 不会调用云端点,而是通过 JNI 与 llama.cpp 通信:Kotlin ↓ JNI ↓ C++ ↓ llama.cpp ↓ Gemma GGUF。模型不包含在 APK 中。首次启动时,应用程序会使用 WorkManager 将其下载到应用程序的私有存储中。当前模型大小约为 278 MiB。下载后,Grassling 会进行验证:预期的文件大小、GGUF 头部、SHA-256 校验和。只有在验证通过后,模型才会被加载。一旦初始下载完成,推理本身就在本地进行。没有 OpenAI API,没有 Gemini API,也没有在其他地方运行模型的服务器。手机实际上是在进行推理。

Running Gemma on a real phone I tested the local inference directly on a Samsung Galaxy A54. For the current quantized model, I measured approximately: Metric Result: Time to first token ~212 ms, Prompt processing ~222 tokens/s, Token generation ~27.5 tokens/s, Native model loading ~759 ms. That performance was much better than I originally expected from local inference on a mid-range Android phone. The harder problem turned out not to be speed. It was quality. I ran 20 simulated quest-acceptance interactions across the different moods. Every request returned a response, but the quality was inconsistent. Some responses reflected…

在真机上运行 Gemma:我在三星 Galaxy A54 上直接测试了本地推理。对于当前的量化模型,我测得的结果大约为:首个 token 时间 ~212 毫秒,提示词处理 ~222 token/秒,token 生成 ~27.5 token/秒,原生模型加载 ~759 毫秒。这种性能比我最初预期的中端 Android 手机本地推理表现要好得多。事实证明,更难的问题不是速度,而是质量。我在不同情绪下进行了 20 次模拟任务接受交互。每个请求都返回了响应,但质量参差不齐。一些响应反映了……