Files
hypercross 345832e389 docs: reorganize docs into bgm and status folders
Group the bgm spec cluster under docs/bgm and move dev logs and plans under docs/status, add an overview index, and update cross-references in the docs, README, and source comments.
2026-08-16 11:57:19 +08:00

95 lines
4.4 KiB
Markdown

# 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/overview.md`](docs/overview.md) for the docs index,
[`docs/architecture.md`](docs/architecture.md) for the architecture, and
[`docs/status/implementation-plan.md`](docs/status/implementation-plan.md) for the plan.
## Setup
```sh
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 the URL path |
| 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
```sh
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`](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.