feat(tabletop): add tilt and zStart/zEnd stacking options

Replace the per-part lift with a local Y-axis tilt that fans the stack,
and add zStart/zEnd to ramp the stack's height across the curve so it
arches in 3D. Update the poker deck and docs accordingly.
This commit is contained in:
2026-08-09 23:36:42 +08:00
parent 2da04940c2
commit c5d6dff12d
9 changed files with 127 additions and 28 deletions
+14
View File
@@ -359,6 +359,9 @@ layout:
limit: 5
align: center
steps: 4
tilt: 0.1
zStart: 0
zEnd: 30
```
- `curve` — an SVG path string to spread the content along, relative to the
@@ -368,6 +371,13 @@ 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 radians 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.
- `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`.
#### positioning process
@@ -377,6 +387,10 @@ layout:
`step length × (# of parts 1)` on the curve.
3. **Place each part.** Part `#0` is at the start, the last part at the end,
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.
### Edge cases
+5 -2
View File
@@ -118,10 +118,12 @@ interface GameState {
### 7. Stacking (`stacking.ts`) ✅
- `useStacking(route.stacking, index, stackSize)` → `{ offset, rotation }`.
- `useStacking(route.stacking, index, stackSize)` → `{ x, y, rotation, z, tilt }`.
- Implements the format's positioning process (`bgm-format.md` §4): step
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.
- Curve length from an SVG path string (small helper; no new dep).
### 8. Public API (`index.ts`) ✅
@@ -148,7 +150,8 @@ consumers share them (see Open decisions).
- `state.ts` — derived render state: enabled surfaces, route matching,
candidate selection, stacking index/stackSize.
- `stacking.ts` — positioning process: step length, alignment, limit.
- `stacking.ts` — positioning process: step length, alignment, limit, z ramp,
tilt.
- `setup.ts` — seeding + bare-type expansion.
- `mount.ts` — mount tree resolution (table/hud/child, children refs).
- `partView.tsx` — geometry from a part def (size/fillet/crop), sprite UVs.
+1 -1
View File
@@ -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, 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. `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 per-part fan about the card's local Y (long) axis. `PartPlacement` consumes it.
## 5. usage