Devlog 00: it's all cubes
First devlog for the game I'm building under the working name Project SCRAM. Most of the machine is built and it still looks like a box of cubes, but it's my box of cubes.
I spent an embarrassing amount of last week watching a cube look for me. It had seen me, I ducked behind a wall, and instead of forgetting I existed it walked to where I’d been standing and started sweeping the room. When it gave up and went back to its patrol, I felt like I’d gotten away with something. Then I noticed I’d been grinning at a grey box for a solid minute, which is more or less the state of the project.

the cubes are lazy on purpose
Everything the AI does happens on the host. I made that call early for an unglamorous reason. I did not want to chase a desync bug across two machines every time a guard did something strange. One brain, one place to put the breakpoints.
A single line-of-sight ray is cheap. A whole room of agents re-scanning for targets, re-checking what they can see, and re-planning where to walk, all on the same frame at sixty frames a second, is not. The expensive part is the deciding, not the ray. So the deciding runs on a timer, and how often is set by how alert the agent is.
// how often an agent re-evaluates, projected from its state machine
public static AiCadenceProjection Project(AiFsmState state) => state switch
{
Idle or Patrol => new(Calm, PeriodMs: 250),
Suspicious or Alert or Search => new(Hunt, PeriodMs: 150),
Engaged or Withdraw => new(Combat, PeriodMs: 100),
};
Patrolling, an agent re-evaluates four times a second. Hunting, closer to seven. In a fight, ten. There’s also a hard cap on how many of those rays the whole AI system will spend in a single frame, so a chaotic moment can’t spike the simulation. It started as a way to spread the work out, calm enemies cost almost nothing, but it turned out to sell the fiction too. A guard wandering a quiet corridor should be a beat slow on the uptake, and the one mid-search up there should not be. The cadence ends up doubling as personality.
only a few shoot at once
Once a room full of guards can all see you, what stops them from opening fire at once and erasing you in a frame? In a lot of games the answer is nothing, and it feels cheap. So there’s a small token pool. An enemy has to claim a fire slot before it’s allowed to shoot, holds it while it fights, and hands it back when it loses you or goes down. Everyone without a slot keeps moving, flanking, taking cover. They just aren’t all shooting at you at the same time.
The number of slots scales with the size of your crew, so a solo run isn’t secretly the same fight as a full team.
// how many enemies may be actively shooting at once, scaled to crew size
public static int ComputeCapacity(int playerCount) =>
BaseCapacity + PerPlayerCapacity * playerCount; // currently 2 + 2 per player
Those numbers are placeholder, real balancing is a later job, but the shape is the point. Pressure that grows with the party instead of a wall of simultaneous gunfire.
host decides, everyone else asks
The netcode follows from the same instinct. It’s co-op, not competitive, so I don’t need rollback or to defend against a cheating client. The host runs the world; clients send it intent (fire, reload, move) and trust the answer that comes back. I test it the way you’d expect: two copies open on the same machine, trying to get them to disagree with each other, which they still manage from time to time, loudly.
the inventory detour
The gear loop took a detour. I built the spatial inventory first, the kind where every gun is a rectangle you rotate to make it fit in the grid. It looked fantastic. Then I lived with it for an evening and realized I’d started making an inventory-management game by accident. So I pulled the grid back out. Items now cost cells against a budget, a rifle is simply bigger than a medkit, and nobody spends the back half of a firefight playing backpack Tetris. I kept the look I wanted and dropped the busywork I didn’t.

one seed, one ship
The thing I’m quietly proud of is that a whole level is one number. A contract carries a seed, every player rebuilds the same ship from it on their own machine, and the layout never crosses the network.
Getting there had a gotcha I learned the irritating way. If every system draws from the same random stream they’re all entangled, so adding a single dice roll to the loot logic shoves every wall after it somewhere new, because everything downstream shifted by one. The fix is to fan the seed out into separate streams that don’t touch each other.
// one mission seed -> three decorrelated streams, identical on every peer
_layout = new MissionRng(MissionRng.DeriveChannelState(seed, channel: 0));
_loot = new MissionRng(MissionRng.DeriveChannelState(seed, channel: 1));
_aiSpawn = new MissionRng(MissionRng.DeriveChannelState(seed, channel: 2));
Now I can mess with loot without rearranging the architecture, and two people boarding the same derelict see the same corridors because the math agreed, not because I shipped them a map.
next
Mission content. Objective types and the contract board, so a run is a job you chose instead of a sandbox.
// end of transmission · return to feed