# Full Setup View — Plan > **Scope:** A new page that renders every loadable object in a save into a > single shared 3D scene, sharing materials and geometry where possible. This > complements the existing per-object inspection on the mod page. ## Motivation The mod page (`apps/web/src/pages/ModPage.tsx`) inspects one object at a time inside a shared `Scene`. A "full setup" view renders the whole save at once — every tile, token, card, and custom model laid out on a virtual table — so a user can see the entire scene at a glance. ## Current architecture (relevant pieces) - Each viewer owns its own `` wrapper (`components/viewers/Scene.tsx`), which provides the camera, lights, orbit controls, contact shadows, and post-processing. The mesh content lives inside each viewer: - `TileViewer` → `` (extruded tile, textured top face) - `TokenViewer` → `` (alpha-traced extruded token) - `CardViewer` → `` (rounded-rect card, sprite UVs) - `CustomModelViewer` → `` (GLTF/OBJ/FBX via `FlexibleModelLoader`) - `@tts/extract` provides `flattenObjects(mod)` / `traverseMod` to enumerate every object in a save. - `@tts/mesh` provides the shape + extrusion helpers used by the viewers. - Every object in a save carries a `Transform` (position/rotation/scale in TTS world units), which the proxy passes through in the raw BSON. The shared type now models it as `TTSObjectTransform`. ## Plan ### 1. Refactor viewers to expose their mesh content (no `Scene`) Each viewer currently wraps its mesh in ``. To compose everything into one scene, extract and export the inner mesh components, keeping the existing viewers as thin wrappers: - `TileViewer` → export `TileObjectMesh` (accepts a `TTSObject`) - `TokenViewer` → export `TokenObjectMesh` - `CardViewer` → export `CardObjectMesh` - `CustomModelViewer` → export `CustomModelMesh` (the `` + fallback box) Each viewer now renders ``, so the object-facing wrapper is the single source of truth for both the per-object view and the full-setup view. ### 2. New page `FullSetupPage.tsx` at `/mod/:id/setup` - Reuse `useModStore` (same load path as `ModPage`). - `flattenObjects(mod)` → filter to renderable classes (those registered in `components/viewers/register.ts`: `Tile`, `Custom_Tile`, `Custom_Token`, `Card`, `CardCustom`, `Deck`, `DeckCustom`, `Custom_Deck`, `Custom_Model*`). - Render **one** `` containing all renderable objects as `` entries, dispatching to the right `*Mesh` by `Name`. - Show a summary header (total objects, rendered count, skipped count) and loading/error states matching `ModPage`. ### 3. Layout - Place each object at its real position from the save's `Transform` instead of a generated grid. - `components/viewers/transform.ts` converts a TTS transform to three.js: TTS is left-handed (Y up, +Z toward the player), three.js is right-handed, so Z is reflected in position and rotation. Each class also gets a base-size correction (our viewer meshes are authored for inspection, in arbitrary units) and a "lay flat" rotation for the extruded tile/token/card meshes. - `Scene`'s `Bounds fit` auto-fits the camera to the full layout, so no camera work is needed. - Non-renderable objects (bags, dice, boards without assets) are skipped and counted, not dropped silently. ### 4. Share materials & geometry - **Geometry cache (module-level `Map`):** key by a canonical string — `card:{w}:{h}:{t}`, `tile:{type}:{aspect}:{t}`, `token:{url}:{t}`, `model:{meshUrl}`. All cards of the same size, or tiles of the same type/size, reuse one geometry instead of rebuilding per object. Token geometry is keyed by the source image URL (the trace is cached per URL, so the silhouette is deterministic). - **Material cache (module-level `Map`):** key by `textureUrl + color + roughness`. drei already caches textures by URL globally, so sharing the material on top avoids per-object material allocation for tiles/tokens with the same image. - **Cards:** the face/back textures are shared (drei caches them by URL) and the sprite cell is selected via a per-material UV transform injected into the shader (`cardMaterial.ts`), so cards share texture, shader, and geometry — only the material uniforms differ. Materials are cached per card id + tint. - Dispose shared resources on page unmount, or accept a module-level cache for the session (see Open decisions). ### 5. Routing & navigation - Add `} />` in `apps/web/src/App.tsx`. - Add a "Full setup" link/button on `ModPage` next to the download button. ### 6. Edge cases - Objects with no asset (no `ImageURL`/`MeshURL`) render as neutral-colored placeholders (matching the current viewers' fallback behavior). - Token tracing suspends per URL (already cached in `TokenViewer`); the shared `Scene` Suspense boundary handles it. - Large saves: the grid + shared geometry keeps it performant, but cap or warn on very large object counts if needed. ## Files touched - `apps/web/src/components/viewers/{Tile,Token,Card,CustomModel}Viewer.tsx` — export object-facing mesh wrappers - `apps/web/src/components/viewers/sharedResources.ts` — new geometry/material caches - `apps/web/src/components/viewers/transform.ts` — new TTS→three.js placement conversion - `apps/web/src/components/viewers/sharedResources.ts` — `objectTint`/ `tintedColor` helpers for the per-object `ColorDiffuse` tint - `packages/shared/src/types.ts` — add `TTSObjectTransform` and `ColorDiffuse` - `apps/web/src/pages/FullSetupPage.tsx` — new - `apps/web/src/App.tsx` — route - `apps/web/src/pages/ModPage.tsx` — nav link - Possibly a small `geometryCache`/`materialCache` helper under `components/viewers/` ## Open decisions (defaults in bold) - **Layout style** — **real `Transform` placement** (was a grouped grid before the transform data was wired in). - **Geometry/material cache lifetime** — **session-level module cache** (simplest, consistent with drei's global texture cache) vs dispose-on-unmount.