continue. FIX ROUND 8: the valves, two parts. The owner looked at the outlined engine and said the valves are out of time and maybe the wrong shape. He is right on both. The design is already decided: just implement. Keep your reasoning short: one edit, run the checks, look at crops, next edit. PART A - TIMING (root cause found and confirmed by arithmetic, so do not hunt for another cause). index.html lines ~589-590 rotate BOTH cam lobe groups by st.camAngle only: $('camIntakeG').setAttribute('transform', 'rotate(' + st.camAngle ... ' 856 204)'); $('camExhaustG').setAttribute('transform', 'rotate(' + st.camAngle ... ' 944 204)'); But SPEC.md line 40 says the lobe NOSE angle (clockwise from up) is theta/2 + 126.25 for the intake and theta/2 - 126.25 for the exhaust, so that the nose points straight down at peak lift, and assets/engine-state.js already returns exactly those as st.camNoseIntake and st.camNoseExhaust. The drawn lobe path (camPath) has its nose at phi = 0, so the group must be rotated by the NOSE angle. The valves lift correctly from the spec curve, but the lobes are drawn 126.25 degrees out of phase, so the roller floats away from the nose while the valve is open (gap up to 15.5 px at peak lift) and the lobe overlaps the roller while the valve is shut (6.4 px). A probe with the lobe rotated by the nose angle gives a worst roller-to-lobe gap of 1.8 px (discretisation of the 2.5 degree lobe path): read cam_probe.py (it only prints, never writes) and run it with py -3.14 -I. THE FIX: rotate camIntakeG by st.camNoseIntake and camExhaustG by st.camNoseExhaust (same centres 856,204 and 944,204). Nothing else in Part A. CHECK A (add to measure_engine.mjs as two new checks, then run it): (1) at every sampled time the camIntakeG rotation equals camNoseIntake and camExhaustG equals camNoseExhaust, both within 0.05 degrees; (2) the roller touches the lobe: with the lobe path rotated by the transform, the distance from the roller centre to the nearest lobe-surface point minus the roller radius (8 px) is within +-3 px at every sampled time, for BOTH valves (use the maths in cam_probe.py, or measure it from the DOM with getPointAtLength / a point-in-polygon sampling, your choice). The existing 17 checks must also still pass. Control: temporarily revert the rotation in a COPY of index.html (never in place) with --file and confirm the new checks FAIL on that copy (they must fail on the old behaviour or they prove nothing). LOOK A: 4x head crops (crop=330:260:760:150 then scale 4x) at t=1.0, 2.0, 3.5, 5.0, 12.0, 13.6, 17.8: where a valve is open the lobe nose must visibly be pushing its roller down (nose pointing straight down at the lift peak: intake peak at t = 2.39 s and 18.39 s, exhaust peak at t = 13.61 s and 29.61 s); where a valve is shut the roller rests on the round base of the lobe. PART B - SHAPE. The valve heads are flat rectangular plates (T-shaped valves). Real poppet valves have a flat-bottomed mushroom head: a short vertical margin at the rim, a conical seat face going up and inward, then a neck into the stem. Implement it like this, keeping every existing id and every position the measuring tool and the leader dots use: B1. Keep the existing and exactly as they are for geometry (the leader dots and the measuring tool read them) but make them invisible: fill="none" stroke="none" (do not delete them, do not change their per-frame y/height code). B2. Add one polygon per valve, drawn where the old rect was drawn in z-order: valveShapeIntake and valveShapeExhaust, fill #C5CDD5, with the same dark outline as the other tinted parts (add the fill to the existing CSS outline rule, or set stroke #0E1116, stroke-width 2, stroke-linejoin round on the polygon). Author each polygon at the CLOSED position and move it every frame by transform translate(0 dy) where dy = (current head rect y) - (closed head rect y), so it follows the head exactly. Points, with cx = the valve axis x (856 intake, 944 exhaust), y0 = the closed top of the head rect, W = the head rect width (56 intake, 48 exhaust), head height 16 px: (cx-W/2, y0+16), (cx+W/2, y0+16), (cx+W/2, y0+13), (cx+11, y0+2), (cx+7, y0), (cx-7, y0), (cx-11, y0+2), (cx-W/2, y0+13). The stem (14 px wide, half-width 7) joins the neck at y0, so stem and head read as one valve; the stem and barrel keep their tint. B3. The seat. A cone needs a matching seat or the closed valve leaves ugly triangular gaps. In the head casting, at each valve, add a seat wedge on each side of the valve: a steel-gradient polygon with the usual dark outline whose underside is exactly the head cone, i.e. left wedge = (cx-W/2, y0+13), (cx-11, y0+2), (cx-W/2, y0+2); right wedge = (cx+W/2, y0+13), (cx+11, y0+2), (cx+W/2, y0+2) (read the real pocket numbers in the headG group around lines 96-125: the pocket walls are at x = cx-W/2 and cx+W/2, y0 is the roof line; adapt if your numbers differ, the intent is: with the valve closed the head cone is in contact with the wedge undersides, no gap, and with the valve open a clear gap opens between cone and wedge so the gas path reads). Put the wedges after the dark pocket rects and before the valve shapes in z-order. They do not move. B4. Do not change the valve lift, the port paths, the gas dots, the leaders, the dial, the captions, the springs or the outlines elsewhere. CHECK B: the measuring tool's 17 checks (plus the two from part A) all pass. 4x head crops at t=7.0 and 9.0 (both valves closed): the cones seat against the wedges with no visible gap; at t=2.0 (intake open) and t=13.6 (exhaust open): the open valve shows the poppet shape with a gap between cone and wedge; the leader dot still sits on the head (t=5.5, 11.5, 21.5, 27.5). THEN: BEFORE rendering back up the round-7 renders: final.mp4 -> final_v7.mp4 and review.mp4 -> review_v7.mp4 (do not overwrite older backups). Render review.mp4 and final.mp4; ffprobe gate (h264, 1 video stream, 1920x1080, 30/1, 32.0 s +/-0.05, no audio); append round 8 to REVIEW.md, REPORT.md, NOTES.md. Final report: per part done or not done with the numbers (the roller gap before and after, the control run) and the gate, files written; honest about anything still imperfect. Say plainly in REPORT.md that the lobe rotation bug existed from the first build and that your earlier reviews did not catch it.