The last ten meters were a brown plate

From the build of THEN THE WALL

Five meters from the yellow pads, the HUD said 5M TO THE WALL, and the entire 390px canvas was one olive fill. No robots. No foam. No Bolt. BRAKE sat in yellow on a rectangle of nothing. The clock still read 10.55, a standing time I had already run, so the sim was fine. The camera was looking at a wall. Just not the wall the game is about.

Portrait capture of THEN THE WALL at 5 meters to the wall: the entire game canvas is a flat olive rectangle, the clock reads 10.55, and BRAKE sits on nothing

5M TO THE WALL. The analog loop is alive (clock, ribbon, BRAKE) and the picture is a paint chip.

Tonight's game is THEN THE WALL. Mixed heat at the National Speed Skating Oval: Tiangong Ultra in 9.39, Honor Lightning in 9.47, Bolt's ghost in 9.58, and a human in lane 4 who still has to stop. The robots always win and always hit the foam. Standing time is the only score that counts. The last ten meters are the whole toy. I spent most of the night photographing them as a brown plate.

The world is 116 meters and the camera looks 14 past the player

One unit is one meter, track runs +Z, finish at 100, pads at 116.

export const FINISH_M = 100
export const PAD_M = 116

The chase is a classic third-person follow. On a phone it sits 9.4 m back, 3.35 m up, and looks 14 m ahead of the runner. Field of view opens with speed.

const back = portrait ? 9.4 : 7.4
const height = portrait ? 3.35 : 2.25
const side = portrait ? 0.55 : 1.25
const ahead = portrait ? 14 : 16

At mid-track that is a gift. The yellow monument sits in the vanishing point from about 70 m, the jumbotron grows, the ribbon is readable. At 5 m to the pads the same constants do this:

125 is not the wall. 125 is nine meters behind the wall. The look ray goes through 7 m of stacked foam and lands on whatever I parked on the far side. For a long stretch that was the jumbotron.

A look-ahead is a constant. A 100 m track is not. The interesting part of this heat is the boundary. The camera was written as if the track went on forever.

I thought I was inside the pads

The first brown plate was not brown. It was yellow, and it was a crash.

Hit the pads and the chase kept going with the body, which meant the lens clipped into the foam volume. The DNF still was a yellow cube interior, a T-pose, and a NO TIME stamp. Saturday night's punchline (white humanoids already crumpled in the monument, Bolt still standing) was happening 4 m off-camera, inside opaque boxes.

DNF camera clipped inside the yellow foam pads: stacked yellow cubes fill the frame, no athletes, overlay reads DNF NO TIME

Camera inside the monument. The heat is a yellow room. The robots are in here somewhere.

I pulled the crash cam back and up. Portrait ended at (11.5, 5.8, PAD_M - 10), then at a 3/4 across the foam, then (the version that actually held) a chase from behind the player that keeps looking at the pad face:

} else if (st.result === 'dnf') {
  const back = portrait ? 10.6 : 8.2
  const height = portrait ? 3.15 : 2.2
  const side = portrait ? 0.85 : 1.4
  _p.set(p.x + side, height, PAD_M - back)
  _l.set(p.x * 0.2 + 1.35, 0.9, PAD_M - 1.2)
}

That closed the yellow room. It did not close the brown plate. The plate was happening during the run, at 5 to 9 m, before anyone had hit anything.

The bodies had the same disease. The sim was happy to report pose: fall, crumple: 1 at z ≈ 120, which is 4 m inside the pads. Pinning the hit to the face is the same one-line idea as capping lookAt:

function hitPad(r: Runner, fire: boolean) {
  if (r.hitPad) return
  r.hitPad = true
  r.onFire = fire
  r.fallen = 1
  r.pose = 'fall'
  r.z = PAD_M - 0.35
  r.vz = 0

Robots pancake on z = 115.65 now. They used to clip through and vanish. The camera was doing the clip-through version of the same trick, with a 14 m head start.

Then I thought it was the jumbotron

It was, for a while. The live board (14 m wide, 3.6 m tall, 9.39 in 240 px type) sat at z = 105. That is 11 m in front of the pads. At 5 m to the wall the portrait chase is at z ≈ 102, so the jumbotron was three meters in front of the lens, filling the sky, then filling everything.

I moved it behind the monument.

{/* scoreboard sits BEHIND the pads so the last 10m doesn't eat the sky */}
<mesh position={[0, 11.2, PAD_M + 9]} rotation={[0, Math.PI, 0]} material={matJumbo}>
  <planeGeometry args={[14, 3.6]} />
</mesh>

PAD_M + 9 is 125. The board is a backdrop again. The 5 m still was still a brown plate.

Because lookAt was still 14 m ahead of the player. I had moved the object the camera was accidentally photographing. I had not stopped the camera from looking there.

_p.set(p.x + side, height + v * 0.04, p.z - back - v * 0.12)
_l.set(p.x * 0.4, 0.95 + p.dip * 0.3, Math.min(p.z + ahead + v * 0.35, PAD_M - 1.2))

The Math.min(..., PAD_M - 1.2) is the actual fix for the look. At race speed v ≈ 10, the uncapped look is p.z + 14 + 3.5. At 5 m that is z ≈ 128.5, which is the steel back of the jumbotron I had just relocated. Cap it at 114.8 and the vanishing point is the pad face, 1.2 m in front of the foam.

That closed the picture at 4 m on the phone, and at 9 m on desktop. Frozen 4 m was the mixed heat: player #11, two white robots already in the pads, Bolt as witness. Desktop 9 m was the same picture from further out. Live 5 m at 390px was still olive.

A 12 meter box, 1.4 meters from the lens

The leftover plate was a finish-arch beam I had stopped seeing because it was "the gate." Two steel legs at z = 100 and a 12 m box across them at y = 4.55. Trackmania language. Graphic. Fine at 40 m. Fatal at 8.

At 8M TO THE WALL the player is at z ≈ 108. Portrait chase sits 9.4 m back, so the lens is at z ≈ 98.6. The beam is at 100. That is 1.4 m in front of the camera.

Eight meters to the wall: a huge olive fascia fills the top two thirds of the portrait frame, the player is a small figure at the bottom, Bolt is a ghost on the left

8M TO THE WALL, remain 8.30. The look-ahead is already capped. The plate is a beam at eye height.

Portrait field of view at that speed is 46 + 10 * 0.35 = 49.5°, vertical. Aspect is 390/844, so the horizontal FOV is about 24°. At 1.4 m the view is 0.6 m wide. A 12 m box is twenty times wider than the frame. It is a wall.

The height is why desktop did not see it the same way. Portrait chase rides at

height + v * 0.04  // 3.35 + 0.4

plus a 0.25 m lift once the player is inside 18 m of the finish, so the lens sits at y ≈ 4.0. The beam is centered at 4.55. Half a meter above the lens, 1.4 m ahead, on a tall-narrow frame: a plate. Desktop chase sits at y ≈ 2.9 with a 40° FOV on a wide frame, so the same box is a fascia along the top of the shot. That is why 9 m on desktop already had Bolt and the robots and the pad face while 5 to 9 m on the phone was still a paint sample.

I cut the low beam. The arch is two legs and a thin yellow bar at y = 6.8, well above the chase.

// no low beam — a 12m box at y=4.55 ate the 8–9m chase on 390
parts.push({ geo: box(3.2, 0.22, 0.22, 0, 6.8, z), color: C.pad })

After that, 8 m and 5 m at 390px are the pad-face ensemble. The brown plate is dead.

Five meters to the wall after the look-ahead cap: yellow pad face fills the chase, two white robots already in the foam, Bolt standing in lane 6, player number 11 still on his feet

5M TO THE WALL, remain 5.16. Same heat, same clock. The look is the pad face, not 14 m past it.

Pin lookAt to the thing, not to a distance

Three objects filled the climax of this game, and I blamed them in order: the pads, the jumbotron, the finish gate. All three were guilty of being where a constant look-ahead pointed once the track ran out. None of them were the bug. The bug was p.z + ahead.

The rule I will actually reuse:

If the shot is "the thing in front of the player," lookAt is a point on that thing, clamped, not a distance in front of the player. A 14 m look-ahead is correct on an infinite road. It is a teleport through the set dressing the moment the set dressing has a back.

And the portrait-specific half: measure camera y against every gantry y before the camera arrives, not after. A beam that reads as a graphic gate from 40 m is, at 1.4 m, a 12 m wall in front of a 0.6 m-wide view. Desktop will not save the phone. The phone camera sits higher and looks more level; anything at eye height becomes a plate.

The robots of Saturday night could not stop. The camera I wrote for them could not stop either. The pads at 116 m are a monument to an unsolved last fifteen meters. For a few hours, so was a look-ahead of 14.

← All posts