# Sandlot: build multiplayer games with your own AI

Sandlot is a browser-based multiplayer 3D game builder designed to be built
by AI agents. A game is a declarative *world* (platforms, coins, checkpoints, hazards, moving parts,
rules). Your AI agent (Claude, ChatGPT or any other MCP- or HTTP-capable agent) edits it through a
small set of well-defined tools, and everyone in the world sees each change live.

> Most people build with the **built-in AI chat** in the game (no setup). This guide is the
> advanced path: connecting an agent you already use, such as Claude Code, Claude Desktop, ChatGPT or your own scripts.

You need:

- the **world id**: the `?world=...` part of the game URL;
- the **edit key**: shown in the game's "Connect your AI" panel. Reading works without it; changing the world requires it. Treat it like a password.

## Connect over MCP (recommended)

MCP endpoint (Streamable HTTP): `https://sandlot.foxhollow.games/mcp?world=<worldId>&key=<editKey>`

- **Claude Code**: `claude mcp add --transport http sandlot "https://sandlot.foxhollow.games/mcp?world=<worldId>&key=<editKey>"`
- **Claude Desktop / claude.ai**: Settings > Connectors > Add custom connector, and paste the URL above.
  claude.ai connects from Anthropic's cloud, so the server needs a public HTTPS URL (see below).
- **ChatGPT** (developer mode connectors): Settings > Connectors > Create, paste the URL, authentication "No authentication" (the key is in the URL). Also needs a public HTTPS URL.
- **Other MCP clients** (Cursor, VS Code, Gemini CLI, ...): add a remote/HTTP MCP server with the URL above.
  Instead of query parameters you may send the headers `X-Sandlot-World: <worldId>` and `Authorization: Bearer <editKey>` (or `X-Sandlot-Key`).

Without `?world=` you can still connect: use `list_worlds`, `open_world` or `create_world` to pick a world in the conversation.

## Connect a ChatGPT custom GPT (Actions)

1. Create a GPT, then Configure > Actions > Create new action > Import from URL:
   `https://sandlot.foxhollow.games/api/openapi.json`
2. Authentication: API Key, Auth Type **Custom**, header name `X-Sandlot-Key`, value = your edit key.
3. In the GPT's instructions, paste: "Before building, read https://sandlot.foxhollow.games/api/worlds/<worldId>/guide and follow it." and tell it your world id.

## Plain HTTP (scripts, other agents)

- `GET https://sandlot.foxhollow.games/api/worlds`: list worlds. `POST https://sandlot.foxhollow.games/api/worlds` with `{"title": "..."}`: create one (returns id, key, playUrl, mcpUrl).
- `GET https://sandlot.foxhollow.games/api/worlds/{worldId}`: the world as JSON. `GET .../guide`: the builder guide (markdown). `GET .../history`: recent changes (time, who, what), newest first. `GET .../players`: who is in the world.
- `POST https://sandlot.foxhollow.games/api/worlds/{worldId}/tools/{tool}` with a JSON body of the tool's arguments and header `X-Sandlot-Key: <editKey>`.
  Returns `{"ok": true, "text": "...", "data": ..., "world": "<worldId>", "actor": "<your name>"}`. **Check that `world` is the world you meant to change.** A tool that ran but failed returns HTTP 200 with `ok: false`; bad arguments return 400, a missing/wrong key 401, an unknown world 404.
- Full OpenAPI 3.1 description: `https://sandlot.foxhollow.games/api/openapi.json`.
- **Name yourself**: send `X-Sandlot-Actor: <name>` (e.g. "Claude for Sam") so players see who made each change. Without it you appear as "AI agent (API) #abc", where the tag is derived from your key, user agent and address.
- Before editing a world others may be working on, read `.../history` (or call `get_history`) to see what changed since you last looked.

## Public HTTPS URL for cloud agents

claude.ai and ChatGPT connect from the internet, so a server on your computer needs a tunnel:

- `cloudflared tunnel --url http://localhost:8787` (prints an https://....trycloudflare.com URL), or
- `ngrok http 8787`

Then use that https URL in place of `https://sandlot.foxhollow.games`. Set `PUBLIC_URL=https://...` when starting the server if the generated links show the wrong host.

## Tools

### `get_world_overview`: World overview (read-only)

Summarize the world: title, rules, jump limits, entity counts, bounds, groups, spawns, checkpoints, finish, and the numbered route from spawn to finish with a playability status. Call this first; use the route numbers to resolve "the third platform".

### `find_entities`: Find entities (read-only)

Search entities by role, name/id substring, group, exact ids, and/or distance from a point; returns one line per entity (id, name, role, shape, position, size, color/material, group, order/value/power, behaviors). Filters combine; results near a point are sorted nearest first.

### `get_player_context`: Players and selections (read-only)

List the players in the world with their positions and the entities they currently have selected (with details). Use it to resolve "this", "that" and "here" when a player asks for a change.

### `check_playability`: Check playability (read-only)

Simulate whether the course can be completed with the current jump physics: spawn, then each checkpoint in order, then the finish. Reports impossible gaps and ledges naming the exact entities, plus unreachable coins. Run after building and fix every ERROR.

### `get_builder_guide`: Builder guide (read-only)

Get the full builder guide: coordinates, jump limits for this world, roles, behaviors, rules, every structure kind with its params, and the recommended workflow. Read it before building if you have not.

### `add_entities`: Add entities

Add one or more primitive entities (platforms, coins, hazards, decor, spawns, checkpoints...). position is the shape CENTER: a 1m-thick platform whose top is at y=T has position y=T-0.5. Role defaults fill in size, color and material. Returns the new ids.

### `update_entities`: Update entities

Change fields on existing entities: role, color, size, material, behaviors (motion), power, value, order, name, position, rotation. Applies the same patch to every listed id (a group id patches every part). behaviors replaces the whole list.

### `move_entities`: Move entities

Shift entities (or whole structures by group id) by a delta [dx, dy, dz]. The course runs toward -Z, so moving a platform "closer" to the one before it is usually +Z.

### `scale_entities`: Scale entities

Multiply entity sizes by factor [fx, fy, fz] (e.g. [1.5, 1, 1.5] makes platforms wider). With aroundCenter=true positions also spread from the selection center, to grow a whole structure while keeping its base on the ground.

### `remove_entities`: Remove entities

Delete entities, or whole structures by group id. Do not remove the last spawn or finish unless replacing them.

### `duplicate_entities`: Duplicate entities

Copy entities (or whole structures by group id) shifted by an offset; copies get new ids, and grouped copies get a new group id. Handy for repeating a section further down the course.

### `build_structure`: Build structure

Build a ready-made multi-part structure as one group, e.g. a whole themed course (full_course), a castle, tower, bridge, moving platforms or checkpoint gate. The result gives the group id and an exit point where the course continues; see get_builder_guide for every kind's params. Kinds: staircase, tower, castle, bridge, platform_path, spinner, wall_with_door, moat, stepping_stones, zigzag, moving_platforms, ring_of_coins, coin_line, lava_floor, checkpoint_gate, finish_arch, obstacle_course_section, full_course.

### `set_rules`: Set game rules

Change game mechanics: lives, time limit, scoring (finishPoints, coinPoints), physics (gravity, jumpVelocity, walkSpeed), killY, ground plane, sky/fog/ground colors. Pass the fields directly, e.g. {"lives": 3, "timeLimit": 90} (wrapping them in "patch" also works). Only the fields you pass change; physics changes alter what jumps are possible, so re-check playability.

### `set_info`: Set title and description

Set the world's title and/or description, shown to players in the lobby and HUD.

### `clear_world`: Clear world

Remove EVERY entity, leaving an empty world (rules and title stay). Only use when the person clearly wants to start over, then rebuild a spawn, course and finish (build_structure full_course is the quickest).

### `style_avatar`: Style a player avatar

Dress up a player's avatar: costume presets (pirate, astronaut, knight, wizard, princess, ninja, robot, superhero...), hats, hair, face, clothes with colors and patterns, wings/capes, held items and pets. Use it when someone asks to change how they (or a friend) look, e.g. "make me a pirate with a parrot" or "match my outfit to this world". Only the fields you pass change. Everyone sees the new look immediately; it does not change the world.

### `undo`: Undo

Undo the most recent change to the world (by anyone: players, you, or other agents). Call again to step further back.

### `redo`: Redo

Redo the most recently undone change.

### `get_history`: Get change history (read-only)

List recent changes to the world, newest first: time, who made it (player or agent name) and a summary. Use it to see what others changed since you last looked.

---

# Sandlot builder guide

Sandlot is a multiplayer 3D obby (obstacle course race) that people build by talking to an AI. You edit a live world: every change appears instantly for every player in it. The person you're helping directs and you build. Make reasonable creative choices yourself rather than asking lots of questions, keep the course playable, and keep your replies short.

## Coordinates and shapes
- Units are meters, seconds and degrees. Y is up. A player is 1.8m tall and about 0.8m wide.
- Courses run from the spawn toward -Z. "Forward/ahead/further" is -Z, "back/earlier" is +Z, "left" is -X and "right" is +X, as seen by a player facing down the course.
- Every entity is one primitive: box | cylinder | sphere | wedge. `position` is the CENTER and `size` is the full extents (cylinder: [diameter, height, diameter]; sphere: size[0] is the diameter).
- Think in top-surface heights: a platform whose walking surface is at height T with thickness 1 has position y = T - 0.5.
- `rotation` is Euler XYZ in degrees; rotation[1] is yaw.
- A wedge is a ramp filling its size box with a flat bottom: low edge at +Z, high edge at -Z (so with rotation [0,0,0] it rises along the course). Yaw it 180 to rise toward +Z. You can walk up it from the low end.

## Roles (what an entity means to the game)
- solid: geometry you stand on or bump into (the default).
- spawn: where players appear. Put one per player (4 is good) on top of the start platform; size [2,0.2,2], not solid.
- checkpoint: touch to save progress; after dying you respawn at the last one. Needs `order` 1, 2, 3... increasing along the course. Usually a [w,3,0.5] gate standing on a pad.
- finish: touch to finish the race (first to finish scores the most). Usually a gate on the final pad.
- coin: collectible worth `value` (default 1); each coin also adds rules.coinPoints to score. The first player to touch it gets it.
- treasure: a big collectible (`value` ~100), a great reward at the end or on a hard side path.
- hazard: touching it kills (lava floors, kill bricks, spinning blades). Costs a life if lives are limited.
- bounce: launches players upward at `power` m/s (default 18).
- speed: boost pad; `power` is a horizontal speed multiplier (default 1.8) that makes longer jumps possible.
- decor: visual only, no collision (flags, banners, trees, arches).
- Role defaults fill in a sensible size, color and material if you omit them.

## Behaviors (motion; pure functions of shared server time, so every player sees the same thing)
- move {offset:[dx,dy,dz], period, phase}: slides from its position to position+offset and back every `period` seconds. Give neighbouring movers different phases (0 and 0.5).
- rotate {axis:"x"|"y"|"z", speed}: spins at `speed` degrees per second (negative reverses). A spinning bar (role hazard or solid) sweeping a platform is a classic obstacle.
- blink {on, off, phase}: present for `on` seconds, then gone for `off` seconds. Blinking platforms test timing; blinking hazards open and close a path.
- An entity's `behaviors` list is replaced as a whole when you update it, so include every behavior you want it to keep; [] stops all motion.

## Materials and colors
plastic, neon (glows), wood, stone, metal, grass, ice, lava, glass. Colors are hex strings like "#ff7043". Pick a coherent palette per area.

## Game rules (set_rules, e.g. {"lives": 3, "timeLimit": 90})
- lives: null means unlimited, N means eliminated after N deaths.
- timeLimit: seconds per race, or null for no limit.
- finishPoints: points by finishing place, e.g. [100,50,25,10]. coinPoints: points per coin.
- killY: falling below this height is death. groundY: height of an infinite floor, or null for none (the void, so falling is possible everywhere). groundColor sets the floor color.
- gravity (default -25), jumpVelocity (default 10) and walkSpeed (default 8) change how far and high everyone can jump. Changing them changes what is playable, so re-check.
- skyColor and fogColor set the mood.

## Jump physics (defaults: gravity -25, jumpVelocity 10, walkSpeed 8)
- A jump rises ~2m; with the controller's auto-step a player can climb onto a ledge up to ~2.3m higher. It crosses at most ~6.4m edge-to-edge between platforms of equal height.
- Jumps within 0.4m of these limits need a perfectly timed takeoff; check_playability flags them as "needs good timing". Keep easy and medium sections well inside the limits.
- Landing higher shortens it: ~5.5m when landing 1m higher, ~4.8m at 1.5m higher. Dropping down lengthens it (~8.3m when landing 3m lower).
- Fun, fair jumps: gaps 1.5-4m, rises up to 1.5m, platforms at least 2.5m wide. Gaps over 5.1m or rises over 1.7m are expert-only.
- Bounce pads (power 18 reaches ~6.5m high) and moving platforms bridge what a plain jump cannot.

## Structures (build_structure)
Prebuilt multi-part pieces are the fastest way to build well. Each build becomes one group: the group id stands for all of its parts in move/remove/duplicate/update/scale. Course pieces start at their origin (where the player stands, at that walking height) and extend toward local -Z. The result reports an **exit** point (top surface where the course continues); put the next piece's origin there. `rotationY` turns a piece (90 = it heads toward -X).
- **staircase**: Course piece: origin = where the player stands before it (y = that walking-surface height); it extends toward local -Z. Steps rising toward -Z. Params: steps=8, rise=0.8 (m per step, keep <=1.8), depth=2 (m per step), width=4, gap=0 (distance before first step), floating=false (thin slabs instead of solid blocks), color, material=stone.
- **tower**: Building: origin = base center on the ground. A square tower with a crenellated roof deck and a spiral of ledges around the outside to climb it. Params: height=12, width=6, climbable=true, treasure=false (treasure on top), color, material=stone. Exit = roof deck.
- **castle**: Building: origin = center of the front gate at ground level; the castle extends toward -Z. Courtyard floor (plinth 1m beyond the walls), walls with a walkway and crenellations, a gate in the front wall, 4 corner towers with flags, a keep at the back with stairs up to a treasure on top. Params: width=24, depth=24, wallHeight=6, towerHeight=10, keep=true, treasure=true, moat=false (lava moat around it with a wooden drawbridge at the gate), moatWidth=5, drawbridge=true, color, material=stone. Exit = top of the keep.
- **bridge**: Course piece: origin = where the player stands before it (y = that walking-surface height); it extends toward local -Z. A plank bridge starting right at the origin (no gap). Params: length=16, width=3, railings=true, arch=0 (m of upward bow in the middle), color, material=wood.
- **platform_path**: Course piece: origin = where the player stands before it (y = that walking-surface height); it extends toward local -Z. A row of square platforms. Params: count=6, gap=2.5 (edge-to-edge, default max jump is ~6.4m flat), size=3, thickness=1, rise=0 (height change per platform, keep <=1.8), sway=0 (lateral zig-zag amplitude), color (omit for a rainbow), material=plastic.
- **spinner**: Course piece: origin = where the player stands before it (y = that walking-surface height); it extends toward local -Z. A round arena with a spinning sweeper bar to jump over. Params: radius=6, gap=2, speed=60 (deg/s), bars=1 (1 or 2), barHeight=0.6 (bar bottom above the floor), hazard=false (true = touching the bar kills), color, material=plastic.
- **wall_with_door**: Building: origin = base center of the wall. A wall across the X axis with a doorway. Params: width=12, height=5, thickness=1, doorWidth=3, doorHeight=3.5, color, material=stone. Exit = just beyond the doorway.
- **moat**: Origin = front-center edge of the enclosed island at ground level; the island spans x in [-innerWidth/2, innerWidth/2], z in [0, -innerDepth]. A sunken lava ring with grass banks outside and an island floor. Params: innerWidth=20, innerDepth=20, width=5 (channel width), island=true, bridge=true (drawbridge across the front channel), color (bank), material=grass. Tip: castle has its own moat=true option.
- **stepping_stones**: Course piece: origin = where the player stands before it (y = that walking-surface height); it extends toward local -Z. Small round stones with a seeded lateral wobble. Params: count=8, gap=1.8, diameter=1.8, jitter=1.2, rise=0, seed=1, color, material=stone.
- **zigzag**: Course piece: origin = where the player stands before it (y = that walking-surface height); it extends toward local -Z. Platforms alternating left and right. Params: count=8, gap=2, size=2.5, amplitude=3, rise=0, color, material=plastic.
- **moving_platforms**: Course piece: origin = where the player stands before it (y = that walking-surface height); it extends toward local -Z. Platforms that slide back and forth, neighbours out of phase. Params: count=3, gap=2.5 (at rest), size=3, axis="x" ("x" side to side, "y" up/down, "z" forward/back), travel=5 (m), period=4 (s), lava=false (adds a lava pool underneath), color, material=neon.
- **ring_of_coins**: Origin = center of the ring. Params: count=8, radius=3, height=1.2 (above origin), value=1, vertical=false (ring stands upright facing the course).
- **coin_line**: Origin = first coin position base. A line of coins toward -Z. Params: count=5, spacing=2, height=1.2, value=1, arc=0 (m of upward bow, e.g. to trace a jump).
- **lava_floor**: Origin = near-center of the slab; its top surface is at origin y. A lava (hazard) slab extending toward -Z. Params: width=20, length=20, thickness=0.5.
- **checkpoint_gate**: Course piece: origin = where the player stands before it (y = that walking-surface height); it extends toward local -Z. A pad with a checkpoint trigger and two glowing posts. Params: order=1 (checkpoint number, required to be unique & increasing along the course), gap=2.5, size=6, color.
- **finish_arch**: Course piece: origin = where the player stands before it (y = that walking-surface height); it extends toward local -Z. The final pad with a checkered finish arch and an optional treasure. Params: gap=2.5, size=8, treasure=true, treasureValue=100.
- **obstacle_course_section**: Course piece: origin = where the player stands before it (y = that walking-surface height); it extends toward local -Z. A ready-made mixed section (platforms, stepping stones, moving platforms, a spinner) ending on a checkpoint pad. Params: difficulty=1 (1 easy..3 hard), checkpointOrder=1 (0 = no checkpoint), seed=1.
- **full_course**: A complete game in one step. Origin = center of the start pad top (use [0,0,0] in an empty world); the course runs toward -Z. Start pad with spawns, themed sections ramping from easy to `difficulty` (hops, stepping stones, stairs, zigzags, moving and blinking platforms, bounce climbs, spinners) with a checkpoint gate between sections, coins along the way, and a finale. Params: sections=4 (1-8), difficulty=2 (1 easy..3 hard, the hardest section), theme="classic" (classic | castle | lava | sky | ice | candy | space), finale (finish | castle | tower; default castle for the castle theme, tower for sky, else finish), players=4 (spawn count), coins=true, seed=1 (change for a different layout). Exit = the finish. Afterwards set_info a title and set_rules sky colors to match the theme.

## Building a whole game quickly
- From an empty world (or after clear_world when someone wants to start over): use build_structure kind "full_course" at [0,0,0]. It creates start pad, spawns, themed sections ramping in difficulty, checkpoints and a finale (finish arch, castle or tower), all in one step. Then set_info (a fun title), set_rules (lives, timeLimit, sky colors to match the theme), check_playability, and add personal touches the person asked for.
- To build by hand: a start pad (12x1x12) with spawns, then 3-6 sections of course pieces chained by their exit points, a checkpoint_gate between sections, and a finish_arch (or castle) at the end. Add coins along jump arcs and a treasure at the finish.
- To extend a course: build from the current finish area, then move the finish gate, its pad and its treasure to the new end, adding a checkpoint where the old finish was.

## Changing mechanics, not just geometry
- "Make it harder": wider gaps (stay under the limits), smaller platforms, faster movers, add blink to platforms, add hazards or spinners, set lives or timeLimit.
- "Make it easier": move platforms closer (move_entities), make them bigger (scale_entities), slow movers (longer period), add checkpoints or a bounce pad.
- "3 lives", "60 second race", "coins worth 10": set_rules. "Jump higher everywhere": raise jumpVelocity. "Low gravity": gravity -12.
- "Make this platform move/spin/disappear": update_entities with behaviors. "Make this deadly": role hazard. "Make this bouncy": role bounce with power.
- "Make me a pirate with a parrot", "give Ben wings", "dress us all to match the lava theme": style_avatar (a costume preset plus hat/outfit fields; "me" is the player who asked). It changes how players look, not the world.

## Resolving references
- "this", "that", "it": what the person has selected (get_player_context shows each player's selection, position and camera look-at point; the built-in builder receives them with the request).
- "here", "over there", "in front of me": the person's look-at point or position.
- "the third platform", "the third jump", "the jump after checkpoint 2": count along the course route that get_world_overview lists. It numbers every platform in the order a player hops along them (the start pad is "start", not #1) and marks each move as "jump N" (with its gap and height change) or "walk" (stepping onto an adjacent surface or stair). "The third jump" is jump 3, whose landing platform and the one before it are the pair to adjust. "That jump" is the gap between two consecutive route entries.
- Group ids stand for whole structures ("move the castle left": move its group id).
- Use find_entities (by name, role, group, or near a point) when you need details.

## Workflow
1. Inspect: get_world_overview first (title, rules, jump limits, route, checkpoints). Use get_player_context to see who is playing and what they selected.
2. Build: prefer build_structure for anything multi-part; use add_entities for custom pieces and small touches, batching many entities per call. Give entities short descriptive names. Edit existing entities (move/update/scale) for tweaks instead of rebuilding.
3. Check: every mutating tool reports a one-line "Course check"; when you finish, call check_playability. It simulates the jump physics from spawn through each checkpoint in order to the finish, and names the exact platforms around any impossible gap or ledge.
4. Fix every ERROR (move platforms closer, lower a ledge, add a stepping stone or bounce pad) and re-check. Warnings about unreachable coins are worth fixing when it's cheap.
5. Never leave a world without a spawn and a finish unless asked. Tool errors explain what was wrong, so fix the arguments and retry.

## Design principles
- Fun first: readable paths, varied rhythm (hops, then a moving section, then a climb), difficulty that ramps, a checkpoint before each hard part, and a reward (coins, treasure) for good routes.
- Make it look good: coherent palettes, descriptive names, decor (flags, arches, trees made of a cylinder trunk plus a sphere canopy) placed where it doesn't block jumps.
- Respect what's there: other players may be building too. Change only what the request covers.
- Players can play right away: changes are live, and the person can share the world's play URL with friends to race together.
- Reply briefly (1-3 sentences) saying what changed and where, crediting the person's idea.
