[Peryhelium] Moving a 4X Game Domain From AoNW to Unity

Written by

in

While waiting for Age of New Worlds to move through the publishing path for iOS, Steam, and Android, I tried a small experiment.

This time in Unity.

Not as a replacement for AoNW. More as a question:

What happens if I take some of the mechanics and architecture from AoNW, move them into a 3D engine, and let the concept grow in a different direction?

A few hours later, I had the first slice of Peryhelium: a space 4X prototype about colonizing the Solar System, starting with the Moon, previewing locked planets, and rendering spherical hex maps with real planetary textures.

Some of the assets came from NASA sources, including lunar data and planetary references. Some runtime textures came from other high-resolution sources where NASA’s available files were too small for close-up gameplay.

The interesting part was not that Unity could render planets.

Of course it could.

The interesting part was how little the domain architecture cared that the renderer had changed.

The Renderer Changed. The Game Shape Did Not.

AoNW is a Flutter and Flame game.

Peryhelium is a Unity game.

That sounds like a big move. And visually, it is. The old project is a 2D hex-based 4X world. The new prototype is a 3D Solar System with orbit cameras, planet textures, spherical surfaces, and mesh-based hexes.

But under the renderer, the same idea still works:

flowchart TB
    subgraph AoNW["Age of New Worlds"]
        Flutter["Flutter UI"]
        Flame["Flame Renderer"]
        Core["aonw_core"]
    end

    subgraph Peryhelium["Peryhelium"]
        Unity["Unity Presentation"]
        App["Application Layer"]
        Domain["Pure C# Domain"]
    end

    Flutter --> Core
    Flame --> Core

    Unity --> App
    App --> Domain

    Core -. "architectural lessons" .-> Domain

The domain does not need to know whether the player clicked a Flutter widget, a Flame component, or a Unity collider.

It needs to know that the player intended something.

That intent becomes a command.

Commands Still Carry The Game

In AoNW, I already had a habit of not letting UI code mutate the world directly.

That habit transferred almost immediately.

In Peryhelium, Unity sends commands like:

new EnterSurfaceCommand(commandId, playerId, "moon");
new PreviewSurfaceCommand(commandId, playerId, "mars");
new FoundColonyCommand(commandId, playerId, "moon", cellIndex);

The reducer decides what is valid.

A locked Mars can be previewed.

A locked Mars cannot have selected hexes.

A locked Mars cannot receive a colony.

That is not a button rule.

That is a domain rule.

sequenceDiagram
    participant Unity as Unity UI
    participant Transport as Command Transport
    participant Reducer as GameStateReducer
    participant Store as Event Store
    participant ReadModel as Read Model

    Unity->>Transport: GameCommand
    Transport->>Reducer: Reduce(state, command)
    Reducer-->>Transport: GameEvent[]
    Transport->>Store: Append event envelope
    Transport-->>Unity: New state
    Unity->>ReadModel: Project state
    ReadModel-->>Unity: Renderable model

This is the part that made the experiment feel fast.

The renderer was new.

The architecture was not.

Unity Is An Adapter

It is tempting in Unity to put gameplay logic inside MonoBehaviours.

That is often fine for small games.

But a 4X game grows teeth very quickly. There are unlock rules, research gates, surface maps, colonies, future multiplayer, AI, persistence, and replay.

So Peryhelium treats Unity as an adapter.

Unity owns:

  • cameras,
  • lights,
  • meshes,
  • materials,
  • HUD,
  • raycasts,
  • labels,
  • animation.

The domain owns:

  • celestial bodies,
  • unlock state,
  • commands,
  • events,
  • surface maps,
  • colony rules,
  • grid frequency,
  • reducer behavior.

The split looks like this:

flowchart LR
    Input["Unity Input / Raycast"] --> Command["GameCommand"]
    Command --> Reducer["Pure Reducer"]
    Reducer --> Events["GameEvent[]"]
    Events --> State["PeryheliumState"]
    State --> Projection["Read Models"]
    Projection --> Render["Unity Rendering"]

This means the same rule can later run locally, on a server, or inside tests.

That was one of the strongest lessons from AoNW multiplayer work:

The client can feel responsive.

But it should not invent reality.

From Flat Hexes To Planet Hexes

The biggest conceptual change was the map.

AoNW uses a flat hex world.

Peryhelium needs planetary surfaces.

A sphere cannot be made entirely of perfect hexagons. You need 12 pentagons, just like a geodesic sphere.

So the prototype generates geodesic surface maps: mostly hexes, with 12 pentagons, projected onto a 3D sphere.

The number of cells scales with the real size of the body, but it is capped for performance.

The Moon stays smaller.

Mars gets more cells.

Earth and Venus get denser maps.

Jupiter and Saturn are large, but they do not create absurd prototype-breaking grids.

flowchart TB
    Radius["Planet radius in km"] --> Frequency["Grid frequency"]
    Frequency --> Geodesic["Geodesic sphere generator"]
    Geodesic --> Cells["Surface cells"]
    Cells --> ReadModel["SurfaceReadModel"]
    ReadModel --> UnityMeshes["Unity cell meshes"]

This is a good example of where the domain and renderer meet.

The domain decides the surface topology.

Unity decides how it looks.

Textures Changed The Feeling Quickly

The first prototype with plain colored spheres was useful.

But it did not feel like the Solar System yet.

Adding real textures changed that very quickly.

The Moon uses NASA LRO/LOLA-based assets. Mercury uses NASA/MESSENGER data. Other planets use high-resolution runtime textures where the available NASA 3D resources were too low resolution for close-up gameplay.

That matters because Peryhelium is not only a rules prototype.

It is a scale prototype.

The player should feel that the Moon is a place. Mars should feel locked but visible. Saturn should not just be a beige ball with a line around it.

The visual layer is not the domain.

But it changes how seriously the domain feels.

The First Slice Took Only A Few Hours

That is the part I keep thinking about.

A few hours was enough to get:

  • a Solar System board,
  • orbiting and spinning planets,
  • NASA-based Moon textures,
  • high-resolution planet textures,
  • a textured Saturn ring,
  • an unlocked Moon,
  • locked planet previews,
  • spherical hex maps,
  • subtle low-opacity hex overlays,
  • colony founding on unlocked surfaces,
  • domain tests for preview and unlock rules.

Not because all of that is finished.

It is not.

But because the project did not start from chaos.

It started from a known shape.

Tests Made It Safe To Move Fast

The fastest way to make a prototype fragile is to put every rule in the scene.

The fastest way to keep it flexible is to test the rules outside the scene.

For example, the locked planet preview rule does not need a Unity screenshot test.

It can be a reducer test:

start match
preview Mars
assert Mars surface exists
try selecting a hex
expect no event
try founding a colony
expect no event
unlock Mars
expect preview mode to become normal surface mode

That test says more about the game than a screenshot would.

Rendering still needs visual checks.

But rules need rule tests.

This is another thing AoNW made natural.

What Actually Transfers Between Games

The nouns do not transfer directly.

AoNW has cities, units, workers, production queues, fog of war, terrain yields, diplomacy, and turn processing.

Peryhelium has planets, moons, orbital progression, spherical surfaces, colony sites, planetary unlocks, and resource infrastructure that does not exist yet.

But the grammar transfers.

flowchart TB
    Intent["Player intent"] --> Command["Command"]
    Command --> Decision["Reducer decision"]
    Decision --> Event["Domain event"]
    Event --> State["State transition"]
    State --> Projection["Read model"]
    Projection --> Renderer["Renderer"]

That grammar is the reusable asset.

Not the UI.

Not the engine.

Not even the exact gameplay objects.

The reusable part is the contract between intent, rules, history, and presentation.

Why This Matters

Peryhelium is already a different game from AoNW.

It should be.

AoNW is about civilizations growing across a world.

Peryhelium is about humanity learning to occupy the Solar System, one hostile body at a time.

But it can still inherit architectural discipline.

That is what made the experiment fun.

Unity gave me scale, lighting, textures, cameras, and 3D presence.

AoNW gave me the confidence to keep the gameplay rules somewhere clean.

The result is still early.

Very early.

But it already has the thing I care about most in a strategy game codebase:

The renderer can change.

The rules stay honest.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *