skip to content

$ cd ../projects

pixity

live

Voxel city builder played in real time, one game day per real day. A save that would not fit forced the whole persistence design.

C++ · Vulkan · Three.js · Python · AWS · Terraform · Jenkins

Live at pixity.cmd-lopez.com. A full save would have been about 750 KB against a 300 KB cap, which is the constraint that shaped the persistence design and most of what is interesting below it. 978 tests across three runners, and a simulation package with zero runtime dependencies that can play the entire game headlessly in Node.

Pixity is a voxel city builder on an island, played in real time. One game day is one real day. There is no pause and no fast forward, so a run is a month long relationship rather than a session. You inherit a city from a fictional uncle, Hollis Vane, with a 2,000 loan and thirty days to make the books worth 105,000. Miss either condition and the charter reverts, the city dissolves, and you start again on a new map keeping only cosmetic unlocks and a line on a public board.

The city keeps running while you are gone. Income, upkeep, interest, construction and occupancy all continue, and events wait for you. Rent is capped at one day per home, so checking in more often is worth exactly the rent you would have overflowed plus the events you would have missed. That cap is the whole reason the game does not punish you for having a life.

What is actually simulated, rather than implied by a number going up: residents commute along roads to the nearest open job, and a road tile carries a fixed number of trips before it chokes. Five materials are priced against real commodity proxies, fetched live. Land is priced by its surroundings. Happiness is a city wide multiplier bought with local decoration. Demand swings across four sectors, and the economy cycles between boom and recession. Population milestones unlock higher building tiers and one off upgrades.

The save is the part I would show first. Serializing the city outright came to roughly 750 KB against a 300 KB ceiling, so the save is a seed plus per tile deltas and the world is regenerated from it rather than stored. Cloud writes are conditional on a version counter, so a second device conflicts instead of silently clobbering the first. The server independently flags implausible saves rather than trusting what the client sends it.

The architectural decision I would defend hardest is that the rules live apart from the renderer. The simulation is its own package, 35 modules with zero runtime dependencies, and the whole game runs headlessly in Node. That is what made a strategy handbook, a policy search and the balance passes possible at all. Balancing an economy by playing it by hand is not balancing. The import graph is kept acyclic and checked.

The client is Three.js for voxel rendering built with Vite, 69 modules of plain ES modules with no framework and no state library. Audio is synthesized in WebAudio, so no audio files ship. The backend is five Python Lambdas using the standard library only at runtime, behind API Gateway, over three DynamoDB tables, covering cloud saves, the public leaderboard, the market endpoint, the scheduled market fetch and a sign up hook. Authentication is Cognito. Delivery is S3 behind CloudFront. All of the infrastructure is Terraform, one root with modules, split across development and production workspaces.

Testing is 345 simulation tests, 448 client tests and 185 Python tests. GitHub Actions runs every suite credential free on each branch, and a Jenkins pipeline owns applies and the production approval gate.

This is the second version. The original was written from scratch in C++ and Vulkan with an ECS architecture, procedural city and road generation, and a save system with offline progress. Moving it to the browser cost me the renderer I had written and bought an audience that does not have to compile anything to play it.