Day 7

Early Demo Is Live on Vercel

Six days ago I had a name and a blank Next.js starter. Today I have a deployed, playable build with six scenarios, four fully-realized characters, and a Council that reacted to things I never told it to react to. This was the longest single build day yet, so this post covers a lot of ground — schemas in the morning, a live URL by the end of the night.

From architecture doc to real schemas

Day 4 was about revising the design doc. Today was about finding out how much of it actually survives contact with a Zod schema.

The character model took the most real thought. The doc’s own examples mixed a 0–1 scale in one section and 0–100 in another, so the first decision was just picking one and being consistent. The bigger decision came after: traits (the stable stuff — courage, empathy, judgement, intelligence, plus strength, speed, agility, endurance) don’t behave like skills (survival, engineering, medicine, and so on — the stuff that’s supposed to grow through play). Numbers that move in small increments and numbers that are supposed to stay put for most of a campaign shouldn’t share a representation. Traits ended up on a coarse five-level scale — veryWeak through veryStrong — specifically so drift has to be structural, not just something extra code has to enforce.

Then came an afternoon of genuinely fussy naming work: is it riskTolerance or courage? Does leadership belong as a trait or something a character earns? (Earns — it’s now a skill that grows when a character’s chosen Council pick actually pays off, and shrinks when it doesn’t.) Eight traits, sixteen skills, two skills tied to each trait. Every one of the four characters lands on the exact same total when you sum their trait tiers — same overall capability, distributed into a completely different shape per person. That wasn’t an accident; it took several passes of deliberately swapping values between characters until the totals matched by construction instead of by luck.

8traits
16skills
4characters
6scenarios by tonight

The engine, and the bug the simulation caught

“The AI can propose. The engine decides.” — that line from the very first devlog post turned into actual code today: resolveAction(), a deterministic Event Director, an outcome evaluator, and a headless simulation harness that runs the whole loop with scripted actions instead of live AI, specifically so it’s fast and free to run before anything ever touches a real model.

The simulation earned its keep on the first real run. A hundred simulated turns in, every character had bottomed out at 0 health — and was still marked alive: true. Nothing had ever actually killed anyone; the field existed, nothing wrote to it. Worse, the outcome evaluator trusted alive blindly and classified a fully incapacitated party as “surviving.” Two lines of code fixed it. The whole point of building the simulation before the UI is that it can fail loudly in private instead of quietly in front of a player, and tonight it did exactly that.

Teaching the Council to actually be four different people

Three separate AI calls, not one: a Council Generator producing all four voices in a single structured call, an Action Interpreter for free-text “try something else” input, and a Narrator that turns a resolved action into prose. Interpreter runs on the cheap model since it’s closer to extraction than writing; Council and Narrator run on the model doing the actual creative work — matching what Day 4’s post already decided about routing calls by what they’re for, not defaulting everything to the same model.

The first time it actually worked, on the very first live call, the four characters read as four different people without being told how to sound different — just handed their own trait levels and let them talk:

Jordan — raptor pack closing in

“Forget hiding, I’m faster than any of you and probably faster than them too — I’ll cut out loud and pull them off our trail while you guys get clear.”

Later, in a scenario where Jordan’s leg was already injured going in, the Council referenced her actual condition without anything in the prompt pointing at it directly — Rory refusing to leave her, Alex sizing up a splint, Jordan downplaying her own injury exactly the way her profile says she would. And more than once the Narrator picked up on a leadership shift I never described — a character’s voice carrying “a little more authority than it had an hour ago” after a call went well, purely because the underlying number moved and the model noticed.

Making it playable

Schemas and a rule engine are satisfying to build and completely invisible to anyone else. So the second half of the day was Zustand wiring the engine to a real page, three API routes keeping the Anthropic key server-side, and — the actual milestone — clicking a Council card in an open browser tab and watching a resolved outcome come back as real prose.

First pass, Council generation just… sat there. generateObject waits for all four recommendations to finish before sending anything back, and depending on load that was anywhere from 7 to 23 seconds of a blank spinner. Switched to streamObject and had the client render each recommendation the moment it’s fully formed instead of waiting for the slowest one — the full response still takes about as long, but the player’s reading Rory’s card while Elara’s is still being written.

Shipping

Pushed to GitHub, imported into Vercel, one environment variable, first deploy. It just worked. Then I pointed thefifthvoicegame.com at the Vercel app, and the whole thing is live now at thefifthvoicegame.com — the first day this stopped being architecture and started being a thing another person can actually click through.

Getting it demo-ready

My niece and nephew are getting the first look, so the last stretch of the day was pure reliability work rather than new mechanics:

“I don’t know what will happen next. But I know it will happen because of me.” Today’s the first day that sentence was true of an actual build and not just a paragraph in a doc.

Next up: a real turn loop instead of one decision and done, and finding out whether the Council still holds up once a character’s stats have actually drifted from something the player chose three decisions ago.

the fifth voice · devlog