# The Fair Arena: how it is made

A twelve-by-twelve island, three foes, and a match whose every die roll is a pure function of a
secret the house committed to before the first move. Built from `sitoolz` alone; the page draws and
listens, the library does the arithmetic.

## What it imports

```html
<script src="../../../sitoolz/js/rangeblends.js"></script>   <!-- the indexed hash the rivers and worlds are seeded by -->
<script src="../../../sitoolz/js/rulerland.js"></script>     <!-- the landscape laws -->
<script src="../../../sitoolz/js/terrain.js"></script>       <!-- SITerrain.window: the island -->
<script src="../../../sitoolz/js/blake2b.js"></script>       <!-- keyed BLAKE2b, RFC 7693 -->
<script src="../../../sitoolz/js/qrand.js"></script>         <!-- the jacobi backend indexrng can use -->
<script src="../../../sitoolz/js/indexrng.js"></script>      <!-- SIIndexrng.Commit, Index, verifyCommit -->
```

Used: `SITerrain.window(world, x0, z0, w, h)` for the floor (its `C` colour classes: 0 water,
1 sand, 2 grass, 3 forest, 4 rock); `new SIIndexrng.Commit(secret, word)` whose `roll(stream, index,
sides)` logs every draw, `commit.commitment` (the hash shown before play), `commit.reveal()` and
`SIIndexrng.verifyCommit(commitment, secret, word, log)`.

## The twenty lines that make it

```js
const win = SITerrain.window("gaussian-isles", 2014, 80, 12, 12);            // an island: a third water, one shore
const land = (x, z) => x >= 0 && z >= 0 && x < 12 && z < 12 && win.C[x][z] !== 0;
const commit = new SIIndexrng.Commit(null, word);                              // the house seals a secret; you add a word
show(commit.commitment);                                                       // published before the first move
const R = (stream, turn, sides) => commit.roll(stream, turn, sides);           // every die: addressed, logged, stateless
const cells = []; for (let x = 0; x < 12; x++) for (let z = 0; z < 12; z++) if (land(x, z)) cells.push([x, z]);
const you = cells[R("place:you", 0, cells.length)], foes = [0, 1, 2].map(k => cells[R("place:foe" + k, 0, cells.length)]);
function turn(t, dx, dz){                                                      // one turn, indexed by t
  const f = foeAt(you.x + dx, you.z + dz);
  if (f){ if (R("you:hit", t, 20) + 1 >= 9) f.hp -= R("you:dmg", t, 6) + 1; }
  else if (land(you.x + dx, you.z + dz)){ you.x += dx; you.z += dz; }
  for (const g of foes.filter(g => g.hp > 0)){
    if (adjacent(g, you)){ if (R(`foe${g.k}:hit`, t, 20) + 1 >= 11) you.hp -= R(`foe${g.k}:dmg`, t, 4) + 1; }
    else if (R(`foe${g.k}:move`, t, 4) !== 0) stepToward(g, you); else wander(g, R(`foe${g.k}:wander`, t, 4));
  }
}
// at the end: the reveal, and anyone can check it
const ok = SIIndexrng.verifyCommit(commit.commitment, commit.reveal(), word, commit.log);
```

The rolls are addressed by what they mean (`"foe1:hit"` at turn 7), never drawn from a stream, so
replaying the log needs no state: `new Index(secret + "\0" + word).roll(stream, turn, sides)` gives
the same number on every machine, which is exactly what `verifyCommit` does.

## How to change the law

- **The island**: change `WORLD`, `X0`, `Z0` at the top of `game.js`. Any of the fourteen worlds in
  `SITerrain.WORLDS` works; `dyadic-isles` at (2038, 86) gives a rockier island with the same share of
  water. `win.C` tells you which cells are water.
- **The dice**: the hit thresholds (9 on a d20 for you, 11 for a foe) and the damage dice (`6` and
  `4`) are the numbers in `act`. Change `R("you:dmg", t, 6)` to `R("you:dmg", t, 8)` for a d8.
- **The backend**: `new Commit(secret, word, "jacobi")` draws from Legendre symbols modulo
  `2^61 − 1` instead of BLAKE2b, and `verifyCommit(..., "jacobi")` checks it the same way.
- **A shareable rematch**: `?seed=word&client=word` fixes the house secret and your word, so two
  people can play the same match and compare logs.

## How to verify it

- `sitoolz/tests/twins-indexrng.test.cjs` against `fixtures/indexrng.json` (written by
  `tests/write_indexrng_fixture.py`): every backend's draws, dice specs, and a commit-reveal
  session that verifies, with a tampered log that does not. The Python and the JavaScript agree
  on every value.
- `sitoolz/tests/twins-terrain.test.cjs` against `fixtures/terrain.json`: the windows of the
  worlds, cell for cell.
- `node smoke.cjs` here plays a match by the fixed seed `lantern`, recomputes every logged roll in
  Node from the revealed secret, and checks the commitment verifies on the page.

## What was missing

Nothing.

![](shot.png)
