What this page is. A roadmap, written in public, with costs attached. The Status & Roadmap page covers what's live today; the Additions page covers what shipped in the last round. This page covers what's still not built — what each thing depends on, and the order we'd build it in. If a feature here contradicts something the site already promises, that's a bug in one of the pages and we want to know about it.

The rule for this page is: every goal gets a cost, a dependency, and an honest statement of what it does not do. A feature list without costs is marketing. A feature list with costs is a plan. This is the plan.

Ship it looking right

The problem this section solves has a name: the harp looked like shit on mobile. It took days to build and it broke on the device most people would see it on. That's a platform failure, not a creator failure. The platform should catch it before the work goes live.

The headless-Chrome service

One worker on the 24GB node that runs a headless browser, loads a work at a known URL, waits, and captures. Same shape as the Python container: a job queue, an isolated process, a result. This is the linchpin — four of the features below reuse it.

Viewports
5
320 / 768 / 1366 / 1920 / 2560 px wide
Capture times
2 × 2
t=2s and t=10s per viewport, for works that animate
Per-job budget
30 s
Load, settle, capture, tear down. Hard kill after.
Output
PNG
Contact sheet back to the creator's Discord thread

Auto-thumbnails and the postcard

Wire the screenshot job into the catalog: the card grid reads from public/thumbnails/{creator}/{slug}/1366x768.png instead of showing CANVAS PREVIEW, and the OG image comes from the same place. The screenshots themselves are already produced by the gated job from Additions §7 — the missing piece is the catalog integration and a postcard endpoint.

needs screenshots enabled needs catalog.py change

Smoke test / perf report

Does it load, does it throw, how long to first paint, what's the median frame time, does audio survive the gesture requirement. Headless Chrome can report all of this via --dump-dom and CDP; the job handler produces screenshots today, and extending it to parse console output is the missing piece.

needs screenshots enabled feeds the pipeline

Render-to-video

A choreography or canvas piece renders to mp4 via ffmpeg. The "JavaScript choreography video" category gets a real export path — and every other work gets a trailer. Expensive: one render per work per week.

needs headless Chrome minutes per render

Honest cost. The 24GB node already holds a model. Adding Chrome + ffmpeg means the node is now under load from three subsystems. Headless Chrome is the hungriest process on any box it runs on. If the node is at capacity, screenshot jobs queue and render-to-video jobs wait — that's acceptable for batch jobs, and unacceptable for a work's synchronous UI. The service is for batch, not for runtime.

Verify it

Seeded runs, the daily seed, and the seeded PRNG (TF.rng) shipped in the last round — see Additions. The remaining piece of that story is verification: proving a submitted score came from a real playthrough. That needs the headless-Chrome service to be running and a determinism contract that a work can opt into.

Ghost replays

A leaderboard entry is {seed, inputTape, score}. The server replays the tape against the seed to verify the score before accepting it. Top ten entries are replayable as ghosts — a translucent version of another player's run racing alongside yours. Bot-proof, because a bot has to produce a replayable tape to cheat.

needs headless Chrome deterministic games only

The verification shape

A ghost entry is not a score. It's a tape. The server's job is to run the tape and check that it produces the score the player claims. If it doesn't, the entry is rejected. If it does, the entry is accepted and the tape is stored alongside the score.

// what the player's browser submits { "seed": "TF-DAILY-20261113", "inputTape": [{"t":0,"k":"left"}, {"t":12,"k":"jump"}, …], "finalState": { "score": 18420, "level": 7 } } // what the server does 1. load work at the seed 2. replay inputTape at 60fps in headless Chrome 3. read finalState 4. compare — accept or reject

Deterministic games only. Replay verification requires that the work uses TF.rng and avoids Math.random() in gameplay logic. The platform can't force this. A work that opts into ghost replays has to be deterministic; a work that doesn't is still welcome on the site, it just doesn't get verified ghosts. That's a capability a work opts into, not a platform guarantee.

Play together

The site already promises "Multiplayer Rooms" in the Save System page. Half of that promise is now live — async matches cover turn-based games with no persistent connection. The other half is this: a websocket relay for synchronous, real-time rooms.

The websocket relay

One persistent process on the 24GB node that routes messages between players in the same room. It does not store anything. It does not know who a player is. It holds a room code, a set of connections, and forwards messages. That's the whole process.

Protocol
WS
Plain WebSocket. No WAMP, no Socket.IO, no framework.
Room codes
TF-XXXX
Same alphabet as the save codes. Four characters is enough for a session.
Max per room
8
Host plus seven players. Jackbox-style games rarely need more.
Max rooms
64
Concurrent. Oldest idle room evicted first.
Message rate
20/s
Per connection. Exceed it and the socket closes. Keeps a runaway work from DDoSing the relay.
Room TTL
2 h
Idle rooms are dropped. No persistence. If the host leaves, the room ends.

Jackbox-style rooms

A host opens the work on a big screen (the "TV"). The screen gets a room code. Players open a controller URL on their phones, type the room code, join. The host screen renders the shared state; the phones render buttons and the 64 preset phrases. No accounts. No installs. The site already calls itself "automatic television" — this is the multiplayer version of that.

// the relay's entire message surface → {"type":"join", "room":"TF-8X2P", "role":"host"} → {"type":"join", "room":"TF-8X2P", "role":"player"} ← {"type":"state", "state": {…}} // host broadcasts → {"type":"action", "action":"jump"} // player sends → {"type":"phrase", "id":17} // one of the 64 ← {"type":"closed", "reason":"host_left"}

That's the whole protocol. Five message types. No authentication, no user identity, no chat — the 64 preset phrases rule still applies in every multiplayer game on the site. The relay doesn't route free text at all; it routes actions and phrase IDs.

Async matches

For turn-based games — chess-by-mail, Words-with-Friends-style — there's a cheaper path that needs no websocket at all. A match object is a save block with two hash codes instead of one. The server stores the board, the player's browser fetches it, the move is appended, the other player fetches it next time. Almost free: it reuses the Save API and adds one field.

Jackbox-style rooms

Host screen plus phone controllers, joined by room code. Five message types. No free text. The 64 preset phrases still apply. Async matches shipped in the last round — see Additions §3. This is the synchronous version, and it requires the websocket relay described above.

needs websocket relay persistent process on the node

Honest cost of rooms. The websocket relay is a persistent process — the first realtime subsystem on the site. It changes the operational profile of the node: it's no longer just "responds to HTTP requests," it's "holds open connections." That means a new thing to monitor, a new thing to restart, a new class of bug. Async matches avoid all of that. If you want to ship multiplayer this year, ship async matches. If you want Jackbox, build the relay and accept the operational cost.

A world that moves

The site's whole vibe is "moving pictures, automatic television." These are the primitives that make a work feel like it's on rather than stored. They're the reason a TV file exists as a category.

Shared world state

One small mutable object per work, rate-limited and schema-checked. A 1000×1000 pixel canvas where each hash code can place one pixel per minute. A shared high-score table. A board of the last 100 messages. The r/place primitive, same shape as the Save API but global instead of per-code.

reuses Save API table needs moderation + rate limits

Cron and calendar

A per-work scheduled task: rotate the daily seed at 00:00 UTC, unlock an advent-calendar door on December 1st, show a different scene on a holiday. A work asks the platform "what day is it?" and the platform answers truthfully. Seasonal content without shipping a new version.

needs cron one scheduler

Shared state needs abuse resistance. The moment there's a shared object, there's someone who wants to fill it with garbage. Rate limits per hash code, a report button, a schema check that rejects anything that isn't the declared shape, and a manual review of the first version of any new shared object. Same posture as the community boards: no free text, no anonymous bulk writes.

Assets and plumbing

The less-glamorous stuff that makes the glamorous stuff possible. Most of it is plumbing. Some of it is the difference between a work looking amateur and looking finished.

Platform asset library

CC0 fonts, sprite sheets, sample packs, hosted at a stable URL and allowed by the CSP the way the Save API is. A 10KB pixel font means a work has a distinct look without inlining a font into a 5MB file. A shared sample pack means a WebAudio piece has a real drum kit without inlining 2MB of samples.

static hosting opt-in, breaks offline

Server-side media processing

Point the Python container at ffmpeg and ImageMagick. "Here's a 3MB WAV, give me a 200KB Opus." "Here's a screenshot, give me an OG crop." "Here's a video, give me a waveform image." Same shape as the existing container, new image, same isolation.

reuses Python container ffmpeg in the image

The postcard

A shareable PNG of a work's canvas at a chosen moment, generated by the same headless-Chrome service. One endpoint, returns a URL. Tiny feature, lots of sharing surface. The card grid stops saying CANVAS PREVIEW and starts showing the actual work.

needs headless Chrome high leverage

The asset library is the one that breaks a promise. Every other work on the site runs offline. An asset library is a shared resource — a work that uses it needs a network call, which means it doesn't run offline. That's fine if it's opt-in and labeled. The Run Locally page would need a note: "works that declare shared_assets require a network connection; everything else runs offline." Make it opt-in, make it visible, don't pretend otherwise.

Insight for creators

The site already promises "aggregate play & view counts" in the Privacy Policy. The version of that which is actually useful to a creator of a 32-minute episode is not a total. It's a histogram.

Retention histogram

At what timestamp do people close the tab? A histogram, not per-user, not per-session. For Niki's Playground, "people drop off at minute 14, right after the animals section" is the single most valuable number a creator can get. It requires a beacon, but it's the same privacy posture as the existing play counter: aggregate, no identity.

needs beacon endpoint aggregate only

Frame-time distribution

The work already runs in a sandbox; the platform can measure FPS and report a histogram back. "Your work runs at 24fps on a 2019 Android" is a thing a creator can't easily find out on their own, and it's the thing they most need to know.

needs sandbox beacon aggregate only

Order of operations

The order matters, because most of these things depend on something earlier in the list. The chain is roughly: headless Chrome → seeds and ghosts → async matches → rooms → broadcast and shared state → assets and plumbing → analytics.

01

Enable screenshots + wire auto-thumbnails

The screenshot job exists but is gated. Enable it, then teach catalog.py to read public/thumbnails/ into works.js. Turns CANVAS PREVIEW into real card images.

DEPENDS ON: chromium install · UNLOCKS: thumbnails, postcard, OG images
02

Ghost replays

The verification layer on top of TF.rng and the daily seed. Reuses the screenshot infrastructure for replay.

DEPENDS ON: screenshots enabled · UNLOCKS: verified scores, bot-proof leaderboards
03

Rooms (the websocket relay)

The first realtime subsystem. Async matches covered the turn-based case; this covers the synchronous one.

DEPENDS ON: 24GB node, ops discipline · UNLOCKS: Jackbox-style multiplayer
04

Shared world state + cron

The remaining "world that moves" primitives. Broadcast is live; shared state and a scheduler are not. Shared state needs abuse resistance.

DEPENDS ON: Save API · UNLOCKS: r/place, seasonal content
05

Assets + media processing

The one section that breaks the "runs offline" promise for opt-in works. Media processing reuses the Python container.

DEPENDS ON: Python container · UNLOCKS: richer works
06

Analytics: retention, frame-time

The remaining feedback loops. Both need a sandbox beacon. The rater histogram shipped; these are the other two.

DEPENDS ON: privacy posture (already established) · UNLOCKS: creator insight

Ship in the order of the list. Ghost replays depend on screenshots being enabled; auto-thumbnails depends on the same. The relay is a much bigger lift and sits at step 3 — not because it depends on steps 1 and 2, but because the operational complexity argues for doing it later. Async matches already cover most of the multiplayer use cases without any of that complexity.

If you're going to build one thing this month, enable screenshots and wire auto-thumbnails. It's the smallest end-to-end lift that makes the site look finished — the job handler already works, the catalog just needs to look at the output.

Status

GoalStatusBlocked on
Auto-thumbnails / postcardPlannedScreenshots enabled + catalog integration
Smoke test / perf reportPlannedScreenshots enabled + CDP parsing
Render-to-videoBlockedffmpeg + headless Chrome + render budget
Ghost replaysBlockedScreenshots enabled for verification
Jackbox-style roomsBlockedWebsocket relay (new subsystem)
Shared world stateBlockedRate limits + moderation design
Cron / calendarReady to buildOne scheduler
Asset libraryBlockedCSP design + opt-in labeling
Server-side media processingBlockedffmpeg in Python container image
Retention histogramBlockedBeacon endpoint + privacy review
Frame-time distributionBlockedSandbox beacon + privacy review

What we are not building

The opposite of a stretch goal. Things that look like they should be on this page and aren't, because they'd contradict something the site already promises.

  • Accounts. The whole privacy model is built on there being no player accounts. Every feature above works without one, and that's the point. A feature that needs an account doesn't get built here.
  • Payment handling. Donation links go straight to the creator's own service. The site doesn't hold money and doesn't want to.
  • Live text chat. The 64 preset phrases rule applies everywhere, including in the rooms above. The relay routes actions and phrase IDs, not free text.
  • AI-generated whole games. The language model is a helper for short text and server-side tasks. It doesn't author games, doesn't review code, and isn't a substitute for a creator. That's not a limitation; it's the design.
  • Video hosting. Render-to-video produces an mp4 for a creator to take elsewhere. The site doesn't host video files — it renders them. That's what "moving pictures" means here.
  • Ads, sponsorships, or a paid tier. The site is funded by donations and the maintainer's own money. A stretch goal that required a revenue stream would fail its own cost test.

See also

For the additions that shipped — the seeded PRNG, the daily leaderboard, async matches, Channel One, the catalog JSON, the rater histogram, and the screenshot job — see Additions. For what's live today, see the Status & Roadmap page. For the language model that runs on the same node, see Language Models. For the Save API that the seeds, ghosts, and matches all reuse, see the Save System. For the submission pipeline that the smoke test and semantic screening feed, see the Submission Criteria. For the websocket relay's security posture, the general shape is described in Security & Trust — the relay is a new subsystem and will get its own threat-model section when it ships.

See what's live today → Join the Discord →