A full description of how the game computes points and the 0–10 grade. A short
version lives in the main README; the full gameplay spec is in
spec.md.
joint_score = max(0, 1 - |achieved - target| / tolerance)
player_score = weighted_mean(joint_scores) # at least 4 visible joints, conf >= 0.30
team_avg = mean(p1, p2, p3)
cohesion = 1 + 0.3 * max(0, 1 - (max - min)) # 1.0 ... 1.3
base_score = team_avg * cohesion * 100
round_score = base_score * pose.multiplier
Bonuses added to round_score:
| Bonus | Condition | Value |
|---|---|---|
| Perfect | team_avg ≥ 0.95 |
+30 |
| Sync | all 3 cross 0.80 within a ≤ 300 ms window | +15 |
| Streak | consecutive rounds with team_avg ≥ 0.80 |
+10 × n |
Within the 3-second pose window the maximum player_score reached in any
frame is taken (holding the pose for a moment is enough; small jitter is not
penalised). A joint with confidence < 0.30 is skipped; with fewer than 4
visible joints player_score = 0 (anti-cheese).
On top of points the app reports an overall 0–10 grade (no halves) — for the judges at the event.
round_grade = round_half_up(team_avg * 10) # 0..10
session_grade = round_half_up(mean(team_avg) * 10) # 0..10
Mapping (round half up, so 0.85 → 9, not 8):
| team_avg | grade |
|---|---|
| 0.00 – 0.04 | 0 |
| 0.05 – 0.14 | 1 |
| 0.55 – 0.64 | 6 |
| 0.85 – 0.94 | 9 |
| 0.95 – 1.00 | 10 |
Grade colours: 0–3 red · 4–5 orange · 6–7 yellow · 8–9 green · 10 gold.
The screen is split into 3 vertical zones. A player is assigned by the X
position of their mid-hip (keypoints (11 + 12) / 2):
< 1/3-> Player 1< 2/3-> Player 2≥ 2/3-> Player 3
A player outside their lane / two people in one lane / an empty lane → score 0
for the affected players in those frames + an on-screen warning.