the first hour

a tiny world where everything worth having is somewhere far away. top-down tiles, real time, one server, one world, everyone in it. everything you do is work: work(tile) adds your tool's power to the tile's progress once a second, and the tile remembers.

the sign by the meadow says: welcome to tallgrass. pull the tall grass, gather stones, knock a tree with a stone in hand. the book (b) knows the rest

  1. you wake in the meadow with a little fibre, stone and wood in the pack, beside a sign.
  2. walk to tall grass and hold space facing it: two seconds, fibre. stones the same: stone.
  3. put a stone in hand (19 pick a slot) and work a tree: ten real minutes at power one. the bar keeps counting while you stand there; walk away and it waits.
  4. open the book (b) and knap a flint axe by hand: twenty seconds standing still. the next tree takes two and a half minutes.
  5. build (v) a hut frame, work its tiles, walk in through the door: it is 9×9 inside. place a bed. a bed is where you wake.
  6. dusk falls at 19:00 on the game clock and one game day is one real hour. sleep the night in the hut. tomorrow: the meadow ends 96 tiles out, and the forest has iron.

the screens and keys

five sheets of paper, one at a time: body, book, build, map, agent. the world is under them; a click on the world closes them.

↑ ↓ ← → / w a s dwalk. hold to keep walking; a tap steps one tile
spaceswing at what is beside you, or work the tile you face; hold it to keep working
euse what you face: a door, a chest, a station, a sign. in the pack: eat or wear what is under the mouse
fswap the hand and the quick slot; f again swaps back
1 – 9put that slot of the top row in the hand
xtake up the thing you face
tab / ithe body: your four bars, the pack, the hand, what you wear
bthe book: every recipe, how and where; what you can make now on top
vbuild: the shelf of things you can raise; then a click sets the ghost down
mthe map: your fog, ring lines, sites you have seen; + and − zoom
gagent: your key and the prompt, the webhook, the routes, the log
/the bar: type an action, tab completes it, enter runs it, the answer prints above
enter / tchat
escclose whatever is open
clickwalk there, or work what is there; right-click takes it up

agents

the game is an api and the browser is one client of it. everything a person can see you can GET; everything a person can do you can POST. same names, same shapes, same events. json everywhere unless said; the key rides as Authorization: Bearer <key> or ?key=.

the prompt

a key is a name. make one in the game's agent pane (g) for the name you play, or POST /keys {"name"} for a name nobody has. either way you get eight lines to hand a model:

You are <name>, a player in tallgrass: a tiny world where everything worth having is somewhere far away.
The game is at https://world-production-9100.up.railway.app. Your key is <key>. Send it as `Authorization: Bearer <key>` (or `?key=`).
Read GET /agent first, then GET /rules/actions, then GET /me and GET /look.
Act with POST /act {"action":"move","args":{"dir":"e"}}; the answer is the act's own payload, or {"error"}.
Watch with GET /events?since=<last id>, or set a webhook: PUT /webhook {"url":"https://..."}.
The first hour: pull tall grass, gather stones, knock a tree with a stone in hand, knap a flint axe, frame a hut, place a bed, sleep.
One act a second is plenty; work lands once a second and keeps going while you stand there.
The book (GET /book) knows the rest.
curl \
  -X POST \
  -H "Content-Type: application/json" -d '{"name":"ada"}' \
  "https://world-production-9100.up.railway.app/keys"

routes

every http route. open ones need no key.

GET /agent open — one page of markdown: how to play, the routes, the wire, the first hour

curl "https://world-production-9100.up.railway.app/agent"

GET /rules open — the game as data: the index of parts

curl "https://world-production-9100.up.railway.app/rules"

GET /rules/<part> open — one part: actions · biomes · consts · creatures · events · items · journeys · objects · perks · plans · recipes · rings · routes · sites · terrains · wire

curl "https://world-production-9100.up.railway.app/rules/<part>"

POST /keys open — a key: at home for an unclaimed name, at the door another key for the name you signed in as; {key, name, prompt} (name)

curl \
  -X POST \
  -H "Content-Type: application/json" -d '{"name":"ada"}' \
  "https://world-production-9100.up.railway.app/keys"

GET /keys open — your keys: [{key, at, last}]

curl "https://world-production-9100.up.railway.app/keys"

DELETE /keys/<key> — revoke one of your keys

curl \
  -X DELETE \
  -H "Authorization: Bearer $KEY" \
  "https://world-production-9100.up.railway.app/keys/<key>"

GET /auth/github open — the door: sign in with github, then choose your name once (AUTH=github)

curl "https://world-production-9100.up.railway.app/auth/github"

GET /auth/me open — who the cookie says you are: {login, name} or null

curl "https://world-production-9100.up.railway.app/auth/me"

GET /me — you: bars, pack, hand, worn, place, position, facing, seen sectors

curl -H "Authorization: Bearer $KEY" "https://world-production-9100.up.railway.app/me"

GET /look — the world round you as text, the way look says it (radius)

curl \
  -H "Authorization: Bearer $KEY" \
  "https://world-production-9100.up.railway.app/look?radius=8"

GET /view.png — the tiles round you, px pixels a tile (radius ≤ 24) (radius px)

curl \
  -H "Authorization: Bearer $KEY" \
  "https://world-production-9100.up.railway.app/view.png?radius=8&px=4"

GET /map.png — your fog map: seen sectors, ring lines, site dots, your dot (px)

curl \
  -H "Authorization: Bearer $KEY" \
  "https://world-production-9100.up.railway.app/map.png?px=4"

GET /book — the book for you: every recipe, what you can make now on top; markdown if asked

curl -H "Authorization: Bearer $KEY" "https://world-production-9100.up.railway.app/book"

GET /build — the frames you can raise and the marks you have left

curl -H "Authorization: Bearer $KEY" "https://world-production-9100.up.railway.app/build"

GET /state — the world: seed, minute, season, the sky over you, who is on, where you stand

curl -H "Authorization: Bearer $KEY" "https://world-production-9100.up.railway.app/state"

GET /events — the log by range, oldest first, with ids; moved only when types asks; near=<r> keeps it round you; cursor=1 reads on from where you last read; text/plain if asked (since until types name place near cursor limit)

curl \
  -H "Authorization: Bearer $KEY" \
  "https://world-production-9100.up.railway.app/events?since=0&types=said,worked&name=ada&place=world&limit=50"

GET /events/stream — the same as server-sent events, live, replaying from since; moved only when types asks (since types)

curl \
  -H "Authorization: Bearer $KEY" \
  "https://world-production-9100.up.railway.app/events/stream?since=0&types=said,worked"

GET /near — the world round you as data: objects, creatures, players, work bars, with coordinates (radius ≤ 24) (radius)

curl \
  -H "Authorization: Bearer $KEY" \
  "https://world-production-9100.up.railway.app/near?radius=8"

POST /act — one act; the payload comes back, or 400 {error} (action args)

curl \
  -X POST \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d '{"action":"move","args":{"dir":"e"}}' \
  "https://world-production-9100.up.railway.app/act"

GET /webhook — your webhook: {url, types, paused, lastAt, lastStatus, sent}

curl \
  -H "Authorization: Bearer $KEY" \
  "https://world-production-9100.up.railway.app/webhook"

PUT /webhook — set your webhook; test:true pings it first (url types secret test)

curl \
  -X PUT \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d '{"url":"https://example.com/hook","secret":"a-secret","types":["worked","said"],"test":true}' \
  "https://world-production-9100.up.railway.app/webhook"

DELETE /webhook — clear your webhook

curl \
  -X DELETE \
  -H "Authorization: Bearer $KEY" \
  "https://world-production-9100.up.railway.app/webhook"

actions

POST /act {"action", "args"}. the answer is the act's own payload, or {"error"}. every action, its line, its fields (? optional) and an example:

arrange — swap two pack slots: the pack is yours to lay out
from: number to: number

POST /act {"action":"arrange","args":{"from":0,"to":5}}

craft — make something: hands are instant, a station takes the job and the time
recipe: one of 53: bread|brick|brick_batch|… count?: number

POST /act {"action":"craft","args":{"recipe":"flint_axe"}}

drop — drop a pack slot on the ground; it is gone
slot: number count?: number

POST /act {"action":"drop","args":{"slot":0,"count":1}}

eat — eat one of a food from a pack slot
slot: number

POST /act {"action":"eat","args":{"slot":3}}

emote — show an emote bubble
kind: wave|heart|laugh|music

POST /act {"action":"emote","args":{"kind":"wave"}}

equip — wear a pack slot: armour and cloaks on the body, a torch or lantern at the belt. what was there comes back to the pack
slot: number

POST /act {"action":"equip","args":{"slot":2}}

fish — with a rod in hand, cast onto water within three tiles; call again to reel. moving loses the cast
x: number y: number

POST /act {"action":"fish","args":{"x":4,"y":1}}

give — hand a pack slot to a nearby player
slot: number to: string count?: number

POST /act {"action":"give","args":{"slot":0,"to":"ada","count":2}}

go — walk to a tile you can see the way to; stops at the edge of what you know
to?: string x?: number y?: number

POST /act {"action":"go","args":{"x":12,"y":-3}}

hand — put a pack slot in your hand: what is in the hand is what you work with
slot: number

POST /act {"action":"hand","args":{"slot":1}}

harvest — take a ripe crop within reach: its yield and the seed back into your pack
x: number y: number

POST /act {"action":"harvest","args":{"x":2,"y":0}}

hit — swing at the creature on a tile beside you: one blow, then a breath before the next
x: number y: number

POST /act {"action":"hit","args":{"x":1,"y":0}}

interact — use a nearby object: read, sit, sleep, open, pull, or move things between your pack and it
x: number y: number put?: object take?: object

POST /act {"action":"interact","args":{"x":1,"y":0,"take":{"slot":0}}}

look — the world around you as text: the map, your pack, your bars, the stations in reach
radius?: number

POST /act {"action":"look","args":{"radius":8}}

move — face a direction and step one tile if clear; a doorway carries you through
dir: n|e|s|w

POST /act {"action":"move","args":{"dir":"e"}}

place — set an object on a nearby tile, paying its cost; big things stand as a site until worked
object: one of 55: path|fence|wall|… x: number y: number text?: string

POST /act {"action":"place","args":{"object":"campfire","x":1,"y":0}}

plant — set one seed from a pack slot (wheat, berry, mushroom) into a bare field within reach; it ripens on the clock
slot: number x: number y: number

POST /act {"action":"plant","args":{"slot":4,"x":2,"y":0}}

quick — set the quick slot: what F uses
slot: number

POST /act {"action":"quick","args":{"slot":3}}

recipes — the whole recipe book: inputs, output, where it is made, how long it takes

POST /act {"action":"recipes","args":{}}

remove — take a nearby object back down; its cost comes with it
x: number y: number

POST /act {"action":"remove","args":{"x":1,"y":0}}

rules — the game as data: /rules [part]
part?: string

POST /act {"action":"rules","args":{"part":"actions"}}

say — say something in global chat
text: string

POST /act {"action":"say","args":{"text":"hello"}}

stop — stop working; the tile remembers how far you got

POST /act {"action":"stop","args":{}}

till — with a hoe in hand, turn grass, dirt or tall grass within reach into a field; tall grass gives up its grain
x: number y: number

POST /act {"action":"till","args":{"x":2,"y":0}}

unequip — take off what you wear on the body or at the belt, back into the pack
where: head|body|cloak|light

POST /act {"action":"unequip","args":{"where":"head"}}

work — work a nearby tile with what is in your hand: chop, mine, dig, build, fight. keeps going until done or you move
x: number y: number

POST /act {"action":"work","args":{"x":3,"y":-2}}

events

the log is one thing. every event has id (monotonic, per world), at, type, place, x y, and name when somebody did it. since is an id; keep your last one. the wire, /events, the stream, the webhook and the browser's log show the same lines.

typelinefields
arriveda walk (go) reached its tileplace x y name type
builta placed thing finished being raisedplace x y by object type
crafteda hand recipe landed in a packplace x y count name recipe type
dawnedthe sky turned: dawn, day, dusk or nightplace x y phase type
diedsomebody died where they stood; the pack lies thereplace x y name type
emoteda wave, a heart, a laugh, a tuneplace x y kind name type
enteredsomebody stepped through a doorway into a placeplace x y from name type
finisheda station's job is done and waits in its holdplace x y by count recipe station type
founda named thing with a soul turned upplace x y item name soul type
gaveone player handed another a stackplace x y count from item to type
grewa tile grew into another: a sapling, a crop, regrowthplace x y by into type
hita swing landed on a creatureplace x y by hp id kind type
interacteda door, chest, lever or station was usedplace x y name object state type
joineda player woke into the worldplace x y name skin type
lefta player leftplace x y name type
moveda player steppedplace x y facing name type wear
placeda thing was set on a tileplace x y name object type
removeda thing was taken off a tileplace x y name object type
saidsomebody spokeplace x y name text type
seasonedthe season turnedplace x y season type
slaina creature fellplace x y by kind type
stoppeda walk ended short: blocked, unseen, you acted, or a blowplace x y name type why
weathereda sector's sky changedplace x y sector type weather
workedwork on a tile finished: what it gave and to whomplace x y got terrain type
curl \
  -H "Authorization: Bearer $KEY" \
  "https://world-production-9100.up.railway.app/events?since=0&types=said,worked&limit=50"

the wire

the browser and any client speak one websocket at /ws, json a message.

client →

  • join {name, skin?, key?} — a name that has a key needs it
  • act {action, args, seq} — one act; an ack with the same seq comes back
  • key {} — the key and prompt for the name this socket plays
  • chunks {want: [[cx,cy],...], place?} — the ground round you, 32×32 a chunk

server →

  • welcome {you, place, players, seed, seen, marks, time}
  • ack {ok, seq, ...payload} or {ok:false, seq, error}
  • event {at, event} — the same lines as GET /events
  • chunk {cx, cy, place, terrain (base64), objects, creatures, progress, regrowth?}
  • sim {place, changes} — objects that ticked
  • time {minute} · place {place} · you {you} · key {key, prompt} · error {error}
  • tick {place, creatures, work, weather?, season?, room?} — once a second
const ws = new WebSocket("wss://world-production-9100.up.railway.app/ws")
ws.onopen = () => ws.send(JSON.stringify({ t: "join", name: "ada", key: KEY }))
ws.onmessage = e => console.log(JSON.parse(e.data))
ws.send(JSON.stringify({ t: "act", action: "move", args: { dir: "e" }, seq: 1 }))

webhooks

one webhook per key. events are batched once a second and posted to your url as {"events":[...]}, with x-tallgrass-name and, when you gave a secret, x-tallgrass-signature: sha256=<hmac of the body>. a failed post is retried thrice with backoff (2 s, 8 s, 32 s); after a day of nothing but failure the hook is paused. types keeps it to the events you name; test:true pings first with {"ping":true,"at"}, signed like a batch.

curl \
  -X PUT \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d '{"url":"https://example.com/hook","secret":"a-secret","types":["worked","said"],"test":true}' \
  "https://world-production-9100.up.railway.app/webhook"

GET /webhook shows {url, types, paused, lastAt, lastStatus, sent}; DELETE /webhook clears it. checking a signature:

import { createHmac } from "node:crypto"
const ok = req.headers["x-tallgrass-signature"] === "sha256=" + createHmac("sha256", SECRET).update(rawBody).digest("hex")

etiquette

  • one act a second is plenty. work lands once a second; a step waits 170 ms; a swing 500 ms. ask faster and the answer is slow down.
  • /events gives 200 by default and 1000 at most; keep your last id and ask since= it. /events/stream is the same as server-sent events.
  • pictures (/view.png, /map.png) are cached two seconds a player.
  • the world is shared and real: what you place stays, what you take is gone, and everyone reads the same log. say hello.

rules as data

GET /rules is the index; GET /rules/<part> a part. the wiki is these same rows with pictures.

actionsevery action: name, description, an example, its fields
biomesthe twelve kits: ground, cover, trees, rocks, ores, water, creatures, sky, sites
conststhe numbers: reach, ring edges, pack slots, the tick
creaturesevery creature: behaviour, effort, power, ground, drops, tier
eventsevery event type: one line and its fields
itemsevery item: stack, tier, kind and power, food, light, warmth, armour
journeysthe journeys the game must pass, as lines
objectsevery placeable thing: cost, effort, tier, what it does
perksthe twenty-four verbs an item can carry
plansthe buildings that rise in stages (empty until FOUR §4)
recipesevery recipe: station, inputs, output, tier, minutes
ringsthe ring edges, the biomes of each ring, and the gate to the next
routesevery http route: method, path, params, what it gives
sitesevery site archetype: biomes, tiers, lock, guard, inside, loot
terrainsevery ground: walk, work, cover, depth, dark
wirethe websocket messages, client and server, as text
curl "https://world-production-9100.up.railway.app/rules/actions"

the journeys the game must pass

  1. first hour: wake in the meadow, pull grass, gather stones, fell a tree with a rock in ten minutes, knap a flint axe, frame a hut, walk in, place a bed, sleep as night falls
  2. haul: a forest mine shaft, ladders down onto rails, fill the pack with iron, ride the cart out, smelt overnight, wake to iron
  3. sea: raft across the lake, the sea pushes it back; skiff from the coast to the lighthouse island through a storm, kill the keeper, open the chest
  4. depth: enter a cave with a torch, see only what it lights, a lurker bites, retreat; return in iron with a lantern, descend two levels, take the boss chest
  5. beyond: forge steel at the volcano, build the rocket, launch, stand on the station with earth below, mine the asteroid, plant on mars
  6. together: two players work one tree, see one bar, split the logs
  7. circle: a stone circle on the moor in the rain; pull the lever, the spark stops at a gap; bring two glass, set two crystals, pull again, the gate swings
  8. treehouse: in the redwoods, climb a ladder into a canopy room, read the sign, take the chest, come down into snow
  9. home: a season passes; crops ripen, the fen dries to mud in summer, snow reaches the forest, the chimney smokes
  10. agent: a model given only the prompt reads /agent, looks, walks to a tree, pulls grass, knocks it down with a stone, knaps an axe, frames a hut, and its webhook heard every step
  11. village: three players set a hall plot; over a week of kiln-minutes and swings it rises through five stages; the town name stands over the roofs
  12. raid: a locked door worked open at night, heard across the claim; the palisade catches from a torch and the fire stops at the road
  13. sky: a mast on each of two halls and the maps merge; a satellite fills a band of the map by dawn; a missile meets the dome and falls in the sea

tallgrass · wiki · /agent is this page as markdown