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
- you wake in the meadow with a little fibre, stone and wood in the pack, beside a sign.
- walk to tall grass and hold space facing it: two seconds, fibre. stones the same: stone.
- put a stone in hand (1–9 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.
- open the book (b) and knap a flint axe by hand: twenty seconds standing still. the next tree takes two and a half minutes.
- 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.
- 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 d | walk. hold to keep walking; a tap steps one tile |
| space | swing at what is beside you, or work the tile you face; hold it to keep working |
| e | use what you face: a door, a chest, a station, a sign. in the pack: eat or wear what is under the mouse |
| f | swap the hand and the quick slot; f again swaps back |
| 1 – 9 | put that slot of the top row in the hand |
| x | take up the thing you face |
| tab / i | the body: your four bars, the pack, the hand, what you wear |
| b | the book: every recipe, how and where; what you can make now on top |
| v | build: the shelf of things you can raise; then a click sets the ghost down |
| m | the map: your fog, ring lines, sites you have seen; + and − zoom |
| g | agent: 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 / t | chat |
| esc | close whatever is open |
| click | walk 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 outfrom: 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 timerecipe: 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 goneslot: number count?: number
POST /act {"action":"drop","args":{"slot":0,"count":1}}
eat — eat one of a food from a pack slotslot: number
POST /act {"action":"eat","args":{"slot":3}}
emote — show an emote bubblekind: 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 packslot: 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 castx: number y: number
POST /act {"action":"fish","args":{"x":4,"y":1}}
give — hand a pack slot to a nearby playerslot: 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 knowto?: 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 withslot: number
POST /act {"action":"hand","args":{"slot":1}}
harvest — take a ripe crop within reach: its yield and the seed back into your packx: 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 nextx: 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 itx: 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 reachradius?: number
POST /act {"action":"look","args":{"radius":8}}
move — face a direction and step one tile if clear; a doorway carries you throughdir: 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 workedobject: 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 clockslot: number x: number y: number
POST /act {"action":"plant","args":{"slot":4,"x":2,"y":0}}
quick — set the quick slot: what F usesslot: 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 itx: 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 chattext: 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 grainx: 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 packwhere: 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 movex: 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.
| type | line | fields |
|---|---|---|
| arrived | a walk (go) reached its tile | place x y name type |
| built | a placed thing finished being raised | place x y by object type |
| crafted | a hand recipe landed in a pack | place x y count name recipe type |
| dawned | the sky turned: dawn, day, dusk or night | place x y phase type |
| died | somebody died where they stood; the pack lies there | place x y name type |
| emoted | a wave, a heart, a laugh, a tune | place x y kind name type |
| entered | somebody stepped through a doorway into a place | place x y from name type |
| finished | a station's job is done and waits in its hold | place x y by count recipe station type |
| found | a named thing with a soul turned up | place x y item name soul type |
| gave | one player handed another a stack | place x y count from item to type |
| grew | a tile grew into another: a sapling, a crop, regrowth | place x y by into type |
| hit | a swing landed on a creature | place x y by hp id kind type |
| interacted | a door, chest, lever or station was used | place x y name object state type |
| joined | a player woke into the world | place x y name skin type |
| left | a player left | place x y name type |
| moved | a player stepped | place x y facing name type wear |
| placed | a thing was set on a tile | place x y name object type |
| removed | a thing was taken off a tile | place x y name object type |
| said | somebody spoke | place x y name text type |
| seasoned | the season turned | place x y season type |
| slain | a creature fell | place x y by kind type |
| stopped | a walk ended short: blocked, unseen, you acted, or a blow | place x y name type why |
| weathered | a sector's sky changed | place x y sector type weather |
| worked | work on a tile finished: what it gave and to whom | place 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 itact {action, args, seq} — one act; an ack with the same seq comes backkey {} — the key and prompt for the name this socket playschunks {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 /eventschunk {cx, cy, place, terrain (base64), objects, creatures, progress, regrowth?}sim {place, changes} — objects that tickedtime {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. /eventsgives 200 by default and 1000 at most; keep your lastidand asksince=it./events/streamis 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.
actions | every action: name, description, an example, its fields |
biomes | the twelve kits: ground, cover, trees, rocks, ores, water, creatures, sky, sites |
consts | the numbers: reach, ring edges, pack slots, the tick |
creatures | every creature: behaviour, effort, power, ground, drops, tier |
events | every event type: one line and its fields |
items | every item: stack, tier, kind and power, food, light, warmth, armour |
journeys | the journeys the game must pass, as lines |
objects | every placeable thing: cost, effort, tier, what it does |
perks | the twenty-four verbs an item can carry |
plans | the buildings that rise in stages (empty until FOUR §4) |
recipes | every recipe: station, inputs, output, tier, minutes |
rings | the ring edges, the biomes of each ring, and the gate to the next |
routes | every http route: method, path, params, what it gives |
sites | every site archetype: biomes, tiers, lock, guard, inside, loot |
terrains | every ground: walk, work, cover, depth, dark |
wire | the websocket messages, client and server, as text |
curl "https://world-production-9100.up.railway.app/rules/actions"
the journeys the game must pass
- 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
- haul: a forest mine shaft, ladders down onto rails, fill the pack with iron, ride the cart out, smelt overnight, wake to iron
- 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
- 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
- beyond: forge steel at the volcano, build the rocket, launch, stand on the station with earth below, mine the asteroid, plant on mars
- together: two players work one tree, see one bar, split the logs
- 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
- treehouse: in the redwoods, climb a ladder into a canopy room, read the sign, take the chest, come down into snow
- home: a season passes; crops ripen, the fen dries to mud in summer, snow reaches the forest, the chimney smokes
- 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
- 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
- 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
- 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