Second Chance DevlogThe Gazette

One playtest, thirty defects, and three of my own diagnoses were wrong

playtestingverificationunreal-engineagentsdebuggingsilent-failure

The setup was almost embarrassingly simple. A human played the game. An agent sat on the editor connection, fired a full-resolution capture whenever the human said "get this shot", copied the frame out, looked at it, and said what it could see. No automation of the playing, no scripted camera path, no attempt to decide in advance what was worth photographing.

A terraced street at night under sodium streetlamps, the lamp glow diffusing through haze, lit windows and wet tarmac.
A terraced street at night under sodium lamps. Frames like this one are why the loop works: the halo, the wet tarmac and the rain streaks are all judgements you can only make by looking.

That loop ran for about two hours and turned up roughly thirty defects, several of which every automated check in the project had been calling clean for weeks. It also produced three confident, plausible, completely wrong diagnoses from the agent, all three caught by reading a number back out of the engine. Both halves of that are the point of this entry.

Everything looked fine at Low and fell apart at Epic

The editor had been left at the lowest quality preset. That is not a neutral state. Below High the engine switches off screen-space reflections and volumetric fog entirely, so a wet road has nothing to reflect and a lamp has no halo. Half the defects in this session were simply invisible until the quality dials went up.

Two settings mattered more than the rest:

  • Screen percentage. The project pins the internal render scale to about two thirds. An

unmodified capture at "1920x1080" was therefore being rendered at 1280x720 and upscaled. Marketing stills must not be upscaled from 66%.

  • The texture streaming pool, which was set to 550 MB. On a dense street that is far too

small, and the symptom is not an error — it is foreground brickwork rendering as smeared low-resolution mush. Letting the frame settle did not fix it, because the pool simply could not hold the textures. Raising it made grass blades and tarmac grain appear immediately.

The lesson that generalises: a screenshot session should begin by reading the render settings back, not assuming them. One console variable in this project controls a delay before a high-resolution capture fires, and it was found set to a value that makes the screenshot never arrive at all — for the second capture session running. It lives in no config file, so nothing persists it and nothing reports it. It just silently eats your output.

I fixed it, every step reported success, and it is still broken

The best result of the session closed a bug that had been open for twelve days: chairs and stools in the pub flying around the room.

A fix had been applied nearly two weeks earlier. It moved the seats from simulated physics to a locked, non-simulating state, and it reported success at every step. The chairs kept flying. Nobody could work out why.

The check that settled it took one query. Count the seats in the live game, and count how many are still simulating physics:

  • 22 seats in the pub.
  • 21 of them still simulating, with the old collision profile.
  • Exactly one in the fixed state.

The fix logged twelve days earlier had claimed 21 seats. That count matching precisely is the whole proof. It was not a partial fix, not a regression, not a second bug — the edit had been applied to 21 seats and written to disk for none of them, which is a fault this project had already documented separately but had never gone back to re-check against the older work.

If you take one thing from this entry: when a fix "did not work", first prove the fix exists in the shipped data at all. A count that matches the original change is far stronger evidence than any amount of reasoning about mechanics.

The NPCs are stuck outside the building

Six characters were piled into a one-metre cluster against the outside of the pub's south wall. Five were completely motionless. The obvious reading is a pathfinding failure, and there had already been work aimed at "NPCs getting stuck outside".

The readback said otherwise. Every one of the six had:

  • the same destination — an identical position, to the unit
  • a state of "drinking"
  • and a distance to that destination inside its arrival radius

They were not stuck. They had arrived. Navigation was working perfectly and had delivered all six to a point about two metres outside the building's south wall, where they were correctly standing still because, as far as they were concerned, they were at the bar.

Two faults were hiding under one symptom: the destination was a single shared point rather than one seat per character, and that point was outside the building. It also explained a completely separate complaint from the same session — the pub's bingo game calling numbers with nobody there to play. The punters were all outside.

The generalisable bit: "stuck" and "arrived at the wrong place" look identical from the outside and are opposite bugs. Reading the destination and the arrival radius separates them in one query, and it points at completely different code.

The character is in the level but not in the game

The barmaid was missing. Her voice lines were playing — you could hear her running the card game — but she was nowhere to be seen.

Her position in the running game was roughly 470 metres away, essentially at the world origin, with a height of about zero. That is a very recognisable signature.

The first instinct was that she was spawned at runtime rather than placed, because a query for level actors returned no barmaid. That inference was worthless, and this is the trap: while the game is running in the editor, the level-actor query returns zero actors of any kind. It was not telling us she was absent, it was telling us nothing at all. Re-running it after stopping showed 13,777 actors — and the barmaid placed exactly where she should be, behind the bar at the correct floor height.

So she is placed correctly and something moves her at runtime. That completely changes the fix: not "put her back", but "find what moved her". The likely culprit is the very work that was done to stop NPCs getting stuck — a routine that resets a stuck character to a default location would produce precisely this.

Her service and shift logic, meanwhile, were sitting correctly behind the bar the whole time, which is why the voice still played. The logic and the body had come apart.

All my quality settings make no difference to the frame rate

An attempt to answer a simple question — what frame rate does this actually run at? — produced this:

| Preset | Measured | |---|---| | Highest quality, large texture pool | 3.14 fps | | Highest quality, small texture pool | 2.33 fps | | Everything on Low | 1.97 fps |

Everything on Low measured slower than everything on Epic. That is not a performance result, it is a broken instrument. A flat response to a setting that certainly does affect rendering means something else is pinning the number — here, almost certainly the editor throttling itself because it does not have focus. An agent driving the editor never gives it focus, so an agent can never measure this honestly.

The documented workaround — switching off the throttle setting — reported success on read-back and changed nothing.

So the honest conclusion was to publish no number at all and record that this measurement cannot be taken this way. A believable-looking frame rate obtained from an unfocused editor is worse than no frame rate, because somebody will act on it. This project has already been bitten once by a playtest running at four frames a second, which the notes say "disguised three other bug reports".

The frame is blown out white — is the material missing?

Three times the agent named a cause confidently and was wrong. All three were caught the same way, by asking the engine for a number instead of trusting the picture.

"Those white spikes are broken geometry." They were rain. The weather state and rain intensity were both readable, and a particle system was active on the player. Worth logging anyway — at that intensity, rain reads in a frozen frame as sparse hard sticks hanging in the air rather than as weather — but that is an art note, not a broken mesh.

"Those blown-out cream surfaces are untextured buildings." A scan for missing or default materials near the camera returned zero. They were correctly textured surfaces caught mid auto-exposure adaptation, because the captures were being fired the instant the camera moved. The fix was procedural, not technical: let the frame settle.

"The white cat is a material instance that fails to load." The material had genuinely returned a load failure — while the game was running. Re-queried after stopping, it loads perfectly: correct parent, a dark grey coat colour, sensible roughness. The load failure was an artefact of asking at the wrong moment. What the cat actually was remains open, and it may turn out not to be a bug at all.

There is a pattern in all three. A visual symptom plus a plausible mechanism is not a diagnosis. It is a hypothesis, and this project has a long history of those being confidently wrong. The bug list that came out of this session is therefore split into two sections — confirmed by readback, and observed but unproven — with a standing rule that nothing moves between them without evidence.

What actually made the loop work

Not the automation. The human chose every frame — walked into the pub, went upstairs, pulled the chain, wandered out to the fly-tip — and that unscripted wandering is what found the defects. No camera path anybody planned in advance would have gone into a toilet cubicle and looked up.

What the agent added was that every frame got looked at hard, immediately, by something willing to then go and query the engine about it. Spotting a slightly-too-dark patch on a wall is easy. Following it up thirty seconds later with "52 splash decals and 7 puddles spawned, the puddles are all at exactly floor height, and the splashes are clustered on the back wall rather than the pan" is the part that turns an observation into a fixable ticket.

The failure mode to watch is the one above: an agent reading frames will generate confident explanations, and some of them will be wrong. The discipline that makes it safe is cheap — before believing any visual diagnosis, ask the engine for a number, and be willing to write down that the number contradicted you.

Nothing here is fixed yet. Everything in this entry is a finding, not a repair, and the list they went onto is ordered so that the first job next session is to prove the save path works at all — because if it does not, none of the other fixes will stick either.

← All devlog entries

Watch it get built. All of this goes up on YouTube as it happens — broken animations, buildings hovering a foot off the ground, the lot.

Subscribe on YouTube