← Development journal

/ 3 min read

Under the hood: from a test scene to the shape of a game

Nothing new to see in this one. Riding, the cinematic, the HUD and the dismount all look exactly as they did. Underneath, though, the project changed shape in two ways that a real game needs.

Ioma on foot, now moving in the rules library

The director came apart

Everything in the ride scene used to live in one actor of about 1,200 lines. It built the world, ran the camera, read the keyboard, moved the horse and Ioma, and drew the HUD. Each new feature made the next one harder to add. It's now the pieces a game is made of:

  • Stage: light, sky, fog, dunes, props, dust.
  • Mount: the horse's rig, gaits, riding model and sounds.
  • Hero: Ioma's seat, the dismount and mount, her clips on foot, her steps.
  • Camera: the cinematic frame, the release, the chase and on-foot cameras.
  • HUD, ambience (score, wind, birds) and capture (shots, frames, recordings).
  • A player controller and a game mode, like any Unreal game.

The director that's left is about 200 lines. It runs the shot's timeline and drives the pieces in a fixed order.

How we know nothing changed: before touching anything, four scripted runs of the scene were recorded as one line of state per frame (the cinematic, riding, getting down and walking, getting back on). After every step of the split, all four were replayed and compared, frame by frame. They never differed. tools/trace_check.sh keeps doing that job for future refactors.

The rules moved into a real library

The game's rules used to live in a C# library from the 2D prototype: screen-pixel units, drag-select group moves, placeholder combat. Nothing in Unreal used it. Rather than port it, we rebuilt the part worth keeping, which is the contract, in C++:

  • a fixed 20 Hz tick
  • seeded randomness
  • commands as the only way to change anything
  • replays that reproduce a session exactly
  • a hash of the whole state, so two runs can be compared bit for bit

sim/core is plain C++ with its own tests, and Unreal compiles the same sources into the game. The old library is kept at the tag csharp-sim-final.

Ioma on foot is its first unit. When she lands from the dismount she's spawned in the sim, the horse becomes an obstacle she walks around, and the keys become steering commands. Her walk and run speeds, acceleration and turning now live in data/units/ioma.json, and changes need no rebuild. That's how the first play-through's "a bit slow" became a one-line edit: walk 1.35 to 1.7 m/s, run 5 to 6 m/s.

Replays work across builds. A session played in the game can be written out (-LwReplayOut) and re-run by the standalone tests, and it reaches the same state hash. The game and the test build compute identical numbers. Every future bug can start as a replay that reproduces it.

Next

The horse's riding model moves into the sim the same way. After that come other units: companions, and the orders question we parked until the story is further along.

Lowwater is in development. These notes capture the game at the time of writing; plans and visuals may change.

More from the making of Lowwater.

Follow along ↗