One and two players
For two players use one of two clear models: alternating turns or a left/right split target. Base target.hit does not have to identify a physical weapon.
Declare support
"players": {
"supported_counts": [1, 2],
"default_count": 1,
"session_modes": ["single", "versus"],
"same_screen": true
}The selected player count arrives in Manifest.Session.PlayerCount and session.start. The game does not change it after start.
Option 1. Alternating turns
The game clearly shows whose turn it is. An impact, a shot series or a finished round changes the active participant according to game rules.
private int activePlayer = 0;
private void OnHit(VorivoxHit hit)
{
if (!roundRunning || paused) return;
scores[activePlayer] += ScoreHit(hit);
activePlayer = activePlayer == 0 ? 1 : 0;
hud.ShowTurn(activePlayer + 1);
}
Option 2. Split the target
The physical plate remains one target, while the scene is split vertically. Player 1 owns the left half and Player 2 owns the right half. In normalized coordinates the boundary is NX = 0.5.
private static int PlayerForHit(VorivoxHit hit)
=> hit.NormalizedX < 0.5f ? 0 : 1;
private void OnHit(VorivoxHit hit)
{
if (!roundRunning || paused) return;
int player = PlayerForHit(hit);
scores[player] += ScoreHalfTarget(player, hit);
}
Drawing two halves
Split the active game area, not merely the browser/window width. After letterboxing and scaling, the left play area must correspond to 0 ≤ NX < 0.5 and the right area to 0.5 ≤ NX ≤ 1. Mirror visuals and rules where possible for equal conditions.
Scores and final result
Keep separate scores and player statistics during the game. Base session.complete remains aggregate; do not serialize a player array into the numeric score field. Add individual persistence only through an explicitly approved platform contract.
Testing
Turns: pause during handoff, final shot and duplicate shot_id. Split target: exact center-line impacts, both edges, mirrored targets and obvious side indication.