Two games on BehaviorGPT's map of 8,409 products.
- ProductGuessr (
/productguessr/): GeoGuessr for products. - MapGolf (
/mapgolf/): get from one product to another in as few shots as possible, jumping only between products the model ranks as similar.
GeoGuessr, but for products. BehaviorGPT has placed 8,409 products on a map where similar things live side by side, grouped into seven continents. You get a mystery product (a blurred photo and a price), drop a pin where you think it lives, and score up to 5,000 points depending on how close you land. Unblurring the photo or showing the name costs 300 points each. From round 3 the map loses a clue each round: first the continent names, then the landmarks, then the legend. Five rounds, a daily challenge, and a shareable result.
It is a small, complete example of building on the BehaviorGPT SDK's embedding map: harvesting products, embedding them as your own catalog, pulling coordinates out of client.umap(), and turning them into a game that runs without an API key.
Status: working prototype.
A side-view golf hole: tee on the left, pin on the right, and a ball that flies forward or backward with every shot. Each turn offers three shots as picture cards, all taken from client.similar_products() for the product your ball is on: a chip (one of the 4 most similar products), an iron (ranks 9–18) and a driver (ranks 25–36). Pick the one that feels closest to the pin, and the game tells you how it went and why: "WOW, what a drive! 310 m closer. Shoppers who look at iTouch Air Smartwatch also look at SAMSUNG Galaxy Watch, and that's Tech & Gaming, where the pin is."
The distance to the pin counts the steps left along the model's links (200 m each, found by a breadth-first search backwards from the pin), plus map distance to break ties. Two of the three shots get you closer whenever the links allow it, and the pin shows up as a putt once it is in reach. Holes are 2–3 steps on the best route, par is two more. Long Amazon titles are shortened on the cards (full title on hover).
| In the game | BehaviorGPT call |
|---|---|
| The products | client.complete(history=[Search(q)]) over 150 everyday queries, plus the cold-start ranking |
| Your own catalog to map | client.embed(parquet) |
| The map itself | client.umap(catalog_id=...), coordinates parsed out of the Plotly page |
| "It lives next to…" after each guess | the product's nearest neighbours on that map |
| MapGolf's shots | client.similar_products(id, limit=60) for all 8,409 products (top 36 kept) |
The built map is committed in docs/data.json, so playing needs no key at all:
git clone https://github.com/Jenspalmborg/maparcade.git
cd maparcade
python3 -m http.server 8000 -d docsOpen http://localhost:8000 and pick a game. Links like /productguessr/?seed=2026-09-27 or /mapgolf/?seed=2026-09-27 replay the same game, so friends can play it too.
The site is plain HTML, CSS and JS with no build step, so GitHub Pages can host it straight from the docs/ folder.
Needs Python 3.11+, uv and a BehaviorGPT API key from unboxai.com/behaviorgpt.
uv sync
cp .env.example .env # paste your key as UNBOXAI_API_KEY
uv run python scripts/harvest.py # ~8,400 products from retail_catalog (~1 min)
uv run python scripts/embed.py --sample 300 # optional: quick test upload first
uv run python scripts/embed.py # upload and embed them (a few minutes)
uv run python scripts/build_map.py # fetch the map, write docs/data.json
uv run python scripts/neighbours.py # similar products for MapGolf (~5 min, resumable)
uv run python scripts/build_golf.py # links -> docs/golf.json, plus a connectivity check- Harvest. The pre-embedded catalogs have no map (
umapreturns a server error for them), soharvest.pycopies products into a catalog of our own. It runs 150 searches across everyday shopping areas plus the popularity ranking, and drops near-identical variants ("Nike Men's Retro" × 20), which would otherwise pile onto one spot and make rounds repetitive. - Embed. The API returns every field as a string (
"None","[1, 2, 3]"), soembed.pyconverts each column back to the types in the catalog format before uploading. Each API key holds one catalog: a new upload replaces the previous one under the same id. - Map.
build_map.pypulls the coordinates out of theumappage, as aic-artworks does. - Continents. The raw map is an archipelago (see below), which reads as confetti. So the categories are folded into seven broad groups, and each group's islands are gathered into one continent: the model's arrangement within a group is kept, shrunk, with far-flung islands tucked in. The biggest continent sits in the middle and the others around it, in the direction the model's map puts them. Products with no category join the group of their nearest neighbours on the model's map.
- Even clouds. The model packs similar products onto the exact same point (8,409 products on ~1,900 spots). Dots that overlap push each other apart, a mild pull keeps each continent together, and a little noise makes it look organic. Neighbours stay neighbours; only the spacing changes.
- Landmarks. For each region of the map, the word that is far more common there than elsewhere ("espresso", "mattress", "iphone") becomes a label, so players can find their way.
- Game.
docs/productguessr/index.htmldraws the map on a canvas with pan, zoom and pinch. Scoring is5000 × e^(−distance / 0.09)in map units. Rounds are drawn with a seeded RNG, from popular products with descriptive titles, spread over different departments. Hovering over products only works after you guess, so no peeking. Difficulty climbs by removing clues per round (CLUESin the page), and every clue comes back on the reveal so players learn the map as they go.
Locally the map is very good: 82% of each product's ten nearest neighbours are from the same department (13% by chance), and the neighbours are the kind of thing you'd expect (phone mounts next to phone mounts, pasta next to ravioli). Globally it is an archipelago rather than continents: each department is spread over many small islands, interleaved with the others.
So be clear about what the game's map is: within a continent, positions come from BehaviorGPT; which continent sits next to which is my layout, guided by the model's map but not measured by it. Knowing a phone mount is "Tech & Gaming" gets you to the right continent; finding the phone-accessory corner of it is where the points are.
scripts/
harvest.py retail_catalog -> data/products.jsonl
embed.py products -> data/productguessr.parquet -> embedded catalog
build_map.py umap page -> docs/data.json (points, landmarks, round pool)
neighbours.py similar_products for every product -> data/neighbours.jsonl
build_golf.py neighbours -> docs/golf.json (36 most similar per product)
docs/
index.html start page: pick a game
productguessr/ ProductGuessr (no build step)
mapgolf/ MapGolf (no build step)
guessr/, golf/ redirects from the old paths
data.json the built map, shared by both games
golf.json MapGolf's links between products
screenshots/ README images
data/ harvested products, parquet, catalog id (git-ignored)
Product names, prices and images come from BehaviorGPT's pre-embedded retail_catalog; images are hot-linked, not copied. The code is MIT licensed.


