Co-Study4Grid / docs /features /game-mode-codabench.md
github-actions[bot]
Deploy 7688ef1
13d4e44
|
Raw
History Blame Contribute Delete
7.18 kB
# Game Mode + Codabench benchmark
Game Mode is a timed, scored wrapper around the Co-Study4Grid study workspace.
A *session* is an ordered list of *studies* (a grid state + an N-1 contingency
each). The player must remediate every contingency with **at most 3 remedial
actions** before a per-study **timer** expires, then advances to the next
study. The session exports a `game_session.json` that a
[Codabench](https://www.codabench.org/) competition scores and ranks.
The Codabench bundle lives in a sibling repo:
`~/Dev/codabench/competitions/costudy4grid_game/` (branch
`feature/costudy4grid-game-mode`).
## How to play
Launch the frontend with `?game=1` (e.g. `http://localhost:5173/?game=1`):
1. **Config screen** — name the session, set the per-study timer (min/sec) and
the action cap (≤ 3), and build the ordered study list. Pre-filled with the
3-study warm-up; add more from the **preset contingency** dropdown (the
fr225_400 set from `data/pypsa_eur_fr225_400/n1_overload_contingencies.json`)
or add custom studies (network + action file + contingency id).
2. **Play** — a fixed HUD shows the study, a live countdown, the action
counter (`X/3`), and the current best resulting loading. The workspace
underneath is the unchanged Co-Study4Grid tool. **Star** the actions you
commit to (the star is capped at the configured max). Click **Next study →**
(or let the timer expire) to advance.
3. **Results** — per-study table + final score, with **⬇ JSON (Codabench)** and
**⬇ CSV** exports.
## Architecture
The whole feature is additive and inert unless `?game=1` is set.
| File | Role |
|---|---|
| `frontend/src/game/GameShell.tsx` | Entry point (mounted by `main.tsx`); state machine config → playing → results, hosts `<App/>` below a fixed HUD |
| `frontend/src/game/useGameSession.ts` | Session state machine: load study, timer countdown, commit + advance, build log |
| `frontend/src/game/gameBridge.ts` | Decoupling singleton (mirrors `interactionLogger`): App registers a study loader + publishes the physical snapshot; the shell drives loads + reads results + enforces the action cap |
| `frontend/src/game/GameConfigScreen.tsx` | Session/study configuration UI |
| `frontend/src/game/GameHud.tsx` | Timer / action-counter / Next-study HUD bar |
| `frontend/src/game/GameResults.tsx` | Results table + score preview + JSON/CSV export |
| `frontend/src/game/scoring.ts` | Shared scoring model (twin of the Python scorer) |
| `frontend/src/game/gameLog.ts` | `GameSessionLog` assembly + CSV + download helpers |
| `frontend/src/game/presets.ts` | Curated **solvable** fr225_400 contingencies |
| `frontend/src/game/types.ts` | Type contract for the whole module |
### App integration (3 touch points, all guarded by `gameBridge.isGameMode()`)
- `loadGameStudy(study)` — swaps network + action catalogue and arms the
contingency, registered with `gameBridge` so the shell can drive it.
- A publish effect pushes `{ baselineMaxRho, chosenActions }` (derived from
`result` + `selectedActionIds` + `n1Diagram.lines_overloaded_rho`) to the
shell on every change.
- `wrappedActionFavorite` refuses to star a *new* action past the cap.
## Scoring
Per study (0–100): `60·R + 25·R·A + 15·R·T` where **R** is the remediation
fraction (1.0 = worst line back under 100 %), **A** rewards using fewer of the
allowed actions, **T** rewards speed. Session score = mean across studies.
`frontend/src/game/scoring.ts` and the in-repo Codabench twin
`scripts/game_mode/scoring_program/score.py` implement the **identical**
formula. Cross-language parity is locked by a **shared golden fixture**
`scripts/game_mode/scoring_golden.json` (hand-authored inputs covering every
scorer branch; expected values regenerated by
`scripts/game_mode/gen_scoring_golden.py`): both `scoring.test.ts` (frontend)
and `scripts/game_mode/test_score.py` (pytest) assert the same expected values,
so a change to either scorer that breaks numerical parity fails a test in CI
(the `test-data-pipeline` job runs the Python side; `test-frontend` runs the
TS side). `apply_reference()` in `score.py` overlays trusted per-study numbers
onto the self-reported log when a `reference.json` is supplied (a no-op
otherwise); the physical session-log replay that *produces* that trusted
reference is the `--replay` mode below (FU-2 in `docs/architecture/followups.md`).
## Local end-to-end check
`scripts/game_mode/e2e_game_session.py` drives the real FastAPI backend
(`/api/config``/api/run-analysis-step1``/api/run-analysis-step2`) for the
preset studies, plays a greedy operator (lowest-rho actions, ≤ cap), writes a
`game_session.json`, and scores it with the Codabench scorer:
```bash
python3 scripts/game_mode/e2e_game_session.py --max-studies 3
# → writes test-results/e2e_*.json and prints the Codabench score
```
This requires pypowsybl + expert_op4grid_recommender in the environment (the
same deps the backend needs). The preset contingencies in `presets.ts` are all
verified `can_proceed=True` by this script, so the game stays winnable.
### Replaying a session log (`--replay`, FU-2)
An exported `GameSessionLog` carries *self-reported* `finalMaxRho` / `solved`.
To rank on **trusted** numbers, `--replay` re-derives them from the log's
recorded `actionsChosen` by re-driving the real backend:
```bash
python3 scripts/game_mode/e2e_game_session.py \
--replay path/to/game_session.json --grid fr \
--reference-out test-results/replay_reference.json --tolerance 0.05
# → writes a trusted reference.json (apply_reference() shape) and prints, per
# study, whether the replayed numbers diverge from the self-reported ones.
# Exits non-zero when any study diverges (tamper / drift signal).
```
For each study it runs `config → step1 → step2`, looks the recorded action ids
up in the freshly recomputed prioritized set (falling back to
`/api/simulate-manual-action` for a manual pick that is no longer prioritized),
takes the best (lowest) resulting `max_rho` as the trusted `finalMaxRho`, and
flags any action the backend can no longer reproduce. `--grid` selects the grid
the log was played on (all studies in a session share one difficulty tier). The
replay *machinery* is guarded hermetically by
`scripts/game_mode/test_replay.py` (a fake backend client — runs in CI); only
*generating a real reference* needs the FR/EUR grid bundle.
## Tests
- Frontend: `frontend/src/game/scoring.test.ts` (Vitest) — scoring + log/CSV
+ the shared golden-fixture parity block.
- Codabench (in-repo): `scripts/game_mode/test_score.py` (pytest) — golden-
fixture parity + `apply_reference` overlay/no-op. Runs in the CI
`test-data-pipeline` job. Regenerate the fixture after a deliberate scorer
change with `python scripts/game_mode/gen_scoring_golden.py` (then update
`scoring.ts` to match).
- Replay (in-repo): `scripts/game_mode/test_replay.py` (pytest) — the FU-2
session-log replay machinery against a fake backend client (action lookup,
manual-simulation fallback, best-of aggregation, divergence detection,
`reference.json` shape). Hermetic; runs in the same `test-data-pipeline` job.