how to build

Project · ★★★ · Advanced

Driftworld

A whole 3D world, grown from one word. Type a seed like "mossy-otter" and hills, lakes, beaches, snowy peaks and forests appear, going on as far as you walk. The sun sets, the fog rolls in, the stars come out. Walk or fly, plant flags where you find something good, and save it: your friend opens the link and stands in the exact same world, with your flags and a picture of the view you saved.

You need

What you'll learn

What this shows the agent can do

The brief

Make a new folder for the project. Copy the brief below into a file called BRIEF.md in that folder, or download BRIEF.md. Open the agent in the folder and say:

Read BRIEF.md and start with milestone 1. Stop after each milestone so I can check it.
# Driftworld — a procedural 3D world you can share (★★★)

Build a 3D world explorer in the browser with Three.js. You type a seed, any word like `mossy-otter`, and the game grows a whole landscape from it: hills, lakes, beaches, snowy peaks and forests, going on as far as you walk. The same seed always makes the same world, so a seed is a world. The sun rises and sets. You can walk on the ground or fly. You can plant markers with short labels where you find something good. Save the world and you get a link: your friend opens it, gets the same land, your markers and a screenshot of the view you saved. As an optional extra, you can describe a world in words and a model on the server turns the description into settings for the generator.

## Done looks like

- A start screen with a seed box, a "Random seed" button and a list of saved worlds on this device.
- The terrain is made from seeded noise: same seed, same world, on any computer. A different seed is clearly a different place.
- The world is built in chunks around you, made as you move and thrown away when far behind, so you can walk for ten minutes without the frame rate dropping or memory climbing.
- Colours by height and slope: water, sand, grass, rock, snow. Trees placed by their own noise, not on steep slopes or under water.
- A day and night cycle of 10 minutes: the sun moves, light and sky colour change, fog thickens at dusk, stars come out at night. A time slider.
- Walk with WASD and mouse look on a laptop; on a phone, a left thumb joystick and drag on the right to look. F (or a button) toggles fly.
- Place a marker where you are looking, with a label up to 30 characters. Markers show as small flags with their label.
- Without any server, `?seed=mossy-otter` opens that world.
- "Save and share" stores the seed and markers in D1 and a screenshot in R2, and gives a link like `/?w=7fk2qd`. Opening it loads the same world, the markers and shows the screenshot first.
- 60 frames per second on a laptop, 30 or more on a mid-range phone.
- No console errors.

## Constraints

- Three.js from a pinned CDN address in an import map, for example `https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js` and add-ons from `https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/`. Never "latest". No other libraries, no build step.
- Seeded randomness: hash the seed string to a number, feed a small generator (for example mulberry32), and write the 2D noise function in the project. Never use `Math.random()` for anything that shapes the world.
- Chunks of 64 by 64 units. Trees as one `InstancedMesh` per chunk. Dispose geometry and materials of dropped chunks. Cap the pixel ratio at 2.
- Server only for saving: one Cloudflare Worker with static assets, a D1 binding and an R2 binding. Use wrangler. Free plan only. The game must still run with `?seed=` if the Worker is down.
- D1 table `worlds(id, seed, name, markers, created_at)` with `markers` as JSON text. R2 key `shots/<id>.jpg`.
- The Worker checks everything: seed up to 40 characters, name up to 40, at most 100 markers, labels up to 30 characters, numbers finite, screenshot JPEG and under 1 MB. World ids are 6 random characters.
- No accounts, no tracking. Phone works.
- The optional AI step (milestone 8) is clearly labelled in the game as optional. It needs Workers AI turned on in your Cloudflare account, or an API key kept as a Worker secret. The browser never calls a model directly and never holds a key.

## Milestones

1. **A scene.** `index.html` with the import map, a renderer, a camera, a sun light and a spinning cube. Resize handled.
   Check: a lit cube spins smoothly, and the browser's network panel shows Three.js loaded from the exact pinned address.
2. **Seeded terrain.** One chunk of terrain from the seed with height colours and a water plane. Print `height(10, 10)` for seed `test` in the console.
   Check: reload three times: the same number prints and the same hills appear; change the seed and the land changes.
3. **Walk and fly.** Walk on the surface with mouse look (Pointer Lock) and the phone controls. F toggles fly.
   Check: you can walk up a hill without sinking into it, fly over it, and do both on a phone.
4. **Endless land and trees.** Chunks around the player, built and dropped as you move. Trees with `InstancedMesh`.
   Check: walk in one direction for five minutes: the frame rate holds and the chunk count in a debug corner stays flat.
5. **Day and night.** Moving sun, sky colour, fog and stars, plus a time slider.
   Check: drag the slider through a full day: the light goes from dawn to noon to a dark night with stars.
6. **Markers and seed links.** Place labelled markers by raycasting the terrain. Keep them in localStorage per seed. `?seed=` opens a world.
   Check: plant three markers, reload: they are in the same places; send `?seed=` to another browser and it shows the same land.
> Up to here the world lives on your own device, and a seed link already lets a friend see the same land. Save and share puts your markers and a screenshot online too.
7. **Save and share.** The Worker, D1 table and R2 bucket. `POST /api/worlds` saves seed, name and markers and returns an id. `PUT /api/worlds/<id>/shot` takes the screenshot. `GET /api/worlds/<id>` loads it. Deploy.
   Check: save a world, open the link on your phone over mobile data: the screenshot shows, then the same land and your three markers.
8. **Optional: describe a world.** A text box, labelled "Optional, needs Workers AI or an API key". The Worker sends the text to a model and asks for JSON only: `treeDensity` 0 to 1, `waterLevel` -1 to 1, `mountainHeight` 0 to 2, `palette` from a fixed list, `landmarks` a list of up to 5 items each `tower`, `ring` or `monolith`. The Worker clamps every value and drops anything else before the game uses it.
   Check: "a flat desert with three stone rings" gives low hills, no water, sand colours and three rings; nonsense text still gives a valid world.

## Stretch

- Caves or overhangs using 3D noise for some chunks.
- Weather: rain particles and darker fog, chosen by the seed and the time.
- A photo mode that hides the controls and lets you tilt the camera.
- Footstep and wind sounds with Web Audio.
- A minimap drawn from the height function.
- A gallery page of recently saved worlds, newest first, from D1.

## Leave out

- Accounts, multiplayer, chat.
- Physics engines, model files, textures downloaded from the internet.
- Any model call from the browser, and any API key in front-end code.

Start by reading WRITING.md and the guide chapter on data if you need storage.

Milestones

The same milestones as the brief. Tick each one off when its check passes. Do one per sitting if you like. The dot in the list marks the point where you have something that works on its own. Everything after it makes it remember things or work for more than one person. Stop at the dot and use what you built before you go on.

  1. A scene. index.html with the import map, a renderer, a camera, a sun light and a spinning cube. Resize handled.

    Check: a lit cube spins smoothly, and the browser's network panel shows Three.js loaded from the exact pinned address.

  2. Seeded terrain. One chunk of terrain from the seed with height colours and a water plane. Print height(10, 10) for seed test in the console.

    Check: reload three times: the same number prints and the same hills appear; change the seed and the land changes.

  3. Walk and fly. Walk on the surface with mouse look (Pointer Lock) and the phone controls. F toggles fly.

    Check: you can walk up a hill without sinking into it, fly over it, and do both on a phone.

  4. Endless land and trees. Chunks around the player, built and dropped as you move. Trees with InstancedMesh.

    Check: walk in one direction for five minutes: the frame rate holds and the chunk count in a debug corner stays flat.

  5. Day and night. Moving sun, sky colour, fog and stars, plus a time slider.

    Check: drag the slider through a full day: the light goes from dawn to noon to a dark night with stars.

  6. Markers and seed links. Place labelled markers by raycasting the terrain. Keep them in localStorage per seed. ?seed= opens a world.

    Check: plant three markers, reload: they are in the same places; send ?seed= to another browser and it shows the same land.

  7. Up to here the world lives on your own device, and a seed link already lets a friend see the same land. Save and share puts your markers and a screenshot online too.
  8. Save and share. The Worker, D1 table and R2 bucket. POST /api/worlds saves seed, name and markers and returns an id. PUT /api/worlds/<id>/shot takes the screenshot. GET /api/worlds/<id> loads it. Deploy.

    Check: save a world, open the link on your phone over mobile data: the screenshot shows, then the same land and your three markers.

  9. Optional: describe a world. A text box, labelled "Optional, needs Workers AI or an API key". The Worker sends the text to a model and asks for JSON only: treeDensity 0 to 1, waterLevel -1 to 1, mountainHeight 0 to 2, palette from a fixed list, landmarks a list of up to 5 items each tower, ring or monolith. The Worker clamps every value and drops anything else before the game uses it.

    Check: "a flat desert with three stone rings" gives low hills, no water, sand colours and three rings; nonsense text still gives a valid world.

Make it yours

Once it works, pick one of these and ask for it as a new milestone.

If it goes wrong

The frame rate falls the longer you walk.

Old chunks are hidden but never disposed, so memory keeps growing. Say this to the agent:

The game gets slower the longer I walk. Show a debug corner with chunk count and renderer.info memory, and dispose geometry and materials of chunks that are dropped.

The shared link shows different land than you saved.

Something in generation uses Math.random() or depends on the order chunks were built. Say this to the agent:

My friend sees different land from the same seed. Find every use of Math.random() in world generation and replace it with the seeded generator, keyed by chunk position.

Three.js fails to load with an import error.

The import map is missing, or an add-on is loaded from a different version. Say this to the agent:

Three.js fails to load. Show me the import map and make every Three.js address use the same pinned version.