phase: 02_level_content_and_validation
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.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# levels/01_first_kitchen.yaml — campaign level 1, the tutorial kitchen.
|
||||
# Schema: docs/LEVELS.md · validated at startup and by `undercooked-levels
|
||||
# validate`. Every '#' is wall, every station stands on walkable floor.
|
||||
|
||||
id: 01_first_kitchen
|
||||
name: First Kitchen
|
||||
order: 1
|
||||
difficulty: 1
|
||||
time_limit: 150
|
||||
target_score: 45
|
||||
blurb: >-
|
||||
Your first shift: grab a tomato and an onion from the shelves, chop them,
|
||||
cook the soup, plate it, and run it to the serve window before the clock
|
||||
runs out. One dish, one of everything, nowhere to bump into each other.
|
||||
grid:
|
||||
- "############"
|
||||
- "#T..C...S..#"
|
||||
- "#..........#"
|
||||
- "#.P......X.#"
|
||||
- "#..........#"
|
||||
- "#..K....O..#"
|
||||
- "#..........#"
|
||||
- "#..........#"
|
||||
- "############"
|
||||
tiles:
|
||||
"#": {kind: wall}
|
||||
".": {kind: floor}
|
||||
"T": {kind: floor, station: spawn_shelf, item: tomato}
|
||||
"O": {kind: floor, station: spawn_shelf, item: onion}
|
||||
"C": {kind: floor, station: chop_board}
|
||||
"S": {kind: floor, station: stove}
|
||||
"P": {kind: floor, station: prep}
|
||||
"X": {kind: floor, station: serve}
|
||||
"K": {kind: floor, station: sink}
|
||||
spawns: [[2, 7], [9, 7]]
|
||||
orders:
|
||||
pool: [soup]
|
||||
concurrent: 1
|
||||
spawn_interval: [16, 20]
|
||||
deadline: 60
|
||||
hazards: []
|
||||
escalation_notes: >-
|
||||
Tutorial baseline (PLAN §8 campaign plan): one recipe, one order at a time,
|
||||
slowest spawn interval, longest time budget, open floor, no hazards. Every
|
||||
later level moves at least two axes up from these numbers.
|
||||
@@ -0,0 +1,49 @@
|
||||
# levels/02_double_shift.yaml — campaign level 2: two dishes at once.
|
||||
# Schema: docs/LEVELS.md · validated at startup and by `undercooked-levels
|
||||
# validate`. The counter bands pinch the kitchen into three rooms joined by
|
||||
# one narrow walk-through.
|
||||
|
||||
id: 02_double_shift
|
||||
name: Double Shift
|
||||
order: 2
|
||||
difficulty: 2
|
||||
time_limit: 130
|
||||
target_score: 80
|
||||
blurb: >-
|
||||
Salads join the soup and the orders overlap now. Two counter bands pinch the
|
||||
floor, so someone is always walking the long way through the gap — plan who
|
||||
crosses and who stays on their side.
|
||||
grid:
|
||||
- "#############"
|
||||
- "#T....C....O#"
|
||||
- "#.....S.....#"
|
||||
- "####=...=####"
|
||||
- "#....P.X....#"
|
||||
- "#...........#"
|
||||
- "####=...=####"
|
||||
- "#..K.....L..#"
|
||||
- "#############"
|
||||
tiles:
|
||||
"#": {kind: wall}
|
||||
".": {kind: floor}
|
||||
"=": {kind: counter}
|
||||
"T": {kind: floor, station: spawn_shelf, item: tomato}
|
||||
"O": {kind: floor, station: spawn_shelf, item: onion}
|
||||
"L": {kind: floor, station: spawn_shelf, item: lettuce}
|
||||
"C": {kind: floor, station: chop_board}
|
||||
"S": {kind: floor, station: stove}
|
||||
"P": {kind: floor, station: prep}
|
||||
"X": {kind: floor, station: serve}
|
||||
"K": {kind: floor, station: sink}
|
||||
spawns: [[2, 2], [10, 5]]
|
||||
orders:
|
||||
pool: [soup, salad]
|
||||
concurrent: 2
|
||||
spawn_interval: [12, 16]
|
||||
deadline: 52
|
||||
hazards: []
|
||||
escalation_notes: >-
|
||||
Moves four escalation axes vs 01_first_kitchen (PLAN §8 requires two):
|
||||
recipe_complexity 1 -> 2 (salad joins), order_pressure 0.056 -> 0.143
|
||||
(2 concurrent at shorter intervals), time_budget 0.30 -> 0.62 target/s,
|
||||
and layout_hostility 0.35 -> 0.48 blocking tiles with the counter pinch.
|
||||
@@ -0,0 +1,80 @@
|
||||
# levels/recipes.yaml — the campaign recipe book (PLAN §5/§8, A8).
|
||||
#
|
||||
# Every dish the game knows lives here; no Python enumerates recipes. The
|
||||
# schema is `src/undercooked/sim/recipe.py` and the reference is
|
||||
# docs/LEVELS.md — every model is strict (`extra="forbid"`), so a typo here
|
||||
# fails the load loudly instead of silently defaulting.
|
||||
#
|
||||
# Step lists mirror PLAN §5's station pipelines: pick from the spawn shelf,
|
||||
# chop (~0.9 s), cook on the stove, plate at the prep station, serve at the
|
||||
# window. `seconds` feeds only the validator's greedy target-plausibility
|
||||
# bound; the simulation (phase 04) re-enacts these steps per tick.
|
||||
|
||||
# Catalog of the item kinds the campaign's dishes consume (documentation of
|
||||
# intent; spawn shelves are what actually put items on the floor).
|
||||
ingredients: [tomato, onion, lettuce, dough, patty, rice, fish, plate]
|
||||
|
||||
recipes:
|
||||
# tomato + onion -> chop -> pot on the stove -> plate
|
||||
soup:
|
||||
id: soup
|
||||
name: Tomato Soup
|
||||
base_score: 40
|
||||
ingredients:
|
||||
- {kind: tomato, form: raw}
|
||||
- {kind: onion, form: raw}
|
||||
steps:
|
||||
- {kind: pick, station: spawn_shelf}
|
||||
- {kind: chop, station: chop_board, seconds: 0.9}
|
||||
- {kind: cook, station: stove, seconds: 5.0}
|
||||
- {kind: plate, station: prep, seconds: 0.4}
|
||||
- {kind: serve, station: serve}
|
||||
tags: [hot, soup]
|
||||
|
||||
# lettuce + tomato -> chop -> plate; the fast, no-fire dish
|
||||
salad:
|
||||
id: salad
|
||||
name: Garden Salad
|
||||
base_score: 30
|
||||
ingredients:
|
||||
- {kind: lettuce, form: raw}
|
||||
- {kind: tomato, form: raw}
|
||||
steps:
|
||||
- {kind: pick, station: spawn_shelf}
|
||||
- {kind: chop, station: chop_board, seconds: 0.9}
|
||||
- {kind: plate, station: prep, seconds: 0.4}
|
||||
- {kind: serve, station: serve}
|
||||
tags: [cold, quick]
|
||||
|
||||
# rice + onion straight onto the stove (PLAN §5: raw rice needs no board)
|
||||
fried_rice:
|
||||
id: fried_rice
|
||||
name: Fried Rice
|
||||
base_score: 50
|
||||
ingredients:
|
||||
- {kind: rice, form: raw}
|
||||
- {kind: onion, form: raw}
|
||||
steps:
|
||||
- {kind: pick, station: spawn_shelf}
|
||||
- {kind: cook, station: stove, seconds: 6.0}
|
||||
- {kind: plate, station: prep, seconds: 0.4}
|
||||
- {kind: serve, station: serve}
|
||||
tags: [hot, grain]
|
||||
|
||||
# patty + dough + lettuce -> chop + pan on the stove -> plate. Defined now;
|
||||
# later levels (phase 11) put it in an order pool.
|
||||
burger:
|
||||
id: burger
|
||||
name: Chef's Burger
|
||||
base_score: 70
|
||||
ingredients:
|
||||
- {kind: patty, form: raw}
|
||||
- {kind: dough, form: raw}
|
||||
- {kind: lettuce, form: raw}
|
||||
steps:
|
||||
- {kind: pick, station: spawn_shelf}
|
||||
- {kind: chop, station: chop_board, seconds: 0.9}
|
||||
- {kind: cook, station: stove, seconds: 5.0}
|
||||
- {kind: plate, station: prep, seconds: 0.5}
|
||||
- {kind: serve, station: serve}
|
||||
tags: [hot, stacked]
|
||||
Reference in New Issue
Block a user