Second Chance GazetteDevlogToken Use

The snooker pocket is sealed shut and the ray probe says it is open

blenderunreal-enginesnookergeometrymaterialsverificationsilent-failure

The arrow on the screenshot

Second Chance has a snooker hall in it. It is called the Baulk Line, it is eight tables in a room off a Midlands high street, and the playable one is built to the WPBSA book to the millimetre – bed, cushion set, pocket fall lines, the D. The ball physics underneath it slides, rolls, spins and throws, and it has been checked against a separate reference model in sixty-one places. You walk in, press E on a cue lying on the rail, and pick it up.

James sent a screenshot with an arrow drawn on it. Slight problem with the pockets that needs fixing so the ball will be able to go down the corner pockets. The arrow circled a triangular spur of green sitting where the mouth of the pocket ought to be.

He was right about the picture and wrong about the game, and it took most of an evening to establish which was which. The balls had been going down that pocket the whole time.

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.
The Baulk Line, late on. The cue is on the near cushion and the corner mouths are open – this is the table after the fix.

It was a Monday. The first script of the day ran before seven in the morning and the last one at twenty past eleven at night, and in between it went sideways into a gazette, a lodge full of men in waistcoats, and a golf club. The snooker took the evening.

The probe that fired off the table

The first instinct was that this was a material fault – that the pass which clothed the cushions in green had caught the pocket jaws as well, and painted cloth over a hole. That reading had been written into the handover the night before, with the geometry fix listed underneath it as a fallback, if the gap measurement says the mouth is genuinely pinched.

Nobody had taken the gap measurement.

When it was taken it came back badly. At ball-centre height the clear width between the two jaw cheeks fell in a straight line from 9.382 cm on the fall line to 0.031 cm five centimetres later. A snooker ball needs 5.250 cm. The two cheeks converge at 43° and genuinely cross each other about 4.6 cm past the mouth. The pocket was not pinched. It was shut.

And the material theory would have made a correct surface wrong. James had a reference photograph of a real pocket on a real table, and on a real table those jaw cheeks are green cloth, tapering to two points exactly at the fall points with the hole open beyond them. Recolouring them would have left the complaint standing and introduced a second defect underneath it. Look at the reference before you trust a diagnosis written without one.

The reason nobody had measured it was that something had already said it was fine. A probe had been fired across each pocket mouth and had come back with a clean table of results: open, open, open, with plausible distances of 35 to 59 cm. It was firing from the foot of the fall plane measured from the model's origin, which at a corner puts the start point at (130.6, 130.6) – off the table, in the room. Every ray escaped into open air and reported clearance.

Corrected, it still lied, and this is the worse half: once a ray leaves the mesh without hitting anything, "escaped" and "found clearance" are the same word. The instrument agreed with the bug twice, and both times the agreement read as a finding.

The thing that settled it was not a ray at all. Walk along the pocket axis in quarter-centimetre steps and ask, at each step, whether any surface at all is within one ball radius of where the ball's centre would be. That question cannot be aimed wrongly and cannot escape. It found solid mesh from one centimetre past the fall line onward. It ran with a control in front of it – a ball centre over open cloth, which must read exactly one radius, the baize underneath it and nothing else – and the control is what made the answer worth anything.

The game had been right all along

Here is the part worth carrying away from it. The balls are driven by the solver, not by collision, and the solver pots a ball the instant its centre crosses the fall line. The sealed region was entirely beyond that line. No live ball had ever been out there. Every ball that had ever been struck at that pocket had gone down it.

So the fault was real, and it was cosmetic, and the fix was to stop drawing the thing. The eight corner jaws start exactly on their fall lines and run outward from them, so clipping the drawn mesh at the fall line removes them completely – ten of eighteen cushion segments swept out of the visible model. The collision contract is untouched: all eighteen are still there, the physics still bounces off all eighteen, and none of the sixty-one checks had to be run again. That is the entire reason to fix a thing on the art side when you can.

The four middle jaws were deliberately left alone. They diverge either side of their pocket instead of converging, they measure a constant 9.600 cm of clear width, and they are the visible shoulders of a middle pocket. A sweep that caught them would have been a sweep that was wrong.

There is a gate on it now, run on every build, walking all six pocket axes with the bare-cloth control in front of it. Six pockets, zero blocked.

Rockstar have a department that measures things like this before they get modelled. He had a Monday evening and a reference photo of a pocket.

Four faults, four instruments, one afternoon

The pocket was not the only one that day, and the pattern held every time.

The baize had looked like plastic for weeks. The obvious diagnosis was that it was too shiny, so the first thing done was to go and look: roughness was already at 0.92. Turning it up would have achieved nothing. What it lacked was grain, a normal and a sheen – one flat colour stretched over three and a half square metres, which the eye calls plastic whatever the number says.

A close view along the bed of a snooker table. The baulk line and the curve of the D are marked in white on the green cloth, with the green, brown, white and yellow balls sitting on their spots along it, the blue on the centre spot, and the racked reds and the black away to the right. A cue rests on the near cushion, and the weave of the cloth is visible across the whole bed.
The bed with the new cloth on it, and the baulk line and the D where the rulebook puts them.

Then the new material refused to compile for the entire session while every check said it had. The validator reported zero issues. The connection graph read back perfectly, node by node. The preview sphere came back grey, which is easy to wave off as a stale thumbnail and is not – a grey preview sphere is the default material, which is what the engine falls back to when yours does not build. The only evidence anywhere was one line in the editor log saying a sampler type was Linear Color and should have been Masks. The fix was a single enum value. Two more from the same hour went the same way, both silent, both in the box below.

Then the camera. Every high framing set up for the screenshots came out at floor level. The call that placed the pawn put it exactly where it was asked and returned True, and gravity pulled it back down before the shutter fired. Four framings were lost to it, and every obvious fix – a higher Z, different placement flags, tracing for the floor – changes nothing, because the placement was never the problem. When a setter returns True and the state is still wrong, stop refining the setter and ask what else is writing that state.

And finally the screenshots themselves. A 5744 × 3824 capture of the hall showed what looked like castellated, stair-stepped geometry along every high-contrast silhouette, worst of all at the cushion ends beside the pocket mouths – which is exactly where a real modelling fault would also show. It is not geometry. The high-resolution capture renders the frame in tiles and restarts temporal accumulation on every tile, so the edges never converge, and at 1:1 the "staircase" resolves into white speckle.

A corner of a snooker table seen at high magnification. The cushion ends step away from the pocket mouth in a hard staircase of blocks rather than a clean taper, the wooden rail and the table legs behind it are tinted faintly green, and a scatter of fine white specks runs along every edge where a bright surface meets a dark one.
The capture artefact that reads as a modelling defect: blocky cushion ends, white speckle down every bright edge, and a green tint on the woodwork that is light bouncing off the cloth.

The faint green wash on the rail and the table legs in that frame is real, and it is not a mis-bound material either – it is light bouncing off the baize. The mesh carries no cloth below the rail at all, and the tops of the legs are green while their bottoms stay mahogany, which is what bounce does and what a wrongly-bound material slot cannot do.

Judge edges from an ordinary viewport grab. Never from a high-resolution capture.

Killing Q

The other thing that happened that day was smaller and probably matters more.

*"Q seems to be another unnecessary key. Can we stick to E to pick up or put down, U to use,

I to stash in inventory?"*

Golf had been sitting on Q because Q was free. It is on E now, with the Q node deleted and every key store checked to confirm it is gone from all three. There are four verbs in this game and they are meant to be the only four a player ever learns: E to pick up or put down, U to use the thing you are looking at, I to stash it, C to consume it.

That sounds like tidying and it is really the argument for the whole game. Everything in Second Chance is interactive and everywhere is free roam from the start – there is no mission that hands you a control scheme for one scene and takes it away afterwards, because there are no missions in that sense. The player is never railroaded. A world where you can pick up anything only works if picking up anything is the same press everywhere, and every key that gets added for one feature is a tax paid by every other feature for ever.

The splice that did it is honest about what it costs, and the cost is real: golf's E now runs in front of the resolver that decides what you are actually looking at, so during a round an E press aimed at something takeable would draw the club as well as take the item. There is nothing takeable on a golf course today, so it is harmless today. Its proper home is the other branch of the resolver and that is where it goes when the bag becomes a thing you can look at.

An opponent that does not exist yet

By the end of the night the rules engine underneath the snooker was finished: scoring, the re-spot cascade, the foul values, the whole shot resolution, checked against a separate oracle nearly thirty thousand times over whole frames, with the adapter that joins it to the physics built on top and the scoreboard built on top of that.

Not one line of it has ever executed. It has all been proved off disk, against a model, without the game running once. A frame of snooker has still never been scored in play, and the next thing that happens to any of it is somebody pressing E on a cue and finding out.

The oracle caught one genuine rules bug before that, and it also produced one failure where the code was right and the test was wrong: it blocked the black spot and expected the ball to fall back to the pink spot, but the pink was still sitting on the pink spot, so the spot was correctly occupied and the ball correctly marched on up the table. A fallback test has to free the slot it expects the fallback to choose. Fixing the test rather than the code is the right ending, and it is worth saying out loud, because the reflex is the other way round.

The reason all of that exists is the opponent, and the opponent is not built. There is a plan, there is a table measured to the rulebook, there are rules checked claim by claim against the primary text – 298 sourced claims, forty of them put to an adversarial reviewer, of which thirty-four came back corrected or refuted – and there is no brain and no body. He will play for money, he will play by the same verified physics and the same rules the player is bound by, and he will be built to behave like a man with intentions rather than a difficulty slider. Most game opponents cheat because the world underneath them is not solid enough to let them play honestly. This one will not have that excuse.

Work in progress. A demo video is coming.

The research underneath all of it – the table geometry against the rulebook, the physics constants, the sealed pocket, and what the rules actually say once you stop reading pre-2019 quotes – is written up at secondchancegame.com/snooker.

And the rest of the argument, briefly: almost every open-world game is Los Angeles or somewhere doing an impression of it. GTA 6 will be the best-looking version of that there has ever been. This is a snooker hall off a Midlands high street where the D is in the right place.

The fault

The corner pocket is sealed shut and the ray probe reports it open

A corner pocket mouth measured 9.382 cm of clear width at ball-centre height on the fall line and 0.031 cm five centimetres later. The two jaw cheeks converge at 43° and cross. A ball needs 5.250 cm.

The probe that had reported the mouth open lied twice:

  1. It fired from the foot of the fall plane measured from the model origin. At a corner

that start point is (130.6, 130.6) – off the table entirely. Every ray escaped into the room and the output was a clean table of "open" with plausible 35–59 cm distances.

  1. Corrected to fire along the axis through the pocket centre, it still said "open" wherever a

ray had merely left the mesh without hitting anything. "Escaped" and "found clearance" print the same word.

The balls were never affected. The solver pots a ball the moment its centre crosses the fall line, and the sealed region is entirely past it. Nothing live had ever been out there. The game was behaving correctly and showing the player that it was not.

The fix

Sweep the drawn geometry at the fall line, and measure with a nearest-surface query and a control

The eight corner jaws begin exactly on their fall lines and run outward, so clipping the drawn mesh at the fall line removes them entirely – 10 of 18 cushion segments dropped from the visible model. The collision contract is untouched: all eighteen still exist and the physics still collides against all eighteen, so none of the 61 physics checks had to be re-run and no class default was wiped. The four middle jaws are excluded on purpose – they diverge, measure a constant 9.600 cm, and are the visible shoulders of a middle pocket.

The instrument to believe is not a ray. Ask "is any surface within one ball radius of where the ball centre would be", stepped along the pocket axis at 0.25 cm. It cannot be aimed wrongly and cannot escape.

The guard, now run on every build: sweep all six pocket axes from the fall line to the pocket centre, fail if anything is within a ball radius, with a bare-cloth control in front of it – a ball centre over open baize must read exactly one radius. The control fails over a marking spot, because the disc stands 0.03 cm proud; that is the test working, not a bug.

The fault

The material compiles clean in every readback and renders grey

validate_material returned zero issues. The connection graph read back perfectly, node by node. The preview sphere came back grey.

A grey preview sphere is the default material, not a stale thumbnail – it is what the engine substitutes when yours fails to build. The only evidence anywhere was one line in the editor log:

(Node TextureSample) Sampler type is Linear Color, should be Masks for T_Roulette_FeltM
Failed to compile Material for platform PCD3D_SM6, Default Material will be used in game.

The fix

Grep the editor log before touching the material graph

One enum: SAMPLERTYPE_MASKS. Three sibling traps from the same hour, all silent:

  • The texture-coordinate pin is called UVs in the API and prints back as Coordinates in a

readback, and connect_material_expressions returns False for a name it cannot find rather than raising. The material then compiles happily sampling at 1× and the only symptom is grain twenty times too coarse. Try UVs/Coordinates/UV and fail loudly if none takes.

  • A bare try/except around expression_collection – which does not exist on UE 5.8 – ate the

clear-the-graph step, so a re-run appended a second complete copy of every node. It still compiled, because the outputs pointed at the newer set. An orphaned duplicate graph is invisible to every readback that matters. Use the engine's delete-all call.

  • Reparenting a material instance does not drop the old parent's overrides. They come across

inert, and the readback then looks like a material driven by values it is ignoring.

The guard: when a material looks wrong, read the editor log before opening the graph.

The fault

The pawn teleports to the camera position, returns True, and ends up on the floor

Every high framing collapsed to floor level. The placement call put the pawn exactly where it was asked and returned True, and gravity pulled it back down before the shutter fired. It reads as a teleport that silently did nothing, so every obvious fix – a bigger Z, different placement flags, tracing for the floor – changes nothing. Four framings were lost before it was spotted.

The fix

Disable gravity before placing, then read the Z back and refuse the shot

Set gravity_scale to 0 and the movement mode to MOVE_FLYING before placing, then read the Z back and refuse to take the shot if it moved.

The general guard: when a setter returns True and the state is still wrong, stop refining the setter and ask what else is writing that state.

⚠ And do not judge mesh silhouettes from a high-resolution capture at all. It renders in tiles and restarts temporal accumulation on each one, so edges never converge and the result reads as castellated geometry – worst exactly where a real modelling fault would show. Take a normal viewport grab instead.

The kit

ToolWhy this one
Blender, headlesswhere the table is authored, and where the pocket could be measured with no engine in the way
A nearest-surface query with a controlthe only instrument that day that could not be aimed wrongly
An oracle in Python29,832 checks over whole frames, with the game never once running
A photograph of a real pocketkilled a plausible fix that would have introduced a second fault

Plain English

TermWhat it means
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".
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.
materialThe recipe describing how a surface looks: colour, roughness, shininess, transparency.
material instanceA copy of a material with some settings changed. Cheap to make and cheap to render, so most surfaces are instances rather than originals.
material slotA numbered surface on a model that takes its own material. One mesh can have several — brick on one, roof tiles on another — and the numbering is how a script says which is which.
UVThe flat pattern that says which part of a 2D image lands on which part of a 3D model. Wrong UVs mean the texture arrives stretched or in the wrong place.
shaderThe small program that actually draws a surface. New materials must be compiled before they appear, which is why a change can take minutes to show.
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.
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.
CDOThe template a Blueprint stamps out copies from. Editing the template does not always reach the copies already placed in the level, which is a classic silent failure.
anti-aliasingSmoothing the jagged edges of a rendered image. Expensive, and the higher settings often cost far more than they visibly gain.
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.
LumenUnreal's lighting system for bounced light and reflections, worked out live rather than baked beforehand.

Things we still need to fix tomorrow:

  • Not one line of the snooker rules engine has ever executed. Proved against an oracle, unproved

in play.

  • The physics still collides against eight corner jaws that are no longer drawn. If a cushion

response ever pushes a ball back across the fall line before the pot test sees it, a ball will visibly rattle off nothing. Not observed – flagged because the art and the collision model now deliberately disagree out there.

  • The balls still read as hovering slightly above the cloth. The contact shadow is set and saved

and cannot be judged in this editor: the viewport runs at Low, where contact shadows do not render, and turning it up asked the 8 GB card for 15.3 GB.

  • Golf's E runs in front of the focus resolver. Harmless until there is something takeable on a

golf course.

  • The snooker opponent is not built. No brain, no body, no frame ever played against anybody.
  • There is still no video of any 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