docs: update mesh, viewers, and selection decisions
Document the separated front/back/walls extrusion, material-level back flips, CardID sprite selection from the parent deck, index-path tree selection, and the Bounds-inside-Suspense camera fit.
This commit is contained in:
+77
-1
@@ -183,4 +183,80 @@ parsing yields a shape the mesh package can consume directly, and alpha-based
|
||||
tracing (the default) is the common case for token/tile art. For shape
|
||||
inset/outset, `clipper-lib` (Angus Johnson's Clipper ported to JS) was chosen
|
||||
over the `polygon-offset` package because the latter crashes on degenerate
|
||||
cases (collapse, hole closure) via a bug in its pinned Martinez dependency.
|
||||
cases (collapse, hole closure) via a bug in its pinned Martinez dependency.
|
||||
|
||||
## D14 — Extrusion exposes separated front/back/walls
|
||||
|
||||
**Decision:** `extrudeShapeParts` returns `{ front, back, walls }` as separate
|
||||
geometries, and `tessellate.ts` exposes `frontFaces` / `backFaces` (with
|
||||
`capFaces` kept as a merged convenience).
|
||||
|
||||
**Context:** Cards and tiles need distinct materials on the front and back
|
||||
faces, and the back must be flipped so it isn't mirrored when viewed from
|
||||
behind. Splitting the caps into front/back at the mesh level lets each viewer
|
||||
apply its own material without post-hoc geometry-group splitting.
|
||||
|
||||
**Alternatives considered:** Returning a single merged caps geometry and
|
||||
splitting it in the viewer (the previous approach for cards). Rejected —
|
||||
required manual triangle-group bookkeeping in the component.
|
||||
|
||||
## D15 — Back faces are flipped on the material, not the geometry
|
||||
|
||||
**Decision:** The back face is un-mirrored by flipping the texture on the
|
||||
material (`flipTexture.ts` negates `repeat.x` and shifts `offset.x`), rather
|
||||
than by transforming the geometry's UVs.
|
||||
|
||||
**Context:** The back cap maps with the same planar UVs as the front, so
|
||||
without a flip it appears mirrored. Flipping on the material keeps the mesh
|
||||
geometry simple and shared, and works for both a full texture and a sprite
|
||||
cell.
|
||||
|
||||
**Alternatives considered:** Flipping the UVs in `backFaces`. Rejected —
|
||||
would bake the flip into the shared mesh package, forcing it on every consumer
|
||||
rather than letting viewers opt in.
|
||||
|
||||
## D16 — Card sprite selection via `CardID` and the parent deck
|
||||
|
||||
**Decision:** A card's face/back sprite is selected from the deck sheet by
|
||||
`CardID` (deck index in the hundreds place, 0-based card number in the last
|
||||
two digits). The sheet config (grid, face/back URLs, `UniqueBack`) is resolved
|
||||
from the containing deck's `CustomDeck[deckIndex]`, which is authoritative
|
||||
over the card's own `CustomDeck` (often keyed differently or absent).
|
||||
|
||||
**Context:** Deck images are sheets divided into a `NumWidth` x `NumHeight`
|
||||
grid. The card's own `CustomDeck` field is unreliable — in the Wingspan dump a
|
||||
card with `CardID 1605` carries `CustomDeck: {14: ...}` even though its deck
|
||||
index is 16 — so the parent deck is the source of truth.
|
||||
|
||||
**Alternatives considered:** Using the card's own `CustomDeck`. Rejected —
|
||||
produces the wrong sprite for cards whose own field is mis-keyed or missing.
|
||||
|
||||
## D17 — Tree selection by index path, not GUID
|
||||
|
||||
**Decision:** The object tree selects nodes by their unique index path (e.g.
|
||||
`0-3-1`) rather than by `GUID`.
|
||||
|
||||
**Context:** Cards in a deck share the deck's GUID — in the Wingspan dump, 292
|
||||
of 606 objects carry a duplicate GUID. GUID-based selection highlighted every
|
||||
card with that GUID and rendered the first match, so the wrong sprite could
|
||||
show.
|
||||
|
||||
**Alternatives considered:** Using `GUID` (the previous approach). Rejected —
|
||||
ambiguous for decked cards.
|
||||
|
||||
## D18 — Camera fit via drei `Bounds` inside Suspense
|
||||
|
||||
**Decision:** The viewer camera is fitted to the object's bounds using drei's
|
||||
`Bounds` component, placed inside the scene's Suspense boundary so it mounts
|
||||
only after the (suspending) content has loaded.
|
||||
|
||||
**Context:** Viewers load content asynchronously (textures suspend, tokens
|
||||
trace via the proxy, models stream in). `Bounds` fits on mount, so it must
|
||||
mount after the content is present. The token viewer was converted from
|
||||
`useEffect` + state to a suspending resource so it participates in the same
|
||||
boundary.
|
||||
|
||||
**Alternatives considered:** A polling `CameraFit` that waited for non-empty
|
||||
bounds each frame; hand-rolled camera math. Rejected — Suspense already
|
||||
signals content readiness, so `Bounds` inside the boundary fits the loaded
|
||||
geometry directly.
|
||||
Reference in New Issue
Block a user