feat(tabletop): add state, setup, mounting, and stacking

Implement the tabletop library's game state store, setup seeding with
bare-type expansion, surface mount tree resolution, part placement, and
the stacking positioning process with a dependency-free SVG path helper.
Wire the public API and add unit plus vite integration tests.
This commit is contained in:
2026-08-09 22:34:18 +08:00
parent ef0695ed04
commit cd08e6af04
20 changed files with 1281 additions and 11 deletions
+30
View File
@@ -0,0 +1,30 @@
/**
* `PartPlacement` — position a part on a surface location.
*
* A stable per-part component that places a part at its route's anchor (plus
* the candidate's anchor when there is one) and applies the route's stacking
* strategy via `useStacking`. Renders the part's mesh with `PartView`.
*/
import type { Package } from '@tts/bgm';
import { useStacking } from './stacking.js';
import type { Placement } from './state.js';
import { PartView } from './partView.js';
export function PartPlacement({ pkg, placement }: { pkg: Package; placement: Placement }) {
const { route, candidate, piece, index, stackSize } = placement;
// `piece` is `package:type#id`; the parts map is keyed by `type#id`.
const part = pkg.parts.get(piece.split(':').slice(1).join(':'));
if (!part) return null;
const { x, y, rotation } = useStacking(route.stacking, index, stackSize);
const anchorX = (candidate?.x ?? route.x ?? 0) + x;
const anchorY = (candidate?.y ?? route.y ?? 0) + y;
const anchorRotation = (candidate?.rotation ?? route.rotation ?? 0) + rotation;
return (
<group position={[anchorX, 0, anchorY]} rotation={[0, anchorRotation, 0]}>
<PartView part={part} baseUrl={part.baseUrl} />
</group>
);
}