Skip to main content
CryptoFlex LLC

From Second Conflict to Third Conflict: Building a New Game with Claude Code

Chris Johnson·February 28, 2026·18 min read

The Working Port Wasn't Enough#

In the first post in this series, I described rebuilding Second Conflict as a modern web app. Four parallel agents, a pure TypeScript engine, 261 passing tests, and the game was live. It played. It had morale, combat, production, and revolts.

In the second post, a five-agent security team found 26 issues and a red teamer dubbed one attack chain "God Mode in 30 Seconds." Four developer agents fixed all 26 findings in about 15 minutes.

So I had a faithful, secure web port of a 1991 shareware game.

And then I thought: what if I actually made it good?

Not "good for a retro remake." Good full stop. The original game was limited by what a single programmer could ship as Windows 3.0 shareware. I had no such constraint. I had Claude Code, a clean architecture that made the engine genuinely extensible, and an unreasonable amount of enthusiasm for a game most people have never heard of.

What followed was a four-phase modernization plan, a "wiring plan" that connected 10 infrastructure gaps, an animated title screen, and an in-game manual. The game went from Second Conflict to Third Conflict, its own thing, carrying the original's DNA but with a feature set that would have seemed like science fiction to Jerry Galloway in 1991.

Here is how it was built.

Time lapse of a city being built from nothing


The Architecture That Made This Possible#

Before getting into the features, I want to explain why adding all of this was tractable. The pure function architecture from the original port paid dividends here.

Every game system is a pure function:

typescript
// Engine functions: data in, data out. No side effects.
function resolveAllCombat(state: GameState): GameState { ... }
function processProduction(state: GameState, techEffects: TechEffects): GameState { ... }
function updateVisibility(state: GameState): GameState { ... }

Adding a new system means writing a new function. Wiring it in means calling it in the right place in turn-processor.ts. The turn processor is the orchestration hub, and its execution order is documented right at the top:

typescript
// Order: Validate orders > Apply orders > Move fleets >
//        Resolve combat > Process production > Generate troops >
//        Check morale > Check revolts > Generate events > Score > AI turns > Advance

That clarity meant every new feature had a natural insertion point. There was no "where does this live?" question because the architecture answered it.

Pure Functions and Extensibility

When game logic is pure functions that take state and return new state, adding new features is genuinely additive. You write the new function, call it in the right sequence, test it in isolation. You are not untangling spaghetti or worrying about mutation side effects. The pure function architecture was the right call in the first port, and it compounded in value during every subsequent phase.

The engine ended up with 20 TypeScript modules in src/engine/. The rendering layer has 10 modules in src/rendering/. The store has 5 Zustand slices. None of these files bleed into each other's concerns.


Phase 1: QoL and Interface Polish#

The first phase was table stakes: features a modern player would expect before they could take the game seriously.

The keyboard shortcut system. The original game was mouse-only, which made sense for Windows 3.0. A modern strategy game needs keyboard shortcuts. The solution was a custom useKeyboard hook that maps key combinations to actions, dispatched through the Zustand store:

typescript
// Every dialog gets a keyboard shortcut. No dialog requires a mouse to open.
F → fleet dialog
P → production dialog
T → tech tree
D → diplomacy
E → espionage
O → empire overview
M → manual
, → settings
Ctrl+S → save/load
Tab → cycle through your systems
H → home system
Space → center map
? → keyboard help overlay

The keyboard help overlay itself (press ?) renders a two-column grid of shortcuts. Small thing. Players notice when it is missing.

The camera and zoom system. The original game's map was fixed. You saw all 26 systems at once in a small window. The remake has a pannable, zoomable Canvas map. The camera state lives in the UI store and gets passed to every rendering function:

typescript
function renderStarMap(
  ctx: CanvasRenderingContext2D,
  state: GameState,
  camera: CameraState
): void {
  // Every rendering call transforms through the camera
}

Panning uses click-drag. Zoom uses scroll wheel. The minimap in the corner shows where you are in the full galaxy. All of these were already in the original port. Phase 1 polished them: smoother zoom interpolation, edge panning when the mouse hits the canvas border, and a "home" key that snaps the camera back to your starting system.

Save slots with named saves. The original game had one save slot. The remake has named save slots stored in localStorage. The save/load dialog lets you name a save, overwrite it, and delete it. A timestamp shows when you last saved each slot. This sounds obvious, but it is the difference between "I can test this" and "I have to play from the start if anything goes wrong."

UI Polish Before Features

Getting the basic interface right before adding features pays for itself. Every hour I spent making keyboard shortcuts work and camera panning feel good was an hour I didn't spend fighting the interface while building Phase 2. Polish the foundation before piling more on top of it.


Phase 2: Strategic Depth#

Phase 2 is where the game stopped being a port and started being an expansion.

The technology tree. The original Second Conflict had no tech system. I designed one from scratch: 15 technologies across three branches (Military, Economic, Covert), arranged in a tree with prerequisites.

Military Branch:
  Advanced Weaponry → Stealth Systems → Precision Targeting
  Heavy Armor → Shield Technology
  Fleet Tactics → Admiral Training Program

Economic Branch:
  Efficient Factories → Industrial Automation → Megastructure Engineering
  Trade Networks → Resource Extraction

Covert Branch:
  Intelligence Network → Counterintelligence
  Propaganda Machines → Sabotage Techniques

Each technology has measurable effects. Advanced Weaponry gives a hitBonus: 0.1 that is applied in combat calculations. Efficient Factories gives a factoryOutputBonus: 0.2 that increases production. Stealth Systems gives an evasionReduction bonus for stealth ships.

The tech effects system produces a TechEffects record that gets passed to every engine function that cares about it. Adding a new tech effect means adding a field to TechEffects and reading it in the right engine function. Claude Code wrote the entire tech tree in one pass, including the UI (a visual tree with lock/unlock states and a research queue), the engine module, and the integration tests.

The admiral system. Named commanders that attach to fleets and provide trait-based bonuses. Admirals have traits like Aggressive (+15% attack), Defensive (+15% defense), and Navigator (-1 turn travel time). They gain experience, can be killed in battle, and are randomly generated with procedural names.

The admiral module was a good example of the write-then-test pattern. I described the requirements, Claude Code wrote src/engine/admirals.ts with full tests, and I reviewed the output. The review caught one subtle issue: the experience gain formula was using integer arithmetic where float was correct, which would have caused admirals to gain experience much more slowly than intended on short campaigns.

Four AI strategies. The original game had one AI personality. The remake has four, each implemented as a separate strategy module:

typescript
// Each strategy implements the same interface
interface AiStrategy {
  generateOrders(state: GameState, playerId: PlayerId): TurnOrders;
}

// src/engine/ai/strategy-balanced.ts   - sound 4X strategy
// src/engine/ai/strategy-aggressive.ts - prioritizes attack
// src/engine/ai/strategy-turtle.ts     - builds defensively, rarely attacks
// src/engine/ai/strategy-expansionist.ts - claims systems as fast as possible

When setting up a game, you assign each AI player one of the four strategies. Against an Aggressive + Expansionist pair, you need to lock down systems early. Against a Turtle + Balanced pair, you have time to build up before the pressure comes. The strategies make repeat playthroughs feel different.

Multiple victory conditions. The original game had one end state: last player standing. The remake has five:

Victory TypeCondition
Last SurvivorAll opponents eliminated
DominationControl 70% of systems
TechnologicalComplete an entire tech branch
EconomicAccumulate 100 factories
ScoreHighest score when turn limit is reached

The scoring engine calculates a live score each turn based on systems controlled, factories built, fleet strength, and research progress. The empire overview dialog shows the current standings. Chasing a Technological victory while your opponent is rushing Domination produces a genuinely different game from a straight elimination fight.

Multiple Victory Conditions Change AI Priority

Once I added multiple victory conditions, I had to update all four AI strategies to account for them. The Turtle strategy now builds factories aggressively because it is actually pursuing an Economic victory. The Expansionist strategy checks if it is close to Domination before deciding to attack. Adding victory conditions is a force multiplier for strategic variety.


Phase 3: Diplomacy, Espionage, and Fog of War#

Phase 3 added the systems that turn a wargame into a strategy game.

The diplomacy system. Players can form non-aggression pacts, trade treaties, and military alliances. Diplomatic actions have consequences: breaking a treaty damages your standing with all players, not just the one you betrayed. The diplomacy engine tracks a relationship matrix between all players, updated each turn based on past actions.

The diplomacy dialog is one of the more complex UI pieces in the game. It shows your current relationship with each opponent, the active treaties, and available actions filtered by your current diplomatic stance. Declaring war on an ally is possible but it triggers a significant relationship penalty with everyone.

Espionage operations. The Covert tech branch unlocks espionage. Operations include:

  • Sabotage: destroy a factory at a target system
  • Intelligence: reveal an opponent's production plans for one turn
  • Revolt Incitement: artificially lower morale at a target system
  • Technology Theft: chance to steal a tech the target has that you don't

Each operation has a base success probability modified by your Covert tech level and the target's Counterintelligence tech level. Failed operations can be traced back to you, triggering a diplomatic incident. The espionage engine is a pure function that returns either a successful result or a "traced and caught" result, and the turn processor handles both outcomes.

Fog of war. The original game showed you everything. The remake has a visibility system: you can see systems you own, systems adjacent to systems you own, and systems where you have fleets in transit. Everything else is hidden. You might know a system exists but not who owns it or how many fleets are there.

The visibility system modifies the rendering layer. The star map rendering functions received a visibility parameter:

typescript
function drawSystems(
  ctx: CanvasRenderingContext2D,
  state: GameState,
  camera: CameraState,
  visibility: VisibilityMap
): void {
  for (const system of state.systems) {
    if (visibility[system.id] === "visible") {
      drawSystemFull(ctx, system, camera);
    } else if (visibility[system.id] === "known") {
      drawSystemDimmed(ctx, system, camera);
    }
    // "unknown" systems are not drawn
  }
}

The fog of war changes the game significantly. You cannot optimize your response to an attack you cannot see coming. Scout fleets become valuable. Stealth ships become worth building. The galaxy feels larger and more dangerous.

Fog of War and the AI

When I added fog of war, I had to decide: does the AI get to cheat? All four AI strategies were reading the full game state, including systems they shouldn't be able to see. The fix was passing each AI strategy a filtered state that respected visibility rules. The AI now plays with the same information constraints as the human player. This made the AI somewhat less effective at optimal responses but significantly more fair.


Phase 4: Presentation, Onboarding, and Polish#

Phase 4 was about the parts of a game that players experience first and remember longest.

The animated battle viewer. Combat in the original game was resolved instantly with a text log. The remake has an animated battle viewer: ships from both sides appear on screen, exchange fire in rounds, and you watch the attrition play out. The animation uses the Canvas API and the draw-systems.ts rendering module for the ship icons. Each round, the combat engine calculates the result and the animator interpolates the visual state over about 500ms per round.

The turn recap dialog. After every turn, a summary dialog shows what happened: which systems changed hands, which fleets arrived, which events fired, combat outcomes, and production completions. Before this, processing a turn was a black box. Now you can read exactly what happened and why. The recap is built from a TurnSnapshot recorded at the start of the turn processor, diffed against the resulting state.

The random events system. The original game had a few random events. The remake has a richer table:

  • Economic boom: a random system produces double output for one turn
  • Plague: a system loses population and morale
  • Diplomatic incident: relationship with a random opponent decreases
  • Piracy: a fleet loses one ship in transit
  • Archaeological discovery: a random system reveals a bonus factory
  • Solar flare: a system's sensors are degraded for one turn

Events are drawn from a weighted table using the seeded PRNG. The same game ID produces the same sequence of events, which means you can reliably replay a run.

The morale and revolt mechanics. These existed in the original port but Phase 4 added the revolt incitement espionage operation, which means players can now deliberately trigger revolts in enemy territory. This required making the morale system visible to players: the system panel now shows a morale bar for every system you own. You can see which systems are at risk and prioritize garrisoning them.


The Wiring Plan: Connecting 10 Infrastructure Gaps#

After four phases, the game had a lot of infrastructure that existed but wasn't fully connected. I wrote a "wiring plan" documenting 10 specific gaps between the engine and the rest of the system. This is probably the least glamorous part of game development and also one of the most important.

The 10 gaps:

  1. Tech effects into combat: hitBonus, damageBonus, and evasionReduction from the tech tree were calculated but not passed to the combat resolver. Fixing it required adding techEffects as a parameter to resolveAllCombat() and reading the relevant fields.

  2. Admiral combat bonus: The admiral system generated bonuses but the combat resolver wasn't applying them. One additional parameter, two lines of code.

  3. Tech effects into production: factoryOutputBonus, productionMultiplier, factoryCapBonus, and factoryBuildTurns from the tech tree were not affecting production calculations.

  4. Tech speed multiplier: Fleet travel time was not respecting the speed bonus from the Fleet Tactics technology.

  5. Fog of war into rendering: The visibility system was calculating visibility but the rendering functions were ignoring it, still drawing everything.

  6. Visual effects from game events: The effects system (src/rendering/effects.ts) had conquest, revolt, and fleet arrival animations defined but nothing was triggering them. The turn processor now fires effects at the appropriate moments.

  7. Sound wiring: The audio module had 12 sound types defined but the UI was only calling a handful. The wiring connected all 12: click, dialog open/close, fleet depart/arrive, combat start/end, tech unlock, event fire, victory, defeat.

  8. Accessibility settings: A settings dialog allowed configuring font size, high contrast mode, reduced motion, and color blind mode. None of these were being applied. The settings store now exports a hook that applies CSS custom properties and class names based on the current settings.

  9. Demand tribute: The diplomacy action "demand tribute" was defined but its effect was a no-op. It now removes 2 factories from the target player's home system.

  10. Turn history snapshots: The empire overview dialog had a "Turn History" tab with placeholder data. The turn processor now records a TurnSnapshot each turn, storing system count, factory count, fleet strength, and score for each player. The tab renders this as a sparkline chart.

None of these were individually difficult to fix. Together they represented the difference between a game that technically runs and a game that actually works. The wiring plan was a forcing function: write down every gap, fix each one, verify the build and test suite after each fix.

Write Down the Gaps

As you build features, you will accumulate infrastructure that is not fully connected: functions that exist but are not called, settings that are stored but not applied, effects that are defined but not triggered. Keep a list. A "wiring plan" that enumerates these gaps is enormously useful because it makes the invisible visible. You can prioritize, assign, and verify each gap individually instead of discovering them through bugs.

After the wiring plan, the test suite ran 330 tests. All passing.


The Title Screen: 850 Stars and a Supernova#

The last feature I added before writing this post was the animated star map background for the title screen.

The main game has a rich parallax starfield. The title screen, the first thing a player sees, was a flat black rectangle with text on it. That gap bothered me.

The title screen background uses the same starfield engine as the gameplay map, but tuned for pure aesthetics rather than game readability. The parameters:

  • 850 background stars with spectral color variation (blue-white O/B stars, yellow G stars, orange-red M stars)
  • 8 nebula patches rendered as translucent ellipses with radial gradients
  • 4 dust lanes for depth
  • Parallax camera drift: the camera slowly moves through the scene, giving the impression of floating through space
  • Supernova effects: two systems in the background render with pulsing halos and lens flares

The spectral color palette uses real stellar classification:

typescript
const SPECTRAL_COLORS = [
  { r: 155, g: 175, b: 255 }, // O/B blue-white
  { r: 200, g: 210, b: 255 }, // A white-blue
  { r: 255, g: 250, b: 240 }, // F white
  { r: 255, g: 235, b: 180 }, // G yellow
  { r: 255, g: 200, b: 130 }, // K orange
  { r: 255, g: 160, b: 110 }, // M red-orange
];

The supernova pulsing uses a sine wave on the halo radius:

typescript
const pulse = Math.sin(timestamp * 0.002) * 0.3 + 1.0; // 0.7x to 1.3x
const haloRadius = baseRadius * pulse;

Simple math. Looks like something that cost more to build than it did.

Beautiful space nebula with glowing colors

The lens flare is the same technique: a set of concentric circles at progressively lower opacity, scaled and colored to suggest a camera artifact. Historically, lens flares are a cheap trick. In a space game title screen, they are contextually perfect.

The Reduce Motion Setting Actually Matters Here

The animated title screen background drifts continuously. If a player has the "reduced motion" accessibility setting enabled, the parallax drift is disabled and the background renders as a static starfield. This is a one-line conditional in the animation loop: if reducedMotion is true, skip the camera drift update. Accessibility settings that don't actually affect anything are theater. The wiring plan caught this gap.


The In-Game Manual#

The last piece was the in-game manual: a dialog with 13 sections covering every game mechanic, accessible from the main menu and from the keyboard shortcut M during gameplay.

The manual content is structured data, not raw text:

typescript
export type ManualBlock =
  | { type: "heading"; text: string }
  | { type: "paragraph"; text: string }
  | { type: "list"; items: readonly string[] }
  | { type: "table"; headers: readonly string[]; rows: readonly string[][] }
  | { type: "tip"; text: string };

export interface ManualSection {
  id: string;
  title: string;
  icon: string;
  paragraphs: readonly ManualBlock[];
}

This structure means the manual can be searched, exported as plain text, or rendered in different formats. The current renderer produces the in-game dialog view. The structured content approach also made it straightforward for Claude Code to write the 13 sections accurately, because the content is clearly separated from the presentation.

The 13 sections cover: Overview, Getting Started, Star Map, Fleet Management, Production, Technology, Combat, Diplomacy, Espionage, Random Events, Admirals, Victory Conditions, and Keyboard Shortcuts. Every numeric value in the manual references the engine constants directly, so when a constant changes, I update one file and the manual stays accurate.


How Claude Code Actually Helped#

Across four phases and a wiring plan, here is a breakdown of what Claude Code did well and where I had to stay engaged.

What it did well:

Writing new pure-function engine modules from a description. Every new system (technology, admirals, diplomacy, espionage, visibility) started as a description of what I wanted and turned into a working TypeScript module with tests. The iteration loop was fast enough that I could catch and correct issues before they compounded.

Implementing complex UI components from a specification. The tech tree dialog, the diplomacy interface, the espionage dialog, the animated battle viewer: each of these is a nontrivial React component. Describing the desired behavior and letting Claude Code generate the first pass was much faster than writing from scratch, and the first pass was usually close enough that the review was about refinement, not rewriting.

The wiring plan execution. Once I documented the 10 gaps, Claude Code could execute each fix systematically: find the relevant file, locate the right function call, add the parameter or the function call that was missing. This is exactly the kind of task where AI assistance multiplies output without multiplying risk, because each change is small, targeted, and immediately verifiable with the test suite.

Where I stayed engaged:

Architecture decisions. The structure of the tech effects system, the AI strategy interface, the turn snapshot data model: I made these decisions. Claude Code implements well within a defined structure, but defining the structure is a human job. Getting that wrong creates technical debt that is expensive to fix later.

Balance and game feel. Whether an admiral's Aggressive trait gives +10% or +15% attack bonus, whether Efficient Factories gives a +15% or +20% output bonus, whether the Turtle AI should ever attack at all: these are judgment calls that require playing the game, not reading the code. I made many of these adjustments manually after playtesting.

Cross-file integration review. The biggest class of bugs I caught were the wiring gaps: infrastructure that existed but was not connected. Catching these required reading the engine and the turn processor together and asking "is this actually being called?" Claude Code did not proactively surface these. The wiring plan was something I had to write by reading the codebase with fresh eyes.

Write a Wiring Plan

After any significant feature addition, spend 30 minutes reading every place that new code touches the existing system and asking: "Is this connected everywhere it should be?" Document the gaps. Fix them systematically. The alternative is discovering them through bugs in the wrong order, which is slower and more stressful.


The Numbers#

After four phases, the wiring plan, and the final polish:

MetricValue
Engine modules20
Rendering modules10
Zustand store slices5
Dialog components12
AI strategies4
Technologies15 (3 branches)
Victory conditions5
Sound types12
Tests passing330
Manual sections13
Background stars (title screen)850
Build statusClean

The 330 tests are split across the engine modules. The pure function architecture means every test is a simple function call: given this state, calling this function produces this new state. No mocking, no component rendering, no browser simulation. The test suite runs fast and the failures are specific.


What Third Conflict Is Now#

Third Conflict is not a port of Second Conflict anymore. It is a new game that uses the original's structure as its foundation and builds something larger on top of it.

The original had 26 star systems, 7 unit types, one AI personality, no tech tree, no diplomacy, and one victory condition. Third Conflict has all of that plus 15 technologies, four distinct AI strategies, a full diplomacy system with treaties and consequences, espionage operations, fog of war, five victory conditions, an admiral system, an animated battle viewer, a full turn recap, named save slots, keyboard shortcuts for everything, an in-game manual, spectral starfields, and a title screen that looks like it belongs in 2026.

Jerry Galloway built the skeleton. Three decades of game design accumulated since 1991, and Claude Code helped translate all of it into a modern stack in a handful of sessions.

Man chef's kissing his fingers to indicate perfection

The architecture decisions from the first post, particularly the pure function engine and the immutable game state, made all of this possible. You cannot stack four phases of features onto spaghetti. The clean foundation was not a luxury; it was a prerequisite.


Lessons Learned#

Plan phases, not sprints. Each of the four phases had a clear theme: interface polish, strategic depth, diplomacy and fog of war, presentation and onboarding. Within a phase, I knew what a "done" state looked like. That clarity made it possible to make real progress within a single Claude Code session rather than accumulating half-finished features.

The wiring plan is a deliverable, not an afterthought. I treated the wiring plan like a feature in its own right: documented it, worked through it systematically, and verified each fix. The alternative would have been discovering these gaps through player bug reports or my own frustrated testing sessions. Writing them down first was better.

Immutable state is non-negotiable for a game engine. Every engine function returns a new state object. This made debugging trivial: I could snapshot the state before and after any function call and compare them. It made testing trivial: every test is a pure function call. And it made React integration trivial: Zustand detected state changes via reference equality without any manual change tracking.

AI assistance compounds on good architecture. Claude Code was most useful when it was implementing within a well-defined structure. When the architecture was clear, the implementation was fast and the review was straightforward. When I tried to add a feature without a clear structural home, the implementation wandered and the review was harder. The lesson: define the structure first, then implement.

Don't Skip the Review

Every feature Claude Code implemented got a review pass before it stayed in the codebase. The review caught the admiral experience formula bug, caught the AI strategies reading unfiltered state after fog of war was added, and caught two cases where new engine functions were not integrated with the test suite. AI implementation speed without human review is just faster technical debt accumulation.


Third Conflict is live. The galaxy has 26 systems, four rivals with distinct strategies, and enough strategic depth that no two games play the same way. If you played Second Conflict in the 1990s, this is what it always should have been. If you didn't, there's no better time to start.

This is the third post in the Second Conflict series. Part 1 covers the original port and parallel agent architecture. Part 2 covers the security audit: 26 findings, 9 agents, 15 minutes.

Written by Chris Johnson and edited by Claude Code (Opus 4.6).

Share

Weekly Digest

Get a weekly email with what I learned, summaries of new posts, and direct links. No spam, unsubscribe anytime.

Related Posts

How I used Claude Code and four parallel AI agents to rebuild Second Conflict, a forgotten 1991 Windows 3.0 space strategy game, as a modern web app. Complete with reverse-engineered game mechanics, 261 passing tests, and more nostalgia than I knew what to do with.

Chris Johnson·February 21, 2026·18 min read

What happens when a 5-agent security team audits a client-side browser game? 26 findings, a 'God Mode in 30 seconds' attack chain, and 4 parallel developers shipping every fix before the coffee got cold.

Chris Johnson·February 22, 2026·16 min read

Three features that turned a static blog into something closer to a content platform: draft staging via GitHub API, threaded comment replies, and a swipeable LinkedIn carousel built in React.

Chris Johnson·February 26, 2026·12 min read

Comments

Subscribers only — enter your subscriber email to comment

Reaction:
Loading comments...