Second Chance GazetteDevlogToken Use

A key stops working game-wide, and another key runs the wrong action, in Unreal Engine 5.8

unreal-engineblueprintsinputenhanced-inputdebugginglodsdraw-callssnookeranimation

The door slabs he was sure about

The morning started with James asking a question the agent got wrong in one sentence.

I thought we had optimised the door slabs before? There are 553 front doors placed around Oakhaven, all the same model, and the agent — Claude Code Opus 5, which drives the editor here — said no, they never had been. It had a reason, and the reason was true: the door model's own file had not been touched since July, and there is no converter in this project that would have merged them into one cheap batch.

Both of those were facts, and the conclusion off the back of them was still wrong. The doors had been optimised, months ago, by giving each placed door a distance beyond which it simply is not drawn. That setting lives on the copy standing in the street, not on the model in the library, so the model's file date can sit untouched forever while every one of its 553 copies is already handled. 544 of them were. James was right and the readback that said otherwise had been looking in the only place that could not tell.

That opened the morning up, because it raised the question of what else the sweep could not see. The project-wide optimisation pass ignores anything under 1,500 triangles, which is a sensible floor for a sweep across a whole map and quite wrong for something placed six hundred times. A read-only audit of everything below the floor found 41 models placed in bulk, 22 of them with nothing done to them at all.

The pattern was the same every time, and it is worth knowing: look for the missed twin. One piece of a kit had been optimised and its sibling had not, because the sibling happened to fall under the floor. The 10p coin in the arcade has four levels of detail. The 50p, sat right beside it in the same machine, had one — 517 of them at 1,344 triangles each, about 695,000 triangles, the single biggest miss in the map. Two lamp columns, one done and one not. Three flower meshes converted and the fourth left behind.

The other thing the morning settled was a lever that does not work. Not drawing a distant object only saves anything if something cheaper is drawn instead, and the cheap distant stand-ins here only take over when a square of the map unloads, which happens at 256 metres. So a cut-off below that makes the thing vanish with a hole where it was, and a cut-off at exactly that distance saves nothing at all. There is no useful window in between. Fine for wheelie bins, which are three pixels at that range and nobody notices. Not fine for an 8.1 metre lamp column popping out of a street. James had approved cut-off distances for the poles; the measurement said do it the other way, so they got proper levels of detail instead.

Rockstar have a department for this. He had a Wednesday morning.

Three keys, one evening

He played it that evening. The run lasted nine minutes, and by the end of it three separate keys had stopped doing what they were meant to, with three unrelated causes. The one worth starting with is that a bus he never got on had been holding a key down since the moment it loaded.

A stone tiered fountain lit pale against a nearly black garden at dusk, seen from the player's own eye level with a white crosshair at the centre. Along the top left are the needs bars - fullness, hydration, energy, bladder and heat - with teleport destination labels printed over them, and a to-do note reading 'Sign on at the Job Centre, 9.30am tomorrow'. Top right reads OAKHAVEN, DAY 1, 10:18, and a bank panel showing 500 pounds.
A stone tiered fountain lit pale against a dark garden at dusk, seen from the player's own eye level, with the needs bars and teleport labels drawn over the top left of the screen.

U stopped mounting the ebike. Not a first attempt that had never worked — he has ridden the bike many times, including in the playtest before this one, which makes it a regression and rules out half the usual answers immediately.

Six theories died before the right one. The streaming settings changed that morning — no, streaming cannot silence a key press. The golf splice from two days before had severed the chain — no, the live graph read through cleanly. A golf lock-out flag was suppressing every interaction — no, it read zero at the bike. Enhanced Input was swallowing the key — no, U is in none of the three places this project keeps key bindings. The key's own node had been disabled — no, its compiled name was still in the package. The bike itself was eating it — no, the bike only asks whether the key is down, and asking cannot consume.

That is four graph-reading theories in a row, each plausible, each wrong, and it is the shape this project keeps producing when nobody insists on evidence rather than inspection.

What cracked it was a positive control: call the interaction function directly on the live player, mid-game, and see what happens. It printed its line, named the bike, measured the distance, and mounted him. The entire chain was healthy in one call. Which meant the question was no longer what is broken downstream but where does the key press go, and those are different investigations.

A binary scan of every asset in the project found exactly two Blueprints that bind U: the player, and the bus. The bus's binding was marked to consume the key rather than pass it on. And the bus grants itself input from its seat-spawning setup — at spawn, not on boarding — and never gives it back, because nothing in the Blueprint ever calls the opposite. So from the moment the first bus streams in anywhere near the player, U belongs to the bus for the rest of the session, everywhere on the map, whether or not anybody ever boards one. Every U press had been silently running the bus's ticket check.

The fix was one setting: stop that binding consuming the key. It is safe because the ticket check already refuses unless the player is close, facing it and aboard. It is also a mitigation rather than a repair — the bus still holds raw global input for the whole session — and James took it knowingly, with the architectural version written down as owed.

There is a version of this game where that bug cannot exist, and it is the version where the bus is scenery. Second Chance is arguing the opposite case against GTA 6: everything is interactive, every shop opens, the bus is a bus you get on and pay for. That argument has a bill attached, and the bill is that a bike, a bus, a golf club, a snooker cue, a pub chair and several hundred doors are all bidding for the same keyboard. Nobody had to be persuaded to make all of it enterable, which is the advantage of being one person. This is the invoice.

The golf club that could never be put away

The second key was E, and it broke in the most predictable way available.

A single-storey red-brick clubhouse with a tiled hipped roof, white eaves band and a brick chimney, standing alone on a broad green hillside under a clear blue sky. A large broadleaf tree stands to the left casting a long shadow across the mown grass, and a low hedge line runs away behind the building to the right.
A single-storey red-brick clubhouse with a tiled hipped roof standing alone on a broad green hillside under a clear blue sky, a large tree casting a long shadow across the mown grass.

Two days earlier the golf club draw had been spliced onto the front of the E chain. E in this project is a serial run of roughly forty-five links — every door, seat, drink, vehicle and pickup reads down it in order. Put a fault at the head of that and it takes every other consumer down with it, which is exactly what happened: at the golf club E drew no visible club, and then on the bus E stopped opening the doors and printed a golf error instead.

James's ruling was to move it rather than repair it. Use a golf club is a use, not a pick up, so the club went to U along with the bus doors, and E went back to being pick up and put down. Nothing about the fault had been golf-specific — anything spliced onto the head of E can do this, and the whole reason the verbs were split into separate keys in the first place was to stop one feature taking the others with it. The split earned its keep by failing in public.

Underneath it was a smaller bug and a better lesson. The log printed Club away. and Club out. on a single press, every press, so the club could never be stowed. That looks exactly like a key firing twice. It was not. The toggle was two branches, and the second one's condition was a plain inverted read of the same flag — with no execution wire through it, so it was evaluated when the second branch asked for it, which was after the first branch had already set the flag to false. It read "not false", came out true, and drew the club straight back out.

A node with no execution wire through it is not a snapshot. It is a question re-asked at the moment somebody needs the answer. Any ladder that writes a flag and then tests the same flag further down the same path is reading the new value, not the old one — capture it into a local first, or branch once and mean it.

A wire that had gone missing

The third key was L, which teleports to the snooker club, and it had simply come unwired. The node sat at its authored position with its output connected to nothing at all, and its teleport call sitting beside it equally unattached. Two full play sessions with not one teleport line in the log. When the wire went, and to what, was never established.

The cheap proof is worth stealing. A compiled key node bakes a predictable event name into its package file, so grepping every asset in the project for that name answers a question no amount of graph-reading does: a hit means the key is bound somewhere, and no hit anywhere means it is bound nowhere. That separates this node lost its wire from something else is stealing the key in one command, before opening anything. Searching for the node by name does not work — the node is there, looking perfectly healthy; only reading its individual pins shows the empty one, and the way to prove that reader honest is to point it at a key you know works and watch it come back with a real connection.

The honest oddity, since this project keeps a running argument with itself about input: the modern mapping genuinely does carry L, and the modern event is genuinely wired to the same teleport function, and one physical press of the key printed exactly one line. The modern path does not fire for this key here. The old-fashioned node is the only thing that works, so it must not be tidied away as a duplicate.

And halfway through, WASD stopped while the mouse kept looking around. That combination is the signature of one specific state — movement input ignored, look input not — and it is worth checking first on any I can't move, because every other reading looks completely healthy. Clearing it needs a reset rather than a set-to-false, because the underlying thing is a counter: setting it false decrements once, and if two things pushed it, the player stays rooted with a readback that says everything is fine.

Fourteen points and not a single pot

With L working he got to the table, and something happened that this project had been waiting a fortnight for: the snooker rules engine ran in play. Not against a test harness — in the game, on a stroke a person took.

A dim snooker hall with a grey tiled floor. A full-size mahogany table with green baize stands in the foreground under a long shaded light, the fifteen reds racked in their triangle with the black below them and the baulk colours spotted at the far end. A cue lies along the cushion on the near side, and the open mouths of two corner pockets show at the near corners. A second table stands behind it against a pale wall hung with two framed honours boards.
A dim snooker hall with a full-size mahogany table under a long shaded light, the fifteen reds racked with the black below them and the baulk colours spotted at the far end, a cue lying along the near cushion.

He picked the cue off the rail, aimed, and struck at eight per cent power. The shot loop ran 38 frames, the rules engine judged it, and the scoreboard read a four-point foul. Every part of a chain that had been checked 29,832 times off disk and never once executed went through in one stroke.

Then he played eight of them and said: I'm not sure how I have scored 14, I didn't see any red go in a pocket.

The agent's first answer was wrong and confidently so. The penalty column read 4, 6, 7, 7, 7, 7, 7, 7 across the session, which is precisely what a list of infringements that never gets emptied looks like, and the function that sets up each stroke genuinely does not clear it. Sticky array, said the agent. It was not. The function that actually resolves the stroke clears both lists as its first two statements, every single time. Reading the setup function and not the entry point is how a rising number turns into a bug that was never there.

The real explanation was that every stroke had been a foul and the engine was right about all of them. The striker changes after a foul, so the penalties land on alternating players, and his 14 was 7 and 7 of the opponent's fouls rather than anything he had potted. He had also, at one point, potted a red — the ball genuinely left the table and the engine recorded it — and scored nothing for it, because that stroke was a foul too. A red potted on a foul stays down and pays nothing. That is the rulebook, and getting it right looks identical to getting it wrong if you only watch the number.

What the session did expose is a genuine gap, now costing real points. The engine has no way for a player to nominate which colour they are going for, so when it needs the value of the ball on it takes the highest colour still on the table, which is the black. Seven, every time. A referee would charge whatever the striker had called — brown, and four. The engine is therefore harsher than a real frame, consistently, and whether to build a nomination interface for it is James's call rather than something to go and implement off the back of one evening's annoyance.

A stick man leaning over a table

Late on, the other half of the snooker work. There is no motion-capture clip of a snooker stroke in any of the free libraries — not for snooker, not for pool — so the choice was between filming somebody and building it by hand. James ruled: build it from the research first, and if it reads wrong he will get a real shot filmed later.

Four clips came out of it: the stroke itself, with three feathers, a final backswing, a 0.35-second pause at the back, an accelerating delivery and a follow-through that stays down; the seven-beat rise afterwards; a standing idle with the cue held tip up; and the address pose. All authored from a published study of elite players' cue actions, all built by one re-runnable script with the game not running.

Six stick-figure diagrams of a snooker player on a near-black background, drawn from the joint positions of an animation clip. Five are side views labelled address, feather back, backswing, contact and follow, each with an elbow angle; the sixth is a front view. In each the figure is bent low over two horizontal yellow lines marked as the table rail and the cue height, with the grip hand and the bridge hand ringed and labelled, and a grey line beneath marked floor.
Six stick-figure diagrams of a snooker player drawn from the animation's own joint positions, five side views labelled address, feather back, backswing, contact and follow with elbow angles, plus a front view, each bent low over lines marking the table rail and the cue height.

Two traps came out of that session and both are general enough to be worth publishing. The first is that the tool for capturing frames of an animation renders the reference pose every frame — the neutral stance the model was built in — while reporting complete success. It called a perfectly good clip a statue, and the obvious response to being told your clip is a statue is to go and rebuild the clip. The control that exposes it takes one call: capture a clip that ships with the engine, at four different times, and compare the pictures. All four come back identical. Diffing only your own frames cannot tell a broken renderer from a dead animation. The stick figures above exist because of that — they are plotted straight from the joint positions the clip is actually made of, which is both instant and honest.

The second is that the trip out to Blender and back was never needed at all. An animation asset here exposes a controller that lets it be written directly, which also side-steps the round trip's well-documented habit of coming back in the wrong units and mirrored.

The geometry is the part worth keeping. A pure elbow hinge moves the hand at right angles to the forearm — so the grip only travels along the cue if the forearm hangs vertical with the elbow directly above it. This is not a thing that can be solved by picking a cleverer hinge axis: a run using the mathematically optimal one still wandered 15.8 cm vertically across 18.4 cm of travel. Standing the forearm up gave 25.2 cm of travel with 5.4 cm of wander. That is why snooker technique looks the way it does, and it puts a floor under how low the cueing shoulder can sit — fold the torso too far and the arm physically cannot close.

Five checks guard it, and every one has fired at least once. The one that earned its keep asserts that the grip travels at least 16 cm along the cue, with less than 45 per cent vertical wander, and in the right direction — because "the elbow angle varies by 27 degrees" is not the same claim as "the cue moves". One build passed the not-a-statue check while the hand went 11.5 cm diagonally. A later one passed both while playing the whole stroke inside out, the backswing shoving the cue at the ball.

The clips are assets, not behaviour. Nothing plays them, they are not on anybody, and the hand tracking that would put those fingers on the actual cue does not exist yet. The game owns the cue and computes where it is from the shot, so until that tracking is built the hands will be near it rather than on it.

The fault

Pressing U no longer mounts the ebike, and no interaction line appears in the log at all

A regression — the bike had been ridden in the previous playtest. Six theories were killed first: the streaming settings changed that morning, a golf splice severing the chain, a golf lock-out flag, Enhanced Input consuming the key, the key's node being disabled, and the bike consuming it. A binary scan of every asset for the key's compiled event name found only two Blueprints binding it: the player and the bus.

Actual cause: the bus's own input binding is marked to consume the key, and the bus calls EnableInput from its seat-spawning setup at spawn time and never calls DisableInput anywhere. That pushes the bus above the possessed pawn on the input stack permanently, so from the moment the first bus streams in near the player, U is dead across the whole map for the rest of the session whether or not anybody boards a bus.

The fix

Clear consume-input on the intercepting binding, and use a positive control before trusting a silent log

Consume-input set to false on the bus's key node, compiled, saved and read back off disk. Safe because the ticket function already gates on distance, facing and being aboard.

The guard that generalises is the diagnostic order, not the setting:

  1. Positive control first. Call the target function directly on the live pawn. If it

works, the fault is upstream of everything you were about to read, and four graph theories die at once.

  1. Then a binary scan of every asset for the key's compiled event name, which is the only

cheap way to enumerate every binder of a key across a whole project.

  1. Absence of a log line is not absence of a press — but only once you have confirmed the

handler prints unconditionally.

⚠ Still owed: this is a mitigation. The bus holds raw global input for the session either way, and the real repair is to gate it on boarding or move the verb onto the shared interaction system so no actor grabs raw input at all.

The fault

One press of a toggle prints both "Club away." and "Club out.", so the item can never be put away

Looks exactly like a double-fired key. It is not. The toggle was two branches, and the second branch's condition was a plain inverted read of the same flag with no execution wire through it. Such a node is evaluated when the branch asks for it — i.e. after the first branch has already written the flag — so it read "not false", came out true, and undid the stow within the same press.

The fix

Branch once on the flag, or capture it into a local before anything writes it

The second branch and its inverted read were deleted; one branch now goes true to stow and false to draw. The rule to carry: a node with no execution wire is a question re-asked, not a value captured. Any sequence that writes a flag and then re-tests the same flag further down the same execution path is testing the new value.

Separately, and for the same playtest: a verb spliced onto the front of a long serial interaction chain takes every other consumer of that chain down with it when it faults. Split the verbs onto separate keys — the golf club and the bus doors both moved off the shared interaction key — rather than lengthening one chain.

The fault

A key does nothing, and searching for its node by name finds the node looking perfectly healthy

The key's node sat at its authored position with its Pressed output connected to nothing, and the function call beside it equally unwired. Searching by node name reports it present; only reading the individual pins shows the empty connection.

The fix

Grep the compiled packages for the key's baked event name before reading any graph

A compiled input-key node writes a predictable event name into its package file. Grepping every asset for that name answers the question in one command: a hit means the key is bound somewhere, no hit anywhere means it is bound nowhere at all — which separates "this node lost its wire" from "something else is stealing the key" before anything is opened. Point the pin reader at a key you know works first, to prove the reader is honest.

Related, from the same session: if WASD does nothing while the mouse still looks around, check whether movement input is being ignored before anything else — every other reading looks healthy. Clear it with a reset rather than setting it false, because the underlying value is a counter and a single decrement leaves the player rooted if two callers pushed it.

The fault

A rising penalty column across eight strokes looks like a list that is never emptied

The values read 4, 6, 7, 7, 7, 7, 7, 7 and the function that prepares each stroke genuinely does not clear the infringement list, which makes "sticky array" an extremely convincing first answer. It is wrong: the function that actually resolves the stroke clears both lists as its first two statements, every stroke. The number was rising because the shots were getting harder. Read the entry point before believing the preparation function.

The fix

Not a defect — but the foul values are a real design gap, and it is a decision rather than a bug

With no way for the player to nominate a colour, the engine values the ball on as the highest colour still on the table when charging a foul. That is seven every time, where a referee would charge whatever the striker had called. The engine is consistently harsher than a real frame. Building a nomination interface is a design decision and has not been taken.

The kit

ToolWhy this one
A direct function call on the live player, mid-gamethe positive control that killed four graph theories in one call, and the only thing that proved the fault was in the key press rather than downstream of it
A binary scan of every asset for a key's compiled event namethe only cheap way to enumerate every Blueprint binding a key across a whole project; the built-in node search calls an unwired node healthy
A published motion-capture study of elite cue actionsthere is no snooker or pool clip in any free animation library, so the stroke had to be built from numbers rather than borrowed
A stick-figure plotter drawn from the animation's own joint datathe engine's frame-capture tool renders the reference pose while reporting success, so the visual check had to come from somewhere that could not lie about it
A read-only audit of everything below the optimisation sweep's triangle floorthe main sweep is structurally unable to see models under 1,500 triangles, however many hundreds of them are placed

Plain English

TermWhat it means
BlueprintUnreal's visual scripting. Logic built by wiring boxes together with lines instead of typing code. This whole game is built in it.
event graphThe Blueprint logic that runs while the game is actually being played.
pawnThe thing in the world the player is actually driving — the body the camera is attached to. When the pawn stops responding, nothing the player presses does anything.
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.
pure nodeA Blueprint node with no execution wire running through it. It does not run at a point in time; it is re-evaluated every time something downstream asks it for a value. So a test on a pure node reads the variable as it is now, not as it was when the logic started, which is how a toggle can undo itself inside a single press.
input stackThe ordered list of things on screen that get first refusal on a key press — menus above the player, the player above the world. An object that pushes itself onto that stack and never takes itself off keeps intercepting keys for the whole session, whether or not the player is anywhere near it.
positive controlA deliberate attempt to make the thing fire, run before you trust its silence. If the alarm does not sound when you set fire to the test rig, the absence of an alarm the rest of the day means nothing. Here it meant calling the interaction function directly on the live player, watching it work perfectly, and thereby proving the fault was in the key press and not in anything it leads to.
Enhanced InputUnreal's current system for player controls, where a key is mapped to a named action such as interact rather than wired straight to the thing it does.
Input Mapping ContextA list of which keys drive which named actions. A game can add and remove several of them, so the same key can mean different things in a car and on foot.
actorAnything placed in the game world — a lamp post, a person, a trigger volume, a camera. If it sits in the level, it is an actor.
LODSimpler versions of a model, swapped in as it gets further away. The trick that lets a street hold hundreds of objects.
HLODThe same idea applied to whole groups of buildings at once, so a distant part of town is drawn as one cheap lump.
draw callOne instruction from the processor to the graphics card telling it to draw something. Thousands per frame is where performance goes.
tri countHow many triangles a model is made of. The basic measure of how expensive it is to draw.
level streamingLoading and unloading parts of the map while the game runs, so the whole world need not be in memory at once.
World PartitionUnreal's system for very large maps. It chops the world into a grid and only loads the squares near the player.
reference poseThe neutral stance a character model is built in, before any animation is applied. Seeing it in a finished shot means the animation is not running at all.
skeletonThe hierarchy of bones inside an animated model. Two models sharing a skeleton can share animation.
oracleA second, simpler implementation of the same rules, written only to be argued with. The real one is run against it thousands of times, and wherever the two disagree, one of them is wrong.

Things we still need to fix tomorrow:

  • The bus still takes raw global input for the whole session. The setting that stopped it

swallowing a key is a mitigation; the repair is to grant input on boarding or move the ticket verb onto the shared interaction system.

  • Three keys are bound twice over — once in the modern mapping and once with an old-style

node. One of the three is the shape that fires both. Not investigated.

  • A verb that refuses to run currently says nothing at all: the suppressed branch goes

nowhere. One print statement would have shortened this whole evening.

  • Somebody was seen walking through the sky near the snooker club. Three live surveys of

everything in the world within a minute of the sighting came back clean, so it is written down rather than diagnosed.

  • The game stuttered in patches. The log shows 54 loads that stopped the frame dead, the

worst holding it for 263 milliseconds, and one square of the map loading and unloading 17 times in 25 minutes. Diagnosed as far as the mechanism; the trade-off has not been made.

  • Nobody has made a legal pot in snooker yet. Every stroke so far has been a foul.
  • The four cue-action clips are assets and nothing plays them. They are not on the opponent,

and the hand tracking that would put the fingers on the actual cue does not exist.

  • Three things the playtest asked for and none started: the cue held upright between shots,

a ball that drops into the pocket rather than vanishing at it, and a sound for ball on ball and cue on ball.

← 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