πI'm the last mail carrier in the land of the dead. An open-weight model writes all the mail.
This is a submission for the Hacktoberfest Open-Source AI Challenge: Week 4: Ghost in the Machine. Kenji cast nets in a muddy river in SΓ£o Paulo in the 1920s. Tonight he's a ghost in MictlΓ‘n, and he stops me outside the
This is a submission for the Hacktoberfest Open-Source AI Challenge: Week 4: Ghost in the Machine.
Kenji cast nets in a muddy river in SΓ£o Paulo in the 1920s. Tonight he's a ghost in MictlΓ‘n, and he stops me outside the post office:
"Little one, would you carry this message to Teodoro? He still hums that old melody whenever the moonlight hits his workshop."
Teodoro made furniture in BogotΓ‘ in the 1990s. The two of them lived seventy years and four thousand kilometers apart. I find Teodoro in the plaza, and he opens the letter:
Dear Teodoro, I remembered the night we sat on that tiny balcony, the air thick with jasmine and the sound of your voice singing softly. I keep that tune in my head, and sometimes the scent of the night brings me right back to that moment. I hope the moon is kind to your steady hands tonight.
Kenji
"Ah, that song! I haven't forgotten that night either. Thank you, Kenji. May your nets be light tonight."
Across town, Kenji rises into the sky as a lantern.
Nobody wrote Kenji, Teodoro, the balcony or the song. An open-weight model running on my own machine did. It writes every ghost in the game, and every letter they send each other.
What I Built
ΓNIMAS is a small 3D exploration game for DΓa de los Muertos that runs in the browser.
You play a little calaca, a skeleton in a striped serape with a crown of marigolds: the last mail carrier of MictlΓ‘n, the Aztec land of the dead, on the one night of the year when the souls go home. The ghosts wandering the town have unfinished business. Some want a letter carried to another ghost. Some miss one small thing (a guitar, a pocket watch, a cup of chocolate) that's waiting somewhere in the world. Deliver it, and they cross over.
The town is meant to be wandered:
- Four barrios of pastel houses around a plaza with a great ofrenda. Every roof is walkable. You climb outside stairs, stacked crates and market awnings, then cross planks from rooftop to rooftop.
- A chapel whose bell tower you climb flight by flight. A hillside of terraces that ends in a lookout and a long, high plank out to La Campana, a floating rock with a bell.
- Rope bridges to the islands around town: a cemetery, a floating market, an orchard, a dock with stepping stones across the water to a tiny islet, and a staircase of floating stones up to El Mirador, the highest point in the world.
- Three houses you can walk into: a grandmother's house with a stove and a little ofrenda, a watchmaker's workshop, the blue house. Each has a staircase up to a hatch in the roof, and something someone is missing waiting inside.
- Golden calaveras hidden on rooftops, towers, awnings and the far ends of things. The game never tells you where they are.
- A minimap that turns with your camera, a full map (
M), and a mail bag that holds up to four deliveries at once, so you can plan a route.
And then there are the wings. Free three souls and they give you wings (Alas de papel): in the air, tap Space to beat them, hold it to glide. Six souls, and they fill with marigolds and carry you further. Ten, and you become el Γngel de MictlΓ‘n, with a halo and a trail of light, flying over the rooftops you used to climb. Some golden calaveras are only reachable by wing, including one on La Nube, a cloud island high above the town. You'll know what you're working towards: a few other (NPC) mail carriers make their rounds through the streets, and the ones who already earned their wings glide over the rooftops above you.
Demo
π Play ΓNIMAS (desktop or phone). The hosted version plays with the ghosts and letters Gemma wrote ahead of time; run the repo locally to have Gemma keep writing new ones.
A few other letters waiting in the mail tonight, all written by Gemma 4 E2B on a 2-core CPU with no GPU:
Dolores β Mateo Β· neighbors who shared sweet bread on the same street
"Do you remember how we tried to bake that disastrous sourdough last summer? It ended up looking like a melted swamp, but we laughed until our bellies hurt."
Mateo: "Oh, the swamp! I still chuckle thinking about that."Nonna Pina β Ama Β· they shared stories of mythical creatures
"I remember the night we huddled under the old mango tree, and you told me the secret names of the forest spirits."Benedita β Felipe Β· a love for forgotten folk songs
"Remember the night we thought the moon was actually a giant, silver cat?"
Felipe: "That tune⦠I haven't heard that melody in fifty years."
Controls: move the mouse to look (click the world once), WASD walks wherever you're looking Β· Shift run Β· Space jump (twice for a flip; with wings, again to flap, hold to glide) Β· E talk Β· 1/2/3 choose Β· M map Β· Tab next delivery Β· F dance. On a phone: a thumbstick on the left, drag the right side to look, buttons for jump, talk and dance.
When the game runs with its keeper server (npm start + Gemma), you can also summon someone you miss at the great ofrenda. Describe them in a sentence and Gemma turns them into a ghost who wanders the town tonight, for strangers to meet. It's the only place in the game where you type, and your words are never stored, only the ghost.
How I Built It
Code: github.com/longphanquangminh/animas (MIT). There's no game engine, no build step and no 3D model files. The client is Three.js with hand-written shaders. The server is plain Node with zero npm dependencies. Gemma 4 runs behind Ollama or llama.cpp.
Two grammars: one for people, one for mail
The keeper (that's what the server calls the model) doesn't get asked nicely for JSON. It gets a grammar. The server sends a JSON Schema, the runtime compiles it into a grammar, and at every decoding step any token that would break the schema gets a probability of zero.
Ghosts have a name, a hometown, a life, a personality, a greeting, a memory, the object they miss, an ask, a thank-you and an epitaph. The object is an enum of twelve things that physically exist on pedestals around the world, so every errand Gemma invents is solvable.
Letters are written later, between two ghosts that are both wandering right now. The server picks a sender and a recipient, hands Gemma both of their lives, and asks it to invent the connection:
const LETTER_SCHEMA = {
type: "object",
properties: {
relation: { type: "string", maxLength: 70 }, // "neighbors who shared sweet bread on the same street"
ask: { type: "string", maxLength: 170 }, // the sender asks you to carry it
letter: { type: "string", pattern: "^Dear [^,]{1,30}, .{60,400}$" },
reply: { type: "string", maxLength: 200 }, // the recipient, out loud, after reading
},
required: ["relation", "ask", "letter", "reply"],
};
That pattern is there because of a lesson. In my first batch, about half the "letters" came back as descriptions of letters: "From Kofi to Sal, referencing shared memories (film/ramen)." The model understood the task and summarized it instead of doing it. A one-line example in the prompt, plus a grammar that forces the field to literally start with Dear <name>,, fixed it. Every letter since has been a real letter.
The same thing happened with objects. When Gemma could pick any of the twelve, 9 of 24 ghosts chose the marigold crown, and then asked in their dialogue for a spoon or a fountain pen. The structured field was perfect; the prose wandered off. Now the server rolls dice (name, place, decade, job, personality, soul color, and an object that follows from the job: a baker misses pan dulce, a clockmaker a pocket watch) and builds a grammar for that one ghost, with the object pinned as a single-value enum placed first, before any prose. The grammar guarantees the shape. The dice guarantee the meaning.
Gemma 4 and the empty thought channel
Gemma 4 can think before it answers, inside a special thought channel. On llama.cpp the grammar is relaxed inside that channel, so the model sometimes rambled there and never came back to the JSON. The fix: I render the chat template myself and append an empty thought channel (<|channel>thought\n<channel|>) before generation starts, so the grammar governs every token from the first one. (On Ollama, think: false does the same job.) That's only possible when you control the prompt down to its special tokens.
Writing ahead of the player
On the box where I built this, Gemma 4 E2B takes about a minute to write a ghost and 15 to 60 seconds to write a letter. Nobody waits for it. The server keeps a pool of ghosts written ahead of time, and a second loop keeps about half of the wandering ghosts holding a letter for another wandering ghost. When a letter's recipient crosses over before you arrive, there's a red mailbox by the Correo that sends it to the other side. The model works like a novelist in the back room, not a chatbot at the counter.
A town you can climb
The town is generated in code from a fixed seed, so every player gets the same streets. Collision is three small ideas:
- pads: round islands whose cliffs are walls,
- boxes: houses, crates, awnings, the tower (a walkable top and solid sides),
- ramps: stairs, planks and rope bridges (walkable, no walls).
The character controller steps up anything under 0.8 m, jumps about 2 m (3.5 m with the double jump), and has coyote time. Fall into the river and it carries you back to the last shore you stood on. The camera pulls in when a house gets between it and you.
To make sure the town is fair, I wrote a reachability check: a breadth-first search over the whole map using the same rules as the character (step up 0.8 m, jump up to about 3 m, drop any distance). It runs in a headless browser and reports anything that can't be reached from the plaza. The first run found that the stepping stones to the islet and to El Mirador were a little too far apart. Now every calavera, every object and every ghost passes.
A skeleton made of code
There are no model files in the repo. The calaca is a few dozen primitives: a sugar-skull face painted onto a canvas texture, glowing pupils that blink, bone limbs on pivots, a serape whose stripes come from the player's hue, a crown of flowers. Walking, running, jumping, the mid-air flip, carrying an object overhead, gesturing while talking and dancing (F, try it) are procedural animation with squash and stretch. Ghosts are a lathe-turned sheet with a shader: a glow in their soul's color, a rippling hem, and a mouth that moves while their words type out in Animal-Crossing-style babble, at a pitch derived from their name.
Around them: thousands of instanced marigolds and candles, papel picado on sagging ropes, glowing windows, a mirror-water river, drifting marigold wisps, souls rising slowly while petals fall, monarch butterflies (said to be the returning souls), bloom and film grain. All the sound is procedural Web Audio.
Why open innovation matters here
I needed control below the API. Compiling my own schemas into grammars, pinning the object per ghost, forcing a letter to begin with "Dear", appending an empty thought channel token by token: none of that is available through a closed chat API, and the game design depends on all of it.
A town that writes its own mail needs an AI that costs electricity, not tokens. The server writes ghosts and letters in the background forever. With a per-token bill I'd ration them. With open weights on my own box, the mail never stops.
Some of these ghosts are real people. When someone summons a grandparent at the ofrenda, those words are grief. They don't belong in a third-party provider's logs. With an open model, the memory goes from the browser to a server I control, becomes a ghost, and the original words are thrown away.
A memorial shouldn't disappear when an API does. Open weights on disk and a dependency-free server will still run in 2036, and anyone can host a MictlΓ‘n for their own family.
Come pick up the mail
Play ΓNIMAS. Deliver a few letters, earn your wings, then tell me in the comments who wrote to whom and how many golden calaveras you found. (There's one on top of the bell tower, and one even higher. That's all I'm saying.)
They're not gone while someone still remembers. πΌπβοΈ
Prize categories: Best Use of Gemma. Gemma 4 writes every ghost and every letter in the game, under JSON-Schema grammars that keep every errand solvable and every letter a real letter.
Solo submission by @longphanquangminh.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.


