Second Chance GazetteDevlogToken Use

One key press triggers every interaction at once in Unreal Engine 5.8

unreal-engineblueprintsenhanced-inputcollisioneditor-toolingsilent-failure

The rod that turned the torch off

James took the fishing rod out and the head torch went off.

Both are on E. So is talking to people, opening the doors he is allowed to open, picking up litter, sitting down, buying a pint, and about forty other things. They are not on E because anybody chose that; they are on E because every one of them was added on a different evening, each one hung off the end of the same wire, and each one polite enough to check whether it was the thing being pointed at. Press the key once and all forty-five of them wake up and decide. Most say no. On a riverbank with a torch on your head, two say yes.

That is the bill for the thing the game is actually for. GTA 6 will have a context button that does the sensible thing wherever you stand, because somebody at Rockstar was paid to decide in advance what sensible means at every spot on the map. Second Chance has no such person. It has a Midlands town where every shop opens, every bin lifts and anything you can look at you can pick up, and one bloke working out what a key press means at half nine on a Saturday morning. Being small is why the whole town is enterable — nobody had to be talked into it — and it is also why forty-five things ended up on one key.

A refuse lorry seen from behind driving up a narrow terraced street, green wheelie bins out along both kerbs, red-brick houses and coloured front doors either side.
The bins are out and the lorry is running. The evening went on the camera following it, not on the lorry.

It was Saturday, so for once he could start something that would not fit in an evening. It took all of it, and the gym bag stayed where it was.

Forty metres of nothing, parked on the pavement

Before any of that he could not get off the pavement.

Outside the player's own front door on Radworth Road you could walk up and down the flags quite happily, and not step into the road. The agent — Claude Code Opus 5, which does the typing while James does the looking — measured the bounds of everything nearby and reported nothing large enough to be in the way. Then it ran a scan for anything blocking the pawn and got back a clean zero.

The clean zero was the instrument, not the answer. Every collision query in that scan was sitting inside a bare except, so each one that threw was quietly discarded and the empty list read exactly like nothing here is blocking you. This project has a rule about that which predates yesterday, and it got broken again anyway: never write except: pass in a script whose output is your evidence.

The real culprit was the delivery bike parked at the kerb. Its top box had been re-imported the day before to correct a scale error — the mesh had come in 100 times too big, a 47-metre top box — and the re-import fixed the geometry. It did not fix the collision. The body setup still carried two shapes: a box at the correct 47 × 42 × 40 cm, and a convex hull about 40 m across left over from before. An invisible blob the size of the street, sat over the whole frontage with its underside near head height.

A bounds read cannot see that, because bounds describe the visible mesh. The honest instrument turned out to be asking a component for the closest point on its collision to a spot you pick: from 13 m away it answered 7.4 cm, which is not a thing a 44 cm box can say. After the fix, from the same spot, 1,307.5 cm. Collision lives on the asset rather than the placed copy, so the bike picked the repair up with no re-place and no level reload.

A rule that had been wrong for nine days

The verb rebind needed spare keys, so the agent audited what was already bound. It has done this before and the audit was wrong before, in the other direction — a key table built by reading the mapping contexts had once declared F free when F moves furniture.

There are three separate places a key can be bound in this project, and each is blind to the other two. Mapping-context rows, which are the modern way. Legacy keyboard-event nodes wired straight into the player Blueprint, which is how fifteen keys in this game actually work. And plain polled reads — code that asks was this key just pressed with the key name typed in as text, from a tick, on an actor rather than on the player. That third store was the new find, and it is carrying live controls: the cave teleport, the rope climb, the bike dismount, the snooker cue, 30 separate buried-treasure actors all sitting on E. A scan of the player Blueprint cannot see any of it, because none of it is on the player.

To make it worse, a mapping context has two stores inside itself — a deprecated list and the current one — and writing to the current one while reading back the deprecated one makes the write look like it did nothing.

Which is where the nine-day-old rule came from. Since late August the project's own notes had said, in the strongest terms available, that mapping-context rows do not produce working physical keys here and every new key must be a legacy node. James had pushed back on that at the time and had been told, politely, that the evidence was in. Reading both stores of all four contexts settled it: exactly one mapping context is ever added to the player, the other three are dead weight nobody removed, and the rows in the live one drive real keys with no legacy node behind them. E is one of them. E is the most-used key in the game.

Golf's K had been dead for a fortnight not because the mechanism was broken but because its action has no row in any context, in either store. Never mapped. The rule that had grown up around it was an elaborate explanation for a missing line.

One press, one object, one verb

The replacement is three verbs. E picks up and puts down, U uses, I stows in the inventory. The interesting half is not the keys, it is the arbitration: before a verb runs, a trace from the middle of the crosshair resolves exactly one focused object, and exactly one verb reaches it. No tags, no exclusion lists, no priority chain of forty-five polite refusals.

It works. It also spent the afternoon teaching the agent things it had asserted otherwise about.

A collision box cannot be the parent of a Blueprint. Neither can a sphere, a capsule or any other shape component — none of them are marked as usable that way, and the only collision-capable component you can build a Blueprint on is a static mesh component. So the smart trigger volume is a hidden cube scaled to fit. Worse, the call that creates the asset returns nothing at all when the parent is refused, without raising, so the script carried on happily and fell over somewhere unrelated ten lines later.

The distance sentinel had to be −1, not 0. The obvious default for nothing is focused is zero, and it is wrong, because standing inside a trigger volume genuinely reports a distance of zero. A reach test written as distance greater than zero would have silently refused every object the player was actually stood in.

Then two guarded calls fired on the same press and put the item straight back down. Taking sets holding, which makes the put-down branch true on the same press, and there are no local variables in this graph language to snapshot into. One flag, reset at the head of every press, closes whichever branch did not act.

The diagnostic itself lost an hour. The print that says this verb ran had been put behind the focus check, so a press that found nothing printed nothing, and no target looked exactly like the key is dead. It now prints unconditionally and the build script refuses to finish if it does not.

And two of the tests failed on geography rather than code: the editor spawns the player at wherever the camera happens to be looking, not at the player start, so a test object placed carefully at the player start was 207 m away when the game began. It is now placed 211 cm from the parked bike, which is somewhere the player has actually stood.

The last check needed a human. The agent can fire an action into a running game, but it cannot press a key, so it can never prove a new binding works — only that the thing behind it does. James pressed E then I at the test object and a lighter appeared in the inventory panel, which is the one link no log line can show.

Late on, the dropped items got their real meshes, and the real meshes immediately exposed a bug that had been sat there for weeks. Everything dropped had been wearing the same placeholder carrier bag. The code that chooses where a dropped item lands was reading the centre of the sphere it traced with, not the point where the sphere touched the ground — always 30 cm out. The bag's pivot sits about 27 cm above its own centre. The two errors very nearly cancelled, so it looked like it was on the floor. A lighter, which is 7.3 cm long and has its pivot at the base, simply hung in the air. One wire moved the gap from 30.0 cm to zero.

Driving the bin round with the game switched off

By nine in the evening the verbs were in and James wanted to watch the bin lorry do a street.

Overhead view of the refuse lorry stopped on a terraced street with a loader in hi-vis at the rear hopper, wheelie bins and a red post box on the pavements.
The crew and the lorry, stepped by hand one editor frame at a time. Nothing here is running the game.

Playing it is not free. Starting the game in the editor here flies the player through a minute of intro, blocks asset saves while it runs, and takes the camera away from you. So the agent did not start the game. The editor's own interface ticks every frame regardless, and anything the game can call, a script can call — so it registered a callback on the editor's own tick and stepped the lorry's state machine by hand, one frame at a time, reading the real frame delta. Measured travel came out at 45.1 cm/s against a 45.0 target. The whole loop running, at the game's own speed, with the game switched off.

Two things went wrong and both looked like something else.

The lorry started going faster every time the script was edited and re-run. Each run gets a fresh namespace, so a stop function that clears its own handle cannot see the callback the previous run left behind, and now two callbacks step the same actor, then three. Travel per tick went 6 cm, then 12, then 18. The agent read that as the editor running at 3 fps and said so with some confidence. The frame rate was fine. It was an exact integer multiple, which is not what a slow machine looks like.

The chase camera really was slow, and for a different reason: writing the viewport camera position every frame forces the editor to redraw synchronously and drops an idle 30 fps callback to 4. Because the lorry still reads the true frame delta it covers exactly the right ground while juddering, which reads as a camera bug rather than a frame rate one. Spawning an actual camera actor and piloting it took the same shot at 17.5 fps.

A bin loader in a yellow hi-vis polo walking behind the lorry, one bare leg pushing out through the front of his grey trousers and a shoe hanging clear of the foot.
The loader's leg coming out through his trousers. Nothing was wrong with the trousers.

Then the loader's clothes started smearing behind him. That one was a single argument: moving an actor without saying teleport treats the move as swept, which carries a velocity into the skinning, and a MetaHuman wearing leader-posed garments shows that as his trousers trailing a frame behind his legs. It looks exactly like a wardrobe bug. It is not a wardrobe bug. His wardrobe is genuinely still wrong, mind — he is doing the round in a hi-vis polo because the teal work gear has not been fitted yet.

The lorry went up Tollerton Rise at twenty to midnight with the bins out and the crew walking behind it, and the whole thing was running in an editor with the game stopped.

The fault

One key press triggers every interaction at once in Unreal Engine 5.8

Taking out the fishing rod switched the head torch off. Both are on E.

About forty-five Try* calls hang in series off a single IA_Interact.Triggered pin in the player Blueprint, and every one of them runs on every press. Each holds its own private gate — a distance check, a tag, an overlap — so several can pass on the same press and each one acts. Priority was being maintained by hand-ordering the chain.

There is also nothing suppressing input during a minigame. DisableInput appears in zero assets project-wide, so a global key fires inside the fruit machine, the snooker table and the penny pusher as well.

The fix

Resolve one focused object, then let exactly one verb reach it

Split the single key into three — E take/place, U use, I stow — and put a focus resolver in front of all three. It traces from the crosshair centre, adopts at most one object, and publishes what that object will accept. The verb asks the object; it does not poll the world.

Three things that are load-bearing and not obvious:

  • The "nothing focused" distance must be negative, not zero. Standing inside a trigger

volume reports a distance of exactly 0.0, so distance > 0 refuses every object the player is stood in. Use < 0 as the sentinel and treat the focused component, actor and location as stale whenever it is set.

  • One flag, reset at the head of every press, is the mutex. Take sets holding, which

makes the put-down branch true on the same press and puts the item straight back down.

  • A shape component cannot be a Blueprint parent. Box, sphere, capsule and the rest are

all refused; the only collision-capable class you can build on is StaticMeshComponent. The asset-creation call returns None without raising, so check the return value.

The guard: every new behaviour ships with its diagnostic print before the effect and outside the gate. A print behind the focus check makes "found nothing" and "the key is dead" produce identical output — an empty log.

The fault

The player cannot step off the pavement and nothing shows up as blocking

An invisible wall along the frontage of one house. Bounds reads on every nearby actor came back the right size, and a "what blocks the pawn here?" scan returned zero blockers.

Cause: a mesh re-imported the previous day to correct a 100× scale error. The re-import fixed the render geometry and carried the old collision forward — a convex hull roughly 40 m across, on a parked vehicle, with its underside near head height.

get_actor_bounds(only_colliding_components=True) reported the visible size and not the hull. A bounds read is not a collision read. ☠ The zero-blockers scan was wrapping every collision query in a bare except, so a thrown query and an absent blocker produced the same empty answer.

The fix

Re-measure the body setup after any re-import that changes scale

Treat the collision as a separate artefact from the mesh. After any re-import that changes scale, read the body setup's box, sphere, capsule and convex counts and sizes directly, and delete what does not belong.

For "is something invisible blocking the player here?", the instrument that does not lie is PrimitiveComponent.get_closest_point_on_collision(world_point) — it answered 7.4 cm from a point 13 m away from a 44 cm box, and 1,307.5 cm once the stale hull was gone. Run the control first: clean air far from anything must read clear, or the instrument is broken rather than the level.

The guard: never except: pass in a script whose output is your evidence. Print the exception. A swallowed error and a clean result are the same shape.

The kit

ToolWhy this one
Unreal Engine 5.8where all of it lives
The editor's own Slate tickran the bin round at the game's real speed with the game stopped — no intro, no blocked saves, camera stays free
A piloted camera actor17.5 fps for the chase shot, against 4 fps for writing the viewport camera every frame
A human fingerthe only thing that can prove a new key binding works; injecting an action tests the code behind the key, not the key

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.
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.
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.
tickCode that runs every single frame. Cheap once, ruinous sixty times a second across a thousand actors.
SlateThe toolkit the Unreal editor's own interface is built from. It ticks every editor frame, which makes it a way to run something repeatedly without starting the game.
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.
collision hullThe invisible simplified shape used for bumping into things. It is not the shape you can see, which is why invisible walls happen.
traceFiring 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".
pivotThe point a model rotates and scales around, and the point it sits on when placed. A pivot in the wrong place makes an object float or sink.
MetaHumanEpic's system for realistic human characters — face, body, hair and clothing, ready to animate. Every named person in the game is one.
static meshA 3D model that does not bend — a wall, a bin, a bottle. It can be moved as a whole, but its shape is fixed.

Things we still need to fix tomorrow:

  • E now drops a held item on the ground when nothing is focused. It compiles and it has

never been played.

  • If the ground trace misses entirely, a dropped item hangs in mid-air. Both drop paths do

this and neither has a decision behind it yet.

  • The bin crew are doing the round in hi-vis polos. The teal work gear exists and is not

fitted.

  • The on-screen prompt that names the right key is designed and not built, and it was blocked

all day by the rebind it depends on.

  • Nine of the twenty item meshes are under 15 cm and a dropped lighter is genuinely hard to

find. The meshes are the right size; the answer is a highlight, which is not built either.

  • The three-verb interaction has been working since teatime and there is still no video of it.

← 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