It's 11 pm on the 1st of November. Your car has died on a mountain road, the rain is coming sideways, and the only light for miles is a mansion on the hill. You reach for the knocker.
The door opens before you touch it.
What I Built
The Ofrenda House is a first-person horror mystery that runs entirely on your own machine. No cloud, no API keys, no accounts. Unplug your Wi-Fi and the ghosts are still there.
You step inside to shelter from a storm. The door slams behind you and the lock turns on its own. Lightning flashes through tall windows, and for a moment you see a hallway lined with portraits of a family that died here in 1931. Then it's dark again, apart from the narrow beam of your flashlight.
The family didn't die of fever. One of them poisoned the others, one by one, for the inheritance. The dead dragged the killer down the cellar stairs on the last night, but the poisoner came back as La Sombra, a faceless shadow that now hunts anyone who wanders in.
To get out alive you have to solve the murder:
- 🕯️ Find the spirits. Each victim waits in their own room, hidden somewhere in an endless house of shifting corridors that rearrange every time lightning strikes.
- 👻 Talk to the dead. They ask who you are and why you're here, then test you with a riddle about their own life. Answer well and they hand you a clue. Answer badly and they fade into silence.
- 🔍 Catch the liar. One of the "friendly" spirits is the killer in disguise, and their clue contradicts someone else's. Spot the two cards that don't agree.
- 🌼 Make your accusation. At the ofrenda in the dining room, lay a marigold on the portrait of the person you think did it. Get it right and the dead finally rest. Get it wrong and your face is the next portrait on the wall.
Every run is different. The household, their names, their secrets (a bookkeeper skimming the estate, a housekeeper who was secretly a lover, a child who discovered they were adopted) and the killer change every time you play.
How I read the theme: "Ghost in the Machine" felt literal. I wanted the ghosts to live in my machine, voiced by a model running on my own laptop rather than rented from a server somewhere. Día de los Muertos gave the soul of it: a story about the dead wanting to be remembered and wanting the truth told, with marigolds and an ofrenda at its heart.
Demo
What to watch for:
- 0:00: the door that opens by itself, and locks behind you
- The first lightning strike through the portrait hall
- A spirit's riddle, spoken aloud in a voice generated on-device
- A bat exploding out of the dark (sorry)
- The accusation at the ofrenda
Code
Run it yourself:
# 1. Required: Node 26 (or newer) and git
brew install node git
# 2. Get the game
git clone https://github.com/adybose/the-ofrenda-house.git
cd the-ofrenda-house
npm ci
# 3. Optional: spirits that improvise their lines (Ollama + Gemma 3 4B, about 3.3 GB)
brew install ollama
ollama serve & # or open the Ollama app instead
ollama pull gemma3:4b
# 4. Optional: spirits that speak aloud (Piper + four voices, about 240 MB)
python3 -m pip install --user piper-tts
npm run setup:voices # creates voices/ and downloads the voice models, once
# 5. Play
npm start
How I Built It
The open-source AI
| Piece | What it does |
|---|---|
| Gemma 3 4B (open weights) via Ollama | Voices every spirit: their greetings, their suspicion, their riddles and the Shadow's whispered taunts |
| Piper (open-source text-to-speech) | Speaks each line aloud, with a different voice per spirit, then pitched down and drowned in reverb |
| three.js | The 3D first-person world, the flashlight and the real-time shadows |
| Web Audio API | Every sound, synthesised live |
| Claude Code | My pair programmer for the whole build (more below) |
Everything runs comfortably on an M1 Pro MacBook with 16 GB. Gemma uses about 3 GB and runs on the GPU through Metal, with plenty left over for the game.
The rule that made it work: code owns the truth, the model owns the voice
The scariest bug in an LLM mystery game is a ghost who makes up a clue. If the model invents a fact, the mystery can become unsolvable.
So at the start of every run, plain deterministic code builds a hidden case file: who is in the household, who holds which secret, who the killer is, and exactly which facts each spirit knows. The killer's disguised form is assigned one specific lie that contradicts another spirit's truth. A test runs the generator over 1,000 seeds and proves every single mystery can be solved.
Gemma never decides anything. For each line it gets the spirit's persona, the facts they're allowed to reveal and a strict JSON format, then writes the words. The server validates every response before it reaches the screen, and if Ollama is slow or offline, hand-written fallback lines take over so the game never breaks.
Riddles are multiple choice. The right answer comes from the case file, and Gemma writes the riddle and the wrong answers. The player never types; they pick a reply with a click or the 1 to 4 keys.
No asset files at all
There's no assets/ folder full of downloaded art. Everything you see and hear is generated by code at runtime:
- The house is an endless grid built in chunks around you. Key rooms are placed by the case file, filler rooms and corridors are generated from a seed, and the unseen ones reshuffle whenever lightning strikes.
- Textures (wallpaper, wood floors, stains) are drawn on a canvas when the game loads, in a deliberately lo-fi, PS1-style look.
- Portraits are painted procedurally from each character's traits.
- Sound: rain, thunder, creaking doors, keys turning in locks, heartbeat and stingers are all synthesised with Web Audio and placed in 3D.
- Light: a focused flashlight beam that casts real shadows, and lightning that throws window-frame shadows across the floor for 150 ms before the thunder rolls in.
Built spec-first with Claude Code
With four days to build, I used spec-driven development. I wrote the story bible first, then broke the game into 16 small specs (scaffold, seeded RNG, case file generator, endless house, rendering, lighting, audio, LLM bridge, voices, dialogue UI, spirits, the Shadow, jump scares, endings), each with acceptance checks that a test or screenshot could prove.
For every spec, Claude Code planned first, wrote the tests for the checks, wrote the code until they passed, took headless-browser screenshots for anything visual, then committed and merged. A CLAUDE.md "constitution" held the rules every spec inherited: offline only, no external assets, the model never owns facts, and every AI call has a fallback.
Pure-logic specs (the case file and the LLM bridge) ran in a second Claude Code session in a separate git worktree while the main session built the 3D world.
Why Does Open Innovation Matter?
Because a haunted house shouldn't need a server to stay haunted.
- It runs anywhere, forever. No API key expires, no service shuts down, no price changes. If this game still runs in ten years, it'll be because the model is a file on your disk.
- It works offline, in a cabin on a stormy night, which is exactly where you'd want to play it.
- It's private. Everything you say to the ghosts stays on your machine.
- It's free to play and free to change. Anyone can clone it, swap Gemma for another open model, write new spirits, or translate it. A closed API would put a meter on every ghost's sentence.
- Small open models are enough when you design around them. A 4B model on a laptop can't be trusted with the plot, but it doesn't need to be. Give it the facts and a voice, and it gives the dead their personality.
There's something fitting about a Día de los Muertos story where the spirits live on your own machine, kept alive locally and remembered without asking anyone's permission.
The Spirit of It
Día de los Muertos isn't about fear. It's about remembering. Families build ofrendas with photos, candles, food and marigolds so that the dead can find their way home for one night.
I wanted the horror to sit on top of that tenderness. The spirits in this house aren't monsters; they're a family who were murdered and never got justice. They're suspicious of you because the last people who came in never left. When you finally lay the marigold on the right portrait, it isn't a boss fight. It's a family being allowed to rest.
There's one more secret in the house for players who are honest with the spirits about who they are. I won't spoil it here, but it's the reason the door opened for you in the first place. 🕯️🌼
Prize Categories
- Gemma: every spirit's voice is generated locally by Gemma 3 4B through Ollama.
Top comments (0)