A 3D farming game set in the real streets of Agartala, Tripura.
You fly home from wherever you live now to your grandfather's bari on the bank of the Haora river, behind Maharaja Bir Bikram (MBB) Stadium. When the game is deployed, your hometown comes from where you log in (Cloudflare or Vercel IP geolocation).
The game covers a detailed part of Agartala, about 3.3 × 2.2 km: the old town round Ujjayanta Palace (the Town Hall, Motor Stand with PRM Centrepoint at Kaman Chowmuhani, and Agartala City Centre at Paradise Chowmuhani), and Shibnagar, Math Chowmuhani, MBB Stadium, Ramnagar and Joynagar. You arrive at the stadium gate and ride your first tomtom to the fields by the river. Then you grow paddy and Queen pineapple, tap rubber at dawn, and help neighbours across the paras with daily kaam. The festivals run from Garia Puja to Durga Puja at Modern Club.
Every road, lane, pond and lake, and the river, come from OpenStreetMap and sit where they really are. About 6,800 real building footprints are filled out to about 8,900 houses (see A fuller city below).
It runs in the browser, with no install: sign in with Google and play. Three.js on the front, a small server with one set of rules shared by the browser and the server.
npm install
npm run dev # → http://localhost:5191That's the whole game, server included. The Vite dev server also runs the API (api/*.ts), and saves go to JSON files in .data/, so you need no database or account to develop.
npm run typecheck # TypeScript, strict
npm test # unit tests: rules, story, economy, storage contract, API
npm run build # production build → dist/
npm run assets # rebuild the 3D asset kit → public/models/kit.glbIf you deploy only dist/ to a static host with no API, the game still works: it notices there's no server and saves in the browser.
| Key | What it does |
|---|---|
| WASD / arrows | walk; hold Shift or press R to run (faster still on a bicycle) |
| Mouse drag, wheel | look, zoom |
| E | do the thing in front of you: till, sow, water, harvest, tap, cut, talk, trade, take a tomtom |
| Z | sleep till 06:00 (after 19:00) |
| M | city map: type a place and press Jump, or click anywhere on it to jump there |
| L | your farm: farm, people, Grandpa's album and the boards |
| P | photo mode: hide the HUD and save a postcard |
| E by a vehicle | get on; then W/S throttle, brake and reverse, A/D steer, Space brake, E get off |
| H | help |
On phones there's a joystick and an Act button.
browser (Three.js) Worker (worker/ → api/) Cloudflare D1 (SQLite)
┌──────────────────────────────┐ actions (batched) ┌──────────────────────────┐ ┌──────────────────┐
│ render · walking · UI │ ──────────────────▶ │ re-check every action │ ──▶ │ kv: saves (json, │
│ applies moves instantly │ ◀────────────────── │ with the SAME rules code │ │ versioned, CAS) │
│ (optimistic), rolls back if │ the true save │ on the server's clock │ │ leaderboard │
│ the server says no │ └──────────────────────────┘ └──────────────────┘
└──────────────────────────────┘ ▲ src/shared/ — the rules, used by both sides
These are pure TypeScript, used by both the browser and the server.
rules.ts: every action and its checks, including rides (ride/arrive), kaam, parcels (pickup/deliver), sights (visit), hometown and home site.errands.ts: the daily parcels and the 12 sights. Money, land, crops and missions change only throughapply(), so a tampered client gains nothing.time.ts: the game clock, which runs from real timestamps. Crops keep growing while you're away, up to a week.economy.ts: Gopal's seasonal rates (pineapple is cheap in the monsoon glut), and net worth.missions.ts: the nine-mission story.save.ts: the save format and how old or hand-edited saves get normalised.farm.ts: the farm layout.stands.ts: the tomtom stands.spots.ts: generated from OSM.
scripts/build-assets.mjs generates public/models/kit.glb: 18 Agartala-style building models and a tomtom, all sharing one texture atlas plus a night-window glow atlas. The buildings are:
- tin-roof huts (one with bamboo walls)
- Assam-type houses with verandahs
- 1–4 storey RCC houses with balconies, chhajjas, parapets and the black rooftop water tank
- shop-houses with rolling shutters and Bengali-style signboards
- school/office blocks
- godowns
Each model is built at a nominal size, facing +Z. The map bake works out each OSM footprint's smallest bounding rectangle and which side faces the nearest road. The game then picks a model from the footprint's size, its street (main-road frontage gets shops) and the neighbourhood, fits it, and draws it with instancing.
Wall faces carry a _TINT vertex attribute, so every building gets its own paint colour. Detailed models draw within about 700 m of you, and plain blocks of the right height draw beyond that.
To use your own models, export a GLB from Blender with the same node names (rcc2, shop2_a, tomtom, …) and drop it in as kit.glb.
The title screen asks where home is. You can type a neighbourhood, chowmuhani, bazar or sight (the list comes from the map), or pick a spot on the map.
The game then finds an open plot beside a lane near that spot (findHomeSite in src/client/map/places.ts) and moves your stilt house, Grandpa, your bed and the Home tomtom stand there. The arrival story, Grandpa's lines and the mission text all follow it.
The family fields stay by the Haora with their own stand, so farming becomes a commute by tomtom like it would be. You can move home later from the Farm panel (L).
- Kaam: five neighbours ask for goods each day.
- Parcels: three a day to carry across town. You pick one up from one neighbour and deliver it to another, carrying one at a time. Pay depends on the distance.
- Real places and sights: the area's real buildings and places from OpenStreetMap and Wikipedia (
src/shared/places.ts): MBB College in its yellow and brown with its gate, MBB Stadium with galleries, Tripura Government Law College, Mahatma Gandhi Memorial School, the Modern Club Shiv Mandir, Modern Club Park, College Tilla Lake and College Lake, and more, each named on its own building. Eleven are sights, each with a little history. - Built from the real footprints: the landmark buildings (MBB College's halls, the stadium's pavilion and galleries, the law college, the schools, the clinic, the apartment block, the masjid) are extruded from their actual OSM outlines with proper facades (arched windows for the college), parapets and lit windows at night, not fitted with a generic model. MBB College's main block has a portico. The cricket ground has a pitch with creases and stumps, mowing stripes, a boundary rope, sight screens and a scoreboard. The lakes have benches, lamps, boats and a bathing ghat by the Shiv Mandir; Modern Club Park has a fence, benches, a swing and a slide. Code in
src/client/scene/realplaces.ts. - Street life: black water tanks on the flat roofs, concrete electric poles with sagging wires along every lane, and painted shop boards in English and Bangla over the shutters on the main roads (
src/client/scene/realism.ts). Your first visit to each pays ₹25. - Tracking: the Farm panel lists everything for the day. The minimap marks kaam in blue, parcels in orange, unseen sights in purple and your house with a house icon.
- A bazar that moves: Gopal's rates follow the season, one item is in demand each day (+30%), and selling a lot at once pushes the rate down for a few days. Fruit, fish and pitha go off overnight unless you build a cold store.
- More to grow and make: tea on the third tilla (plant once, pluck every morning), agar trees (a month to grow, worth a fortune), rohu and katla in the pukur, cane baskets from bamboo, pitha from paddy in the kitchen.
- Vehicles you drive: Grandpa's old cycle-rickshaw from day one, and a bicycle, scooter, motorbike, tomtom and car from Gopal. They park by the house; walk up and press E. Each handles differently (the car is quick but wide, the rickshaw slow and heavy), and they bump off walls and stop at water. Stop a rickshaw or tomtom at any tomtom stand to pick up a passenger, follow the gold marker, and get paid a fare (up to 12 a day).
- Rent a ride: every tomtom stand has a rental point (green on the map) with a few vehicles: bicycles everywhere, scooters, motorbikes, rickshaws, a tomtom at the stadium and cars at Math Chowmuhani and Joynagar. Pay the first game hour up front, then the meter runs (₹3 an hour for a bicycle up to ₹30 for a car) until you return it at any rental point. A forgotten rental is charged for a day at most. Code in
src/client/vehicles.tsandsrc/shared/vehicles.ts. - Kit: diesel pump set (waters every plot at once), cold store, and a farm hand who waters and harvests at dawn for a week.
- Energy and food: work tires you; rest, sleep, or eat at the food stalls by every tomtom stand (momo, mui borok, chakhwi, wahan mosdeng…).
- Seasons: monsoon floods in Asharh and Srabon (paddy grows faster, the lunga can't be tilled, rides are slow on muddy roads).
- Festivals every year: Kharchi Puja fair, Ker Puja (no tomtoms till 18:00), Durga Puja at Modern Club, Diwali lamps on the lakes, Pous Sankranti pitha.
- Bus trips: Neermahal, the Tripura Sundari temple (a four-day blessing on your crops), the Akhaura border retreat at sunset, and the weekly Kamalasagar border haat.
- People: friendship with each neighbour (friends pay more and invite you over in the evening), a Kokborok and Bangla phrasebook you learn by talking, and ten chapters of Grandpa's stories.
- Other players: signed-in or not, other farmers walk the same streets with name tags. Visit a friend's farm by their visit code, water their crops, or send them a gift. Weekly boards by category, and para against para.
- Polish: street lamps and a crowd that follows the hour, stadium floodlights at night, ambient sound (rain, crickets, birds, temple bells, the dhak), photo mode with postcards, and an installable app that opens offline.
Movement has some inertia: you build up to a run and ease to a stop. Walls are slid along rather than stopping you dead, and fast movement is split into small steps so you can't clip through corners.
When you run, the stride, arm swing, forward lean and bob all grow with speed, the camera pulls back and widens a little, and footsteps sound on each foot-fall. Running is Shift (hold) or R (toggle), and on touch screens you push the joystick all the way.
OpenStreetMap is missing many of Agartala's houses, especially in the newer paras. During the map bake, scripts/bake-map.mjs marks everything that must stay open on a 2 m grid: roads, water, parks, fields, landmarks and existing buildings. It then fills the gaps with modest houses facing the lanes, a front row plus houses in the backs of blocks, only where the neighbourhood is already built-up.
That adds about 2,000 houses to OSM's 6,800. The infill is seeded, so it's identical on every build, and these houses are marked from index bs in the map file.
About 700 people walk the lanes. They're drawn with a handful of instanced meshes, so they're cheap even on a laptop. Each has their own clothes, skin and walking pace; most keep to the edge of the road, some cross, and some stand and chat.
The crowd and the 70 tomtoms follow you around the city: anyone who falls far behind reappears on a road ahead of you, and after a jump or a ride the new area fills straight away. Stop anyone with E for a chat.
- Routing:
src/client/route.tsbuilds a road graph from the OSM streets and finds routes with A*, preferring main roads the way drivers do. - The ride: you sit in the back with the driver up front and the camera follows. A golden line on the road shows where you're going, and E or Space skips ahead.
- Timing: the server sets the ride length, so the tomtom's speed is set to cover the real route in exactly that time. While you ride, game time passes at the ride's pace, not the usual rate, and the server books the rest of the time if you skip.
- Fares: your first ride is free. After that it's ₹20.
- Staying on the road: the tomtom keeps left but squeezes toward the middle of narrow lanes, so it never clips a wall. The map bake also drops the few OSM footprints that sit right across a street (gates, overhangs, mapping slips).
- Neighbours: ten named neighbours live round the paras: a tea stall by the Modern Club Shiv Mandir, a tailor off Math Chowmuhani, the stadium groundsman, a boatman at the Haora bridge, and so on. Every day five of them ask for something.
- Passers-by: people walking the lanes stop, turn to face you, and chat when you press E near them.
- The Farm panel (L): lists today's kaam, and the minimap marks who needs help with blue dots.
map/data.ts: loadspublic/agartala-map.jsonand answers spatial questions (inside a building? in water? which road?).scene/:world.ts: ground, parks, ponds, roads, extruded buildings, rooftop water tanks, trees and sky.landmarks.ts: the stadium floodlights, the Modern Club pandal, the bazar stall, tomtom stands, the bus and NPCs.farm.ts: the farm models.people.ts: people and traffic.
game.tsandnet.ts: optimistic moves, batched sync, rollback, and the offline fallback.interact.ts: what E does, the dialogue, and where the mission marker points.player.ts,sky.ts,audio.ts(all sound is synthesized),auth.ts(sign-in buttons), andui/.
Each one is a Web Request → Response handler, so the same files run on the Cloudflare Worker (through worker/index.ts), on Vercel, and inside npm run dev.
session.ts: create a guest, return a farm for its token, or restore one by farm code. A new farm's hometown and distance come from Cloudflare'srequest.cf(or Vercel'sx-vercel-ip-*headers).state.ts: load a save.act.ts: apply a batch of moves. Saves are compare-and-set onrev, so two devices playing at once never lose moves.leaderboard.ts: the public board of named farms, ranked by net worth.auth-config.ts,auth-start.ts,auth-google-callback.ts,auth-email-callback.ts: sign-in with no third-party auth service (see below).health.ts, anddev.ts(a dev-only time skip that returns 404 when deployed)._lib/store.ts: the storage backends: Cloudflare D1, Supabase over REST, memory, and files for local dev (_lib/file-store.ts). The same contract tests run on all four; D1 is tested against real SQLite.
Players start as guests with no sign-up. Signing in makes a farm follow them to any device:
- Google: the standard OAuth code flow.
auth-startreturns Google's URL, with a signedstatenaming the farm and a nonce cookie that pins the round-trip to this browser. The callback swaps the code for Google's ID token, checks it, and linksuser:google:<id>to the farm. - Email: a signed link that works once and expires in 15 minutes, sent through Resend. It is limited to 5 emails per farm per hour.
- If the account already has a farm, the device switches to it; otherwise the current farm becomes the account's. The new session comes back in the URL fragment (
/#auth=…), so it never reaches a server log.
Secrets (AUTH_SECRET, Google client secret, Resend key) live only on the server. Each method shows up in the game only when its keys are set.
-
Area: the paras round MBB Stadium: fetched with bbox
23.810,91.282 → 23.840,91.314and played inside a 1.9 × 1.9 km box (src/shared/area.ts), at 1 game unit = 1 metre. Map data © OpenStreetMap contributors, ODbL. -
Rebuild it from fresh OSM data:
npm run map # = map:fetch (Overpass → data/raw/agartala-osm.json) + map:bake (→ public/agartala-map.json, src/shared/spots.ts)To cover a different area, pass
node scripts/fetch-osm.mjs --bbox S,W,N,E. -
Building heights: OSM rarely records storeys in Agartala, so heights are estimated from footprint size.
-
Hand-placed on real spots: OSM doesn't name Ramnagar, Joynagar or Math Chowmuhani, so their centres are set by hand in
src/shared/area.ts(PARAS); adjust them there if they're off. The farm sits on open land between the stadium and the Haora (FARM_ZONEinsrc/shared/farm.ts, kept free of houses by the bake). Its three small tillas are invented. Kharchi Puja happens by bus, because the Chaturdasha Devata Mandir is about 8 km east, off the map.
Everything runs on one Cloudflare Worker: the static game as Workers Static Assets, the API in worker/index.ts, saves in D1. The free plan is enough.
-
Sign in to Cloudflare from this folder:
npm install npx wrangler login
-
Create the database and paste the printed
database_idintowrangler.toml:npx wrangler d1 create agartala npm run cf:db # creates the tables (cloudflare/schema.sql) -
Deploy the Worker:
npm run cf:deploy
You get a
https://agartala.<your-subdomain>.workers.devURL, and the D1 binding comes fromwrangler.toml. -
Subdomain: in the Cloudflare dashboard, go to Workers & Pages → agartala → Settings → Domains & Routes → Add → Custom domain, and enter
agartala.biratdatta.tech. Becausebiratdatta.techis already on Cloudflare, it adds the DNS record and certificate for you. -
Secret for sign-in:
openssl rand -base64 48 | npx wrangler secret put AUTH_SECRET -
Google sign-in:
-
In Google Cloud Console, go to Create credentials → OAuth client ID → Web application.
-
Authorized redirect URI:
https://agartala.biratdatta.tech/api/auth-google-callback. Add yourworkers.devURL +/api/auth-google-callbacktoo if you want sign-in on that URL. -
On the OAuth consent screen, set the app name and your email, and publish it so anyone can sign in.
-
Then set the two secrets:
npx wrangler secret put GOOGLE_CLIENT_ID npx wrangler secret put GOOGLE_CLIENT_SECRET
-
-
Email links (optional):
-
Create a Resend account and verify
biratdatta.techthere. Resend shows DNS records you can add in Cloudflare DNS. -
Then:
npx wrangler secret put RESEND_API_KEY npx wrangler secret put EMAIL_FROM # e.g. Amar Agartala <[email protected]>
-
Sign-in required: REQUIRE_SIGNIN = "1" in wrangler.toml means nobody gets a farm without signing in with Google: no guest farms, and farm codes don't open farms. This keeps bots from filling the database. It only takes effect once the three Google/auth secrets are set; set it to "0" to allow guests again.
- Redeploy (
npm run cf:deploy) so the secrets take effect, then openhttps://agartala.biratdatta.tech/api/health. It should say"store":"D1Store"./api/auth-configshows which sign-in methods are on.
Auto-deploy from GitHub instead: under Workers & Pages → Create application → Import a repository, pick this repo, with build command npm run build and deploy command npx wrangler deploy, and add NODE_VERSION = 22 as a build variable. The D1 binding comes from wrangler.toml; add the secrets under the Worker's Settings → Variables and Secrets.
Test the Cloudflare build locally: copy .env.example to .dev.vars, fill in AUTH_SECRET, run npm run cf:db:local once, then npm run cf:dev (→ http://localhost:8787). For Google sign-in locally, add http://localhost:8787/api/auth-google-callback as a redirect URI.
Players can ask for a missing place from the map ("Missing a place?"). Each request is a row in the D1 table place_requests: name, kind, note, the spot (game x/z and lat/lon), the farm's public id, email, status and created_at. The table is created on first use. To read them:
npx wrangler d1 execute agartala --remote --command "SELECT created_at, name, kind, note, lat, lon, email, status FROM place_requests ORDER BY created_at DESC LIMIT 50"Mark one done with UPDATE place_requests SET status = 'added' WHERE id = '…'. You can also browse the table in the Cloudflare dashboard: Storage & Databases → D1 → agartala → place_requests.
Each farm can send five a day.
The same api/ files run as Vercel functions:
- Storage is Supabase: run
supabase/schema.sql, then setSUPABASE_URLandSUPABASE_SERVICE_ROLE_KEY. - Set the sign-in variables from
.env.example.
Deployed without a database, the API refuses to run instead of silently losing saves.
api/ the server: one file per endpoint + _lib (store, sessions, auth, http)
worker/ Cloudflare Worker entry that routes /api/* to api/
src/shared/ game rules shared by client and server
src/client/ the browser game
public/ agartala-map.json (baked OSM data)
scripts/ fetch-osm.mjs, bake-map.mjs, build-assets.mjs
public/models/ kit.glb (the 3D asset kit)
docs/ atlas-preview.png
cloudflare/ schema.sql for D1 (wrangler.toml at the root)
supabase/ schema.sql (if you host on Vercel)
tests/ vitest suites
data/raw/ the raw OSM extract
- Add window detail and real storey counts where mappers have tagged them.
- Add more landmarks: Gedu Mia Mosque, Lakshminarayan Bari, M.B.B. College, Neer Mahal (as a day trip).
- Add a Kokborok and Bengali UI toggle, checked by native speakers.
- Add fishing in Dim Sagar, and a Kharchi Puja fair you can walk around.
The code is MIT. Map data © OpenStreetMap contributors (ODbL).