The score had to survive the crossing

The phone owned the session. The games owned the round. Neither was allowed to forget what the other had just said.

The handoff

A round started in the app, ran inside one of three games, and came back as a score. If that message landed late, or twice, the player saw a result that never happened.

The game can crash. The score cannot.

The phone kept the score. The game only knew the round.

What crossed

The message

One shape, both sides. The app sent it. The game answered with the same id.

type Round = {
  id: string
  game: 'one' | 'two' | 'three'
  instruction: string
}

type Result = {
  id: string
  score: number
}

Who talked

GameAppGameAppstart roundscore

That was the whole contract. Three games, one conversation.