curltastic/README.md
Jason Dekarske 78b8ebeb7d docs: move product spec to .pm/board.yaml
Replace REQUIREMENTS.md as source of truth with the pm-board yaml.
Drop README friction-scalar lies. Include pending HUD frontend work.
2026-09-07 23:06:31 +00:00

82 lines
3.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## curltastic
A multiplayer 2D curling game for mobile browser. Any number of clients can join a room, pick **Team 1** or **Team 2** freely, and throw when it is that team's turn (solo pass-and-play or remote opponents). Default palette: Team 1 red, Team 2 yellow.
Product must-haves and decided hows live in `.pm/board.yaml`.
- **Backend** — Rust, Axum, WebSocket, Rapier2D physics (server-authoritative, 120 Hz).
- **Frontend** — TypeScript, Vite, Canvas2D, portrait-first touch UI.
### Run locally
1. Install Rust via [rustup](https://rustup.rs/) (if not already installed).
2. Start the backend:
```bash
cd backend
cargo run --release
# listens on 0.0.0.0:3000
```
3. Start the frontend dev server:
```bash
cd frontend
npm install
npm run dev
# opens on 0.0.0.0:5173 by default
```
4. Open one or more browser tabs to the same room URL, e.g.:
```
http://localhost:5173/?room=DEMO1
```
Use the team dropdown to switch between Team 1 and Team 2 anytime. Use *Copy share link* to invite another device.
### Controls
- On your team's turn, drag on solid ice to place the broom (aim is **not** limited to the house).
- Otherwise, drag to pan (default framing shows house + sidelines; pan up toward the hog line).
- Team dropdown: free switch mid-game (solo: throw for Team 1, switch, throw for Team 2).
- Weight slider: draws 110 then takeouts hack, board, control, normal, peel. Curl buttons.
- Tap **THROW**. Anyone joined as the current turn team may throw.
- Parallel multi-stone trajectories; rotation θ comes from physics.
- Skeuomorphic 2×8 stones-left HUD; end-of-end overlay with scoreboard (player dismisses).
### Physics (high level)
- Discrete weight labels map to calibrated release speeds (m/s) on the server.
- Ice friction µ(v) table (interpolated) on the server; linear and angular damping share the table.
- Curl: initial |ω| = 5 rot / 14 s; lateral continuous model (clockwise → right).
- Stonestone contacts are **nearly elastic** (`STONE_RESTITUTION = 0.9`) so takeouts launch both rocks along the impact line instead of plastic-sticking.
- Back line and sidelines are **not** colliders — touch → out of play after sim.
- Stone ids: `{ team, n }` with n = 1…8 per team per end.
- Foot-derived radii use `FEET_TO_METERS = 0.3048`.
### Architecture / protocol
- WebSocket JSON, snake_case tags.
- `game_state` includes totals, hammer, turn_team, **scoreboard** (per end: hammer, team1 pts, team2 pts), **stones_remaining**, stones.
- `trajectories` message: `{ stones: [{ stone_id, rotation, team, trajectory: [[x,y,theta], ...] }] }` sampled at 40 Hz from 120 Hz sim (`t = index / 40`).
- No room-full limit; no `end_scored` message (scoreboard replaces it).
### E2E tests
With the backend running (`cargo run --release` on :3000):
```bash
cd e2e
npm install -g ws # or npm install ws
node e2e_test.cjs
node e2e_score.cjs
node e2e_persistence.cjs
node e2e_multi_client.cjs
node e2e_end_score.cjs # full end → scoreboard entry + next end
node collision_trajectory_qa.cjs
node load_test.cjs # optional concurrent rooms
```
Unit tests: `cd backend && cargo test`, `cd frontend && npm test`.
### Limitations
- No accounts, persistence, anti-cheat, replay log, or turn timer.
- Sweeping not implemented.
- Stones past back/sideline/hog rules are removed from play after simulation.