VisionLab — A Soccer Vision Index from 360° Game Moments

VisionLab turns a soccer player’s reading of the game into a number, and shows its working. The player is put inside a stadium at the eye point of a real position — goalkeeper, centre back, winger, striker — with the moment frozen around them. They can turn a full 360 degrees, the clock is running, and there is one question to answer about what the picture gives them. Eleven moments, one from every position, and a Soccer Vision Index built from five measured pillars: visual scanning, decision quality, anticipation, spatial awareness and pattern recognition.

It is a coaching tool and a demonstration, not a verdict. It does not measure eyesight, it does not diagnose anything, and it does not predict what a player will become. The percentile that comes back is printed with its comparison group and its size, and while the pool is still synthetic the screen says so in the same box as the number.

Tools and approach

  • BUILT WITH: Node 24 and its built-in SQLite — no framework, no build step, no npm dependencies at all. Three.js draws one inverted sphere. The panorama the sphere carries is baked ahead of time by a pure-JavaScript renderer (no canvas, no GPU, no headless browser), so the whole app is HTML, CSS, a handful of ES modules and eleven PNGs totalling 2.3 MB. The image is about 60 MB.
  • THE COORDINATES ARE THE ONLY SOURCE OF TRUTH: a puzzle is one file — 22 bodies, the ball, the eye point and the starting look. The panorama, the bottom-right tactical map and the scoring all read that same file, so the map cannot disagree with the 360 view about where a player is standing. Every bearing in the codebase is atan2(dx, dz) in degrees, 0 straight at the opponent goal, +90 to the player’s right.
  • THE 360 ITSELF: SphereGeometry with the geometry inverted (scale(-1, 1, 1)) and the camera pinned at the centre, exactly as the design document specifies. Drag, arrow keys or a finger on a phone. Only the current panorama and the next one are ever loaded, and the texture of the one you left is disposed — on a 4 GB server and a mid-range phone, holding 110 equirectangular textures is how this app would crash.
  • THE SCORING: scanning 35%, decision 25%, anticipation 15%, spatial 15%, pattern 10%. Version 1 is face value — a correct answer is 100, a wrong answer is 0, with no luck mitigation — but the other four pillars are measured from behaviour, not from the answer: how much of the world was examined, how near the gaze came to the bearing the answer lives on, how long it stayed there, and how early a correct call arrived inside the clock.
  • THE DATA: anonymous by default. An age band, a position and a random player code. Per puzzle the database keeps the chosen answer, the response time, the head-orientation trail sampled ten times a second, the scan count and whether the tactical map was opened. A display-name field exists and is switched off; turning it on needs no migration.
  • WHERE IT RUNS: its own container on the same Hetzner box as this site, behind the same Traefik router and Cloudflare, at visionlab.nashvilleaiclub.com. One process, one SQLite file on a bind mount, a health check, and a deploy script that builds on the server.
  • THE PERCENTILE: filtered by age band and position, as the design document requires. The pool today is 36 synthetic calibration rows per age-and-position cell plus every real completed session, and the result screen prints both counts next to the standing.
VisionLab puzzle screen: a 360 degree stadium view from the goalkeeper's position, the question and four answers, and the tactical map open on the right
A goalkeeper’s moment, with the tactical map open. The map is drawn from the same coordinates as the panorama, so the two views cannot disagree.

The question is deliberately not answerable from the starting look. Each scenario points the camera at the ball and puts the evidence somewhere else — over a shoulder, behind the press, on the far side of a shifted back four — so the only way to score well is to turn your head. That is the whole premise: the index is meant to reward the player who scans before the ball arrives, and the scoring treats a still head as a low-scanning session even when the answer is right.

Review

THE GOOD: the panorama and the tactical map cannot drift apart, because neither is an asset — both are functions of the same coordinates. That also collapsed a 110-image problem into a data problem: a new puzzle is a scene, not a photoshoot. The second thing that earned its keep is the mapping check. A panorama on an inside-out sphere can be silently mirrored left-to-right, and nothing about a screenshot tells you it is wrong. So the browser harness points the camera at a known body’s bearing and reads the pixels at the centre of the screen: they have to be that body’s kit colour, and nothing of that colour may sit at the opposite bearing. That is the check that would have caught the bug that looks fine in a picture.

THE BAD: every authored puzzle put its correct answer first. In the app that is invisible — four buttons are four buttons — but a player who notices it has beaten the test without reading the pitch. Options are now shuffled per session from a seed, and a test fails a session where the right answer lands in fewer than three different slots. The second one cost a deploy: the first re-render of the panoramas changed nothing on the live site, because the images are served immutable for a year and Cloudflare kept the old ones. The fix is the estate’s standard one — a build stamp on every asset URL — and it is now a rule in the deploy script rather than something to remember.

TO IMPROVE: eleven live puzzles, not 110. The schema, the queue and the scoring are built for 110 slots — ten per position, numbered one to 110 — and ninety-nine are reserved; the honest way to fill them is authored scenarios, not jittered copies of these eleven. The calibration pool is synthetic and labelled as such; it is a placeholder until real sessions exist, and a percentile against 36 rows is a provisional standing, not a ranking. And the panorama is stylised rather than photographic: it is legible, it is consistent, and it is nowhere near the picture a real 360 camera would give you.

What we learned

A cognitive score is mostly a process score. If the index counted only correct answers it would rank a lucky click the same as a scan. The pillars that carry half the weight here are measured from what the head did — how far it travelled, how near it came to the cue, how long it stayed — and writing tests for those meant asserting relationships rather than numbers: scanning more of the right arc can never score lower, a fast wrong answer can never earn anticipation credit, and a session of correct answers alone cannot reach a top score.

One source of truth is cheaper than any amount of consistency checking. The same coordinates produce the picture you look at, the map you check it against and the bearing the score is measured from. Nothing reconciles, because nothing is duplicated.

A number that ranks a player has to show its comparison group. “Top 5%” next to a synthetic pool is a claim about a pool that does not exist yet, so the result screen names the pool, its size and its composition in the same box as the standing. That is not modesty; without it the number is unusable in the only conversation it exists for — a coach talking to a player and a parent.

VisionLab result screen: Soccer Vision Index 89.3 out of 100, a percentile note naming the comparison pool, five pillar bars with their weights, and a per-puzzle debrief with the cue for each moment
The result: the index, the five pillars with their weights, the standing with its comparison pool named, and a debrief that explains where the answer was in every moment.