New Model, Old Galaxy: Claude Fable 5 Rebuilds Third Conflict's Game Feel
I know very little about game development. My retro 4X remake now has animated turn playback, a parallax starfield, a working AI planner, and 221 new passing tests anyway, thanks to one session with Claude Fable 5 and seven agents running in parallel. Also included: the difficulty system we caught making easy mode stronger than hard.
Full disclosure: I am not a game developer. I couldn't write a particle system, an AI planner, or parallax camera math if you paid me. But my hobby project, Third Conflict, a free browser strategy game, now has all three, plus 221 new passing tests, because I spent one session pointing Claude Fable 5 at it.
The Roadmap I Almost Believed#
Third-Conflict is my web remake of Second Conflict, a 1991 Windows 3.0 shareware 4X game that nobody remembers except me. Over the course of this series it's grown into a real project: a Next.js 15 app with a pure-function game engine, a seeded random number generator so replays are deterministic, a PixiJS galaxy map, and 924 passing tests. I understand maybe two thirds of it on a good day.
It also had a homework file: docs/plans/production-enhancement-roadmap.md, an audit a previous session left behind. Finding number one was blunt. Turn resolution was invisible. You click End Turn and the game just swaps state. Fleets teleport. Battles are log entries you read after the fact.
Further down was a list of fully built, fully tested systems that nothing actually used. A GOAP AI planner: 33 passing tests, zero callers. Veterancy: 29 tests, zero callers. Supply lines: 34 tests, zero callers. And my favorite, a complete procedural music system whose volume slider in Settings worked perfectly. It adjusted the volume of nothing. There was never any music.
This was my first real session with Claude Fable 5, Anthropic's new Mythos-class model that sits above Opus. I wanted to see what it would do to a roadmap like that in one sitting. The answer was a lot, including one thing that went wrong in the most interesting way possible.
What Is GOAP?
Goal-Oriented Action Planning. Instead of following a fixed priority list, the AI scores a set of possible goals (expand, defend, attack, research) against the current situation every turn and pursues the top few. The goals compete for its attention.
The Plan: Two Deep, Two Light, One Bug Hunt#
The first thing Fable did in plan mode was refuse to trust the roadmap. Three exploration agents checked its claims against the actual code and came back with six corrections: things flagged as broken that were already fixed, things described confidently that worked differently in practice. That set the tone for the whole session.
The scope we landed on:
- Deep: graphics. Turn playback, a parallax starfield, particle pooling, a frame-budget watchdog.
- Deep: AI. Wire up the dead GOAP planner, rebuild difficulty from stat cheats into actual behavior, and build a self-play harness to measure any of it.
- Light: gameplay. Switch on veterancy and supply lines behind new setup toggles.
- Light: quality of life. Music, error boundaries, save validation, clickable turn recaps.
- Bug hunt. Three read-only reviewer agents sweeping the whole app, independent of the feature work.
Execution was seven named agents running in parallel: four building features, three doing nothing but reading code and reporting what's broken. I mostly watched, asked questions, and clicked things in the browser to check they were real.
Turn Playback: Watching a Turn Actually Happen#
This was the flagship fix. The old processTurn swapped the entire game state in a single frame. One moment your fleet is at Sol, the next it's orbiting Fomalhaut. Did a battle happen? Check the log, I guess.
The design Fable came up with keeps the instant state swap (autosave and game-over logic depend on it) and adds a presentation layer on top. A pure function called buildTurnTimeline diffs the before and after states into an ordered list of playback events: departures, arrivals, battle beats, ownership flips. No rendering code, no store, just data in and data out, which is why it anchors the biggest test suite added this session.
Battle beats get ranked (your battles first, then conquests, then by casualties) and capped at four, so a chaotic turn doesn't become a five-minute cutscene. Then the whole thing gets squeezed proportionally into a fixed 3.5-second window:
// ---- Scale everything proportionally into the duration budget ----
let total = 0;
for (const e of events) {
total = Math.max(total, e.startMs + e.durationMs);
}
const scale = total > maxDurationMs ? maxDurationMs / total : 1;
const scaled = scale === 1
? events
: events.map((e) => ({
...e,
startMs: e.startMs * scale,
durationMs: e.durationMs * scale,
}));
A sequencer walks that timeline and tells the renderer what to draw each frame: ghost fleet markers flying their routes, the camera gliding between battles, outcome text rising and fading. Click anywhere, or hit Space, Enter, or Escape, and it all skips straight to the end state. Reduced Motion, a new Fast Turn Resolution setting, and game-over skip playback entirely.
Swap First, Animate Second
The ordering is the whole trick: commit the real game state immediately, then animate on top of it. Nothing in the game logic ever waits on an animation, and skipping is a pure visual cancel. That's what made this safe to bolt onto an already-working turn processor.
The engine itself didn't change at all. The only new code is a diff pass that turns state A and state B into a short story about how you got from one to the other. Roadmap complaint number one: gone.
Painting the Galaxy#
The map used to have one flat starfield. Now it has three layers at different depths panning at different rates as the camera moves, drifting nebulas, stars that twinkle in staggered groups (rather than 500 individually animated sprites, which I'm told is a frame-rate disaster), and dashed fleet routes that flow in the direction of travel.
This is the section where I most obviously did not write the code, because I couldn't have. The math is one small pure function solving a problem I didn't know existed until Fable explained it: the background layers live inside the world container so the bloom effect covers them too, but that container already has the camera transform applied. Making a layer look farther away means stacking a second transform on top of the first:
export function computeParallaxTransform(
camera: Camera,
depth: number,
anchorX: number,
anchorY: number
): ParallaxTransform {
const scale = Math.pow(camera.zoom, depth - 1);
const camDX = anchorX + (camera.x - anchorX) * depth;
const camDY = anchorY + (camera.y - anchorY) * depth;
return {
x: camera.x - camDX * scale,
y: camera.y - camDY * scale,
scale,
};
}
At the galaxy center with zoom at 1, this collapses to identity and the layer tracks the world exactly. Move the camera and each layer scales by zoom^(depth - 1), so deep layers barely move while shallow ones hug the foreground. It's the classic side-scroller parallax trick adapted to a top-down camera that can zoom.
The particle system also got rebuilt, from one Graphics object per particle to a pooled sprite system with a shared texture and a hard cap of 1,000. A dev-only watchdog now complains about any frame that takes longer than 8ms. So far it has complained exactly once, at startup.
Reduced Motion Was a Lie
The in-game Reduced Motion toggle never reached the renderer. It toggled a CSS class, while the canvas checked the OS-level preference once at mount, so flipping the setting mid-game changed nothing. The fix checks the store setting OR the OS preference, live, every frame.
Teaching the AI to Practice#
You can't tune an AI you can't measure, so the AI work started with something that isn't AI at all: a headless self-play harness. It loops the exact same processTurn the real game uses, no browser, no rendering, and plays the AI against itself.
npm run ai:harness -- --seeds 10 --turn-cap 150
That one command plays 120 full games in about 0.4 seconds, around 300 games per second. The baseline run, before anything changed: Balanced won 83% of its duels, Expansionist 83%, Turtle 33%, Aggressive 0%. I had no idea whether those numbers were good. Didn't matter. They were written down, and everything after got compared to them.
Measure Before You Tune
Every AI change after this point got a before-and-after harness run instead of a "feels right" judgment call. That habit is the only reason the difficulty inversion in the next section got caught instead of shipped. A 0.4-second test suite removes every excuse.
Wiring Up the Dead Planner#
With measurement in place, the GOAP planner finally got connected to something. Step one was deliberately boring: gather the strategy constants scattered through the AI code into a single StrategyProfile struct, changing zero behavior. Every test stayed green, so any later failure would point at the new logic and not at moved furniture.
Step two did the wiring. Each turn the AI scores its available goals, keeps the top three, and lets the winners nudge a few bounded knobs. If attack_weak wins, for example, the AI accepts a thinner numerical edge before committing to an attack, down to a hard floor of 1.05. Thinner, never losing.
Each faction also got personality weights on top: the Krell lean hard into attacking the weak, the Velthari favor research, the Draxi expand aggressively. The weights drift turn over turn based on how the game is going, so a Krell AI that's been getting pounded for ten turns plays differently than one that's been steamrolling. That detail delights me more than it probably should.
The Difficulty Inversion#
This is the part of the session I keep telling people about, because the first version was wrong.
The old difficulty system was a stat cheat: harder AIs just produced more stuff. The new one changes behavior. Lower difficulties get a bounded chance to misjudge targets and a cap on how many attacks they can launch at once. The misjudgment function comes with a determinism contract I would never have thought to ask for: at amplitude 0 it draws zero random numbers, so hard difficulty stays byte-identical to the old behavior and none of the replay or save-parity tests can tell it exists.
export function makeEvaluationNoise(
rng: RngState,
amplitude: number
): EvaluationNoise {
if (amplitude <= 0) {
return { noise: () => 0, rng };
}
const { value: salt, rng: nextRng } = nextU32(rng);
const noise = (systemId: number): number => {
const u = hash32((salt ^ systemId) >>> 0) / 4294967296; // [0, 1)
return (u * 2 - 1) * amplitude; // [-amplitude, +amplitude)
};
return { noise, rng: nextRng };
}
Why Exactly One RNG Draw?
The engine threads a seeded RNG through pure functions, so every random draw has to be accounted for or replays and save-reload parity break. One salt per AI per turn adds exactly one predictable draw, and everything else derives from a stateless hash. No Math.random(), no wall clock, no surprises.
Then the sanity check: a mirror match, same strategy on both seats, easy AI versus hard AI. Easy won 15 of 20 games.
The handicap had backfired. Capping how many attacks an AI can launch doesn't weaken it when the winning doctrine in this game is fewer, bigger attacks. Hoard your fleet and strike once with everything is strong play, and the cap implements it for free. The fix was a third lever, offensiveForceScale: easy and medium now also launch undersized strike forces, so the handicap finally costs something.
| Difficulty | noiseAmplitude | maxOffensivesPerTurn | offensiveForceScale |
|---|---|---|---|
| Easy | 0.35 | 1 | 0.45 |
| Medium | 0.15 | 3 | 0.85 |
| Hard and above | 0 | Infinity | 1.0 |
With that in place, hard beat easy in three of the four strategy doctrines. The fourth, Aggressive, self-destructs no matter who's piloting it.
Handicaps Can Invert
Reducing an AI's options is not automatically a weakening. If the smaller option set happens to overlap with strong play, the "handicapped" side comes out stronger. You catch this by running the mirror match and reading the win rate, not by reasoning about what the difficulty knob sounds like it does.
The Balance Confession#
Difficulty tuning was a win. Strategy balance was not, and I'd rather show you the real numbers than round them into something flattering.
The target, set before tuning started: every strategy winning 30 to 65% of its round-robin duels. After nine tuning rounds: Expansionist 78%, Turtle 75%, Aggressive 42%, Balanced 5%.
Not close. The diagnosis from the harness's economy dumps is structural: a 1v1 duel rewards single-minded doctrines. Turtle hoards and counterattacks. Expansionist grabs neutral systems and snowballs the economy. Balanced hedges by design, and hedging loses head-to-head against opponents that never do. One real bug fell out of the digging: Balanced's defensive trigger fired on any threat greater than zero, dumping its entire military budget into defense the moment anything looked scary. Fixed now. Not 73-points-of-win-rate fixed.
The Target Was Not Hit
Nine tuning rounds did not bring the win rates into the 30-65% band, and the numbers above are the real ones. The proper fix is redesigning the Balanced doctrine around pooled, multi-system offensives, and that's documented as follow-up work instead of being tuned around until the results merely looked acceptable.
Two things kept this from being a disaster. The new veterancy and supply toggles shift win rates by under 7 points, so they aren't distorting balance. And player-facing difficulty still works fine through the untouched economy modifiers. The AI isn't broken. One specific target got measured honestly and missed.
Gameplay Toggles and the Placebo Checkboxes#
The light gameplay pass switched on the two dormant systems: veterancy (units earn experience and fight better) and supply lines (fleets deep in hostile territory fight worse). Both are new setup toggles, on by default for new games, and old saves read as off, so nothing existing changes.
The reason this stayed light: both systems fold their combat effects into a single CombatModifiers object, which the combat resolver and the replay generator both already consume. Replay parity held without touching the replay code at all.
While in that corner of the codebase, Fable noticed that three other setup toggles (Total War, Arms Race, Fog Eternal) were wired to nothing. They rendered, they saved their state, they did absolutely nothing. They're real now: Total War starts every AI at war, Arms Race halves research costs, Fog Eternal removes persistent map memory. Ironman, the fourth modifier, blocks manual saves.
Placebo Controls Hide in Plain Sight
A checkbox that renders correctly and saves its state correctly but does nothing is one of the easiest bugs to miss, because everything about it looks right until you trace what actually reads the flag. If you inherit a settings screen, check every toggle for a consumer.
One more latent bug nearby: faction trust bonuses were never applied at galaxy setup, because the players array was never passed into initializeDiplomacy. Every six-faction game ever started had flat trust values, and nobody noticed, including me.
The quality-of-life pass finally gave the music system a caller: ambient audio starts on your first click, the mood follows the war state, music ducks under dialogs. And the save validator, also dead code until now, got wired into the load paths. That one came with a landmine.
The Save Validator's Landmine
The unused validator had a blanket rule clamping negative numbers to zero. Sounds sensible, except neutral systems are marked owner: -1 and morale legitimately runs negative. Switching it on blindly would have quietly converted every neutral system into player-owned territory. Reviving dead code isn't "does it run", it's "does it agree with every convention the codebase silently relies on".
The Bug Hunt's Greatest Hits#
While the feature agents built, the three reviewer agents read, and they came back with roughly 25 verified defects.
The store reviewer's best find: End Turn's dynamic import chain had no .catch, and React error boundaries don't catch promise rejections. If the engine threw during that import, the End Turn button silently froze forever. No error message, no crash screen, just a game that stopped responding.
The rendering reviewer found that loading a save mid-playback soft-locked the game permanently, that fleets in transit vanished during battle beats, and that per-frame visibility checks were O(N²) when they could be a map lookup. The engine reviewer's best catch was my favorite: drain a system's veteran troops to zero, recruit green replacements into the same slot, and the rookies inherited the dead veterans' experience, because nothing cleared the XP ledger between occupants.
Promises Don't Trip Error Boundaries
React error boundaries only catch errors thrown during render. An unhandled rejection in an async chain, like a failed dynamic import(), sails right past them, and the UI can freeze with no visible error at all. Any async chain gating a critical user action needs its own .catch.
What Fable 5 Actually Felt Like#
"The new model is smarter" is easy to claim and hard to back up, so here's only what I can point to in the transcript. It treated the inherited roadmap as claims to verify and found six errors in it. It read the save validator closely enough to spot the landmine before arming it. It measured every AI change with the harness, which is the only reason the difficulty inversion got caught. And when the balance target wasn't hit, it reported the miss instead of massaging the numbers. For someone who can't personally review parallax math, that last habit is the one that matters most.
About Claude Fable 5
Claude Fable 5 is Anthropic's first Mythos-class model, part of the Claude 5 family, positioned above Opus in capability. I ran this session with reasoning effort turned up. I'm not claiming these behaviors are unique to this model, just reporting what showed up in this session.
By the Numbers#
| Metric | Value |
|---|---|
| Tests passing | 924 to 1,145 |
| Files changed | 87 (41 engine pass, 46 presentation pass) |
| Lines changed | +8,804 / -813 |
| Named agents | 7 build/review (plus 5 planning agents: 3 Explore, 2 Plan) |
| Verified bug fixes | ~25 |
| AI harness speed | ~300 games/sec |
| Balance tuning rounds | 9 |
| Turn playback budget | 3.5 seconds |
| Engine test coverage | 84% (85% overall on covered dirs) |
Everything shipped as two commits to main, with the full test suite green, TypeScript clean, a production build passing, and a live browser check with zero console errors before either landed.
The infographic at the top and the slides throughout came from feeding the finished draft to NotebookLM. If you want the session in shareable form, the full 10-slide deck is available as a PDF.
Closing the Session#
None of this makes Third Conflict a finished game. Balanced needs a real redesign, and a codebase that hid a silent music system probably has more corners like it. But turns are watchable now, the galaxy looks like a galaxy, and the AI provably plays differently at every difficulty, confirmed by a command that runs in under half a second.
I still can't write a particle system. But I own one now, I roughly understand it, and I watched the thing that built it catch its own mistake using a test harness it also built. No feature on the list beats that.
If any of this sounds fun, Third Conflict is free to play in your browser. No install, no account. Watch a turn play out, flip the new toggles on, and see if you can beat the AI that practices at 300 games per second. And yes, easy mode actually is easier now.
Written by Chris Johnson. Third-Conflict is a web remake of Second Conflict (1991), rebuilt as a modern browser game using Next.js 15, React 19, TypeScript, PixiJS v8, Zustand v5, and Tailwind CSS v4. Previous posts in this series: The 5,250-Line Upgrade, Securing a Retro Game: 26 Findings, 9 Agents, 15 Minutes, and Rebuilding a 1991 Space Game with AI Agents.


Comments
Subscribers only — enter your subscriber email to comment