Next up: docs/handoff-capture-perf.md — the capture pipeline stalls the admin server; three changes, measured.
Admin: I-36
🥇 1st: NH-D15 G2 🥈 2nd: NH-U12A chromax.black 🥉 3rd: NH-D12L --> Rules: we need two tabs "rules" & prizes on that page --> Prizes tab: add the podium like on the dashboard but instead of showing a bean: show a picture of the prize
Saturday from 14:00 -19:00 --> when it's not yet saturday 14:00, can you do a countdown till the game start on the dashboard? --> also add text there: join the discord https://discord.gg/J8msxSh6v and add the Fall Guys role
TODO:
- Check that WITH sound actually works
- How does the "now playing" work? on dashboard vs on Results?
- Test run!
Also?
- Cut the show mp4 per round
- Cut stuff and put them on the website (or youtube?) "HIGHLIGHTS" :D
Scoring and leaderboard for the Fall Guys solo tournament at the FOM LAN party.
Participant list: https://www.fom.be/compos/view/507
Rules handed to the FOM board: docs/rules.md
- Check server is
Europe - Your crown rank on the website? Send me a screenshot!
- Your FOM Name on the website? Send me a PM!
I am: AnotherAccount58
Fall Guys is on a different monitor at every venue, and recording the wrong one is silent until the shows are over.
- Set
event.dateindata/event.jsonto the day being played. - Start the game and put it on the screen it will stay on.
- Record ten seconds of each monitor until one of them is the game:
The admin's Info tab names the folder. Play back the newest
CAPTURE_OUTPUT=0 bun run dev
seg-*.mkvundersegments/<newest>/. - Put the one that worked in
.env, which everybun runreads:echo CAPTURE_OUTPUT=1 >> .env
bun run live, and confirm the header badge says recording.- Play one round and confirm a frame with a trophy pill turns up in the capture panel.
| Achievement | Points |
|---|---|
| Finishing a round first | 3 |
| Qualifying for a show's final round | 1 |
| Winning the final | 5 |
Simultaneous winners split the 5, rounded down.
bun run live # every save is committed and pushed
bun run dev # saves stay on this machine
bun run dev --no-record # and leave the screen alone — see belowBoth serve the admin on http://localhost:3000/admin and the board beside it.
bun run live is the event itself: recording a show writes data/, commits it under
data: record show N — Name and pushes, which rebuilds the published site. bun run dev
writes the same files and stops there.
Nothing is pushed unless data/event.json and data/players.json parse and hold the
shape the board reads. A file that fails is still saved, so no typing is lost, but the
publish is refused with the field named, and the admin carries a banner until it is
fixed. The Commit & push button runs the same check under a message you write.
bun run cli show # start a show
bun run cli round # record a race or survival round
bun run cli final # close the show: final map, finalists, winners
bun run cli penalty # deduct points
bun run cli shows 21 # which shows work at this headcount
bun run cli board # print current standingsEach command commits data/event.json, and pushes when a remote is configured. Add
--no-commit for a dry run.
data/players.json maps in-game names to FOM names. The in-game name is what the board
calls a player; fom is optional and shown under it, for the players who have one.
{ "ingame": "OptiBean", "fom": "Optinux_Prime", "discord": "optinux" }A player without an ingame shows on the leaderboard under their FOM name, on zero, and
cannot be scored.
crownRank is the crown level the game shows beside their in-game name, typed in by hand
in the admin's Players tab:
{ "ingame": "GrumpyTrout294", "fom": "Grumpy_Trout", "crownRank": 45 }The admin runs the event instead of competing, and is marked so they stay off the leaderboard:
{ "fom": "Wouter_Van_Schandevijl", "admin": true }The game's Player.log records every player's result, but against numeric playerIDs
that carry no name and change from show to show. Log parsing was tried and dropped; see
data/logs/2026-09-01-join-order.md. Results go in through the CLI.
data/shows.json holds the minimum and maximum player count for every custom playlist,
taken from the
Fall Guys wiki. A show
the headcount cannot support is skipped. Refresh it if the game changes the numbers.
data/sangu.json is read off Fall Guys @ Sangu: the crowns each
crown level costs, which levels have a clip page there, and which have an icon. A crown badge
carries the first as a tooltip; a round on the results page draws its map with the icon and links
the name out to the clips. Refresh it when a season adds levels or moves the crown track:
bun run sanguThe icons themselves land in site/img/levels/ and are committed, so the board still draws on a
LAN with no way out; bun run sangu only downloads the ones missing. Every map played has an
icon, but a quarter have no clip page yet and those go unlinked.
bun run build
bun run dev # http://localhost:3000/admin
PORT=3100 bun run devThe admin page runs only against the local dev server, never on GitHub Pages. It reads
the Fall Guys log to fill in each show's rounds, player counts and finals, so the only
typing is names. It writes data/players.json and data/event.json directly.
Names already entered are offered as a dropdown on every name field, so a player is typed out once and picked from the list after that.
Commit & push commits data/ and pushes, which rebuilds the public site. Only
data/ is committed, so anything else staged is left alone.
Custom lobby code is published beside it and shown as a badge on every page of the board. It is filled in from what is already published, so clearing the box is what takes the badge down.
× takes a misfired show back off the leaderboard, after a confirmation. Only the last
recorded show has it: a show's slot is its number in the log, and pulling an earlier one
out would re-point every show after it at the wrong log entry. The show's JSON is written
to deleted-show.json in that show's own capture folder first — numbered on collision, so
nothing is ever overwritten. Restoring is pasting it back into data/event.json.
It finds Player.log under AppData/LocalLow/Mediatonic/FallGuys_client for any user
on the C: drive. Set FALLGUYS_LOG to override.
Shows must be recorded in the order they were played: only the next unrecorded show is editable, so its number matches the log.
Player IDs in the log are reassigned every show — even inside one lobby — so the log can never say who did something. See data/logs/2026-09-01-join-order.md.
bun run build # bundle site/ and data/ into dist/
bun run dev # serve dist/ on http://localhost:3000, admin on /adminFive pages, all built from data/ and docs/rules.md:
| Page | What it shows |
|---|---|
| Dashboard | Which show and round is on, who is left in it, the podium, and the rest of the field |
| Standings | Every player with their races, finals, wins and penalties |
| Results | Every show round by round, newest first, with the field coloured |
| Rules | docs/rules.md |
| Show order | The ten shows plus the if-time-allows replays, marked played / playing now / upcoming |
Every show carries its field: gold won it, green got through the last board read, grey is still in
the round on screen, red is out with the round number that did it. The field is the roster —
everyone in players.json who is not an admin and has an ingame name — because a player knocked
out in round 1 is named on no screen at all.
Clicking any of those badges — or anywhere on a standings row — opens that player's whole tournament: one row per show, one cell per round.
| Mark | Means |
|---|---|
| ⚡ | crossed the line first |
| ✓ | named on that round's qualification board |
| ✗ | still in going into the round, absent from its board |
| 👑 | won the show |
| ? | nobody read a board for that round, so it says nothing either way |
| · | the show has no such round, or they were already out |
A show reads Winner, Finalist or Contestant. Only the last show can read Still in: an
earlier show missing its winners was typed in short, not left unfinished.
Every page but the rules refreshes itself every 15 seconds, so nobody has to reload during the event. Players who gained points since the last refresh are ringed in green for a few seconds.
Run from bun run dev, the status ribbon comes from the Fall Guys log over /live.json, so
it names the show and the round on screen before a single result has been typed in. Only
the machine running the game can serve that file; on GitHub Pages it is absent and the
ribbon falls back to event.json — the last show recorded, and the round after the last
one entered.
So the board projected at the LAN should be the local one on http://localhost:3000. The
published site is for everyone else, and is as current as the last push.
Pushing to main publishes dist/ to GitHub Pages via
.github/workflows/pages.yml. Set the Pages source to
"GitHub Actions" in the repository settings.
Shooting by hand is the backup now — see Capturing the screens automatically. It still works exactly as described here, and a hand-shot capture and a frame cut from the recording are treated identically.
ShareX captures the active window with Alt + Print Screen. Rebind under Hotkey
settings → ... → Task = Capture → Active window.
Set Fall Guys to Borderless (Settings → Display → Display Mode, or Alt + Enter).
Exclusive fullscreen captures as a black frame.
Shoot the race finish, the finalists and the winner screen. A final with several winners has no winner screen; shoot the surviving beans instead.
The admin shows them in a panel down the right. Click a round, the finalists block or the winners block and the panel fills with the captures taken during it, so the names can be read off the screen instead of remembered. Click a capture for full resolution.
It reads Documents/ShareX/Screenshots for any user on the C: drive, and inside it only the
month folder the event falls in, taken from date in event.json. Set SHAREX_DIR to
override. Nothing is copied and nothing is committed — the folder is only ever read.
A capture is placed by its modified time against the log's round times, so a file copied into the folder lands wherever its new mtime falls. Anything that matches no round shows under This show, between rounds or Outside every show at the bottom of the panel.
The log stamps every line with a UTC clock and no date, so the day comes from date in
event.json and every time on the admin page is converted to Europe/Brussels. A log started
the day before the event would shift every window.
The admin reads the captures and fills the names it finds into fields that are still blank. Three screens are read:
| Screen | Fills | Read from |
|---|---|---|
| Qualification board | rounds[n].qualified |
the name over every green card |
| Winner screen | winners |
the nameplate under the bean |
| Qualified toast | rounds[n].first |
the pill wearing the gold trophy |
The trophy is what marks first place, not the pill's position — the column does not run in finish order.
The board comes up after every round, and every one of them is read: the survivors go onto the round they were read after. The finalists are the survivors of the round before the final, so they are not stored separately.
Names are matched against the ingame names in data/players.json. Everyone playing the
tournament is registered, so the roster is the answer key rather than a spelling aid: each name
read is given the one roster entry it is closest to, and a name already used on that board is not
offered again. Where two entries are equally close the field is left as read rather than guessed
at — so two players whose in-game names differ by a single character cannot be told apart, which
is worth a glance over players.json once everyone has reported.
A name the roster does not hold goes in as it was read. That is what happens when testing outside the tournament, where players are not registered.
Only blank fields are filled, and a filled field is ringed in green with the capture it came from on hover. Type over one and it is yours — nothing later overwrites it, and clearing it on purpose does not bring the name back.
data/ingame-names.txt is every in-game name the tournament has seen, one per line. It is what
the reading is scored against, and it only ever grows.
bun run scripts/collect-names.ts # from event.json, players.json and the fixtures
pbpaste | bun run scripts/collect-names.ts # and whatever you read off a screen by eyeevent.json cannot tell a name somebody typed from a reading nobody corrected, so a name that
reaches no roster entry goes in exactly as it was read. Run an eye over the file after an evening
of testing outside the tournament.
bun run scripts/ocr-score.tsReads the boards fixtures/manifest.json gives names — read by eye, in
board order — and reports how many names come back exactly, the character error rate, and how many
reach the right roster entry against two rosters. That last pair is what to move:
| Roster | What it stands for |
|---|---|
board |
The board's own names — a lobby where exactly those players were registered |
everyone |
data/ingame-names.txt — the size of pool the event really hands it |
Each board is committed next to the names it is scored against, so the numbers are the same on
every machine. To name a new one, put the capture under fixtures/ and let the reader do the
typing:
bun run scripts/ocr-score.ts --dump auto-7-finalists-005302-2.jpgIt prints what the reader saw, in board order, ready to correct and paste into manifest.json. It
looks under fixtures/, the auto-capture folder and ShareX, so a board still only on this machine
can be dumped before it is committed.
Not read: the lobby's View Names screen, whose nametags follow the beans around in 3D and overlap into pileups in a full lobby, any name not written in the Latin alphabet, and the board while its plate still reads N REMAIN! — see below.
The first run downloads Tesseract's English model, about 5MB, into .ocr-cache/. Do that once
before the event — nothing afterwards needs the network. Read names are cached in the same
folder against each file's modified time, so restarting the server does not re-read everything.
The server records the screen for the whole event and cuts the frames the reader needs out of the recording afterwards, so nothing has to be shot by hand. The screen naming who finished first can be gone in a fraction of a second when a dozen beans qualify together, and no capture that reacts to an event can catch it.
Frames are found by the clock stamp inside the log line, never by when the line arrived, so a log that flushed late still names the right frame. The same recording is cut into one mp4 per show.
One frame per show is kept for the picture rather than for the names: the board after round one while its plate still reads N REMAIN!. Every card is a bean under its own nameplate then — the eliminated only turn into a nameless pink X once the plate goes green — so it is the one screen that shows the whole field. It is filed with the other captures and never read, because before the plate settles every card looks qualified.
CAPTURE_OUTPUT=1 bun run live # records
bun run dev --no-record # does notRecording is on by default, because a show that was not recorded cannot be recovered. It grabs a
whole monitor and writes gigabytes an hour, so --no-record is there for working on the admin.
Either way the console says Recording on with the folder, or off, on every start.
| Setting | Default | What it is |
|---|---|---|
CAPTURE_OUTPUT |
0 |
Which monitor to record, numbered from 0 |
CAPTURE_DIR |
/mnt/c/temp/FallGuysCapture |
Where segments, clips and frames go |
CAPTURE_AUDIO |
virtual-audio-capturer |
dshow device to record sound from; off for silent |
FFMPEG_PATH |
/mnt/c/Program Files/ShareX/ffmpeg.exe |
ShareX ships the one this uses |
The header carries a recording badge. If it reads NOT RECORDING, nothing is being captured
and Alt + Print Screen is the only thing still working.
Everything one show produced lands in one folder, CAPTURE_DIR/shows/show-2026-09-02T23h25-solos-4,
named for the clock its first round loaded on:
| In the folder | What it is |
|---|---|
2026-09-02-show-04-solos-4.mp4 |
the show, cut from the first round to just past the victory screen without re-encoding |
transcript.txt |
that show's transcript lines, and no others |
round-01-first-race-finisher-01.jpg |
the frames, named for the round and what they show |
Rounds are numbered from 1; the winner screen is filed under the final's number. The whole evening's
transcript stays at CAPTURE_DIR/2026-09-02.transcript.txt.
The raw recording sits under CAPTURE_DIR/segments in one folder per run, stamped
2026-09-02T21h41m03: every start of the server, and every recovery from an ffmpeg that died
mid-event, gets its own, so nothing ever writes over footage that is already there. A clip cut
across such a recovery has a jump in it, and the console says which one. Nothing is cleaned up:
CAPTURE_DIR is yours to empty. A static desktop runs about 1.7 Mbps, so budget a few gigabytes an
hour and leave 30GB free.
- Install
virtual-audio-capturer(from the screen-capture-recorder installer) and confirm ffmpeg lists it:Without it the recording still happens, silently, and the badge says"/mnt/c/Program Files/ShareX/ffmpeg.exe" -list_devices true -f dshow -i dummy
recording — no sound. - Pick the monitor and prove the captures work: Tournament day.
This machine has no NVENC or AMF encoder, so the recording uses Quick Sync (h264_qsv).
bun install
bun test
bun run typecheck