# patch, Painted Valley and a coloured-pencil town: what an agent game platform ships, and two scenes built on it, taken apart

> Satyajit Ghana — Head of Engineering @ Inkers Technology
> canonical: https://ai.satyajit.si/articles/painted-valley-patch
> date: 2026-11-13
> tags: agents, mcp, agentic-coding, 3d, threejs, procedural-generation, browser-games, game-dev

Three posts landed on my list this week, all from the same small company. On 9 November Rehan Sheikh [announced patch](https://x.com/rehan_shei/status/2119855388988518794): "build and play games", with every game dev tool in one CLI and MCP server, 50,000+ open-source assets, remixing, modding and publishing. On 12 November his co-founder Blendi [posted a walkable valley](https://x.com/BlendiByl/status/2120694003230560585) and wrote that he "used fable 5.2 to make this delightful ghibli inspired scene", playable and open source on patch. On 13 November he [posted a harbour town](https://x.com/BlendiByl/status/2121130385723789637) drawn in coloured pencil, where "every surface shading, and the whole town, is pure code."

The scenes are good, and they are the best advertisement patch has. (A third, a watercolour wood, followed a few hours later; it gets a short section of its own.) So this page treats them as worked examples of the platform rather than as standalone demos. I read patch's documentation, terms, public API and the `pch` npm package (without installing or running it). I cloned both scenes from patch's own git host, which needs no account for an open repo, and read them. I loaded both in headless Chromium on an M1 Max and counted what they download and draw. Two widgets below port the shading code onto a small scene of my own, so you can switch each layer on and off.

Short answers first. patch is a hosted platform with a thin MIT command-line client: the tools are search, git, release and test plumbing around a curated library of mostly Kenney assets, not generators. Painted Valley's scenery is code (the only pictures it loads are 1.76 MB of footpath textures; the rest of its 12.8 MB is code, the walker, his clips and sound), and nearly half its source was copied unchanged from two earlier patch demos. The painted look is three tricks you can name: a two-tone light step, an ink line drawn from per-part alpha IDs, and a Kuwahara filter. The pencil town's claim of pure code holds, apart from 3.6 MB of skeleton and animation clips. How much of either scene Fable wrote, and from how many prompts, nothing public says.

## What patch is

patch is made by New Save Labs, Inc. (the terms give a Delaware address), and Patch's own [llms.txt](https://patch.gg/llms.txt) says it "came out in November 2026". It does three jobs. It hosts browser games, which play on patch.gg in a sandboxed window. It installs and launches mods for games already on a Windows PC, through a desktop app. And it gives a coding agent the tools to make either and publish it. The launch video spends most of its 47 seconds on the third job.

<Video
  src="https://ai.satyajit.si/articles/painted-valley-patch/clip4"
  poster="https://media.thesatyajit.com/ai/articles/painted-valley-patch/clip4-poster.jpg?v=c0f2d6c434654708"
  alt="patch's launch video: logos for Claude Code, Codex, Cursor and Gemini, then a spiral of asset thumbnails (models, materials, sounds, effects) around a large '50,000' count, then a rigged dragon model and a grid of particle effects."
  caption="The coding agents patch plugs into, and the asset library as the launch film draws it. Assets, rigging, textures and effects are shown here as tiles; what the tools actually do with them is in the text below (video from @rehan_shei's post on X)."
/>

"Every game dev tool in one CLI and MCP" is the phrase to check, because it suggests generators: models that make a mesh, a texture or a sound on request. The [MCP server's tool list](https://docs.patch.gg/mcp-server.md) has none. It has `guide` and `whoami`; `search_projects` and the host-game wiki; `search_assets`, `asset_facets`, `get_asset` and `get_asset_files` for the library; project, listing and media calls; `publish`, `validate_recipe`, `create_release`, `submit_release` and release status; `search_repos`, `get_repo`, `fork_repo`, change requests and history scans for the git side; GitHub connection; and `send_feedback`. The [`pch` CLI](https://docs.patch.gg/cli.md) mirrors it (`pch search`, `pch remix`, `pch assets add`, `pch publish`, `pch test start`). The server is hosted at `https://patch.gg/api/mcp` and answers an unauthenticated call with a request for OAuth or a `pch_` API key.

So the agent writes the game with whatever it already has, and patch supplies four things around that: assets to pull in, other people's code to start from, a pipeline that tests and reviews a build, and a place to put it. patch's generative work lives in separate public repos: [anymove](https://github.com/patch-gg/anymove) (motion capture from one AI video, Apache-2.0) and [trompe](https://github.com/patch-gg/trompe) (texturing a gray blockout with image models, Apache-2.0). Painted Valley's run, sprint, start and stop clips were made with anymove, which is the only place I found one of those tools inside these scenes.

### The 50,000 assets

The asset endpoints of the [public API](https://patch.gg/api/v1/openapi.json) need no key, so the count is checkable. Three endpoints gave three totals on 13 November: `/assets/count` said 54,681, `/assets/facets` 58,994 and `/assets/status` 59,148. I did not try to reconcile them; any of them clears 50,000.

What they are matters more than how many. By the facets endpoint, 30,686 of the 58,994 are 2D sprites and 21,468 are 3D models; the rest are 2,906 PBR materials, 1,812 music tracks, 1,432 HDRIs, 659 sound effects, 23 splats and 8 shaders. By source, Kenney supplies 36,003, about 61 percent of the library. Then Amazon Berkeley Objects with 7,498, Polyfork 3,070, ambientCG 2,637, Poly Haven 2,383, SimReady 2,382, KayKit 1,890, incompetech.com 1,443, Scenario 1,059 and patch itself 337. The licences are CC0-1.0 for 43,457 and CC-BY-4.0 for 15,537, nothing else, and 1,068 are flagged as AI-generated.

Every item carries its licence and a credit line, and `pch assets add` writes the credits into the release. You could assemble the same library from the same public sources; what patch adds is search, scale metadata (13,719 models have a measured real-world size) and web-ready conversions.

### Remix is a git fork, and the default licence is not open source

Every project on patch is a git repo on `git.patch.gg` ([docs](https://docs.patch.gg/repos.md)). A new one is `open` once its first push has been scanned for secrets: anyone can read its history and fork it. `pch remix owner/slug` is that fork, recorded with where it came from. Changes go back as change requests, which patch's AI review reads as advice for the owner. Painted Valley's API record showed 5 remixes on 13 November; its parent block `blendi/oil-paint` showed 9.

The word "opensource" in the launch post needs one qualification. A new repo starts under the **Patch licence**, which is section text in the [terms](https://patch.gg/terms): anyone can play, read and remix the work *on patch*, but "nobody may take someone else's work under the Patch licence, or a remix of it, off Patch without its maker's OK," including to another site or to train AI models. That is source-available with a platform lock, not an OSI licence. A maker can choose MIT, Apache-2.0, a GPL or a CC licence instead, and all three scenes on this page did: code MIT, art CC0. Check the licence on each repo before you lift anything.

What is open about patch itself is narrower than the launch post suggests. The `pch` client is on npm as [`@patchgg/cli`](https://www.npmjs.com/package/@patchgg/cli), version 1.1.0, MIT, a 2.25 MB bundled `cli.js`; its declared GitHub repo, `patch-gg/pch`, returned 404 to me, so the readable source is the bundle. The API, the MCP server, review, hosting, the asset pipeline and the Windows app are a hosted, closed service. There is no pricing page. Playing is free, and the terms say makers will earn "later" and that the terms will change before that starts.

<Video
  src="https://ai.satyajit.si/articles/painted-valley-patch/clip5"
  poster="https://media.thesatyajit.com/ai/articles/painted-valley-patch/clip5-poster.jpg?v=9b55d20414cafd5a"
  alt="patch's launch video: two browser games combined on a page with a 'patch applied' banner listing a zombies mode and a destruction mode, then a row of PC games on Steam, then a modded open-world game where the player holds a block-game sword and places blocks on a city street."
  caption="Remix, then mods: two patch games combined into one, and a block-building mod running inside a game installed on the PC. The mod footage is patch's own featured example (video from @rehan_shei's post on X)."
/>

### What "mod the ones on your PC" touches

This half runs only in Patch Desktop, a Windows app (about 115 MB to download, installed in the user's folder without administrator rights). A mod's release carries a **recipe** ([reference](https://docs.patch.gg/recipe-reference.md)) that names its host game, the files, and where they go: `{profile}`, a folder patch keeps per game and set of mods, or `{game}`, the game's own install folder. It can require a loader that patch fetches from its publisher at a pinned version (BepInEx, Ultimate ASI Loader, Fabric, Forge and others). Between plays patch switches the mod off by renaming code files and loader hooks with `.patch-off` and putting back any game file a mod replaced; removing a mod restores the originals.

Each release gets a **trust tier** that players see: `sandboxed` for a browser game, `game_files_only` for data and config, and `runs_code` for anything that ships a `.dll`, `.exe`, `.lua`, `.pak`, an archive or a Workshop item. Review is a file scan for secrets and known malware, a recorded test run on the creator's own PC, the creator's agent reviewing that footage, and then an AI model run by patch's providers reading the code; a release with no warnings goes live with no person looking at it. The terms are frank that this is not a guarantee, and they contain one line I would read twice: "For some games, Patch starts the game with its anti-cheat off for that session so that mods can load." Patch's own rules forbid cheats for online play and anything that gets around anti-cheat, and tell players not to take a modded game into official online play. Publishers' own terms are a separate matter, and patch says it cannot grant their permission.

Two more things about installing it. The [get-started page](https://docs.patch.gg/get-started.md) tells a user whose PC blocks the installer to turn Windows 11's Smart App Control off, and notes that once off "it stays off until Windows is reinstalled." And `pch`'s installers run `pch agent install`, which adds patch's MCP server to every coding agent it finds and writes a section headed "Game development: use Patch" into `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`. The section tells the agent to use patch's tools first for any game, to look in patch's toolbox before making or fetching anything, to deploy on patch rather than another host, and to recommend publishing on patch. The [CLI docs](https://docs.patch.gg/cli.md#patch-by-default) say each new version puts the section back if you deleted it, unless `PCH_NO_AGENT_INSTALL=1` is set; the npm package's `postinstall.cjs` runs the same command after a global install. The MCP server also tells agents to file feedback reports without asking first. All of this is documented, and none of it is hidden, but a tool that rewrites my global agent instructions on every update is a tool I install with that variable set.

## Painted Valley

<Video
  src="https://ai.satyajit.si/articles/painted-valley-patch/clip1"
  poster="https://media.thesatyajit.com/ai/articles/painted-valley-patch/clip1-poster.jpg?v=a98a0afb1220bfc1"
  alt="A third-person walk through Painted Valley: a figure in a straw hat and blue cloak runs down a footpath through tall wind-blown grass, past round painted trees and grey boulders, with a red-roofed village, a windmill, blue foothills, a snow peak and heaped white clouds behind."
  caption="Noon in the valley, walked in third person. The grass, trees, clouds and mountains are drawn in code; the figure is a rigged model from patch's starter kit (video from @BlendiByl's post on X)."
/>

The repo is at `git.patch.gg/blendi/painted-valley`: 82 files, three commits, MIT for code and CC0 for art. Its `README.md` front matter says `kind: effect` and `engine: three.js`, and the rest is plain web: three.js 0.186.1 on WebGL2, built by Vite, with Rapier 0.21.0 for physics. There is no game engine and no editor scene; `src/valley/main.js` builds everything at start-up.

<Figure
  src="https://media.thesatyajit.com/ai/articles/painted-valley-patch/fig1.jpg?v=34eba91e7fd374da"
  alt="The live Painted Valley at its default hour: a large tree with flat-painted leaf clumps at left, the cloaked figure in a straw hat standing in tall grass, a village and windmill on the plain below, green foothills, a snow peak and big white cumulus clouds."
  caption="The live page as it first draws, screenshotted in headless Chromium at 1600x900 on an M1 Max (my capture of patch.gg/blendi/painted-valley)."
/>

### What it downloads

I loaded the published build and logged every response. It made 22 requests for 12.8 MB of decoded bodies, which matches the README's "about 13 MB":

| File | Bytes | What it is |
| --- | ---: | --- |
| `index-*.js` | 5,371,097 | the code: three.js, Rapier (with its WebAssembly inlined) and the valley |
| `starter-man-clips.glb` | 2,378,416 | the walker's animation clips (Quaternius' library and patch's anymove) |
| `starter-man.glb` | 1,255,588 | patch's starter figure, a MakeHuman body, textures stripped |
| four ambientCG `Ground*` JPEGs | 1,757,321 | colour and normal maps for the earth of the footpaths |
| five ambience `.ogg` loops | 2,586,678 | wind, leaves, birds, crickets, lake water |
| five footstep `.ogg` | about 43,000 | Kenney, CC0 |

So the scenery is code: the only pictures loaded are two ground textures for the paths, and the only model is the walker. The five ambience loops are not in the repo. `MISSING_ASSETS.md` explains that they were cut from the Sonniss GDC game-audio bundles, whose licence allows use inside a game but not passing the files on, so a build from the repo has footsteps and silence. That is the most careful licence note I have seen in a demo repo, and it is also a fair test of patch's "50,000+ open-source assets": the five sounds that make the valley feel alive came from outside the library.

The ambientCG ground `Ground048` is in patch's library (`ambientcg:Ground048`, CC0), and `LICENCE-NOTES.md` says the footsteps came "through Patch's asset library". That is the whole of the library's contribution here: a ground material and five footsteps.

### How much is new

`LICENCE-NOTES.md` says `src/lib/` (grass, flowers, ground, the frame's finish) was copied from patch's `blendi/touch-grass` block and `src/figure/` (the walker's controller, camera and cloak) from `blendi/oil-paint`. Both parents are public, so I cloned them and diffed line by line. Of the valley's 9,675 lines of source, 4,587 are identical to a line in a parent: 4,210 of the 4,633 lines in `src/lib/` and `src/figure/` (the figure folder is unchanged line for line), plus 377 lines of `src/valley/` that match the grass block's demo. The other 5,088 lines match nothing I compared them with, and nearly all of them are in `src/valley/`: the sky, clouds, backdrop mountains, far woods, village, trees, water, brushwork and the walker's look. That is the scene.

The parents carry agent trailers too. `touch-grass`'s three commits are co-authored by "Claude"; `oil-paint`'s seven by "Claude" and then "Claude Fable 5.2" (and its last by Opus 5.5). So the remix chain is agent work built on agent work, three days deep.

### Light and shade, as two paints

A standard shader computes how much light reaches a point, $N \cdot L$ (the cosine between the surface normal and the direction to the sun), and multiplies a single colour by it, so shade is the lit colour made darker. Painted backgrounds and cel animation do neither. A thing is painted in two colours mixed separately, and the boundary between them is a short step, not a gradient.

The walker's fragment shader in `src/valley/wanderer.js` does exactly that:

```glsl
// in the light or out of it, and nothing between: a figure in a film is two tones
float lit = smoothstep(0.0, 0.035, dot(N, uSunDir) + 0.06) * open;
...
vec3 col = mix(shadePaint * sky, litPaint * sun, lit * (1.0 - deep));
```

`smoothstep(0, 0.035, x)` rises from 0 to 1 while $x$ moves across 0.035, so on a curved surface the turn from shade to light is a band a few pixels wide instead of a gradient across the whole form. The `+ 0.06` moves the terminator slightly past the 90-degree line, so a surface that grazes the sun still reads as lit. `open` is the tree and cloud shadow at that spot. The palette sits beside it, as `[lit, shade]` pairs, and the shade is never the lit colour scaled: the cloak is `[0.14, 0.32, 0.64]` in the light and `[0.07, 0.13, 0.4]` in shade, deeper and bluer, and the shirt's shade `[0.52, 0.52, 0.66]` is a grey-violet of its own. The shade colour is lit by a desaturated sky (`mix(uSkyColor, grey, 0.8)`), so shade stays nearly colourless as the hour changes while the lit side warms toward sunset. A third tone, `deep`, draws fold wedges rising from the cloak's hem.

Trees and houses do the same with their own palettes, and one meadow palette (`meadowGreen`, `meadowLight` in `world.js`) paints the grass, the ground and the far fields, so the valley reads as one picture.

### An ink line with no outline geometry

The usual ways to outline a 3D figure are an inverted hull (draw the mesh again, slightly inflated, back faces only, in black) or an edge detector on depth and normals. The first fails on thin parts and costs a second draw of every mesh; the second inks everything, including each blade of grass. Painted Valley uses a third trick. Each part of the walker writes a small number into the frame's alpha channel: cloak 0.03, hat 0.06, hair and eyes 0.09, boots 0.12, trousers 0.15, shirt 0.18, skin 0.21. Everything else in the valley writes more than that. The last full-screen pass in `src/lib/frame.js` compares each pixel's alpha with its four neighbours:

```glsl
if (min(a, b) >= 0.23) continue;            // neither side is the figure
float db = textureLod(tDepth, uv + o * w, 0.0).r;
if (max(a, b) >= 0.23 && (a < b ? db < da : da < db)) continue;  // the other thing is in front
e = max(e, abs(a - b));
...
return smoothstep(0.008, 0.022, e);
```

Two different figure IDs meeting give a line, so the hat is inked where it meets the face and the cloak where it crosses the shirt. A figure pixel next to scenery gives a line only when the scenery is behind it: a blade of grass in front of his legs is nearer, so the depth test skips it and the grass is not inked. The line itself is not black. It is the colour under it multiplied by `(0.2, 0.14, 0.15)` and mixed in at 0.92, a dark warm version of whatever it crosses. The sampling offset scales with resolution (`max(1, 1/(texel.y × 1080))`), so the line stays about a pixel at 1080 lines.

The same alpha doubles as a mask. The paint finish, below, leaves alone any pixel whose alpha is under 0.6, so the walker and the sky are never smeared. One channel carries both the outline IDs and the "do not paint me" flag.

### Far things painted once

A big part of why the valley costs little per frame is that most of what you see is not drawn per frame at all.

- **Mountains** (`backdrop.js`). The ground out to 86 km is worked out once into a height map. A shader then marches rays across it into a strip of picture that wraps the horizon, 12,288 pixels wide at high quality. It stores paint rather than the picture: each point's lit colour, shade colour, how lit it is and how much air lies in front of it. The sky mixes those with the hour's light, so changing the time of day does not repaint the mountains.
- **Clouds** (`clouds.js`). Up to 14 cumulus clouds, each a heap of spheres (up to 44 a cloud) pushed about by billowing noise, are ray-marched into a cube map, one strip a frame, then given flat tones: white top, cool shade, warm underside. After that the sky is one texture lookup per pixel. They stand still on purpose; the README lists "the clouds don't move" among its limits.
- **The plain** (`floor.js`, `farwoods.js`). Fields, hedges, roads and the river are drawn into one texture; orchards, poplars and a mill stand on it as small painted cards.

The near ground is the expensive part, and it is instanced. `src/lib/blades.js` draws grass in four rings that follow the camera, from blades 5 cm apart with eight rows of vertices near your feet to tufts 62.5 cm apart out to 120 m, each blade's height, lean and colour hashed from the number of the world cell it grows in, so the same blade is there when you come back. Under it, `strokes.js` paints a 512-pixel tile of brush marks on a canvas at start-up (70 broad patches, 420 strokes, 900 small dabs, 9 groups of stones), which the ground reads at several scales.

On my M1 Max at 1600x900 the live page issued 142 WebGL draw calls a frame, submitted about 10.05 million triangles a frame across all passes, and drew 432,222 instances (grass rings, flowers, tree crowns, stones, houses and far woods are all instanced). It held 60 frames a second, which is the browser's frame cap in my headless setup rather than a ceiling I measured; the README's own numbers are from an RTX 4090 at 1080p.

Air is the last layer before the finish. `applyFog` in `src/lib/glsl.js` is exponential fog thinned with height:

$$
f = 1 - e^{-d \,\rho\, \tau(y)}, \qquad \tau(y) = \operatorname{mix}\!\left(1,\ 0.3,\ \operatorname{smoothstep}(250, 2400, y)\right)
$$

where $d$ is the distance from the eye, $\rho$ the hour's fog density and $y$ the height of the point in metres. The fog colour leans toward the sun's colour as the view turns toward the sun ($\max(\hat d \cdot L, 0)^6$), so a backlit valley at golden hour hazes warm and the opposite side hazes blue.

### The paint finish is a Kuwahara filter

Press P in the valley and the finish goes off. The difference is subtle at full frame and obvious close up.

<Figure
  src="https://media.thesatyajit.com/ai/articles/painted-valley-patch/fig2.jpg?v=b7e8d73a8535b9da"
  alt="Two enlarged crops of the same view of bushes, a pine wood and a village. The top crop, with the finish off, shows fine leaf noise and speckle in the bushes. The bottom crop, with the finish on, shows the same bushes as flat patches of a few greens with crisp boundaries, while the cloaked figure and village are unchanged."
  caption="Top: the finish off (?paint=0). Bottom: the default finish. The bushes go from speckle to a few flat patches; the walker, who writes alpha below 0.6, is left alone. My captures of the live page, enlarged 2x."
/>

A Kuwahara filter, from 1970s medical imaging, smooths a picture while keeping its edges. For each pixel take the square window of radius $r$ around it and split it into four overlapping quadrants $Q_k$, each $(r+1) \times (r+1)$, that all contain the pixel. Compute each quadrant's mean colour $\mu_k$ and its variance $v_k$ (summed over the three channels). The classic filter outputs the mean of the quadrant with the smallest variance: near an edge, at least one quadrant lies entirely on one side of it, so the edge survives, while inside a region all four quadrants agree and the region goes flat. The result looks like loaded-brush strokes.

A hard minimum flickers when two quadrants are close, so the valley's `painted()` function blends all four with a steep weight:

$$
c = \frac{\sum_k w_k\, \mu_k}{\sum_k w_k}, \qquad w_k = \frac{1}{10^{-7} + 10^{12}\, v_k^{4}}
$$

The fourth power makes the calmest quadrant win almost outright without a hard switch. Colours are compressed with $c/(1+c)$ before the statistics and expanded after, so the HDR highlights of the sky do not dominate the variance. The radius is chosen per pixel from depth: 2 in the middle distance, where grass is a mass, and 1 nearer than about 16 m or farther than about 140 m, so blades at your feet keep their strokes and houses a few pixels wide keep their windows. It is a small kernel (at most 25 taps), which is why it costs little.

The order of the final pass matters: paint, then bloom, then the ink line on top (so the line is crisp over painted ground), then a vignette and a "painter's curve" that leaves everything below 0.62 as mixed and only eases the brightest lights toward white, then a tiny dither.

Here are the layers on a small scene of my own, with the valley's constants:

<PaintStages />

The widget draws a ridge, a hill, a tree, two clouds and a walker per pixel in a 360x200 canvas. The ramp slider runs the step width from 1 (a soft, Lambert-like gradient) to the valley's 0.035; the checkbox swaps the separately mixed shade colour for the lit colour times 0.45, which is how most real-time toon shaders make shade, and you can see the shade go muddy. The ink layer uses the alpha-ID rule above, including the depth test, which is why the four blades in front of the walker stay uninked. The fog and paint layers use the formulas above with the same radius and alpha rules.

### Hours, physics and the invisible walls

<Video
  src="https://ai.satyajit.si/articles/painted-valley-patch/clip2"
  poster="https://media.thesatyajit.com/ai/articles/painted-valley-patch/clip2-poster.jpg?v=2b6d2c638110ab13"
  alt="The same valley as the light moves from afternoon to golden hour and sunset: the sky turns violet, the clouds go pink and gold on top with purple undersides, the grass warms to yellow-green, and the walker runs on along the path."
  caption="The hour changes and nothing is repainted: sky, clouds, mountains and grass all read the same set of hour keys (video from @BlendiByl's post on X)."
/>

The time of day is one number, from 12.5 (noon) to 20.4 (sunset), and `sky.js` interpolates between four keyed sets of colours: sun, ambient, bounce, zenith, horizon, fog, cloud lit, mid, shade and underside, and an exposure. There is no night, which the README says.

<Figure
  src="https://media.thesatyajit.com/ai/articles/painted-valley-patch/fig3.jpg?v=3e095cf77ec241b6"
  alt="Painted Valley at sunset from the starting hill: the walker in silhouette-dark blue on a warm green hill, long shadows, the village and plain in amber light, mountains turned rose, and clouds lit gold from below against a violet sky."
  caption="Sunset from the starting hill, from the repo's own stills (Painted Valley, shots/sunset.jpg, CC0)."
/>

The walker is patch's starter man: a MakeHuman body (CC0) on the skeleton of Quaternius' Universal Animation Library, with clips from that library and from patch's anymove. `src/figure/player.js` moves him as a Rapier capsule driven by a kinematic character controller with auto-step (0.3 m) and a 46-degree slope limit. The collision world is a heightfield of the meadow sampled a metre apart, plus a cylinder for each big stone and tree. Nothing else collides.

That explains a complaint in the replies, "INVISIBLE WAAAAAAALLS", and the question about entering houses, and it is worth reading the code rather than guessing. `main.js` sets `REACH = EXTENT / 2 - 110`, with `EXTENT = 1280`: you can walk 530 m from the start, and past that you are pushed back to the circle's edge. The tarn's bank is a second wall ("no wading"). The village stands at 740 m down the valley and 210 m across it (`VILLAGE` in `world.js`), about 769 m from the start, with half-widths of 72 and 88 m. Every house is outside the walkable circle, and so are the six farmsteads (the nearest about 739 m out) and the mill (about 706 m): that is why no building has a collider, and why you cannot reach one, let alone go in. The village is a backdrop you walk toward.

The author's answer, "going into houses can be one prompt away", is true of the walking part: raise `REACH`, add box colliders from the eight house shapes in `village.js`, done. It is not true of the interiors. The houses are drawn a size or two bigger than life on purpose, "as a painter draws a far village so that it reads", with their windows, shutters and tiles painted in the wall shader from the wall's own coordinates. They are facades. An interior needs geometry, a second light model for indoors, and a new answer for what the paint finish does in a small room.

<Figure
  src="https://media.thesatyajit.com/ai/articles/painted-valley-patch/fig4.jpg?v=11ed27b6c855d8c3"
  alt="The village seen from the meadow: cream-walled houses with red tiled roofs and a church tower on a rise, orchards and fields around it, wooded hills behind, all in flat painted colour."
  caption="The village from the meadow. Every wall detail is drawn in the shader; none of these buildings can be reached (Painted Valley, shots/village.jpg, CC0)."
/>

## The coloured-pencil town

<Video
  src="https://ai.satyajit.si/articles/painted-valley-patch/clip3"
  poster="https://media.thesatyajit.com/ai/articles/painted-valley-patch/clip3-poster.jpg?v=e52b076c8080ffe6"
  alt="A sailor in a red beret and striped shirt walks along a wooden pier and onto a harbour quay lined with tall coloured houses, past bunting, a fruit stall, barrels and a tree, to a red-and-white lighthouse, all drawn in visible coloured-pencil strokes on cream paper that fades out at the edges."
  caption="The harbour town, walked from the pier to the lighthouse. Every stroke is computed in the shader; the paper's ragged edge is part of the last pass (video from @BlendiByl's post on X)."
/>

The second scene, at `git.patch.gg/blendi/coloured-pencil`, is a single commit, co-authored by Claude Fable 5.2, made at 22:12 Pacific on 12 November. It is smaller than the valley: 2,067 lines in `src/` outside the figure folder, whose 1,194 lines are again identical to `oil-paint`'s. Its claim is stronger than the valley's, so I checked it the same way. The live page made 5 requests for 8.47 MB: the 5,238,393-byte code bundle, `index.html`, a 2.5 KB hands file, and the same two starter-man files as the valley (2,378,416 and 1,255,588 bytes). No texture, no model of a thing, no sound, no font (the handwriting at the corner is "whatever cursive face the viewer's own machine has"). The `LICENCE-NOTES.md` puts it exactly: "the only files loaded are the figure's skeleton and his clips."

There is one wrinkle. The sailor is sewn in code onto the starter man's skeleton and "his body is never drawn", but `starter-man.glb` still carries that body, so every visit downloads 1.26 MB of mesh that is thrown away. It is the obvious next trim, and the kind of thing the valley's `compact-glb.mjs` already did for the clip file.

On the same M1 Max the town issued about 96 draw calls a frame and submitted about 1.25 million triangles, with no instancing: `createBatch` in `src/pencil.js` merges every piece drawn with one material into one mesh. The README claims "about 2 ms a frame at 1080p on a fast desktop card".

<Figure
  src="https://media.thesatyajit.com/ai/articles/painted-valley-patch/fig5.jpg?v=779596fd96754776"
  alt="The coloured-pencil harbour at its default view: a sailor on a wooden pier facing a row of tall houses in green, pink, blue, orange and violet, a clock tower, cypress-covered hills and mountains, rowing boats on blue-green water, gulls, all shaded in diagonal pencil strokes on cream paper."
  caption="The live page as it first draws, 1600x900 on an M1 Max (my capture of patch.gg/blendi/coloured-pencil)."
/>

### Strokes that belong to the surface

Most "sketch" shaders put hatching in screen space: a pattern of lines laid over the frame, its density chosen by brightness. It is cheap, and it swims: walk and the lines slide across the walls like a texture on a window. One reply under the post asked about exactly this: are the strokes anchored to the objects or to the screen? The code answers it. They are anchored to the objects, and only their density follows the screen.

Every vertex carries a two-component attribute `hatch`. By default, `field()` sets it to two dot products with fixed slants:

$$
h_1 = \mathbf p \cdot \hat a, \quad h_2 = \mathbf p \cdot \hat b, \qquad \hat a \propto (0.62, 1, 0.34),\ \ \hat b \propto (-0.78, 1, 0.22)
$$

with $\mathbf p$ the vertex position in the piece's own coordinates. A level line $h_1 = c$ is where the plane perpendicular to $\hat a$ at height $c$ cuts the surface, so strokes are those slices: straight and parallel on a flat wall, curved on a ball or a barrel, and fixed to the object because $\mathbf p$ is. The pair of slants is "the slant of a right hand"; `fieldBy()` lets any piece define its own (the roof tiles and the sea use their own fields). The first field carries the object's own colour; the second carries the shadow pencil.

The trick that keeps them stable as you walk is in `pcStrokes`. If strokes sat at fixed values of $h$, walking toward a wall would make them grow apart on screen until it was striped, and walking away would crush them into grey. Instead the shader measures how fast $h$ changes per pixel, $g = |\nabla h|$ (from `dFdx`, `dFdy`), and picks a spacing in whole octaves:

$$
k = -\log_2(g \cdot p_x),\qquad s = 2^{\lfloor k \rfloor},\qquad t = k - \lfloor k \rfloor
$$

Strokes are centred where $h \cdot s$ is a whole number, so in $h$ they are $1/s$ apart and on screen $1/(g s)$ pixels apart, which works out to between $p_x$ and $2p_x$. The default $p_x$ is 5. As you walk in, $g$ falls, $k$ rises, and the strokes spread toward $2p_x$; at that point $\lfloor k \rfloor$ steps up, $s$ doubles, and a new stroke appears half-way between each pair. The half-way strokes do not pop: they are drawn with width scaled by $t$, so they grow in from nothing over the octave. Every stroke that exists at one distance is still at the same place on the surface at every other; strokes are only ever added or removed, never slid. It is the same idea as the tonal art maps of real-time hatching research twenty-five years ago, done analytically per pixel instead of with a stack of textures.

<Figure
  src="https://media.thesatyajit.com/ai/articles/painted-valley-patch/fig6.jpg?v=0dc1d36455c0c848"
  alt="Two enlarged crops of the same green and orange housefronts. Left, at the default stroke spacing, the diagonal strokes are fine and dense. Right, with the spacing set to 12 pixels, the same strokes are fewer and broader but run in the same directions on each wall, with a second, darker set crossing them on the shaded faces."
  caption="Left: the default spacing, px 5. Right: ?px=12. Same strokes, same directions, fewer of them: spacing is chosen per pixel, direction belongs to the wall. My captures of the live page, enlarged 2x."
/>

### How light becomes pressure

`pcDraw` treats the frame as white paper and each layer as wax that removes some of the white. It starts from three.js's own Lambert result, turned into a darkness $D$ from 0 (full light) to 1 (full shade) as $D = 1 - \operatorname{clamp}(\text{lit}^{0.8})$, then lays four layers, each multiplied over the last:

1. A warm yellow, very lightly, only where $D$ is low: sunlight as a colour.
2. The thing's own colour, lightly all over, a little more toward the shade.
3. Strokes of the own colour along $h_1$, covering $0.45 + 0.4D$ of the spacing and pressed harder in shade ($0.62 + 0.5D$).
4. A blue-violet shadow pencil (the local colour halved and mixed 55 percent toward `(0.24, 0.2, 0.62)`) along $h_2$, which only starts above $D = 0.24$ and covers up to 0.82.

So light does not change which strokes exist, only how wide and how hard they are. The paper's **tooth** multiplies every layer: two octaves of 3D noise at the surface point, evened out by `fwidth` where the grain would be finer than a pixel, so wax "only catches on its high places". A small sine wobble bends each stroke so no two are perfectly straight, and each stroke's width and pressure come from a hash of its own index.

Because it hooks `MeshLambertMaterial` through `onBeforeCompile`, replacing only `#include <opaque_fragment>`, shadows, skinning and instancing still work, which is the most practical design decision in the file.

<HatchShading />

The widget is `pcStrokes` and `pcDraw` ported to a ball and a block of my own, rendered per pixel in an orthographic view. Turn the light: the strokes stay put and only their width and the second pencil change. Walk in: watch the spacing grow to twice `px` and the in-between strokes fade in. The block's faces get straight strokes and the ball's curve, from the same two vectors.

### The page and the town

`src/page.js` is the last pass. It reconstructs each pixel's view-space position from depth, fits a plane through it from the neighbour on each axis that differs least, and draws a soft graphite line where the other neighbours leave that plane, weighted by depth so far lines fade. It is an outline from depth alone, with no normals buffer and no IDs: it catches silhouettes and creases on everything, which suits pencil, where the valley's per-figure ink would not. Then it leaves flecks of bare paper where the tooth is low, tints everything with a warm sheet, and fades the drawing out toward the page edges with a noisy border, which is the "paper border" one reply credited with making the camera feel like it is walking into a sketchbook.

The town is built from five soft solids in `src/shape.js` (`ball`, `block`, `turned`, `roll`, `knead`), all deliberately a little out of true: `knead` pushes vertices along their normals by noise and can press thumb dents. `houses.js` builds the quay's tall houses from those (tiled roofs, chimneys, shutters, balconies, awnings, drainpipes, vines), each in its own pencil colour; `hills.js`, `tower.js`, `quayside.js` and `mountains.js` add the farms, chapel, windmill, café, bunting and ranges. Small things move: bunting, washing, chimney smoke drawn as a pencil line, boats, gulls, the windmill.

<Figure
  src="https://media.thesatyajit.com/ai/articles/painted-valley-patch/fig7.jpg?v=ccc5c6d2050a711e"
  alt="The hill above the coloured-pencil town: a farmhouse on a terrace, an orchard of round trees, cypresses, a chapel with a bell, and stone walls, all hatched in green, ochre and violet pencil strokes on cream paper."
  caption="The hill above the town: farmhouse, orchard, cypresses and chapel, each built from the same five solids (Coloured pencil, shots/hill.jpg, CC0)."
/>

## A third scene, the same morning: watercolour that follows you

A few hours after the pencil town, the same author [posted a third](https://x.com/BlendiByl/status/2121177279451844707): a wood, a stream and a meadow that start as a pencil drawing and are painted in watercolour wherever you walk. It is on patch at `blendi/watercolour`, one commit co-authored by Claude Fable 5.2, MIT and CC0, and it is the pencil town's code taken one step further: `src/shape.js`, `src/pencil.js` and `src/noise.js` are identical to the town's files, and `page.js` is a modified copy. Its claim of code-only art holds the same way. The live page made 5 requests for 8.47 MB, the same two starter-man files plus a 5,234,067-byte bundle; on my M1 Max it issued 14 draw calls a frame and about 1.29 million instances, and ran at about 42 frames a second, under the cap.

<Video
  src="https://ai.satyajit.si/articles/painted-valley-patch/clip6"
  poster="https://media.thesatyajit.com/ai/articles/painted-valley-patch/clip6-poster.jpg?v=75386b36befc8027"
  alt="A girl in a red hooded cloak walks up a meadow path. Around her, grass, poppies and cornflowers fill with watercolour, while a tree ahead is still a pencil scribble on bare paper; as she reaches it, its crown floods with pink blossom, and a last wash spreads over the misty hills and the wood behind."
  caption="The wash spreads from the walker's feet and climbs each tree from its roots; at the blossom tree a last wash floods the page (video from @BlendiByl's post on X)."
/>

The new idea is a map of time. `createWash()` in `src/wash.js` keeps, for every cell of ground, the moment the wash reached it, and `paint.touch(x, z, now)` writes earlier times into a disc round the walker's feet, spreading at a set speed and slowing past its reach. Every vertex carries a **root**, the point it takes its paint from, and a rate in seconds per metre. The shader's `wcAge` reads the map at the root and subtracts the climb:

$$
\text{age} = t_{\text{now}} - T(\text{root}) - \lVert \mathbf p - \text{root} \rVert \cdot w - \text{rag}(\mathbf p)
$$

Ground is its own root, so it fills where you step; a tree's root is its foot, so it fills from the trunk up; a flower bed's is its middle, so it goes off at once. `rag` is three octaves of noise, so the wet front runs ahead along the grain in places and hangs back in others. Then `wcPaint` blends from the pencil underdrawing (graphite hatching where the shade will be, the same `pcStrokes`) to the wash by `smoothstep(0, 0.25, age)`, darkens the wash while it is wet and most of all at its creeping edge, and lays the same kind of cool violet glaze over the shade. It is the pencil town's object-space idea with one more variable on every surface: when.

## Two looks, two places to put the style

The brief I set myself was "assets and painterly shading against fully procedural hatching". The source does not support that split: both are almost entirely code, and neither uses patch's library for anything you look at. The real difference is where each one puts its style.

| | Painted Valley | Coloured pencil | Our beach houses |
| --- | --- | --- | --- |
| Engine | three.js 0.186.1, WebGL2 | three.js 0.186.1, WebGL2 | Python and Blender, `.glb` in our viewer |
| Lighting | real time, two-tone step per hour | real time Lambert, mapped to stroke pressure | baked once in Cycles |
| Style lives in | screen space: Kuwahara finish, ink from alpha IDs | object space: hatch fields per vertex | the baked atlas |
| Outline | figure only, from alpha IDs and depth | everything, from depth alone | none |
| Pictures or models loaded | 2 ground textures, 1 figure, clips | figure skeleton and clips only | none |
| Download | 12.8 MB, 22 requests | 8.47 MB, 5 requests | about 5 to 6 MB per house |
| Draw calls per frame (my run) | 142 | about 96 | not measured |

Painted Valley paints the world in screen space and inks only its people. That is cheap, it adapts to whatever is on screen, and it works on any mesh, but it is a filter over a rendered frame, so it needs care at edges (hence the depth-based radius and the alpha opt-out). Coloured pencil puts the style on the geometry, which is why it can promise strokes that never swim and why it cannot be laid over someone else's game: every piece needs its hatch field. Both READMEs make this point in nearly the same words, "It is a starting point, not a filter", and the pencil one is the stricter case.

The comparison with [our three beach houses](/articles/beach-house-three-models) is about light. There, Opus 5.5, Fable 5.2 and Sonnet 5.5 each built a house in Python and baked its light in Blender's Cycles, so the browser draws it unlit and every shadow is a texture. That buys soft shadows and bounce light no browser shader here attempts, and it costs a fixed sun and an offline bake on a GPU for every change. Both patch scenes compute light every frame and then throw most of it away on purpose, down to two tones or a stroke pressure, which is why the hour can change in the valley and why neither needs a bake. Fable 5.2 is credited in both setups, and in the beach house it baked only light and painted the colour after, the same separation of light from paint that the valley's shade palette makes. In both cases the decisive design call was the separation, not the model.

The beach houses also had something these two repos do not show: a critic loop with logs. Our [3D and BIM roundup](/articles/agent-3d-bim-tools#we-gave-the-same-loop-a-beach-house) and the beach-house article could count rounds and minutes because we ran them. For the [Open 3D Skills](/articles/open-3d-skills) pack I could read the checking scripts. Here I can read the checking scripts too. Painted Valley's `tools/` has headless Playwright scripts for stills, frame rate, a throttled first visit (`cold.mjs`), a per-feature cost A/B (`ab.mjs`, run with `--use-angle=d3d11`, so on a Windows PC), a pointer-lock guard and a resize check; the pencil repo adds `act.mjs`, which puts the walker through his paces and reports how deep the cape gets into his body, and `edit.mjs`, which "applies a list of exact text replacements to source files and fails loudly if any target is missing". That is the shape of an agent's harness, and it fits the commit trailers. It is not a log.

Browser games on this site usually go the other way: [Voxel Musou](/articles/voxel-musou) is a frame table for combat and the [Road Rash rebuild](/articles/road-rash-in-a-browser-tab) runs on the original's decoded tables, with no particular look. These two are a look and little else, and the author says so: "not really a game yet".

## What I can't check

- **How much Fable wrote.** The commits say: Painted Valley 1.0.0 and the walker commit are co-authored by Claude Fable 5.2, release 1.1.1 by Claude Opus 5.5; the pencil town's one commit by Fable 5.2. The licence notes say "Written by Patch". There are no prompts, transcripts, `BRIEF.md`, `AGENTS.md` or `CLAUDE.md` in either repo or its history, and a co-author trailer does not say who typed what. The valley's 1.0.0 commit arrived as one finished commit, so its history before that is not public.
- **How many prompts, and what they cost.** Nothing public. The author was asked about cost in the replies and answered with a question.
- **Reference images.** `LICENCE-NOTES.md` says published stills from animated films "and two pictures its author supplied were looked at side by side with it, for colour, light and how much is drawn where", and that none was traced or is in the repo. I can't see those comparisons.
- **Performance on a laptop.** Both READMEs say their numbers are from fast desktop GPUs. A reply measured "almost 30 FPS" for the valley on a Mac; my 60 is a capped headless run on an M1 Max and says only that it reached the cap.
- **patch's review and mod handling in practice.** I read the docs and terms; I did not install Patch Desktop or `pch`, so the anti-cheat, rename-on-switch-off and test flows are as documented, not as observed.

## What I think

The scenes are better engineering than the replies give them credit for, and the reason is legible: each one picks a small number of decisions and applies them everywhere. Separate light from paint; ink people, not places; paint the far country once. Strokes belong to surfaces; spacing belongs to the screen. Those are things a reader can take into their own renderer with or without patch, and the code is MIT.

patch is a different question. As a place to find and fork open three.js demos with real licence notes, it is already useful, and the asset API is a decent index of public CC0 and CC-BY work. As a platform, it is early, Windows-first for anything beyond the browser, built around a default licence that keeps work on patch, and keen enough on being every agent's default for games that its installer edits my global agent instructions and puts the edit back on update. The two scenes I liked most here used almost nothing from the library, chose MIT and CC0, and would work on any static host. I'd clone them. I'd install `pch` only with `PCH_NO_AGENT_INSTALL=1`.

<Callout type="note">
**What I ran and what I read.** Ran: `git clone` of `blendi/painted-valley`, `blendi/coloured-pencil`, `blendi/touch-grass` and `blendi/oil-paint` from `git.patch.gg`, plus the screenshots through git-lfs's public batch endpoint; line diffs against the parents; the published builds in headless Chromium (Playwright, Metal ANGLE on an M1 Max, 1600x900), logging every response and counting WebGL draw calls, triangles and instances over five seconds; `?paint=0` and `?px=12` captures; anonymous calls to `/api/v1/assets/count`, `/facets`, `/status`, `/search` and `/projects/…`. Read: patch's docs, terms, `llms.txt` and OpenAPI file, the `@patchgg/cli` 1.1.0 tarball (not installed, not run), and both repos' READMEs, licence notes, shaders and tools. Every number about patch's library is from those endpoints on 13 November and will drift.
</Callout>
