forked from eros/curltastic
Replace REQUIREMENTS.md as source of truth with the pm-board yaml. Drop README friction-scalar lies. Include pending HUD frontend work.
82 lines
3.3 KiB
Markdown
82 lines
3.3 KiB
Markdown
## 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 1–10 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).
|
||
- Stone–stone 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.
|