Spaces:
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 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):
- 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). - 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. - 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 withgameBridgeso the shell can drive it.- A publish effect pushes
{ baselineMaxRho, chosenActions }(derived fromresult+selectedActionIds+n1Diagram.lines_overloaded_rho) to the shell on every change. wrappedActionFavoriterefuses 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:
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:
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_referenceoverlay/no-op. Runs in the CItest-data-pipelinejob. Regenerate the fixture after a deliberate scorer change withpython scripts/game_mode/gen_scoring_golden.py(then updatescoring.tsto 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.jsonshape). Hermetic; runs in the sametest-data-pipelinejob.