Spectateurs
Endpoint proposé : /ws/spectate, sans authentification. Seules spectate.join et spectate.leave sont acceptées comme commandes. Toute tentative de modifier une partie, déplacer un joueur ou combattre sur ce canal produit UNAUTHORIZED.
Le backend réutilise son Match et ses Player existants pour la carte ; aucune simulation parallèle n’est maintenue. Proposition simple : un abonnement par connexion, remplacé par une nouvelle inscription après validation du nouveau UID.
| Événement | Direction | Fonction, comportement serveur et restrictions |
|---|---|---|
spectate.join | Web → Serveur | Vérifier la partie, abonner puis envoyer l’instantané. Aucun Player ajouté, aucune place occupée. |
spectate.snapshot | Serveur → Web | Envoyer phase, joueurs, HP, positions, rotations, équipes et scores à l’inscription, à la réinitialisation d’un round et à la fin de partie. |
spectate.leave | Web → Serveur | Désabonner cette connexion sans modifier la partie ; répétition sans abonnement sans effet. |
spectate.playerMoved | Serveur → Web | Relayer un mouvement accepté avec username et séquence pour déplacer le marqueur de carte. |
spectate.playerDied | Serveur → Web | Marquer le joueur éliminé avec hp: 0, sans supprimer son identité. |
spectate.stateChanged | Serveur → Web | Actualiser la phase et le numéro de round ; le spectateur ne décide jamais de transition. |
spectate.scoreUpdated | Serveur → Web | Diffuser les équipes et scores actualisés après un résultat de round. |
Charges utiles
Abonnement, puis instantané :
{ "type": "spectate.join", "matchUid": "match_123", "data": {} }
{
"type": "spectate.snapshot",
"matchUid": "match_123",
"roundNumber": 2,
"data": {
"state": "ROUND_ONGOING",
"players": [
{
"username": "alice",
"teamId": 1,
"position": { "x": 12.5, "y": 0, "z": 8.2 },
"rotation": { "x": 0, "y": 90, "z": 0 },
"hp": 100
},
{
"username": "bob",
"teamId": 1,
"position": { "x": 10, "y": 0, "z": 8 },
"rotation": { "x": 0, "y": 90, "z": 0 },
"hp": 100
},
{
"username": "chloe",
"teamId": 2,
"position": { "x": 20, "y": 0, "z": 8 },
"rotation": { "x": 0, "y": 270, "z": 0 },
"hp": 0
},
{
"username": "david",
"teamId": 2,
"position": { "x": 22, "y": 0, "z": 8 },
"rotation": { "x": 0, "y": 270, "z": 0 },
"hp": 40
}
],
"teams": [
{ "teamId": 1, "color": "bleu", "players": ["alice", "bob"], "score": 1 },
{ "teamId": 2, "color": "rouge", "players": ["chloe", "david"], "score": 0 }
]
}
}
Désabonnement :
{ "type": "spectate.leave", "matchUid": "match_123", "data": {} }
Mouvement et élimination :
{
"type": "spectate.playerMoved",
"matchUid": "match_123",
"roundNumber": 2,
"seq": 152,
"data": {
"username": "alice",
"position": { "x": 12.5, "y": 0, "z": 8.2 },
"rotation": { "x": 0, "y": 90, "z": 0 }
}
}
{ "type": "spectate.playerDied", "matchUid": "match_123", "roundNumber": 2, "data": { "username": "chloe", "hp": 0 } }
Phase et scores :
{ "type": "spectate.stateChanged", "matchUid": "match_123", "roundNumber": 2, "data": { "state": "ROUND_ENDING" } }
{
"type": "spectate.scoreUpdated",
"matchUid": "match_123",
"roundNumber": 2,
"data": {
"teams": [
{ "teamId": 1, "color": "bleu", "players": ["alice", "bob"], "score": 2 },
{ "teamId": 2, "color": "rouge", "players": ["chloe", "david"], "score": 0 }
]
}
}
L’élimination reste visible jusqu’au prochain instantané de round réinitialisé ; un mouvement isolé ne signifie pas une réapparition. Après reconnexion web, renvoyer spectate.join et remplacer la vue par l’instantané. Les résultats historiques sont consultables via REST ; l’abonnement direct exige encore un état disponible en mémoire, sinon MATCH_NOT_FOUND.
Les messages suivent les conventions communes. La machine à états décrit les phases affichées ; l’API REST donne accès aux résultats historiques.