Downwind
Seven sessions, one editor
It was Saturday, so for once the work could run all day without anybody having to go to a job in the middle of it.
By the evening there were seven sessions of the agent - Claude Code Opus 5.5 - waiting for the same copy of the Unreal Editor. One was building the town's week. One had the river animals. One had the hedgerow birds, one had an old angler and his fish, one had a cave and a pile of stones, one had the rubbish on the pavements, and one had a doorman. Only one of them can drive the editor at a time, and only one can press play, so they keep a queue. They message each other: who has it, who is next, how long they need. When one finishes it saves, closes nothing it did not open, and sends the next one a line that says go.
At about quarter past eight James reordered the queue. The birds went to the back. Later, through a different session, he said it plainly: what mattered most right now was new people and their routines, not the animals. The bird session saved what it had, wrote down where it had got to, and handed the editor on.
That is how a Saturday looks when one person runs the studio. Rockstar have a department for each of these. He had seven tabs and a queue.
Crisp packets
The brief for the litter was one paragraph. Small bits of rubbish should blow with the wind when the player is near them, at the speed and in the direction the wind is actually blowing. They should stop when they hit something - a kerb, a wall. Heavy things should stay put. Empty crisp packets should go easily, and a bottle lying on its side should roll if the wind catches it side-on.
The town already has a wind. The weather system carries a speed and a direction, and the golf and the snow and the flags already read it. So the litter did not need a wind of its own. It needed to know what each bit of rubbish was.
The agent counted first. There were 554 pieces of tagged litter loaded round the town. It went through every mesh and sorted them into things that fly, things that slide and things that roll, and found two surprises before writing any logic. Every empty can in the game had been modelled crushed flat - a cola can about 7.5 by 7.4 centimetres and under four deep - so none of them could roll. They became sliders: drink cans scrape along in a decent wind, steel food tins barely move at all. The other was a bread bag on the list whose mesh did not exist. It came off the list. Thirty-one small plastic bags turned up in the street rubbish without any profile, and went in as light flyers. In the end 339 of the 554 pieces had a reason to move.
All of it runs off one director in the town, a single object that wakes up the litter near the player and leaves the rest asleep. Nothing is simulated a street away.
The test was done twice. At 14 mph, at a cluster of rubbish near the rats, wrappers and trays and chip papers slid between 13 and 20 metres downwind in about 35 seconds. Three of them ended up in a heap together and then moved less than two centimetres in the next minute, which is what stopping against something looks like when it is measured. The upright bottles, the crushed tins and the broken glass did not move. Then the agent pushed the wind to 24 mph. Glass bottles lying on their sides rolled seven to ten metres. The crisp packets and the chocolate wrapper piled up against each other and stayed there.
Pushing the wind turned out to be its own small job. Asking the weather for a storm ramps the wind up the way real weather does, slowly, and forty seconds after the request it was still blowing 12 mph. For a test it was quicker to set the base wind speed directly and put it back afterwards, so that is what the agent did.
James watched it in play afterwards. He saw a bit of litter lift and skitter off down the street and said it looked good. That was the tuning signed off.
GTA 6 will have a bigger city than this. Here, a crisp packet is lighter than a tin of mackerel because somebody sat down and decided it was. That is what everything being interactive means in practice. It goes all the way down to the rubbish.
The function that was not there
One piece of the litter logic took longer than all the rest. The agent writes Blueprint graphs from a text description, and one function went in, compiled, and read back empty. No error. The call returned nothing and the function had no nodes in it.
Each line of it written on its own worked. Together, nothing. The difference was an IsValid check - a macro, which branches the graph - sitting in the middle of the function with more steps after it. With the check moved to be the last thing in the function, wrapped in a one-line helper, the whole thing wrote first time. It had cost the best part of an hour to find, and it looked from the outside exactly like a success.
The otter that was not invisible
Down at the river, the otter session had spent the morning failing to film an otter. Frame after frame came back with the river empty, or with half the picture black. The agent's working theory was that the otter was not rendering - a material problem, or the animal spawning somewhere wrong.
James looked at one of the frames and said the river was there. It was. The camera was not. Every shot of the invisible otter had been taken from a camera parked inside the hill of the riverbank, looking out through the ground.

With the camera moved out over the water, behind the animal and looking along its heading, the otter was there and always had been. The next frame showed the second problem straight away. It was swimming nearly half a metre too high, legs out, like it was standing on the river.

That one came from an assumption. A note in the project said the river is drawn about 20 centimetres above the height the engine reports for its surface, and the otter's swimming depth had been set to match. The agent put cubes in the water at the otter's holt and measured. At that spot the river is drawn almost exactly where the engine says it is. The 20 centimetres is true somewhere else on the river, not here. The note now says the offset varies, the otter sits 55 centimetres lower in the water, and the next shot is the one that proves it.
A salmon, and then everything else
The angler on the riverbank catches salmon, and James had not liked the look of them. They came out grey and flat, he said. The agent found two causes: the texture was too pale, and the sheen on the material was set high enough to wash the colour out. A new texture and a lower sheen fixed the salmon.
Then James asked for the trout to look better too. Then the perch. Then the fins, then the eyes, then the scales, then the mouths. Each one was a separate request and a separate pass, and the afternoon went fish by fish.
Two things came out of it worth keeping. The eyes had been painted as neat circles in the texture, but the texture is stretched over the fish's body in a way that turned each circle into an eye about seven centimetres wide and two tall. The agent rewrote the painter to work out, for each pixel, where on the actual fish it lands, and now the eyes are round. The other was worse because it was invisible. The texture generator was meant to give the same spots every time it ran. It used Python's built-in hash to seed its randomness, and Python salts that hash differently in every process, so every run painted a different fish. It now uses a checksum that does not change.

James had one more report from the riverbank. The angler's boots seemed to hover above the grass when he sat down. Then, before anyone had touched anything: perhaps it was just the lack of shadow. It was. In that test the virtual shadow maps drew no shadows at all - not from the man, not from the chair, not from the trees - so everything that touched the ground looked like it was floating. His feet were within two or three centimetres of the ground the whole time. Switching the virtual shadows off brought the shadows back and put his boots in them.

Sunday
The week was the job James cared about most that day. Until Saturday every person in the town lived the same day seven times. Now they do not. Each citizen has a schedule for each day of the week. People who work days stay home on Sunday. One of them is off on Monday as well. The barber works Tuesday to Saturday. The man who works at the church is there for the Sunday service. A citizen on a two-week rota works different days in each week of the fortnight.
The agent checked it the hard way. It walked the game clock across every day of a week, then on to day eight and day fourteen, asked the real routine code where each of 26 citizens should be at each point, and compared the answer with the schedule written down. That is 260 checks. None disagreed.
There was supposed to be more. The plan had a church visit on Sunday for the Christians in the town and the five daily prayers at the mosque for the Muslims. James paused both for now, and the switch for them is off.
The routine was right and one man still did not get to work. He set off on time and stuck about a metre and a bit inside his own front door. The door had stayed locked, because nothing told a front door that the person behind it lived there. Doors now carry a flag for a citizen's home, and his opens for him.
Under the stones
The last thing in before the night was in the cave. James had decided the week before that loose stones in the cave should lift with E, the key for picking things up, with no crouching, and that the reward should be animals and, rarely, a coin.
At four minutes to five it worked. E on a stone tipped it up on its far edge. Underneath, a damp patch and rosy woodlice, five of them, which scatter for the dark. E again put it back and they were gone. With something already in his hand, E set the held thing down first and then lifted the stone. A stone lifted twice in one day gives nothing the second time. Then 399 pieces of rubble went in along the wall feet, with 30 liftable stones among them and three coins under the deepest.
Placing the rubble hit one trap. The agent drops each piece onto the cave floor by tracing down to it, and on the first pass more than half of them missed. The editor keeps simplified copies of distant buildings for drawing them cheaply, and one of those copies was a second cave in exactly the same place, solid enough for a trace to hit. The rubble was landing on the copy. Ignoring it put the pieces on the real floor.
The woodlice were confirmed in the log. Walking the scatter and looking at it is next.
The fault
Street litter in Unreal Engine 5.8 needs to blow with the wind and stop against kerbs and walls
The ask: small litter blows with the live weather wind (speed and direction) near the player, stops on contact, heavy items stay put, empty crisp packets go easily, bottles on their side roll when the wind is side-on.
Found in the census before any logic was written:
- every empty can mesh is modelled crushed flat, so none can roll
- one profiled mesh did not exist
- 31 placed items had no profile at all
Testing trap: requesting storm weather ramps the wind slowly (still 12 mph 40 s after the request). Set the weather manager's base wind speed for a test and restore it.
The fix
One wind-reading director, a per-mesh profile, and only the litter near the player wakes
One director actor reads the existing weather manager's wind and drives only the litter near the player. Each mesh has a profile: flyer, slider or roller, with its own threshold. Crushed cans are sliders; steel tins effectively never move.
Measured in PIE: at 14 mph wrappers slid 13-20 m in about 35 s and three piled up and then moved under 2 cm in 60 s. At 24 mph bottles on their side rolled 7-10 m in 25 s. Upright bottles, tins and broken glass did not move at either speed.
The guard: every litter mesh was checked for a collision hull before anything was allowed to move, so nothing can blow through the world.
The fault
write_graph_dsl returns nothing and the Blueprint function is empty, with no error
A function body written as text compiled and read back with no nodes. Every statement wrote correctly on its own. The cause was an IsValid macro in the middle of the function body with statements after it. The macro branches the graph, and the whole body was silently refused.
The fix
Put the branching macro last
Move the IsValid check to be the final statement, or wrap it in a one-line helper function and call that. The guard: after any write, read the graph back and count nodes. A call that returns nothing is a failure until a readback says otherwise.
The fault
Animal invisible in a PIE capture, frame half black
A swimming otter did not appear in any capture for a morning, and several frames were black below a ragged horizon. The otter was rendering. The capture camera was inside the riverbank landscape.
The fix
Put the camera over the water, behind the animal, and check the frame before blaming the asset
Place the capture camera over open water, behind the animal along its heading, never on the line towards the near bank. A frame with a hard black lower half is a camera inside geometry. Then measure the waterline locally with cubes: the drawn river surface sat level with the engine's reported surface at this spot, not 20 cm above it as a note from another stretch said, so the otter's swim depth was lowered from -8 to -55.
The fault
Procedurally generated texture changes every time the script runs
A fish texture generator meant to be reproducible painted different spots each run. It seeded its randomness from Python's built-in hash(), which is salted per process.
The fix
Seed from a stable checksum
Seed from zlib.crc32 of the name instead of hash(). Regenerate once and confirm two runs produce identical files.
The fault
Character looks like it floats above the ground in PIE, Unreal Engine 5.8
A seated NPC's boots appeared to hover. Collision put his feet within about 2-3 cm of the ground. In that PIE session virtual shadow maps drew no shadows anywhere: not from him, the chair or the trees.
The fix
Confirm shadows render before judging ground contact
Look for a tree's shadow first. For a still, r.Shadow.Virtual.Enable 0 with sg.ShadowQuality 2 brought the shadows back. Restore both afterwards. Whether the packaged game loses them too is a separate playtest question.
The fault
Editor traces hit an invisible duplicate of the level geometry
Rubble dropped onto a cave floor by downward traces grounded only 202 of 479 pieces. The editor's HLOD instancing actors held a co-located copy of the cave mesh with query collision, and the traces hit the copy.
The fix
Ignore HLOD actors in editor placement traces
Exclude the HLOD instancing actors from any editor-time trace used for placement. A fresh PIE load is not affected.
The kit
| Tool | Why this one |
|---|---|
| Unreal Engine 5.8 | the game, the weather and every PIE test |
| Claude Code Opus 5.5 | seven sessions sharing one editor by queue |
| unreal-mcp and Monolith | writing Blueprint graphs and driving PIE from outside the editor |
| Python in the editor | the litter census, the week check and the rubble placement |
| Engine cubes | a waterline measurement you can see |
Plain English
| Term | What it means |
|---|---|
| PIE | "Play In Editor" - pressing play inside the editor to test the game without building it first. Fast, and it behaves subtly differently from the real build. |
| Blueprint | Unreal's visual scripting. Logic built by wiring boxes together with lines instead of typing code. This whole game is built in it. |
| DSL | A compact text format for writing Blueprint logic, so an agent can author graphs as text instead of dragging boxes with a mouse. |
| macro | A reusable bundle of Blueprint nodes that is pasted into the graph wherever it is used, rather than called like a function; some can branch the flow of a graph in ways an ordinary node cannot. |
| collision hull | The invisible simplified shape used for bumping into things. It is not the shape you can see, which is why invisible walls happen. |
| trace | Firing an invisible line into the world to find out what it hits - used for "what is under this object" and "what is the player looking at". |
| landscape | Unreal's ground system - the sculpted, painted terrain the town sits on. |
| HLOD | The same idea applied to whole groups of buildings at once, so a distant part of town is drawn as one cheap lump. |
| virtual shadow maps | Unreal Engine 5's default high-detail shadow system; it can be switched off for a test, which is the quickest way to find out whether a missing shadow is a lighting problem or a placement one. |
| random seed | The starting number a random generator works from; the same seed always gives the same "random" result, so a generator whose seed changes between runs gives a different answer every time it is asked. |
| texture | An image wrapped onto a surface to give it colour and detail. |
| material | The recipe describing how a surface looks: colour, roughness, shininess, transparency. |
| handover | A running notes file for one strand of the work, written so the next session, human or AI, can pick it up cold and know what is done, what is not and what will bite. |
Things we still need to fix tomorrow:
- The otter's new swim depth is in. Re-shooting it at all three holts is next.
- The hedgerow birds' escape loop - every bird within 45 m flushing again before it had settled - has a fix built and saved. Proving it in PIE is the bird session's next slot, then perching on fences and rooftops.
- The salmon's dorsal fin sits a bit close to the angler's chin. His grip moves forward next.
- Church on Sunday and prayers at the mosque are paused by choice. The switch is in the week file when they come back.
- Walking the cave scatter and looking at the stones is next, along with the springtails that jump when a stone lifts.
- Whether the packaged game loses its shadows the way PIE did is the next playtest question.