Mr. Phil Games’ Blog

Posts for Tag: Async Programming

Stellar Throne Devlog #8: Validation, Parity, and Precision

Monday was a critical day of validation and bug hunting for Stellar Throne, my 4X strategy game built with Godot for the UI client and Zig for the high-performance simulation engine. After enabling the Zig backend in production over the weekend, my goal was to make absolutely sure both engines stayed perfectly in sync.


Dual-Engine Validation

The big picture: the Zig simulation now runs about fifty-two times faster than the original Godot implementation. But speed means nothing if the results aren’t identical. Monday’s focus was validation, testing, and fixing any divergence between the two engines.

I started the day tracking down a fleet position serialization bug. When fleets moved between star systems, their positions were stored correctly in Zig, but once the data returned to Godot for rendering, the interpolation broke — fleets would jump around the screen instead of moving smoothly along their paths.

The culprit was the serialization layer. Zig and Godot were using different coordinate formats. I standardized the data structure and updated the movement interpolation code to properly handle the incoming data. With that fix, fleet movement became smooth and visually accurate again.


Building Systematic Validation

Ad-hoc bug fixes weren’t enough; I needed systematic validation. I built a comprehensive serialization parity test framework to catch any mismatch before it caused a production bug.

The new test infrastructure compares both engines after every operation. It serializes game state from Godot to JSON, deserializes it in Zig, runs a simulation step, then serializes Zig’s results back to JSON and compares every field — ensuring no corruption or drift between engines.

This framework immediately revealed several subtle issues:

  • Inconsistent resource formatting across empires

  • Rounding errors in planet population data due to floating-point differences

  • Missing serialized fields in certain edge cases

I spent the rest of the day tightening the serialization code. Every numeric field now includes explicit precision validation. Optional fields have null checks. Missing legacy data gets default values. The result is a much more defensive and resilient serialization layer.


Achieving Population Growth Parity

Once the parity tests stabilized, a new challenge surfaced: population growth parity. The tests passed for most systems, but population numbers were diverging slightly between engines.

The root cause turned out to be integer math in Zig where floating-point calculations were required. After switching the intermediate values to floating-point, both engines produced identical results down to the last decimal place — restoring confidence in one of the game’s most important mechanics.


Fixing Test Infrastructure Race Conditions

The deeper I tested, the more subtle issues emerged. The asynchronous turn-processing code in the test framework was causing race conditions: some tests were checking results before a turn had actually finished.

I fixed this by adding proper sequencing and awaits. Each turn now fully completes before validation begins, eliminating false failures and making the test results rock-solid.


Floating-Point Precision in Max Population Calculations

Another precision issue appeared in maximum population calculations. Planets calculate their population limits from habitability scores. Godot used single-precision floats, while Zig used double-precision. Over long playthroughs, these tiny discrepancies added up.

I standardized both engines to use double-precision floating-point for all population math. Now, population caps match exactly — no more subtle drift.


Debugging Star Rendering

The day ended with a star rendering issue. After running turns through the Zig simulation, a few stars failed to render correctly on the galaxy map. The investigation pointed to a coordinate transformation problem in the rendering pipeline. This one’s still in progress, but it’s the next problem on my list.


Technical Breakdown

  • Ten commits completed

  • Three hundred twenty-seven lines added for the serialization parity framework

  • Eighty-one lines changed for population growth fixes across four files

  • Six functions updated to standardize floating-point precision

  • Fifty-three lines added to improve asynchronous test sequencing

The parity suite now runs fifteen validation scenarios, covering empire resources, fleet positions, planet populations, star system states, and cross-turn consistency. Every test executes both engines side by side and compares outputs field by field.


Current Status

The Zig backend is now fully running in production with comprehensive parity validation.
✔ Population growth matches exactly
✔ Serialization round-trips are verified
✔ Fleet movement is smooth and accurate
⚙️ Star rendering issue still under investigation

This validation work might not be flashy, but it’s essential. The dual-engine architecture only works if both engines produce identical results. These parity tests prove that the fifty-two-times performance gain isn’t coming at the cost of correctness.


Next Steps

  • Finish debugging the star rendering issue

  • Add new parity tests for edge cases like empire bankruptcy and fleet combat

  • Begin profiling the Zig simulation to find remaining performance bottlenecks

  • Explore new simulation features that take advantage of the speed boost


Thanks for following along with the development of Stellar Throne.

You can find more updates and technical breakdowns at MrPhilGames.com.

🚀 Stellar Throne Devlog #7: The Zig Simulation Goes Live

Hey everyone, Mister Phil here with another devlog update!

Today marks a major milestone for Stellar Throne, my sci-fi 4X strategy game. For those just joining: Stellar Throne runs on a dual-engine architecture, using Godot for the UI client and Zig for the high-performance simulation backend.

Yesterday’s work was all about one huge goal — finally enabling the Zig simulation backend in production and fixing the critical serialization bugs that had been blocking deployment for days.


🧩 The Context: A Fully Functional Engine That Couldn’t Run

At the start of the day, I had a complete Zig simulation engine — all tests passing, a 52× performance improvement validated, and everything ready to go.

Except… I couldn’t actually use it.

The bridge between Godot and Zig — serialization — was broken. The game state couldn’t properly flow between the two, leaving the new engine stranded behind a wall of data mismatches.


⚙️ Fixing the Turn Processing Hang

The first issue was a classic async race condition. The game was using await to wait for a “turn completed” signal, but the listener was being connected after the signal was emitted.

Result: the code waited forever for an event that had already happened.

Fix: I stored the signal reference before calling the turn coordination function. That ensured the listener was ready before any signals could fire. I also added yield calls in the turn sequencer to give the engine a frame to process signals between phases.

✅ Result: Turn processing is now stable and reliable.


🔄 The Real Challenge: Serialization Across Engines

The real battle was with serialization mismatches.

Godot properties used snake_case (star_system_idfleet_name), while Zig structs used camelCase (starSystemIdfleetName). Every deserialization failed because of these naming conflicts.

I added manual field mapping in Zig — tedious, error-prone, and partial. Empires loaded correctly, but fleets, planets, and star systems still broke.

The breakthrough.
I implemented bidirectional camelCase serialization across all four major data structures:

  • SimEmpire

  • SimFleet

  • SimPlanet

  • SimStarSystem

Each gained custom toJSON() and fromJSON() methods that output camelCase and accept both camelCase and snake_case.

Struct Lines Added
SimEmpire +210
SimFleet +274
SimPlanet +44
SimStarSystem +131

All test fixtures were also updated to camelCase, covering five scenarios:

  • Edge Case Deficit

  • Edge Case Empty Empire

  • Fleet Upkeep

  • Single Turn Minimal

  • Ten Turn Economy


🧠 Type System Fixes & Data Integrity

Once serialization worked, I enabled the Zig backend in production. Immediately, Godot’s type system rebelled.

Files like Building.gdEmpire.gd, and Fleet.gd had overly strict type annotations that didn’t match the Zig output. I went through each file and refined type declarations to handle the new data correctly.

Then came the fleet disappearance bug — fleets vanished after turn processing. Turns out:

  1. The location field was structured incorrectly.

  2. fleet_name sometimes wasn’t serialized at all.

Both fixed.

And finally, empire colors were lost during serialization. The culprit?
Godot’s Color type can appear as RGB, RGBA, hex, or even name strings. Zig wasn’t handling that variety.

Solution: standardize on RGBA objects — {r, g, b, a} — for full round-trip fidelity between engines.


📈 The Numbers

Category Changes
Zig serialization +659 lines
GDScript type fixes 6 files, +41 lines
Fleet & color bug fixes +89 lines across 5 files
Test framework +150 lines new assertions
Test fixtures +59 lines camelCase JSON

8 commits in total.
The Zig backend is now fully active in production.
Game state round-trips cleanly. Colors, fleets, and names all persist. Integration tests are green across the board.


⚡ The Results

The game now runs 52× faster with the Zig simulation engine enabled by default.
Large galaxies that once took seconds now resolve in milliseconds. The performance leap is live — and dramatic.


🧭 Next Steps

  • Monitor gameplay for any hidden serialization edge cases

  • Expand integration test coverage

  • Begin profiling the Zig engine for further optimization


That’s the devlog update!
Thanks for following along as Stellar Throne crosses another huge milestone.

👉 Visit mrphilgames.com for more updates, devlogs, and behind-the-scenes deep dives.