Phase `02_level_content_and_validation` — final verification **green**. - **Gate:** `bash .agents/validate.sh` → ruff ✅ · pyright 0 errors ✅ · `undercooked-levels validate` OK (2 levels) ✅ · **668 passed** (incl. 10 Playwright e2e) ✅ · coverage **100%** ✅ · repo hygiene ✅ - **Criteria:** `validate` exit 0, one line per level; `list` = `01_first_kitchen`, `02_double_shift` in campaign order; live `:8000` `/healthz` 200, `/api/levels` (+detail) carries both levels with numeric `target_score`/`time_limit` and no grid/station leakage, `/api/config` correct, unknown id → 404, startup log `content loaded: 2 level(s), 4 recipe(s)`; `validate --dir tests/fixtures/levels/_bad/` → exit 1 with named `RECIPE_STATION_MISSING` / `SERVE_REACHABLE` / `TARGET_IMPLAUSIBLE`; A8 lock test proves a YAML-only level reaches loader+API+CLI. - **Defect fixed (pre-existing path, phase-02-triggered):** `undercooked` with unusable content printed the report then a raw traceback and exited `1`. `src/undercooked/__main__.py` now catches `ContentError` → one closing line, exit `2` (like a bad setting); 2 new tests in `tests/unit/test_main.py` (incl. non-content failures still propagate). - **Docs pass (full, project-wide):** README (content gate + exit `2`, config section), AGENTS.md §3, `docs/LEVELS.md` (startup refusal + exact CLI exit codes), `docs/DEPLOYMENT.md` (crash-loop diagnosis bullet); DESIGN/TESTING re-read, consistent. - **Container re-verified (no phase-01 regression):** `podman build -f Containerfile` succeeds; the image ships `levels/` and serves `/healthz` 200, `/api/levels` (2 levels), `/` 200 as non-root. - **Deviation to note:** the phase overview names `tests/fixtures/levels/bad/`; the actual broken-fixture dir is `tests/fixtures/levels/_bad/` (the `_` prefix is what keeps it out of normal discovery). Overview files are read-only to me — README/LEVELS.md document the real path. - No git index/history commands run; all changes left unstaged for the harness. ntfy notification sent.
50 lines
3.7 KiB
Markdown
50 lines
3.7 KiB
Markdown
# DESIGN.md — architecture and the decisions behind it
|
||
|
||
**Status:** stub written by phase 01, module map refreshed by phase 02 · **Filled in by phase 09**, when the client, the
|
||
protocol and the first two-player end-to-end story make the prose verifiable.
|
||
|
||
## Purpose
|
||
|
||
This document explains the shape of the server and the client in prose: which module owns
|
||
what, why the boundaries are drawn where they are, and the reasoning behind the LOCKED
|
||
decisions A1–A12 of `.agents/PLAN.md` §3. `PLAN.md` states *what* was decided and stays
|
||
frozen; this file is where the *why* lives and where a later phase records the
|
||
consequences of those decisions once the code proves them out.
|
||
|
||
## The two-line version
|
||
|
||
One asyncio process holds every room. The browser is an untrusted thin client: it sends
|
||
inputs, receives snapshots, and interpolates. All truth lives in
|
||
`src/undercooked/sim/`, a pure function of `(state, dt)` with no I/O, no clock and no
|
||
unseeded randomness, which is what makes headless determinism tests (A5/A21) possible at
|
||
all. Everything around that kernel — `server/` (FastAPI app, WebSocket protocol, room
|
||
supervisor, in-memory session records) and `app/static/` (vanilla ES modules, Canvas 2D)
|
||
— exists only to move bytes between a player's hands and the simulation.
|
||
|
||
## Module map (as it stands after phase 02)
|
||
|
||
| Path | Owns | Filled in by |
|
||
|---|---|---|
|
||
| `src/undercooked/config.py` | the only read of the environment → typed, validated `Settings` | phase 01 (done) |
|
||
| `src/undercooked/debug.py` | `DEBUGPY`-gated debugger bootstrap (A18) | phase 01 (done) |
|
||
| `src/undercooked/server/app.py` | app factory, `/healthz`, `/`, `/static`, startup logging, mounts the content router | phase 01 (done) |
|
||
| `src/undercooked/server/routes_content.py` | `/api/levels`, `/api/levels/{id}`, `/api/config` behind `extra="forbid"` response models, plus the startup gate that refuses broken content | phase 02 (done) |
|
||
| `src/undercooked/server/headers.py` | CSP + the four other security headers on every response | phase 01 (done) |
|
||
| `src/undercooked/sim/model.py`, `grid.py` | the entity vocabulary (`Vec2`, items, stations, players, orders, hazards, `KitchenState`) and tile geometry/walkability | phase 02 (done) |
|
||
| `src/undercooked/sim/recipe.py`, `level.py`, `content.py` | the content contract (A8): recipe book, level schema + ASCII legend, discovery and campaign ordering | phase 02 (done); schema reference in `docs/LEVELS.md` |
|
||
| `src/undercooked/sim/validation.py` | PLAN §8's named playability rules and the escalation axes | phase 02 (done) |
|
||
| `src/undercooked/sim/purity.py` | the A5 guard: no I/O, asyncio, clock or FastAPI inside `sim/` | phase 02 (done) |
|
||
| `src/undercooked/tools/levels.py` | the `undercooked-levels` author CLI — a shell over the loader and the rules, never a second implementation | phase 02 (done) |
|
||
| `src/undercooked/sim/step.py` | game truth: the tick, interactions, scoring, hazards | phase 03–04 |
|
||
| `src/undercooked/server/room.py`, `rooms.py` | one match, its FSM and tick task; the registry that owns all of them | phase 05–06 |
|
||
| `src/undercooked/server/protocol.py` | versioned wire messages, whitelists, inbound validation | phase 06 |
|
||
| `src/undercooked/server/sessions.py` | bounded in-memory progression records (A6: no database) | phase 05 |
|
||
| `app/static/js/` | screen state machine, net buffer/interpolation/prediction, renderer, HUD | phase 07–08 |
|
||
|
||
## What this document will contain when it is filled in
|
||
|
||
The request/response and tick data flow with real timings, the room lifecycle state
|
||
machine as built, the arbitration rule for contested resources, the client prediction and
|
||
reconciliation model, and one worked paragraph per anchor in A1–A12 covering the
|
||
alternatives that were rejected and the measured cost of the choice that was taken.
|