Spaces:
Sleeping
Sleeping
| # 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. | |