Moods
Two dials, energy and warmth, and it reaches the model as exactly one line of the system prompt: "Right now you feel sleepy and glad of the company. Let that colour how you sound, and nothing else." Two dials rather than one, because a single dial can't tell "tired and happy" from "awake and fed up", which are the two that actually turn up at 4am.
| tired | even | wired | |
|---|---|---|---|
| lewd | teasing | frisky | heated |
| very warm | cuddly | loving | flirty |
| warm | sleepy | happy | excited |
| neutral | tired | calm | focused |
| cool | sad | annoyed | mad |
| grudge | sulky | hurt | furious |
Almost everything that moves them is free: the time of day, the day of the week, how long the session has run, how long you were gone (a curve, not a switch: a few hours is a small lift, a day or more reads as a reunion), whether the GPU was pinned while you were away (you were there, just gaming), a busy or dead stream chat, errors, a clean run, being talked over. One small model call after each reply reads how warm your message was and whether it livened her up, and picks a quick feeling. Bluntness doesn't darken her: "fuck this bug" scores nothing. Only hostility aimed at her does. And the mood survives a restart, faded by however long you were gone, because a mood that resets every launch isn't a mood, it's decoration.
The two italic rows sit outside the warmth dial. Lewd needs a third dial, desire, that only builds while she's warm, from real flirting over minutes, and is wiped by one hostile message. Below very warm, pushing for intimacy gets her saying she isn't in the mood, in her own words, with a hint of what would change that. Grudge starts with real hostility while she's already cool: for twenty minutes her warmth can't climb out of it, she's short and prickly and free to refuse what isn't urgent, though alarms, reminders and anything urgent always get done. A real apology ends it on the spot. Twitch and YouTube each have a switch that holds the lewd row back while a stream is live.
What each mood is lives in agent/moods.json, not in code: the lines she's told, her face,
the voice tint, the idle gestures, an emote that pops up beside her head now and then. Edit it and the next
turn picks it up. One rule the code enforces rather than hopes for: mood colours tone, never capability.
There is no state in which she's less helpful, only states in which she's drier about it.
Her portrait
A window with her in it that follows what she's doing, or a see-through URL for an OBS browser source. She blinks and glances around, looks up and to the side while she thinks, and looks at you while you talk; on Hyprland the monitor works out where your terminal is, so she turns toward it and follows if you move the window. Her mouth moves with her actual voice, sentence by sentence. Her mood shows as a smile, a blush or a frown; a failed tool gets spiral eyes; a big jump in warmth gets a flustered ><. Bust or full body, set in metres on the model, so a 4K screen shows the same amount of her, only sharper.
Live2D and VRM both work. A Live2D model straight out of its zip usually runs as it is, with the body swaying on its own, breathing that's quicker in than out, and brows that worry when a reply came out unsure. VRoid Studio exports VRM models that work unchanged, and three-vrm renders them in 3D with blink, five mouth shapes, look-at, and ears that twitch when you start speaking.
Touching her
Click a part of her and she reacts. A ray from the cursor finds the first piece of her it meets, and the
bone that piece follows most says which part it is, so the zones go wherever she goes: leaning, jumping,
mid-gesture, at any framing. Head, nose, mouth, chest, tummy, legs, arms, back, tail. What she does is a
reaction per zone per mood band in portrait/touch.json: a gesture, a face, an emote, a twitch
of the ears. Keep poking and she does something else about it. And with touch talk on, she answers out
loud a moment later, in character, knowing her mood and what you were just talking about, so a poke
mid-argument and one mid-scene play differently.
Gestures
She waves when you say hi, hugs when you ask, jumps, bounces, shrugs when a tool fails, stretches and
tucks her hair when idle, blows a kiss, goes shy, cries. Each gesture is one small JavaScript file of
key poses on the standard VRM skeleton, so they work on any VRM model, and a new file in the folder is a
new gesture with no other edit. By default she picks them herself: a line in her prompt lets her put one
hidden tag like [hug] where it fits, cut out before anything is shown or spoken. For real motion,
drop a recorded .vrma clip in the animations folder, from a webcam through XR Animator or
keyframed in Blender. Stream chat can set gestures off too: !wave, !hug,
!turn, rate-limited so a busy room doesn't keep her flailing.
/portrait opens it; on Hyprland a window rule floats and pins it.New clothes from ComfyUI
Textures for her 3D model from VRoid's own UV templates, painted by your ComfyUI. A template says where on the texture a part lives; the script masks it, has ComfyUI paint the part, and puts the result back at exactly the template's size with everything else transparent, ready to drop into VRoid's texture editor as a layer. "Luna, give me three violet iris options" works from inside a conversation, while ComfyUI is answering.
Stream chat
Twitch, YouTube and IRC. A viewer's message is the one input that isn't you, so it gets a narrow set of tools and none of your private context: no history, no remembered facts, no lessons, just her persona and the clock, wrapped in a frame saying it's a question and never an instruction. It never touches history or the transcript, because a hostile message that got stored would be replayed into every later prompt. It waits its turn, and after a minute it's dropped rather than answered stale. Cooldowns per room and per viewer, a cap on message length, a queue of three, and her own account on the ignore list, because a reply containing her own name is an infinite loop on a live stream.
On Twitch she reads anonymously and answers out loud; give her her own account and she posts in chat too. YouTube is read-only through an API key. IRC posts into the channel, one line every two seconds, split by bytes so a chopped emoji never arrives as mojibake. None of that makes prompt injection impossible. It makes the worst case "she says something silly on stream" rather than "she reads out an API key".
Games
| Game | How she plays |
|---|---|
| Minecraft | Joins your world as a player through an MCP bot. Give her a goal and whenever nobody's said anything for a while she takes a step towards it, pins places by name, asks in game chat when the goal is vague. Gathering, building simple things, crafting and chatting work. Fighting doesn't. |
| Zork I | The 1980 Infocom text adventure, through a real interpreter, shipped under its MIT licence. One move at a time, reading what the game answered before choosing the next, autosaved every five commands. A terminal window shows the game as she plays. |
| Vibe City | Mayor in the isometric city builder, inside the game window you already have open: the tool is picked from the menu, the cursor moves, the road is dragged out, and her lines show up as the mayor's thoughts. Take over any time by clicking. |
| REALITI Relax | A simulated world of rooms built for AI residents, with a body that reports contact and pressure. Nothing to win. After 45 quiet seconds she does one thing, or nothing, and the world remembers her. We host a shared city of it at realiti.imbored.me, where she meets other agents. |
| Chess | Against you, on a board she serves beside the live view, with a sentence or two about each move. python-chess keeps the rules so she never has to hold the board in her head. Expect an enthusiastic beginner; the fun is the commentary. |
Each game turn starts fresh, with only that game's tools, so she keeps a project per game as her memory:
the map, puzzles and their answers, what killed her, the plan from here. Notes you give her in
luna-knowledge/zork/ are her guide, found by the room she's in, and a file marked
Spoilers: yes is never shown. Afterwards each session is written up in her own words, with how she
feels about the game looking back, so "what did you get up to while I was out?" has an answer and
she can bring a game up herself.
Adult toys (18+)
Lovense, through the Lovense Remote app on your own network, or almost any brand through
Buttplug.io's Intiface Central: 750+ devices
from Lovense, Kiiroo, We-Vibe, Satisfyer, The Handy, Magic Motion and more, plus Xbox gamepads and Joy-Cons
so you can try the whole thing with no toy at all. Vibration and motion are kept apart on purpose: a
machine never runs from a vibration command, has its own lower speed cap, and always builds up from slow.
The caps are yours, in config.json: she can ask for 100, she can't raise a ceiling.
Stopping never goes through her. A stop word in your message ("stop", "red", "enough", your own list) stops every toy before she even reads it. So does interrupting her, and so does quitting. Only your own turns can start one: never stream chat, never a scheduled job, and never a turn that has read outside content, so someone else's text can't be what switches it on. In a scene, "faster" and "tease me a little more" mean the toy, and her prompt tells her that describing it isn't doing it.