TinkerIndustries

Turret

An Electron application that hosts Claude Code sessions, with modes.

Game mode runs your own built game as a child process and drives it a tick at a time. The game moves only when something asks it to, which is what makes a moving thing possible to debug.

The clock is frozen

Nothing advances the game but a command. Not a timer, not a frame callback, not the wall clock. Asking for twenty ticks runs your step function twenty times, and between one call and the next the game sits exactly where it was left, however long that takes.

The game’s window really does receive keyboard and mouse events from the operating system, and every one of them is thrown away. A game that read them would move under a hand nobody in the session can see.

What a project has to provide

The harness is three C++ header files you copy into the project. One holds the machinery that talks to Turret and takes over main. The other two are renderers, one for raylib and one for SDL3, and your code includes whichever of those two you picked. Pick raylib unless the game needs something raylib will not give it: it is one dependency and it is what the example is written against.

On top of that you need one C++ file defining the callbacks below, and a build that links the renderer and produces an executable. A C++17 compiler and one renderer is the whole of what the harness needs. It brings in nothing else.

Turret has no build tool of its own. You build the project through its own build system exactly as you would outside Turret, then load the binary that comes out. Load it again after every rebuild, because a running process holds its own copy in memory and never notices a new file on disk.

The five callbacks

CallbackWhat it does
buildSets the world up from a seed
stepAdvances exactly one tick, taking the input as an argument, and draws nothing
drawDraws the world for a person to look at, and changes nothing
drawSchematicDraws the same world for the model to read
describeWrites down what is in the world as numbers

Four more are optional: setting the world to a state, accepting a change to one of the project’s own knobs, saving and restoring the world as bytes only your game understands, and receiving a packet from another copy of the game. Leave one out and the game still runs. It just refuses that particular request, by name.

Three rules hold the whole thing up, and breaking one fails nothing at build time. Never read the clock or the operating system’s keyboard. Never draw from step. Never change the world from a draw. That last split is what lets a picture be taken at any tick, twice, for the same pixels, at no cost in ticks.

The description

The describe callback is what the model actually reads. One row per thing in the world, each carrying an id, a kind, a position and whatever else is worth saying. Turret adds the tick and collects the events itself.

Ids matter more than they look. Following a value across a run means naming it by id, and two copies of the same game are compared by id and position and by nothing else. Give the same thing the same id every tick and on every copy.

Events are how a game says why something changed rather than only that it did. A collision, a refused move, a phase change, a trigger firing. Emitting one costs about as much as a printf, and each is reported once and then cleared.

The schematic

The second drawing is for the model: bold outlines, arrows and names, drawn over the same world. The harness ships the drawing kit, so this is twenty lines rather than two hundred. Labels shove themselves clear of labels already placed, and refuse to draw below a legible size however far the camera is zoomed out.

When the picture does not show what is needed, the answer is a better schematic rather than a cleverer crop. Another outline, another label, another arrow, rebuilt and loaded. Overlays that would crowd a single picture become layers, declared once in the description and switched on and off from the pane or by the model, with no rebuild.

Ten thousand particles outlined one by one read as a grey wash. Draw the structure they came from instead: each emitter, its spawn volume, an arrow for its direction, labels carrying the counts, and the bounds the whole effect currently occupies.

Working on a situation

Read the world, write it back with two numbers changed, hold an input down for a named number of ticks, then read what happened. That is how you work on a situation instead of playing your way to one.

Every write runs a fidelity check. The world is described, that description is fed straight back in, and the two are compared field by field. A field the description writes and the write-back ignores comes back named. It is worth fixing, because a replayed checkpoint loses the same field every time.

Marking a tick hands back an id to return to later. A game that can save its own world as bytes gets an exact restore. A game that cannot gets the run rebuilt from its start and replayed up to the mark, which is as faithful as the description round trip is.

Several copies at once

The same binary can be loaded more than once from the same seed, and a step advances every copy together by the same number of ticks. The copies have no way to reach each other directly. Everything that passes between them goes through Turret, which holds the latency, loss and reordering dials and reports what is still in flight and when it is due.

Turret watches for the copies disagreeing and names the first tick they did, along with the thing responsible. It never tries to fix one. Deciding what to do about a desync is yours.

Visual effects

A project can declare itself an effect rather than a game and gets its own tab in the pane. Authoring one runs the same loop: change something, advance time, look.

What you change is parameters, which the project declares for itself and Turret draws as controls. Turning one never resets the run, so the tick before and after the change is the same. A step can also hand back one picture per tick across the whole run instead of one at the end, which is how you watch a change play out rather than only where it landed.

Give the backdrop a knob of its own and check the effect against all of them. Black reads an additive glow at full strength. White finds dark cores and fringing. Mid grey shows a colour’s true value. A checker shows what alpha is doing, and its squares are a size reference. Draw a scale reference behind the effect as well, a silhouette at the player’s height or a grid at a known pitch. A frame on its own says nothing about how big the effect is.

The pane

The picture, the tick it was taken at, the transport controls and the event log. Reset, step by a number you type, run at a rate, and switch between the normal view and the schematic. There is no zoom and no click-through. The picture is a photograph of a moment.

Beside it sits an Assets tab holding the art and sound the repository declares for itself. There is no server behind it and nothing to upload: Turret reads asset-library.yaml files out of the working directory, each row naming a glob and the tags every file it matches carries. The tags are your own words. Nothing in Turret knows what enemy means.

Starting out

A band across the top of a game session writes a setup guide into the repository: the three headers to copy, a complete buildable game in about 150 lines, a working CMake file, and the reference for every callback and every drawing call. The headers it hands out are the ones this Turret speaks, so the band comes back after an update when the copy in the repository has fallen behind.

When something is wrong

What you seeWhat it means
Loading refuses, and the game never connectsThe binary does not link the harness, or it crashed before it reached the socket. Run it directly and read the error.
The build says to include a renderer headerA source file includes the machinery header on its own. Include the raylib or the SDL3 one instead.
An unknown command, or fewer rows than ticksThe headers in the project are older than this Turret. Copy all three across and rebuild.
Nothing changes when the game is steppedThe game read the operating system’s keyboard instead of the input it was handed.
A layer toggle changes nothing on screenThe drawing it should hide goes through raw renderer calls, which sit under no layer.