Edge-On
Six quid a car
It was Saturday, so the gym first and then the whole day on the game, and the whole day was needed. By the end of it the agent - Claude Code Opus 5.5 - had spent most of the day making a jet of water visible, and James had spent fifty minutes playing the game at frame rates a slideshow would be embarrassed by. Both of those were progress. Neither looked like it at the time.
The morning was the autograss. Second Chance has a raceway now, out past the edge of town: the Radworth & District club, eight buggies, an oval cut into a field, heats that run on a loop whether anybody is watching or not. Autograss is about as British as motorsport gets - a farmer's field, a tea hut, a car you built in a garage - and it is not the kind of thing a game set in somewhere pretending to be Los Angeles thinks to put in.

The first job was a check of the whole meeting: a heat, a cool-down lap, the cars queuing and driving into their bays. It worked, except the screen in the infield, which was idle, then black, then nearly black, for three separate reasons. James said the screenshots looked good. They moved on.
At about quarter past ten he said: start the jet-wash job.
The design the agent built is simple and it is the kind of thing this game is for. Every three heats the cars park in their bays and the doors come down. If the player is within 150 metres of the garage, the doors roll back up instead and a line appears: wash the race cars, six pounds a car. The lance hangs on a hook by the washer. E picks it up, U sprays, and a car that has been sprayed all over pays out. Walk away and the doors come down, the lance goes back on its hook, and the racing carries on. Nobody sends you a text about it. Nobody explains it in a cut scene. It is there if you walk past.
By five past eleven it worked end to end. The agent took the lance, sprayed one car zone by zone, got the line that car is clean, watched the player's money go from nothing to £6, walked 200 metres off and saw the doors come down. It reported every step of that, accurately, with a screenshot of each one.
The jet in those screenshots was a few grey dots.
Nothing it spawned would draw
The rest of the afternoon went on that jet, and for most of it the agent was looking in the wrong place with complete confidence.
The first pass found something real. The water was a duplicate of the spray from the cellar watering can, and its droplets were under half a centimetre across. The agent made them bigger, built a proper refracting water material for them at the request of James, who wanted it white and full of air like real pressure-washer spray, and added a mist. Still nothing. It then said the jet was firing due east whatever the lance pointed at. Plausible. Still nothing. It was a long afternoon.
By two o'clock the agent's position had hardened. It reported that in a running game nothing it spawned would draw - not the jet, not the splash, and not even the stock fountain template that ships with Unreal, placed three metres in front of the camera. James asked whether it was the scalability settings. The agent tested three of them and said no. It then attached four effects to the camera, saw only the cigarette smoke draw, and wrote down a verdict: the whole family of water-stream effects in the project was broken.
It was not, and another session said so. This is how the project runs on a big day now - several agents, one editor, a queue, and a message to the next session when you are done with it. The session that had built the cellar spray pointed out that its effects draw nothing at their default settings by design, because the game sets their colour and speed when they are used. At three o'clock it went further and checked the agent's screenshots for it. Several of the frames the agent had been reading as the running game were the editor's own viewport, with the little axis gizmo in the corner to prove it, and the file numbering had drifted so that the newest file was not always the one the agent had just asked for. Water did draw at the garage. Only the jet did not.
It was the instruments, again. Rockstar have a department that checks the instruments. He had a Saturday, and a second agent.

The answer came out of a test on a residential street, four jets side by side at four different speeds. Pointed down across the road, all four drew. Pointed straight ahead, along the line of sight, none of them did.
The droplets were velocity-aligned sprites - flat pictures turned to point along the way they fly, so a fast drop reads as a streak. Look at one from the side and you see the streak. Look at it from directly behind and it is edge-on: a line with no width. A jet that fires exactly where the player is looking is a jet of nothing but edges. There had been a second fault under the first, too. Every droplet size had been set by passing two numbers as 4,22, and the engine had stored that as text it could not read. Almost certainly the droplets had been size zero all day.
The fix was a fresh copy of the working watering-can spray, with the droplets turned to face the camera and the sizes written the way the engine wants them. James was shown three versions side by side and picked the dense, slow one on the left. At ten to six there was water coming out of the lance.

Weather decides
James looked at it for about a minute and gave three notes. The spray went in random directions, not forward out of the lance. The stream looked weak. And the car had not looked muddy to begin with.
The first one was the agent's earlier guess come back the other way round. The jet's direction was being read in the lance's own local space, not the world's, which is also why the street test had appeared to fire towards the camera when it was turned round. One node converting the aim into the lance's space fixed it, and the stream got bigger and heavier while they were in there.
The third note turned into a design decision. The cars only get dirty by racing - dusty in the dry, muddy after rain - and the mud values were not editable from outside, so the agent could not just paint a car filthy for the test. It offered to add a floor of permanent mud. James said no. Weather decides. The agent added a debug switch instead, for testing only, that nothing in the game calls.
At quarter past ten that night, with the switch thrown, the filthy test car took about 45 seconds of slow spraying to go from mud to that car is clean, six pounds. James said that looks good, and the wash rate stayed where it was. The splash where the jet hits still throws up a tall fountain rather than fanning out along the panel. He did not object. It is on the list.

After midnight the same session put a hire car on the south straight for the player to drive round for a lap time. That is a different entry.
Fifty minutes at nine frames a second
The evening was supposed to be the easy half.
At seven, with the editor closed, a batch job capped 119 textures and gave 43 meshes proper level-of-detail chains or Nanite. The capped textures went from 930 MB to 206 MB, the MetaHuman body bakes from 1,557 MB to 389 MB. Two signs came out worse - a cap left them at a height the compressor would not take, so they fell back to uncompressed - and were put back.
Then James played it. The game on its own, editor shut, the way a player would run it - about fifty minutes, from the snooker hall to the Sweatbox club, the lodge car park, the river in the snow, the caves, the bus in a thunderstorm and a country lane. The agent recorded the frame rate at each stop and took three full profiler traces.

Standing still in the club: 10.4 frames a second. The lodge car park: 8.6. The river, snowing: 5.7. The caves ran at about 30, and James said it ran well in the caves, which was true and was also the only place on the map that was true. One of the lines this game argues against GTA 6 is a plain one: above 30 fps on one 8 GB card, by optimising all the way through rather than at the end. On Saturday night it was a long way off, and the profiler put the time on the game thread rather than on the graphics card's own work - 96 ms a frame in the club, 176 at the river.
The biggest single line in the club trace was the snooker table, at 40.7 ms a frame. Two NPCs play each other in the hall, with real ball physics, and that match was running at full precision while James stood in a nightclub in a different building. On the country lane it cost 43.2.
This was supposed to be impossible. On 21 September the agent had built a coarse mode for exactly this: when nobody is watching, a shot is resolved in one step instead of simulated. It had been built, compiled and read back. The game log from James's run said 48 shots finished in fifty minutes and the coarse mode engaged for none of them.
There were two reasons, and neither of them errored. One condition checked that no walk was in progress by testing a switch that turns the whole walking feature on, which is always true, so the gate could never open. The other asked whether the table had been rendered recently, which sounds like a fair test for is anyone looking. The table is Nanite. For Nanite the engine does the hiding behind other things on the graphics card, so the answer the code gets back is only is it inside the camera's cone. The agent proved it in a running game: from 270 metres away, behind half the town, facing roughly towards it, the table reported itself rendered. Turned round, it did not. With its shadow switched off it still did.
Both were fixed by half nine. From the spawn point the table dropped into coarse mode after 5.6 seconds and played four quick shots in forty seconds. With the player moved six metres from the table it came straight back out, and the snooker NPC stepped up and played a full shot.
The doors measured nothing
The rest of the night went down the list of things ticking that had no reason to.
Every front door in town checks, a few times a second, whether an NPC is about to walk through it. There are 547 house doors and 334 front and back doors, and in the club trace they were costing 3.5 ms between them. The agent gave each one a gate: tick every frame within 40 metres of the player, twice a second beyond. Its first build passed every check it ran and measured every door in town at zero metres from the player, so every door counted as near and nothing changed. The distance node had bound the player to the wrong side of the sum and left the other side empty - and the readback looks exactly the same for the right wiring and the wrong one. Only a lower-level inspection showed it. Rebuilt, 511 house doors and 304 front and back doors were sitting at half a second, and every door near the player at full speed.
The birds and hedgerow animals had each been searching the whole level for vehicles and predators every frame. They keep a list now.
Then the fuel pumps. Between two of the memory reports the number of pump nozzles in the world had gone from 48 to 96. Each pump makes its six nozzles when it starts. But the nozzles are not part of the pump's patch of the map, so when that patch unloads and comes back, the old six stay and the pump makes six more. The fix destroys them when the pump goes. The test of that crashed the editor at 22:14 - graphics device removed, the card full - and after it the whole machine blue-screened. It was rebooted.
The last batch went in after the reboot: the delivery riders, the toilets, the autograss engine sounds, all gated by distance, compiled and saved. The first proof of those is the next standalone run, because a running game inside the editor on this map now takes the graphics driver down with it.
Earlier that evening the agent had also told him the player's car was drawing at 132,000 vertices close up. It was not. The setting behind that figure had never stuck on either of the two skinned meshes, and the full 1.2 million were being drawn the whole time. The fix was to rebuild the car, the fox and the police car on their own simpler versions: the police car's top level went from 1.75 million triangles to 611,000, the fox from about 976,000 vertices to 9,091. On disk the car went from 344 MB to 109.
The fault
Unreal Engine 5.8 Niagara spray is invisible when fired where the player is looking
A pressure-washer jet drew nothing in PIE while splash, rain and smoke drew. A stock fountain test and a scalability A/B (levels 0, 2, 3) both misled, and so did several frames that turned out to be the editor viewport rather than PIE.
Two causes, stacked:
- The core emitter used velocity-aligned sprites. Seen along their own velocity they are edge-on and have no visible width. A jet aimed at the crosshair is exactly that view. Tilted down across the view, the same jets drew at all four test speeds.
- Sprite sizes set as
Vector2through Monolith'sset_module_input_valueas an array or"4,22"were stored as unreadable text, almost certainly size 0.
Then, once visible, the spray went in random directions: the AddVelocity cone axis is read in the Niagara component's local space.
The fix
Camera-facing sprites, Vector2 written as (X=..,Y=..), aim converted to local space
- Rebuilt from a known-drawing spray with the renderer alignment set to unaligned (camera-facing), Random Non-Uniform size
(X=2.5,Y=2.5)to(X=5,Y=5). - Every
Vector2input passed as a"(X=..,Y=..)"string. The splash system had the same text bug and was rewritten the same way. - Each tick,
SprayDir = InverseTransformDirection(jet world transform, aim direction).
The guard: reject any capture showing the editor axis gizmo, and count files per screenshot call - the numbering drifts.
The fault
NPC snooker physics costs 40 ms a frame with the player in another building - WasActorRecentlyRendered always true
A coarse mode for unwatched NPC shots compiled, read back and never engaged: 0 coarse shots, 48 full shots in 50 minutes of standalone play. Unreal Insights put BP_SnookerTable at 40.7 ms/frame.
- The "no walk in progress" leg tested the walking feature's on switch, which is never cleared (true on 748 of 748 sampled frames).
WasActorRecentlyRenderedon a Nanite mesh is a view-frustum test, because occlusion happens on the GPU. True from 270 m behind the town, false turned 180°, still true with its shadow off.
The fix
Test the real state, and only trust the render test up close
The walk leg now tests that both players are actually standing still. Observed now means dist² < ObsDist² OR (rendered AND dist² < (2·ObsDist)²), so the render term counts only within 60 m. PIE: coarse on 5.6 s after spawn, four coarse shots in about 40 s; full simulation back at 6 m.
The guard: before trusting WasActorRecentlyRendered as a relevance gate, check whether the mesh is Nanite.
The fault
Distance tick gate does nothing - GetDistanceTo returns 0 for every actor
GetDistanceTo written positionally with GetPlayerPawn bound the pawn to Self and left OtherActor empty, so all 881 doors measured 0 m and stayed near. The graph readback is identical for the correct and the broken wiring.
The fix
Name the pin, and check the node, not the readback
Wire OtherActor explicitly and confirm it with get_node_details. SetActorTickInterval also needs its TickInterval pin named - written positionally it dropped a literal to 0.0. Result: 815 far doors at 0.5 s, near doors at 0.
The fault
Actors spawned in BeginPlay duplicate every time a World Partition cell streams back in
Fuel nozzles went from 48 to 96 between two memory reports. Spawned actors are not part of the spawner's cell, so a stream-out leaves them behind and the next BeginPlay spawns a new set.
The fix
Destroy what you spawned in EndPlay
EventEndPlay destroys each nozzle behind an IsValid check. Built; the standalone check is obj list class=BP_FuelNozzle_C holding at 48 after travelling.
The kit
| Tool | Why this one |
|---|---|
| Unreal Insights | the only thing that named the snooker table; the stat overlay only said the game thread was slow |
PlayGame.bat, editor closed | every frame-rate number taken with the editor open is wrong |
| A second agent session | caught the editor-viewport frames the first was reading as PIE |
| A street of four test jets | a side-on view of the same jets that were invisible head-on |
Plain English
| Term | What it means |
|---|---|
| autograss | British grassroots motor racing on an oval of grass or mud, usually in a farmer's field, with classes running from stripped-out hatchbacks to purpose-built buggies. |
| Niagara | Unreal's particle system - rain, smoke, sparks, dust, fizz on a pint. |
| velocity-aligned sprite | A flat particle picture turned to point along the direction it is flying, so a fast drop reads as a streak - and seen from straight behind, edge-on, it is a line with no width at all. |
| 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. |
| standalone | Running the game on its own with the editor closed, the way a player would. The only honest place to measure frame rate, because the editor eats memory and processor time of its own. |
| Unreal Insights | Unreal's recording profiler: it records every frame for a while and shows, afterwards, exactly which bits of code took the time. |
| game thread | The part of the engine that runs the game's own logic - every Blueprint, every tick - one thing after another. If it takes 90 milliseconds, nothing else matters: the frame cannot finish sooner. |
| render thread | The part of the engine that prepares each frame for the graphics card, running alongside the game thread; if either one is slow, the whole frame waits. |
| tick | Code that runs every single frame. Cheap once, ruinous sixty times a second across a thousand actors. |
| tick interval | How often an object's every-frame code is allowed to run; setting it to half a second makes a far-away object check in twice a second instead of sixty times. |
| Nanite | Unreal's system for drawing extremely detailed models without hand-made LODs. It does not cover everything - foliage and transparency are the awkward cases. |
| frustum culling | Not drawing things outside the camera's view. |
| occlusion culling | Not drawing things hidden behind other things. The single biggest saving in any indoor scene. |
| World Partition | Unreal's system for very large maps. It chops the world into a grid and only loads the squares near the player. |
| BeginPlay | The moment each object in the level starts running when the game starts. Every object gets one, and in a big streamed world the order they arrive in is not guaranteed, so one object can quietly undo what another has just set up. |
| LOD | Simpler versions of a model, swapped in as it gets further away. The trick that lets a street hold hundreds of objects. |
| VRAM | Memory on the graphics card. This project has 8 GB of it, and that ceiling shapes most of the decisions in the devlog. |
Things we still need to fix tomorrow:
- The splash throws a tall fountain at the hit point; it should fan along the panel and back towards the player.
- Puddles under the cars and drips off them are designed, and next after the splash.
- A standalone run to measure the snooker table, the doors, the far-off tickers and the rebuilt car, fox and police car - and to watch the nozzle count hold at 48.
- The club's 1.5 fps trace spent 530 ms of each 674 ms frame waiting on the graphics side. The suspect is the card running out of memory; the next trace in the club, after the rebuilt meshes, says whether it is.
- The hire car on the raceway's south straight is stage one of driving at the meeting.