diff --git a/docs/bgm-format.md b/docs/bgm-format.md index 52c8aff..2718671 100644 --- a/docs/bgm-format.md +++ b/docs/bgm-format.md @@ -371,10 +371,9 @@ layout: - `align` — `start`, `end`, or `center` of the curve. - `steps` — the maximum number of parts per curve length unit. Defaults to `1`. See the positioning process below. -- `tilt` — rotation in degrees per shown part about the card's local Y (long) - axis. Each part tilts `tilt` more than the previous, fanning the stack so - its edges stay visible. It applies even without a `curve`, so a bare `tilt` - fans a straight pile. +- `tilt` — rotation in degrees applied to every shown part about the card's + local Y (long) axis. It applies even without a `curve`, so a bare `tilt` + rotates a straight pile. - `zStart` / `zEnd` — the height (surface-normal) in mm at the start and end of the `curve`. The stack ramps linearly between them across its span, lifting it in 3D. Requires a `curve`. @@ -389,8 +388,8 @@ layout: each `step length` apart. 4. **Lift each part.** The part's height is `zStart + (zEnd − zStart) × u`, where `u` is its normalized position along the `curve`. -5. **Tilt each part.** Each part is rotated `tilt × # of parts before it` - about its local Y (long) axis. +5. **Tilt each part.** Every part is rotated `tilt` about its local Y (long) + axis. ### Edge cases diff --git a/docs/bgm-tabletop-plan.md b/docs/bgm-tabletop-plan.md index 40c6b80..68f253c 100644 --- a/docs/bgm-tabletop-plan.md +++ b/docs/bgm-tabletop-plan.md @@ -123,7 +123,7 @@ interface GameState { length from curve length / `max(steps, count-1)`, alignment (`start`/`end`/ `center`), and `limit` (`0` all, `n` first n, `-n` last n). - `z` ramps linearly from `zStart` to `zEnd` across the curve's span; `tilt` - fans each shown part about its local Y (long) axis. + rotates each shown part about its local Y (long) axis. - Curve length from an SVG path string (small helper; no new dep). ### 8. Public API (`index.ts`) ✅ diff --git a/docs/bgm-tabletop.md b/docs/bgm-tabletop.md index cdb92bf..b96064a 100644 --- a/docs/bgm-tabletop.md +++ b/docs/bgm-tabletop.md @@ -40,7 +40,7 @@ the render map is per enabled surface: a piece may appear on more than one enabl ## 4. stacking -the format's stacking strategy (`curve` / `limit` / `align` / `steps` / `tilt` / `zStart` / `zEnd`, see `bgm-format.md` §4) is implemented as a hook, e.g. `useStacking(route.stacking, index, stackSize)`, returning the offset/rotation to apply to a piece: `{ x, y, rotation, z, tilt }`. `x`/`y`/`rotation` come from the `curve`; `z` is the surface-normal height ramped from `zStart` to `zEnd`; `tilt` is the per-part fan about the card's local Y (long) axis. `PartPlacement` consumes it. +the format's stacking strategy (`curve` / `limit` / `align` / `steps` / `tilt` / `zStart` / `zEnd`, see `bgm-format.md` §4) is implemented as a hook, e.g. `useStacking(route.stacking, index, stackSize)`, returning the offset/rotation to apply to a piece: `{ x, y, rotation, z, tilt }`. `x`/`y`/`rotation` come from the `curve`; `z` is the surface-normal height ramped from `zStart` to `zEnd`; `tilt` is the rotation about the card's local Y (long) axis, applied to every part. `PartPlacement` consumes it. ## 5. usage diff --git a/games/poker/poker.md b/games/poker/poker.md index b6423c4..11fbf66 100644 --- a/games/poker/poker.md +++ b/games/poker/poker.md @@ -103,12 +103,11 @@ layout: y: 0 rotation: 0 stacking: - curve: M 0 0 C 20 -20 40 -20 60 0 - limit: 0 align: center - tilt: 0.015 zStart: 0 - zEnd: 10 + #zEnd: 100 + tilt: 1 + curve: M -50 -200 C 50 -150 450 -150 550 -200 - route: /community/:slot candidates: $variants: ./community.csv diff --git a/packages/tabletop/src/placement.tsx b/packages/tabletop/src/placement.tsx index 0a5d1d9..59e6b0a 100644 --- a/packages/tabletop/src/placement.tsx +++ b/packages/tabletop/src/placement.tsx @@ -21,7 +21,7 @@ export function PartPlacement({ pkg, placement }: { pkg: Package; placement: Pla // Route anchors and stacking offsets are in mm; convert to world units so // parts land on the (world-scaled) surface. `z` raises the part along the - // surface normal (world +Y); `tilt` fans it about its local Y (long) axis. + // surface normal (world +Y); `tilt` rotates it about its local Y (long) axis. // Angles are authored in degrees; three.js expects radians. const anchorX = ((candidate?.x ?? route.x ?? 0) + x) * MM_TO_WORLD; const anchorY = ((candidate?.y ?? route.y ?? 0) + y) * MM_TO_WORLD; @@ -31,7 +31,7 @@ export function PartPlacement({ pkg, placement }: { pkg: Package; placement: Pla return ( {/* The part mesh extrudes along +Z; lay it flat so its face points up. */} - + diff --git a/packages/tabletop/src/stacking.test.ts b/packages/tabletop/src/stacking.test.ts index 8937e92..bd4684a 100644 --- a/packages/tabletop/src/stacking.test.ts +++ b/packages/tabletop/src/stacking.test.ts @@ -46,7 +46,8 @@ describe('pointAt', () => { describe('stackingOffset', () => { it('returns no offset without a curve', () => { expect(stackingOffset(undefined, 0, 3)).toBe(NO_OFFSET); - expect(stackingOffset({ limit: 5 }, 0, 3)).toBe(NO_OFFSET); + // A stacking strategy defaults to a 1° tilt, so it's not the identity. + expect(stackingOffset({ limit: 5 }, 0, 3)).toEqual({ x: 0, y: 0, rotation: 0, z: 0, tilt: 1 }); }); it('spreads parts evenly along a straight curve', () => { @@ -86,12 +87,12 @@ describe('stackingOffset', () => { expect(offset.x).toBeCloseTo(25); }); - it('tilts each part without a curve', () => { + it('tilts every part the same amount without a curve', () => { const offset = stackingOffset({ tilt: 0.1 }, 2, 3); - expect(offset).toEqual({ x: 0, y: 0, rotation: 0, z: 0, tilt: 0.2 }); + expect(offset).toEqual({ x: 0, y: 0, rotation: 0, z: 0, tilt: 0.1 }); }); - it('tilts parts along the curve', () => { + it('tilts every part the same amount along the curve', () => { const offset = stackingOffset({ curve: 'M 0 0 L 100 0', tilt: 0.1 }, 1, 3); expect(offset.x).toBeCloseTo(50); expect(offset.tilt).toBeCloseTo(0.1); @@ -113,7 +114,7 @@ describe('stackingOffset', () => { expect(last.z).toBeCloseTo(40); }); - it('returns no offset without a curve, tilt, or z ramp', () => { - expect(stackingOffset({ limit: 5 }, 0, 3)).toBe(NO_OFFSET); + it('returns no offset without a stacking strategy', () => { + expect(stackingOffset(undefined, 0, 3)).toBe(NO_OFFSET); }); }); \ No newline at end of file diff --git a/packages/tabletop/src/stacking.ts b/packages/tabletop/src/stacking.ts index 13b8ce2..d180071 100644 --- a/packages/tabletop/src/stacking.ts +++ b/packages/tabletop/src/stacking.ts @@ -43,9 +43,10 @@ export function stackingOffset( const shownIndex = shown.indexOf(index); if (shownIndex < 0) return NO_OFFSET; - // `tilt` fans each shown part about its local Y (long) axis, so the stack's - // edges stay visible. It applies even without a curve. - const tilt = (stacking?.tilt ?? 0) * shownIndex; + // `tilt` rotates each shown part about its local Y (long) axis by the same + // amount. It applies even without a curve. Defaults to 1° when a stacking + // strategy is present but doesn't specify a tilt. + const tilt = stacking?.tilt ?? (stacking ? 1 : 0); // The horizontal position along the curve (or a straight pile when there's // no curve), plus the normalized progress used to ramp the z height.