hypercross 50c6df48b5 fix(tabletop): align cards to the stacking curve
Fix the tangent angle at the curve's start and rotate cards to follow
the curve direction. Add a stacking curve visualization shown with
surface debug.
2026-08-10 00:40:56 +08:00

TTS Workshop

Search the Tabletop Simulator Steam Workshop, fetch full TTS save files, and analyze their contents. A lightweight, client-only pnpm monorepo.

Packages

Package Role Runtime
apps/web React frontend: search + mod pages + 3D object viewers Browser
apps/proxy Hono HTTP server: Workshop search, save fetch, tracing Node
packages/tts Fetch save from Steam, BSON-parse to TTSMod Node
packages/extract Analyze a TTSMod: objects, asset refs, downloads Isomorphic
packages/mesh 2D shapes + extrusion into 3D mesh geometry Isomorphic
packages/shared Shared types + zod schemas Isomorphic

See docs/architecture.md for the architecture and docs/implementation-plan.md for the plan.

Setup

pnpm install
cp .env.example .env   # then set STEAM_API_KEY
pnpm dev               # runs the proxy at http://localhost:3000

Get a Steam Web API key at https://steamcommunity.com/dev/apikey (free).

To run the frontend alongside the proxy, open a second terminal and run pnpm --filter @tts/web dev (serves at http://localhost:5173 and proxies /search, /items, /health, /asset, and /trace to the backend).

API

Method Path Description
GET /health Liveness
GET /search?q=&page= Search the Workshop (scrapes browse page)
GET /items/:id Full parsed TTSMod (BSON save)
GET /items/:id/file Raw save bytes, filename from header
GET /asset?url= CORS-safe proxy for external assets (textures, models)
GET /trace?url=&mode=&format=&offset= Trace an image into a vector shape (BSON); offset insets/outsets in pixels

Commands

pnpm dev          # run the proxy (tsx watch)
pnpm dev:web      # run the frontend (vite)
pnpm build        # compile all packages
pnpm typecheck    # typecheck all packages
pnpm test         # run the unit tests (vitest)
pnpm lint         # lint all packages

Object viewers

The mod page renders each selected object in 3D. Tiles, tokens, cards, and custom models each have a viewer built on @react-three/fiber, @react-three/drei, and @react-three/postprocessing, registered per object class and lazy-loaded so the three.js stack is code-split out of the main bundle.

  • Tiles / tokens — extruded from a 2D shape; tokens trace the image's alpha channel via /trace to match the artwork's silhouette.
  • Cards — a thin rounded rect. Deck images are sheets divided into a NumWidth x NumHeight grid; the face/back sprite is selected by CardID from the containing deck's config.
  • Custom models — GLTF/OBJ/FBX loaded from CustomMesh.MeshURL.

The camera fits the object's bounds on load, and back faces are flipped so they aren't mirrored. See docs/decisions.md for the rationale behind these choices.

How search works

Steam has no official search API. The proxy fetches the Workshop browse page (steamcommunity.com/workshop/browse/?appid=286160) and parses the embedded window.SSR.renderContext JSON (a React Query cache containing a workshop_browse entry with the results). This is more robust than scraping the DOM, but Steam can still change the page structure — if search breaks, that parser is the first place to look.

Notes

  • STEAM_API_KEY is optional. Search works without it. The mod page works without it too when you arrive from a search result: the frontend passes the item's file_url (already present in search results) to /items/:id via a fileUrl query param, so the proxy downloads the save directly instead of calling the Steam API. Without a key and without a fileUrl (e.g. a deep-linked mod page), /items/* returns 500.
  • Each request fetches fresh; there is no caching by design (client-only tool).
  • packages/extract has zero runtime dependencies and runs in browser or Node, ready for a future frontend.
S
Description
No description provided
Readme
2.5 MiB
Languages
TypeScript 98.8%
JavaScript 1%
HTML 0.2%