Race Into Space network protocol
================================

Uses HTTP as an underlayer, and a simple RPC approach of GET variables and
 JSON responses.  So for the most part, we can talk in terms of Python dicts.
Dates are formatted as {'year': year, 'day': day}.  If two players achieve a
 first on the same day, they both get the payout (if eligible).  RIS defines
 the day and year as 24 hours and 365 days respectively.
To receive a JSON response, all pages require the input json=1.  (Otherwise,
 you will get a human-readable HTML page.)
Pages with no "Semantics" section are pure reads.
On error, a page will, instead of its usual outputs, return
    {'err': some_error_message, 'code': some_optional_error_code}
 The following error codes are defined:
    1   Not permitted; attempted a metadata change to a locked game.
    2   Item not found; a lookup by name failed.
    17  Already exists; tried to create using a name that was already in use.
    22  Invalid argument; an input was missing, had the wrong type, or was
        otherwise inappropriate.
    39  Not empty; tried to delete an item which still has contents.

List Games
----------
/
Outputs: {game.name: {'players': [player.name for player in game.players],
                      'mindate': game.mindate}
          for game in games}

New Game
--------
/newgame
Required inputs: name.
Semantics: Create an empty game named <name>.  The <name> must not contain '/'.
Outputs: <redirect to /game>

End Game
--------
/rmgame
Required inputs: game.
Semantics: Delete <game>, which must be empty.

Game Status
-----------
/game
Required inputs: game.
Outputs: {'mindate': game.mindate.dict,
          'players': {player.name: {'date': player.date,
                                    'leader': player.leader}
                      for player in game.players}

Lockout Changes
---------------
/lock
Required inputs: game.
Semantics: Prevent further joins or parts from <game>.
Outputs: <redirect to /game>

Join Game
---------
/join
Required inputs: game, name.
Semantics: Add a player named <name> to <game>.
Outputs: <redirect to /game>

Leave Game
---------
/part
Required inputs: game, name.
Semantics: Remove the player named <name> from <game>.
Outputs: <redirect to /game>

Synchronise
-----------
/sync
Required inputs: game, player, year, day.
Optional inputs: kia.
Semantics: <player> has reached y<year>d<day>, and all completions before that
 date have already been notified.  There have been <kia> dead astronauts since
 the start of the game.
Outputs: <redirect to /game>

Completion
----------
/completed
Required inputs: game, player, contract, year, day.
Optional inputs: tier.
Semantics: In <game>, <player> has completed <contract> at y<year>d<day>.  This
 does *not* imply a sync (i.e. <player> having reached y<year>d<day>), as
 completions might not arrive in order.  Contract is tier <tier> (typically 0,
 or 10 for milestone groups); higher tiers take precedence if multiple
 contracts are completed on the same day (for leader flag handling purposes).
Outputs: <redirect to /result>

Contract Result
---------------
/result
Required inputs: game, contract
Outputs: {player.name: {'date': contract.date[player],
                        'first': contract.first[player]}
          for player in game.players if contract.completed[player]}
Possible values of contract.first[player] are:
    'first': we are known to have got there first; all players have passed our
             date, without completing.  Also, we were eligible for payout.
    'was_leader': we are known to have got there first, but we were the leader
                  at the time so we don't get a payout.
    'unknown': currently unknown.  Our date is the earliest thus far recorded,
               but some players are still before that date.
    'not_first': known not to be first.  Someone has recorded an earlier date
                 than ours.