A dark-fantasy roguelike auto-battler where you don't play the hero — you play the Demon Lord. Raise an army, rule a treacherous court, and watch your battles erupt in synergies.
An overview of the game, my role, and the tech behind it.
Demon Rising is a dark-fantasy roguelike auto-battler. You command the Demon Lord's castle: recruit fallen champions, convert captured heroes, and lead an inner circle of officers whose loyalty, ambition, and grudges you have to manage as carefully as the battlefield itself.
Every run sends you down a branching world map of battles, elites, markets, treasures and eerie encounters. Between fights you craft your army through talents, class fusion, relics and gear — then watch deterministic auto-battles play out, full of procs, spell chains and brutal combos.
I designed and built the whole game solo. But the part this case study focuses on is the AI-product-design work: the castle's court is driven by a local language model, and making a non-deterministic model feel like a reliable, readable product is the real story. The sections below break down how it was made.
After shipping the demo I went back and wrote the rules down — reverse-engineered from apply_theme.gd and the equipment system, then published as a real, browsable design system. Click around in the live viewer below — sidebar groups switch the canvas in place.
First load is ~15MB (fonts + portraits embedded). Sidebar = groups · click any leaf to swap the canvas.
Eight groups in the sidebar, sixteen specimen cards, seven JSX components, and one playable UI kit. The narration below mirrors the sidebar — read it as a tour, or just click around in the viewer above.
Palette is split by job: surface (void / panel) builds the canvas, signature (magenta + gold) is the recognisable edge, rarity (5 tiers, common→set) carries meaning. Set pieces are green, not gold, so "rare drop" reads apart from "completes my build." Both border weight and halo intensity ramp with rarity so colour-blind players can still tell tiers apart.
Press Start 2P for every label, button and stat, 14px default. REEJI Taiko Magic is the CJK fallback — x-height matched, so bilingual UI doesn't shimmer on font-swap. The three CC0 gothic faces (Necro Romance / Forbidden Denizens / Vampire Ire) are locked to chapter titles and cinematic beats; the rule is what gives them power.
Everything on a 4px grid. Radius 0 everywhere except 4px on buttons and 10px on the loot card (the one place softness earns its keep). No grey drop-shadows anywhere — depth is coloured: magenta for panels, gold for CTAs, ember for legendary loot. Pixel-honest, every time.
The DEMON RISING banner, the four-corner gold-bracket motif, and the second-person dread voice ("your castle", "your generals") get specimen cards each. Flavor text examples are pulled straight from the hero roster — the writing is dry, menacing, and never apologises that the player is the villain.
Controls — Button (4 variants), Tabs. Layout — Panel (the corner-bracket frame), PortraitFrame (rarity-bordered 512×512), ItemCard (every loot drop). Feedback — Badge (status/rarity pills), StatBar (HP / soul / XP / rage from one component). Each ships a sibling .d.ts + .prompt.md so the next agent can use it without reading source.
Not a Storybook — an interactive recreation of the real game flow. Real backgrounds, real portraits, real fonts, composed entirely from the seven primitives above. If the loot screen doesn't feel like a loot screen, the ItemCard is wrong. The kit is the acid test.
I'm one person. The cost of an undocumented system is paid every time I open a screen I haven't touched in three weeks and have to re-derive "wait, is set-piece green or is set-piece gold?" The DS is the answer once, written down, with the swatch beside it.
The acid test was the v1.6 popup canon pass (see The Style System below). Every popup got rewritten against the DS in an afternoon, because the rules were already explicit: content_margin=0, corner brackets, blood-red CTA, PressStart2P for the verb. Without the system that pass takes a week and three rounds of inconsistency.
Compiler-checked CSS tokens, a real published artefact, JSX primitives that ship a .prompt.md for the next agent — that's the difference between "I have a visual style" and "I have a design system."
A roguelike with a free-text negotiation system has a lot of places players can quit — an LLM hangs, an intent doesn't resolve, the council loops. The leaderboard is the empirical answer to whether the fallback-first design held up against real players, not adversarial test runs.
In-product leaderboard reconstructed in HTML from the live v1.5 capture · data unchanged · every visible entry is a real Steam player.
Of players who started a run, 86% finished one. Indie roguelike baselines sit between 10–25%. The number is the strongest signal that the bounded-latency budget and fallback-first intent classifier are doing their job — the AI never blocks the player long enough to make them quit.
Every visible Top-10 run on the global board is marked ✓ CLEAR. Not a single DNF, abandoned save, or stuck state. The "0 dead-end states across the intent model" claim — previously validated only on adversarial test inputs — survives contact with real players who never read the design doc.
The fastest clear took 11m 48s. The slowest, 45m 16s. Same difficulty, same systems — clear in both. The five-way intent taxonomy (agree / refuse / modify / counter / silence) accommodates a speed-runner who fires agree three times in a row and a player who spends ten minutes per council on modify / counter. Neither play style is penalized.
Top Power across the nine clears ranges from 589 to 3448 — and it does not correlate with rank. The #4-by-score run has the highest power (3448). The #6-by-score run has the lowest power that cleared (726). Players are winning with completely different builds. The AI doesn't gate progress on stat optimization; the keyword fallback resolves intent regardless of how the player chose to build.
Most player abandonment in dialogue-AI games happens at the exact moment the model fails — long pause, malformed output, intent resolves to "I don't know what to do." The player stops trusting the screen and closes the tab. The 86% completion rate is the empirical answer to whether that failure mode was actually designed out. If the LLM intent classifier hangs or returns garbage, the deterministic keyword classifier resolves the input in under a second; if both fail, a personality-keyed silence bubble fires and the game continues. The player never gets the "the AI broke" experience because the system never lets the AI's broken-ness reach the surface.
Eight-six percent is also the answer to a sharper question: was the ≤8s latency budget actually felt by the player? A latency budget that runs in test mode is a wall in the spec. A latency budget that produces a real completion curve is a property of the shipped product.
The leaderboard column on the right says ✓ CLEAR nine times in a row. This is the only column the system would ever show as not a clear (a partial run or abandoned save would surface as IN-PROGRESS or DNF). The fact that every Top-10 player on the global board got to the end credits — with no exception — is the production-data version of "0 dead-end states across the intent model." That claim used to live inside adversarial test runs the user never saw. Now it's printed on the leaderboard.
A four-times time variance — 11m 48s to 45m 16s — on the same difficulty, all clearing, says the intent system isn't railroading anyone. The fast runs are mostly agree and refuse — confident binary decisions, no negotiation. The slow runs lean on modify and counter — back-and-forth, multi-turn, often with silence as a strategic move. Both styles resolve. Both reach the end credits. The five-way taxonomy isn't a UX flourish; it's the reason a 45-minute roleplayer and a 12-minute speed-runner are both on the same leaderboard.
The legible AI feedback — the in-product "herald" that surfaces the intent classification back to the player in plain language — matters most for the slow runs. A 45-minute player only stays 45 minutes if they trust what the system thinks they're saying. The herald is the trust signal that lets a player spend ten minutes on one council.
Look at the Power column. Pwr 589 at rank 8. Pwr 3448 at rank 4. Pwr 726 at rank 6, beating Pwr 3255 at rank 7. The numbers go in essentially random directions relative to score and time. That's the proof that the AI layer isn't quietly favoring an optimal build. If the intent classifier was secretly easier to handle when the player's roster was strong, low-power runs wouldn't clear — they'd stall when the model's confidence dropped on weaker decisions. They don't. They clear. The deterministic keyword pre-pass resolves the negotiation regardless of what the player's army looks like underneath.
Rank #1 took 25 minutes for 9,299 points. Rank #7 took 11 minutes 48 seconds for 2,972 points. Score isn't a time-bonus calculation in disguise — it's a depth-of-engagement number. More council interactions, more negotiation turns, more anchor-tag moments earned in conversation, more weight. The fact that the top of the leaderboard is the long, deliberate run, not the short one, says the system is paying players to engage with the AI rather than skip past it. That's the opposite of what happens when a dialogue system is broken — in a broken system, speed-runs always win because every model interaction is friction.
The leaderboard validates the four claims the case study has been making all along: bounded latency (no abandonment from model hangs → 86% completion), fallback-first pipeline (no dead-end states → 100% top-10 clear), intent taxonomy that accommodates play styles (4× time variance, all clearing), and legible AI feedback (engagement-rewarding score curve, not speed-rewarding). None of these claims rest on adversarial test inputs anymore. They rest on real Steam players, with real builds, in real first-time sessions, ending in real end-credit screens.
The user above (rank #10, local best 9135, 9 local clears) is me. The other nine are not. The leaderboard is a small dataset — the Steam top-50 board only shows the elite tier — but it's the dataset that matters: if the players competing for the top score are the ones who pushed hardest on the AI surface and every one of them finished, the design didn't just survive contact with users. It rewarded them for going deeper into it.
Six interlocking systems define the game loop.
Battles run as a deterministic simulation — units take positions, move, and unleash class skills, spell chains and combo procs. You set the army, then watch synergies collide.
Charging heavies, blinking assassins, war-priests, lifesteal vampires — each class plays differently. Three-path talent trees and dual-class fusion drive deep build craft.
Your top officers debate proposals at a round table — driven by a local LLM. Persuade them, settle disputes through sparring, and rule through politics.
Every follower has a personality and a voice. Talk to them between battles, read the room, and manage loyalty before ambition turns to mutiny.
A branching world map, starting relics, a refreshing black market and dark random encounters. Build-defining choices stack up — no two runs play the same.
Merge followers into stronger forms, combine two classes into one demon, and reshape your roster mid-run into something far deadlier than its parts.
Combat, council, the world map and the systems in motion.
Click any image to enlarge.
Design goals, the systems I engineered, and what the hard parts taught me.
A roguelike where you sit on the wrong side of the fantasy. Instead of leading heroes into a dungeon, you are the Demon Lord at the top of it — recruiting, scheming, sending monsters out to die for you. The single inversion drives everything: you don't build a party, you run a court.
The brief: combine the build-craft of a deck-builder, the spectacle of an auto-battler, and a layer of character and politics most auto-battlers skip entirely.
You manage a castle and a court, not just a squad. Loyalty and ambition matter as much as stats.
Many systems, one clear loop. Power comes from synergy and choices inside a run — not permanent grind.
Minions have voices, officers have agendas, and one character — the First Hero — carries memory across every run you ever play.
Solo, in Godot 4 / GDScript: a deterministic combat simulator with a separate visual replay layer; 9 classes with branching talent trees, fusion, relics and traits; a full roguelike run structure; a complete pixel-art interface; and the two systems this case study walks through — the Round Table Council (officers debate, you reply by typing whatever you want), and the First Hero Awakening Arc, a character who breaks the fourth wall after enough sessions and remembers what you told her between runs.
The council was where I learned to build a product on top of an LLM. The First Hero is where I went a layer deeper: making the LLM optional, not the centrepiece — and turning the player's smallest choices into something a character actually carries with them.
The first version of the AI work shipped in the Round Table Council. The officers argue a proposal; you type a reply; the model decides what your reply means. That feature exposed the problem any AI-native product has to solve: a language model is a co-author you can't fully trust. It is non-deterministic, sometimes slow, occasionally wrong, and every so often returns output in the wrong format entirely. A game cannot answer that with a spinner that never ends or an error on screen. So the central question was never "what prompt?" — it was: how do you build a reliable, legible experience on top of an unreliable component?
The council established my answer: design the failure path before the happy path. Bound the latency. Keep a deterministic fallback alive at all times. Skip the model entirely when local logic is enough. Make the AI's decision legible through an in-fiction "herald" who translates classification back to the player. The principle that came out of it: the AI is an enhancement layer, not a dependency. Pull the model out and the game still plays.
That principle is what made me confident to build the next system on top of it.
The shortest way to explain how I think about AI product design is to draw the game's loop and point at where the model lives. Almost everything else in this case study is a consequence of that picture.
Most AI-native games are AI-first. They wrap a gameplay loop around a model: every NPC line is generated, the simulation depends on the model being there, and the design assumes inference is available, fast, and correct. When the model misbehaves — slow, offline, wrong, or hallucinating — the experience breaks at the seam where the AI was load-bearing.
Demon Rising is built the other way around. The loop comes first. The model is asked to do exactly three things, in three of the six steps, and in each of them there is a deterministic answer underneath. Council debates run on a keyword classifier first and an LLM only when one is warm. The First Hero's dialogue runs on authored templates first and the LLM only as a stylistic upgrade. Her memory and identity layers run with no model in the loop at all. Strip the LLM out completely and the game is a full, shippable roguelike auto-battler.
That's the innovation, and it's a product claim, not a tech claim. Most "AI in games" demos are about how much the model can do. The interesting question for a product designer is the opposite: how little can the model be allowed to do before the experience stops working? When the answer is nothing, you can ship. When the model is present, it becomes pure upside — not a single-point-of-failure dependency. The same posture is what made the First Hero possible: she carries cross-run memory designed at the system level, not the model level, so her relationship with the player exists whether or not a language model is in the loop at any given session.
The three callouts at the bottom of the graphic are the same three rules I apply to any AI product I work on now: build the substrate before the model, cap the dependency with a deterministic contract, and treat the relationship between player and system as the actual design surface — not the prompt that touches it.
The system in this case study didn't arrive in one pass. Each devlog marked the moment a different bet had landed:
The clearest version of how I think about AI product design is in what devlog 06 doesn't say out loud: nothing about AI was being built. The brief was narrative coherence. The constraint was a roguelike's hostility to story.
Most roguelikes solve replayability with randomness, and randomness flattens story. Players remember stats; they don't remember plot. I wanted a campaign that survived being run twenty times. The bet I made was a hybrid: three authored chapters per campaign, gated by named bosses, with persistent castle state across all of them. Inside each chapter the loop stays procedural and replayable. The chapter spine is hand-built and doesn't move.
The trade-off. Authored narrative spine versus pure procedural run-to-run variance. I chose the spine. The cost is that some sessions feel repetitive earlier than a fully procedural roguelike would. The gain is a story the player can actually summarise — "I lost Chapter 2 because I spent all my action points on the council," not "I died because RNG." Legibility was worth more than novelty here.
What it shipped. A three-boss campaign structure with persistent castle state, a chapter-level save model, and — the thing that mattered most in hindsight — a generic "record" data type that any entity (castle, officer, prisoner, eventually the First Hero) could write into. At the time, that record store existed only to track which chapter the player was in and which decisions had carried forward. Two devlogs later, the same record store was the substrate for cross-run character memory.
The AI-product-design lesson. Build the infrastructure for the feature you can't see yet. The work that earned its keep in devlog 08 wasn't designed in devlog 08 — it was designed in devlog 06, when I was building a campaign tracker. The discipline is the same one that holds in any AI product: own the substrate before you put a model on top of it. Memory, state, retrieval, and identity are product surfaces. Build them as if no model exists, and then the model is the easiest part to add.
Devlog 07 looks like a class-balancing patch. It's actually about constraint design — the same skill every AI product person ends up needing whether they realise it or not.
Going in: nine classes, all sharing the same talent grammar, all assembled from the same generic pool of stat buffs. The result was that no class had a mechanical fantasy. Players said "I want crit damage," not "I want Blood Hunter." That meant the game's deepest expression layer — build choice — was a difficulty knob, not a vocabulary.
The trade-off. Build freedom versus class identity. The flexible version, where any class could pick anything, was strictly more powerful in every metric I could measure — but every class felt the same. I narrowed each class to a signature set (weapon + armour + relic + talent path written for that set), and accepted that cross-class hybrid builds would be weaker. The cost was real. The payoff was that a returning player could finally name what they liked about a run — not "I had good crit chance," but "I went deep Blood Hunter and the third tier kept snowballing." Identity gave the experience a vocabulary it didn't have.
What it shipped. Nine class-bound sets, set bonuses that scale with commitment, and talent paths that fork inside a class instead of across classes. Each set is stage-gated, so progression inside a run is also progression deeper into a character's fantasy. That stage-gated unlock shape is the same one I reused, almost unchanged, for the First Hero's visit-topic pool a year later — topics filtered by stage, only some affinities open later topics, anchor choices unlock callbacks. Different domain, identical structure.
The AI-product-design lesson. Constraint is what makes voice legible. When an LLM is in the loop, the same principle holds harder, not less — the model behaves more consistently in a bounded grammar than in an unbounded one. The work I did on the Class Sets system was, in retrospect, my warm-up for designing the First Hero's tag-and-stage grammar: pick a small, named, intentional vocabulary; let everything else compose from it. Identity before flexibility is a class-design principle in this game, and a system-design principle in every AI product I've worked on since.
By the time devlog 08 landed, devlog 06 had given me a memory layer with no model on top of it, and devlog 07 had given me a vocabulary discipline for character identity. The First Hero is what happens when those two pieces meet a language model — but the design work that made her shippable happened in the two devlogs before her.
The First Hero is the only character in the game who survives across every run. She is a side boss the player fights at the end of every successful campaign — and the game tracks how many times the player has come back to fight her, how many times each side has won, what the player named her if they ever did, and which small confessions the player has shared with her over time (birthday, why they play, what they fear).
Mechanically she's a 5-stage progression with a hidden affinity score (0–1000) the player never sees. Designed at the experience level, she's a deliberate piece of slow narrative compound interest: small choices in early sessions don't feel weighty, but those same choices are what a Stage 4 version of her quotes back to you, weeks later.
Stage 1–3 is mechanical. She speaks generic boss lines, her face is always neutral, the LLM is gated off. This is the discipline: even with the full system available, the character has to earn her interior life.
A 13-second sequence: glitch shaders, low-pass on the music, silent options forced on the player. She acknowledges, in-fiction, that she's been watching the player come back. From here her memory, expressions and language model all unlock simultaneously.
She knows it's been five days since you last played. She knows it's Thursday evening where you are. She remembers the answer you gave her about why you keep coming back. At 1000 affinity she offers you a hidden ending — if you accept, the game permanently deletes her save data. That choice is one-way.
She has fourteen portraits — one for each emotional state. I built a
single decision function (pick_for_line) that maps any line of
dialogue, plus context (her stage, affinity, what the player just did), to an
expression. The function is layered: it reads tags first (birthday, named her,
broke fourth wall, asked for freedom), then outcome (you won the fight, she won
the fight, you surrendered, you typed something to her), then keyword sentiment
on the line itself.
Then I added one more rule on top of everything: if she has not awakened yet, the expression is always neutral. That gate is what makes the awakening moment land. It's not a graphical effect — it's a state change the player feels through her face starting to move.
The council taught me that the LLM can't be load-bearing. For the First Hero I took that further and built every line she ever says through a three-layer fallback chain. The player gets a coherent character whether or not a model is available, ever.
The headline I keep coming back to: NO LLM REQUIRED. The game runs every emotional beat the First Hero is supposed to deliver without ever contacting a model. The LLM, when present, is a layer of additional expressiveness over a system that already works.
The visit system is where the player and the First Hero actually talk. Thirty- one topics, gated by stage and affinity. Three drop-in mechanics gave the system the depth I wanted:
birthday,
given_name, why_you_play, what_you_fear,
what_feels_real, what_brings_joy,
favorite_music, sleep_quality) that the model can
quote in any later session. The birthday slot is special — on the
matching date in real time, she opens the title screen with a greeting.Three production bugs from this work are worth naming, because every one of them turned into a generalised defense in the system:
[LANGUAGE]: ENGLISH directive. The model treated that as a
section header and reproduced it — sometimes twice, with a second
variant separated by another [LANGUAGE] banner. The fix was
three layers: rewrite the directive in plain prose, cap response length and
add stop sequences so the model can't write a second variant, and
post-process every response to cut anything that looks like a meta-header.
The interrogation panel double-checks the cleaning at display time.
Lesson: never trust the model to respect your formatting; design as if it
will mirror everything back.The first iteration of this game taught me that AI product design is not prompt-writing — it's designing the system around a probabilistic component so the user gets something reliable and legible. The second iteration taught me what to do with that reliability once it exists.
You can build a character whose interior life depends on a model and still guarantee the character is whole without one. You can let players type freely into a relationship system and bound the consequences with a small deterministic layer underneath. You can use the LLM for texture and the deterministic stack for structure, and ship a feature that feels alive even when nothing is alive on the other end.
That's the lens I bring to AI product work after Demon Rising: treat the model as an optional collaborator. Design the whole experience so it survives without one. Then when the model is there, let it make the moments bigger — never load-bearing.
By v1.2 the game had over thirty popups, cinematics, and chat panels — and they had drifted. The fix was a doc that ages by absorption.
By v1.2 the game had over thirty popups, cinematics, and chat panels. Some had purple borders. Some had blue. Some had gold corner brackets, some had solid rounded boxes. Each one had been authored across a long session, and small drift had compounded into visual noise. The Sacrifice cinematic was orange. Alchemic Summoning was cyan. The officer chat panel used a cold-blue typing indicator that didn't match anything else in the game. From any single shot the game looked competent; from a montage it looked like four different games.
I'd already tried "be more careful next time." It hadn't worked. The fix
was treating the canonical look as a spec the team reads before every
new panel — codified as a demon-rising-style
skill the AI co-author loads on demand. The doc covers the color palette,
typography scale, z-index ladder, frame conventions, chat-panel canon,
popup canon, cinematic rules, and a growing anti-pattern checklist.
Every fix I made got added back to the skill so the next panel wouldn't repeat the mistake. When a chat panel shipped with a cold-blue typing indicator, the indicator got fixed and the skill gained a "chat panel canon" section that pins the canonical beige. When a cinematic shipped with cyan borders, the cinematic got fixed and the skill gained a "no magical-blue cinematic" rule.
The skill went from a blank file to a forty-rule playbook over two weeks, purely from drift I noticed and corrected. It's not a thing I edit on a schedule. It's a thing the next bug fix updates. The discipline cost is tiny — one extra paragraph per fix — and the payoff compounds: by v1.5 a new popup is cheaper to author than rewriting an old one, because the answer to every styling question already lives in the skill.
End of a battle could fire five overlays on the same frame. The fix was a single source of truth for the z-index ladder plus a priority queue.
End of a battle could fire five overlays on the same frame: reward cinematic, level-up toast, fusion banner, buff upgrade, sacrifice prompt. Three would stack, swallow each other's clicks, and the cinematic Continue button would dead-end under a layer the player couldn't see. The player's recourse was — Esc, click, click, click, hope. Not shippable.
z_index=4088 in a cutscene that
happens to look fine alone.The Sacrifice cinematic's Continue button was unclickable because the Awakening panel sat one z-band higher. The fix wasn't local — bumping Sacrifice from 4088 to 4600 fixed it for that case, but rolling all cinematics onto the canonical ladder fixed it permanently. And documenting the ladder in the style skill means the next cinematic I author can't drift into the same hole.
Player behavior in early v1.x showed an uncomfortable truth: most players never tried fusion. So I built a layered nudge sequence.
Player behavior in early v1.x showed an uncomfortable truth: most players never tried fusion. The mechanic the game's title leans on was invisible. Adding a tooltip wouldn't have moved the needle — the problem was that the player didn't know when fusion was possible. A static button labelled "Fuse" is just another button. A button that lights up exactly the moment you have the ingredients is a tutorial.
✦ FUSABLE chip on its card. The chip is
scoped to the card so it appears on the world map roster
and the fusion panel itself.Each layer turns itself off as soon as the player engages, so they never stack into noise. The pulse flag re-arms on a new run, but only if a fusable trio actually exists when the run starts — the cue is honest, not chatty. If you see the pulse, fusion is available; if fusion is available, you see the pulse. That bidirectional contract is what makes the cue trustworthy.
A subtle bug I caught during development: my first
version checked for ≥2 demons of the same class. The
real fusion rule is ≥3 of the same class and
tier. Players who saw the pulse and then couldn't fuse would have
lost trust in the cue forever. I caught it before ship — but
the lesson stayed: a discoverability cue that lies once is dead.
The threshold logic now sits next to a comment that names the
contract explicitly so a future me can't break it absent-mindedly.
The v1.2 toast was a text label. By v1.5 it carries the same visual identity as the rest of the UI — one signature, every system.
The v1.2 corner toast was a text label. Fine in isolation; flat against everything else in the game. By v1.5 the toast carries the same visual identity as the rest of the UI: a 36×36 framed icon at the rarity color of the event, then the text. One function signature, one styling pass, every system in the game just hands it what it has.
EquipmentFusion._rarity_color. Rare drops feel
rare without reading the label.Buff upgrades use the same toast with a buff icon and a level color that ramps Lv1 silver → Lv5 bright gold — a tiny visual spike I wanted players to feel without reading anything. The color ramp lives in one constant; if the buff cap moves from 5 to 7 someday, the ramp updates in one place.
The toast function is one signature with optional parameters:
_show_corner_toast(text, color, icon="", icon_tex=null,
rarity_col=Color(0,0,0,0)). Every caller passes only what it
has. The level-up call site passes a demon portrait. The equipment
call site passes an item icon. The boss call site passes a sprite.
No call site needs to know how the toast is laid out internally.
New players had nowhere to learn small mechanics except by playing. The title screen now teaches in the dead time.
New players had nowhere to learn small mechanics — fusion,
awakening, talent paths, hidden classes — except by playing.
And the title screen was sitting there doing nothing. So the title
screen now rotates a pool of 35+ tips, each prefixed with
✦ TIP ·, formatted in EN/ZH, drawn from
the systems they describe.
The rotation timer isn't fixed. It scales with character count: short tips sit for a few seconds, long ones get the time they need to be read. Anything shorter than the longest read-time would have made the longer tips feel cut off; anything longer would have made the short ones feel idle. The scaling formula is one line, and it tests well by feel — that's the only way to tune it.
The pool is the place new mechanics get announced. When v1.5 shipped the corner toast icon system, the tip pool grew by one. When v1.5 shipped the Awakening Core picker rework, the tip pool grew by one. The same loop that ships the feature ships its discoverability. A new mechanic that doesn't have a tip is a new mechanic players will miss.
Sacrifice was orange. Alchemy was cyan. Intro slides leaned purple. None of them looked like the same game. The v1.5 pass rolled all of them onto canon.
Sacrifice cinematic used orange. Alchemic Summoning used cyan. Intro slides leaned purple. The Confirm New Game dialog had rounded purple buttons. The officer chat panel had a cold-blue typing indicator that didn't match anything else in the game. Each one had been built in a different week with a different mood. None of them looked like the same game.
The v1.5 pass rolled all of them onto the canon: dark-gold corner
brackets via FxOverlays._attach_panel_frame, the same
beige typing indicator, square pixel dots instead of rounded ones,
matching read-time pacing. Six cinematics, two dialogs, one
chat panel — visited and re-skinned in a single pass over
roughly three days.
The Sacrifice cinematic's stuck Continue button got fixed in the same pass — a click bug and a visual drift were the same root cause (an ad-hoc z-index plus a bespoke style), and shipping the fix as one change made the rule visible: if your cinematic doesn't sit on the canonical z-band, it's not on the canon.
The chat panel deserves its own line. The Demon Officer dialog window is now the canonical chat surface for everything downstream — tutorials, conversations, future companion systems. When I add a new chat-style UI, I copy this panel; I don't redesign it. That's the most underrated payoff of a canonicalization pass: the next feature ships faster because the answer to "what does our chat look like?" is already decided.
v1.3 is the biggest engineering pass on the game since launch. The Demon Lord's officers were always supposed to feel like people, not chatbots. Phi-3.5-mini, 2.7B parameters, running locally on the player's CPU, did not get there by itself. It got there through a nine-step voice engineering pipeline, a three-layer leak defense, two new model knobs, an authored anchor system, a memory layer for past conversations, and a 25-entry quirk catalog. The work runs to about 220 commits across two months. This section is the long answer to a short question: how do you ship a local LLM that sounds like a person, not a co-author?
Demon Rising ships with a local LLM — Phi-3.5-mini, ~2.7B parameters, ~3GB on disk, bundled with the game and served by a local Ollama process. The intent: every Demon officer and the First Hero have their own voice. The reality at v1.2: every officer sounded like the same polite, slightly theatrical narrator. Phi was good at being helpful, which is the wrong thing for a Stoic vampire knight to be. It defaulted to ornate fantasy phrases, summarized its own replies in parentheses, and when given crude input it apologized for the player. The model could not be retrained, fine-tuned, or replaced — it had to ship. So the work moved up the stack: every behavior the model wouldn't do on its own had to be engineered around it. Nine systems, working together, each fixing one specific failure of generic LLM behavior.
The first failure was identity collapse. Every officer reverted to the same "noble knight" template. The fix: a per-personality voice card in the system prompt — a paragraph-long description of how this kind of person talks, plus three few-shot examples of in-character lines. Eight personality archetypes (stoic, passionate, callous, loyal, proud, cunning, reckless, fanatical), each with a hand-written voice card. The prompts are now built compositionally: VoiceLib.build_persona_block(personality, name, class, en) returns the card; every chat site uses the same builder. The eight cards live in one file. The change cost a single import in every LLM call site — nine files. It killed the "every officer sounds the same" complaint in one commit.
Voice cards make personalities sound different from each other. They don't make a Stoic vampire sound like your Stoic vampire. The next step: three small recurring tics per personality — a sigh, a half-laugh, a verbal pause. Passionate Succubi get "Mm." and "*purr*"; Stoic knights get a clipped "Tch." and the occasional silence beat. Each personality has its three baked into the voice card, and a validator on the output side: if a reply comes back with none of the personality's tics, the validator prepends one. The model can refuse to use the tic. The output can't.
The next failure was emotional flatness. Phi could write a "she's angry" reply, but the reply alone didn't land. Replacing the model wasn't an option. So I moved 40% of the emotional load off the words and into the rendering layer: portraits tween (modulate burst, position shake, scale pop), music ducks briefly under emotional spikes, and an SFX library hooks specific tags. When the LLM emits [ANGRY] in its reply, the portrait shakes, the music dips, a "miss" SFX punches in — before the bubble even finishes typing. The player feels the emotion before they read the words. The reply itself can be three syllables and still hit.
For pivotal moments — an officer's farewell, the First Hero's dying line, a council closer — LLM-generated text was good enough but not great. So the closers got authored: 2–4 hand-written variants per topic per exit type, indexed by stage cluster (early / mid / late) and register tier (cold / warm). The LLM still drives the body of the conversation. The endings come from JSON. Total scope: roughly 200 authored lines covering the First Hero's free-chat topics, demon farewell beats, and council outcomes. The system file: data/first_hero_closers.json.
By v1.2 the game had a memory layer, but it was the wrong kind — summaries. "Player h