Files
ducoterra 95ca3fd1fa phase: 07_canvas_client_and_hud
**Final report — phase 07 final verification pass**

- **Verified:** all 5 tasks present in `complete/`; app boots (`/healthz` 200, `/` 200, static + atlas + `/api/levels` served); `docs/CLIENT.md` module map matches the on-disk `app/static/` tree; no code defects found.
- **Tests/lint/coverage:** `bash .agents/validate.sh` → ruff clean, pyright 0 errors, `undercooked-levels validate` pass, **full pytest 1838 passed, 97.21% coverage (floor 90%)**, slow sim stage 2 passed, repo hygiene clean. `uv run pytest tests/e2e -q` → **53 passed** (all 8 suites added this phase).
- **Criteria:** (1) two tabs create→join→ready→start→serve one dish, keyboard only — green via `test_us01/02/03/04` ✔; (2) e2e green ✔; (3) canvas non-blank, pixels change after a move — `test_us04_render_smoke` ✔; (4) HUD score within 200 ms of the `served` event — asserted, green ✔; (5) zero console errors + zero off-origin requests — `test_atlas_loads` / `test_us01_landing_surface` ✔; (6) `docs/CLIENT.md` matches layout ✔; (7) 100% suite + 97.21% ✔; (8) docs pass below ✔; (9) no regression in completed phases (all prior suites green) ✔.
- **README.md fixed:** status still said "next pending phase is 07" → now 07 complete, 08 pending; the phase-07 client text buried in the phase-05 bullet → its own "canvas client, art, HUD (phase 07)" bullet; stale "What does not exist yet" (claimed movement/controls missing) → rewritten to phases 08–14; "once the room flow ships" → "live in the browser today"; Layout `app/static/` "renderer arrives with the rest of phase 07" → full shipped-client map; Layout `docs/` "CLIENT lands with phase 07" → `CLIENT.md` listed; added `gen_art_index.py` Layout entry.
- **AGENTS.md fixed:** "the eight screens" (7 listed) → "the seven screens" (+ `dom.js` is the shared builder, not a screen); e2e naming line → notes the non-story guard `test_atlas_loads.py`.
- **Decisions:** no code changes were needed; only the two doc fixes above. Docs re-verified by `test_docs_commands`/`test_docs_index` (44 passed) plus the final full gate.
- **Next pending phase:** `08_client_prediction_and_touch`.
2026-10-10 18:03:52 -04:00

117 lines
8.3 KiB
XML

<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32">
<!-- Undercooked cooks (phase 07, task 01). Four bodies (cook-1..4),
each 4 facings x 2 walk frames; the torso colour is
`currentColor`, so a <use> recolors it: in the DOM through the
page's cook-colour CSS custom properties, in the canvas through
the var() fallback (app/static/assets/palette.css). The carry
overlay and the stun frame are shared. -->
<defs>
<ellipse id="shadow" cx="16" cy="29.5" rx="8" ry="1.8" fill="#1a1713" opacity="0.18"/>
<g id="legs-0">
<rect x="12.4" y="23" width="3.2" height="6" rx="1.4" fill="#4a4238"/>
<rect x="16.4" y="23" width="3.2" height="6" rx="1.4" fill="#4a4238"/>
</g>
<g id="legs-1">
<path d="M13.5 23 L10.8 28.8" stroke="#4a4238" stroke-width="3.2" stroke-linecap="round" fill="none"/>
<path d="M18.5 23 L21.2 28.8" stroke="#4a4238" stroke-width="3.2" stroke-linecap="round" fill="none"/>
</g>
<g id="chef-torso">
<rect x="10" y="14" width="12" height="11" rx="3" fill="currentColor"/>
<path d="M12 18.5 h8 v3.5 a4 3.4 0 0 1 -8 0 z" fill="#f2ede3"/>
</g>
<g id="chef-hat">
<path d="M11.4 8.6 a4.6 4.2 0 0 1 9.2 0 z" fill="#fbf7ee"/>
<circle cx="16" cy="5.4" r="2.8" fill="#fbf7ee"/>
<path d="M11.4 8.6 h9.2" stroke="#e2dbcb" stroke-width="1.2" stroke-linecap="round" fill="none"/>
</g>
<g id="chef-head-down">
<circle cx="16" cy="11" r="4.6" fill="#f2c9a0"/>
<use href="#chef-hat"/>
<circle cx="14.2" cy="10.6" r="0.75" fill="#3a322a"/>
<circle cx="17.8" cy="10.6" r="0.75" fill="#3a322a"/>
<path d="M15.2 13 q0.8 0.7 1.6 0" stroke="#c98a5e" stroke-width="0.9" stroke-linecap="round" fill="none"/>
</g>
<g id="chef-head-up">
<circle cx="16" cy="11" r="4.6" fill="#f2c9a0"/>
<use href="#chef-hat"/>
<path d="M12.6 13.4 q3.4 2 6.8 0" stroke="#e0b184" stroke-width="1" stroke-linecap="round" fill="none"/>
</g>
<g id="chef-head-right">
<circle cx="16" cy="11" r="4.6" fill="#f2c9a0"/>
<use href="#chef-hat"/>
<circle cx="18.9" cy="10.6" r="0.75" fill="#3a322a"/>
<path d="M18.4 13.2 q0.8 0.6 1.6 0" stroke="#c98a5e" stroke-width="0.9" stroke-linecap="round" fill="none"/>
</g>
<g id="chef-head-left">
<circle cx="16" cy="11" r="4.6" fill="#f2c9a0"/>
<use href="#chef-hat"/>
<circle cx="13.1" cy="10.6" r="0.75" fill="#3a322a"/>
<path d="M12 13.2 q0.8 0.6 1.6 0" stroke="#c98a5e" stroke-width="0.9" stroke-linecap="round" fill="none"/>
</g>
<g id="shape-up-0"><use href="#shadow"/><use href="#legs-0"/><use href="#chef-torso"/><use href="#chef-head-up"/></g>
<g id="shape-up-1"><use href="#shadow"/><use href="#legs-1"/><use href="#chef-torso"/><use href="#chef-head-up"/></g>
<g id="shape-right-0"><use href="#shadow"/><use href="#legs-0"/><use href="#chef-torso"/><use href="#chef-head-right"/></g>
<g id="shape-right-1"><use href="#shadow"/><use href="#legs-1"/><use href="#chef-torso"/><use href="#chef-head-right"/></g>
<g id="shape-down-0"><use href="#shadow"/><use href="#legs-0"/><use href="#chef-torso"/><use href="#chef-head-down"/></g>
<g id="shape-down-1"><use href="#shadow"/><use href="#legs-1"/><use href="#chef-torso"/><use href="#chef-head-down"/></g>
<g id="shape-left-0"><use href="#shadow"/><use href="#legs-0"/><use href="#chef-torso"/><use href="#chef-head-left"/></g>
<g id="shape-left-1"><use href="#shadow"/><use href="#legs-1"/><use href="#chef-torso"/><use href="#chef-head-left"/></g>
</defs>
<symbol id="cook-1-up-0" viewBox="0 0 32 32"><use href="#shape-up-0" style="color: var(--uc-cook-1, #e0533d)"/></symbol>
<symbol id="cook-1-up-1" viewBox="0 0 32 32"><use href="#shape-up-1" style="color: var(--uc-cook-1, #e0533d)"/></symbol>
<symbol id="cook-1-right-0" viewBox="0 0 32 32"><use href="#shape-right-0" style="color: var(--uc-cook-1, #e0533d)"/></symbol>
<symbol id="cook-1-right-1" viewBox="0 0 32 32"><use href="#shape-right-1" style="color: var(--uc-cook-1, #e0533d)"/></symbol>
<symbol id="cook-1-down-0" viewBox="0 0 32 32"><use href="#shape-down-0" style="color: var(--uc-cook-1, #e0533d)"/></symbol>
<symbol id="cook-1-down-1" viewBox="0 0 32 32"><use href="#shape-down-1" style="color: var(--uc-cook-1, #e0533d)"/></symbol>
<symbol id="cook-1-left-0" viewBox="0 0 32 32"><use href="#shape-left-0" style="color: var(--uc-cook-1, #e0533d)"/></symbol>
<symbol id="cook-1-left-1" viewBox="0 0 32 32"><use href="#shape-left-1" style="color: var(--uc-cook-1, #e0533d)"/></symbol>
<symbol id="cook-2-up-0" viewBox="0 0 32 32"><use href="#shape-up-0" style="color: var(--uc-cook-2, #3d84a8)"/></symbol>
<symbol id="cook-2-up-1" viewBox="0 0 32 32"><use href="#shape-up-1" style="color: var(--uc-cook-2, #3d84a8)"/></symbol>
<symbol id="cook-2-right-0" viewBox="0 0 32 32"><use href="#shape-right-0" style="color: var(--uc-cook-2, #3d84a8)"/></symbol>
<symbol id="cook-2-right-1" viewBox="0 0 32 32"><use href="#shape-right-1" style="color: var(--uc-cook-2, #3d84a8)"/></symbol>
<symbol id="cook-2-down-0" viewBox="0 0 32 32"><use href="#shape-down-0" style="color: var(--uc-cook-2, #3d84a8)"/></symbol>
<symbol id="cook-2-down-1" viewBox="0 0 32 32"><use href="#shape-down-1" style="color: var(--uc-cook-2, #3d84a8)"/></symbol>
<symbol id="cook-2-left-0" viewBox="0 0 32 32"><use href="#shape-left-0" style="color: var(--uc-cook-2, #3d84a8)"/></symbol>
<symbol id="cook-2-left-1" viewBox="0 0 32 32"><use href="#shape-left-1" style="color: var(--uc-cook-2, #3d84a8)"/></symbol>
<symbol id="cook-3-up-0" viewBox="0 0 32 32"><use href="#shape-up-0" style="color: var(--uc-cook-3, #7da84b)"/></symbol>
<symbol id="cook-3-up-1" viewBox="0 0 32 32"><use href="#shape-up-1" style="color: var(--uc-cook-3, #7da84b)"/></symbol>
<symbol id="cook-3-right-0" viewBox="0 0 32 32"><use href="#shape-right-0" style="color: var(--uc-cook-3, #7da84b)"/></symbol>
<symbol id="cook-3-right-1" viewBox="0 0 32 32"><use href="#shape-right-1" style="color: var(--uc-cook-3, #7da84b)"/></symbol>
<symbol id="cook-3-down-0" viewBox="0 0 32 32"><use href="#shape-down-0" style="color: var(--uc-cook-3, #7da84b)"/></symbol>
<symbol id="cook-3-down-1" viewBox="0 0 32 32"><use href="#shape-down-1" style="color: var(--uc-cook-3, #7da84b)"/></symbol>
<symbol id="cook-3-left-0" viewBox="0 0 32 32"><use href="#shape-left-0" style="color: var(--uc-cook-3, #7da84b)"/></symbol>
<symbol id="cook-3-left-1" viewBox="0 0 32 32"><use href="#shape-left-1" style="color: var(--uc-cook-3, #7da84b)"/></symbol>
<symbol id="cook-4-up-0" viewBox="0 0 32 32"><use href="#shape-up-0" style="color: var(--uc-cook-4, #b0699a)"/></symbol>
<symbol id="cook-4-up-1" viewBox="0 0 32 32"><use href="#shape-up-1" style="color: var(--uc-cook-4, #b0699a)"/></symbol>
<symbol id="cook-4-right-0" viewBox="0 0 32 32"><use href="#shape-right-0" style="color: var(--uc-cook-4, #b0699a)"/></symbol>
<symbol id="cook-4-right-1" viewBox="0 0 32 32"><use href="#shape-right-1" style="color: var(--uc-cook-4, #b0699a)"/></symbol>
<symbol id="cook-4-down-0" viewBox="0 0 32 32"><use href="#shape-down-0" style="color: var(--uc-cook-4, #b0699a)"/></symbol>
<symbol id="cook-4-down-1" viewBox="0 0 32 32"><use href="#shape-down-1" style="color: var(--uc-cook-4, #b0699a)"/></symbol>
<symbol id="cook-4-left-0" viewBox="0 0 32 32"><use href="#shape-left-0" style="color: var(--uc-cook-4, #b0699a)"/></symbol>
<symbol id="cook-4-left-1" viewBox="0 0 32 32"><use href="#shape-left-1" style="color: var(--uc-cook-4, #b0699a)"/></symbol>
<!-- The carrying overlay: one arm raised with the item slot ring; the
renderer draws the item sprite in the slot. -->
<symbol id="cook-carry" viewBox="0 0 32 32">
<path d="M19 17 l7 -5.5" stroke="#f2c9a0" stroke-width="3" stroke-linecap="round" fill="none"/>
<circle cx="27" cy="11" r="2.4" fill="#f2c9a0"/>
<circle cx="27" cy="9.6" r="4.6" fill="none" stroke="#cfc5b5" stroke-width="1" stroke-dasharray="2.4 2.2"/>
</symbol>
<!-- The stun frame: a dizzy swirl and a star. -->
<symbol id="cook-stun" viewBox="0 0 32 32">
<path d="M19.5 12.5 a3.5 3.5 0 1 1 -3.5 -3.5 a5 5 0 1 0 5 5" fill="none" stroke="#3a322a" stroke-width="1.4" stroke-linecap="round"/>
<path d="M16 2.6 l1 2.2 2.4 0.3 -1.8 1.6 0.5 2.3 -2.1 -1.2 -2.1 1.2 0.5 -2.3 -1.8 -1.6 2.4 -0.3 z" fill="#ffd95e"/>
</symbol>
</svg>