The one bet that made it possible
There are two ways to make a game look painted. The tempting one is a screen filter — run the finished frame through a Kuwahara or oil-paint pass. It looks great in a screenshot and falls apart in motion: smear a moving image and the brushstrokes crawl, swimming across surfaces as the camera moves. At race speed that's nauseating.
The other way — the one Valve took for Team Fortress 2 and Rare took for Sea of Thieves — is to bake the paint into the materials and surfaces, not the screen. A brushstroke that lives on a hull moves with the hull, so it's rock-steady no matter how fast you fly past. We made that same bet early, and everything in this chapter is only possible because of it: you can't spend a "contrast budget" on a screen filter, because a filter doesn't know what's a boost pad and what's a wave.
A contrast budget
Painterly's superpower over photoreal is that you decide where the lights, the darks, and the saturation go. So we treat contrast and saturation as a budget and refuse to overspend it on scenery. The water, the drowned skyline, the props, the sky — all of it is deliberately desaturated and held in a narrow value range. The world is handsome but quiet.
That restraint is the whole trick. Because nothing in the environment is allowed to shout, the moment something does — a saturated hue, a hard edge of light — your eye snaps to it for free, before you've consciously parsed anything. We spend the entire saturated end of the palette on a tiny, reserved set of gameplay signals, and forbid those exact hues in environment art. A boost-cyan in a building's albedo would be a lie the eye has been trained to chase.
Try it in the demo. The scene below is the muted world band — teal water, slate towers, two dull bikes — with three gameplay tokens floating in it. Turn Light the signals off and the tokens sink into the scenery; you have to hunt for them. Turn it back on and your eye is yanked straight to them. That gap is the budget doing its job.
signal-colors.ts vocabulary.
A small, sacred vocabulary
A budget is only worth holding if you spend it consistently. So the signals live in one locked module — a handful of tokens the player learns once and then parses everywhere. Boost is cyan. A pickup is magenta. A hazard is a red-leaning amber. The ideal racing line is green. A full drift charge is violet. Every slice that draws a signal — the bike rims, the racing-line ribbon, the HUD, the particle bursts — imports the same frozen token rather than hard-coding a hex, so a colour means the same thing no matter where it surfaces. The readouts in the demo are pulled live from that module; the hexes you see are the hexes the game ships.
Colour is never the only cue
Leaning on colour alone breaks for a lot of players and in a lot of situations, so two rules are baked into the vocabulary itself.
- Blue/orange, not red/green. Red-green is the most common form of colour-blindness, so the load-bearing axis — the drift-charge ladder — runs blue → orange → violet, and the green racing line is never the sole cue against a red hazard.
- Every signal is double-coded — colour and shape and motion. Boost is a forward-lunging chevron; a pickup is a pulsing ring; a hazard is an angular shape that telegraphs with a deliberate blink. That redundancy is what lets a signal survive grayscale, colour-blindness, and peripheral vision — where colour acuity collapses but motion does not.
Flip the demo to Grayscale — the "peripheral vision" test. The hues are gone, yet you can still tell the boost from the hazard from the pickup, because the shape and the motion were doing half the work all along. A signal that only reads in colour isn't a signal; it's a guess.
The drift ladder was the first customer
Back in Chapter 04, the drift charge built through coloured rear sparks — blue, then orange, then a top-tier flash. Those colours started life as ad-hoc hexes picked by eye. When the vocabulary got formalised, the ladder was the obvious first thing to fold in: it already used the blue→orange axis, it just needed to ramp spark density too so the stage reads in grayscale and in the corner of your eye. Drive the Charge stage control in the demo and watch the player bike's sparks climb the same ladder the HUD and the boost burst use.
Style becomes the HUD
Once the budget and the vocabulary exist, you can start deleting actual interface. Two examples shipped on exactly this idea:
- Rim-as-signal. Drafting matters in a pack, but a number on the HUD is something you have to look away to read. So a rival you're drafting rims cyan, and a rival drafting you rims warm — the additive rim from the re-light, tinted by a signal token. The threat is legible in your peripheral vision, on the very object it's about, with no widget at all.
- The flow ribbon. The ideal line down a track is painted onto the world as a green ribbon with brushstrokes that scroll forward and a chevron in its silhouette — a racing line that's part of the scenery instead of an overlay floating on top of it.
The lesson
The portable idea here isn't "use cyan for boost." It's the discipline underneath: decide what the player must read at speed, then engineer the entire frame so those things — and only those things — are the loudest signal in it. That's a budget you spend, an art rule you enforce on your own environment artists, and a tiny vocabulary you never let grow. The payoff is a game you can read at a glance and a look that's doing two jobs at once.
Read the code
The locked vocabulary — colours, shapes, motions, and the rules that govern them — is
src/engine/render/signal-colors.ts (imported directly by this demo). The
rim-as-signal wiring lives in
signal-state.ts, the racing line in
racing-line-ribbon.ts, and the illustrative re-light in
illustrative-lighting.ts. The full thesis and research is
docs/painterly-legibility-plan.md, sitting on top of the look bible in
docs/art-direction.md.