From dd75aaf6e82799ce88d8e66b01a3d53ba05301c9 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 21 Sep 2026 02:03:45 +0800 Subject: feat(viewer): fit camera to loaded model and add checked-in browser acceptance driver - add three.js Box3/Camera bindings and bbox-driven camera framing (fov-fit with margin, recenters the model so group rotation orbits in place); refit on canvas resize instead of reverting to replay placeholder geometry - expose window.SOMHAIRLES_VIEWER debug handle for numeric verification - add tools/acceptance/e2e_acceptance.py: self-provisioning playwright driver (isolated port/artifact root, unique run ids) asserting framing, frozen-screenshot determinism, GLB hash equality, failure surfacing - gitignore scratch/ and tools/acceptance/deps/; document the clean-checkout verification sequence (fable before build) --- README.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 4 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index ae59ca2..907fc50 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,58 @@ Three.js r160 and GLTFLoader are vendored under `public/vendor/` (import map plus a `three-setup.js` shim exposing `window.THREE`/`window.GLTFLoader`). They are MIT-licensed third-party dependencies, not project assets. +## Browser acceptance driver + +`tools/acceptance/e2e_acceptance.py` is the checked-in end-to-end gate. It +starts the server on an isolated port with its own artifact root, launches +headless chromium, and drives a full run through the page form twice +(desktop 1440x1000 and narrow 390x844 viewports, `?freeze=1`), plus a +failure-path run. It fails unless every check passes; exit code 0 means +green. Checks include: + +- idle page (`待机`, empty-state text), SSE connection, live per-stage + viewport ingestion (`运行中` + `steps/01-foundation.glb`, then `已完成` + + `steps/03-cabin.glb`), manifest link exposure +- geometry framing asserted from the live scene (`window.SOMHAIRLES_VIEWER` + + `THREE.Box3` projected to screen space): model fully inside the canvas + with margins >= 2% of the smaller canvas dimension, pixel coverage within + 40%-95%, center offset <= 10%, on both viewports; stage-1 vs stage-3 + bounding boxes must differ by >= 5% +- frozen-viewport determinism: two consecutive canvas screenshots are + byte-identical (sha256) +- served GLB sha256 equality with pinned reference hashes + (`01-foundation`, `03-cabin`) and `model/gltf-binary` content type +- failure run (pre-existing run directory) surfaces `失败` and the pipeline + error in `run-message`; zero browser console/page errors overall + +Playwright is self-provisioned into the gitignored +`tools/acceptance/deps/` directory (`pip --target --break-system-packages`, +pinned to an already-installed version when detectable) so the driver never +installs into system site-packages; the chromium browser binary is reused +from the user cache when present. + +Full verification from a clean checkout: + +```sh +dotnet tool restore +# Fable must run BEFORE the solution build: the server serves the frontend +# from public/ (copied into wwwroot at build time), and a fresh checkout has +# no fable_modules/App.js yet. Also run the frontend project restore first, +# and use --noCache: fable's project-options cache (keyed by fsproj content) +# is shared across checkouts of the same project and can yield spurious +# "Fable namespace not defined" errors on a fresh clone. +dotnet restore src/SomhairlesDream.Frontend/SomhairlesDream.Frontend.fsproj +dotnet fable src/SomhairlesDream.Frontend/SomhairlesDream.Frontend.fsproj \ + --outDir public --noRestore --noCache +dotnet build SomhairlesDream.sln -c Release +dotnet test SomhairlesDream.sln -c Release --no-build +python3 tools/acceptance/e2e_acceptance.py --port 18201 \ + --bpython /tmp/bpyenv/bin/python +``` + +`--bpython` must point at a Python that can `import bpy` (see below); the +driver refuses to start without it. + ## Server ```sh @@ -130,9 +182,12 @@ dotnet run --project src/SomhairlesDream.Cli -- run \ reference run; a failure run (pre-existing run directory) shows status `失败` with the error surfaced in `run-message`; zero console errors on all pages. -- Frozen-viewport evidence (`?freeze=1` stops rotation): canvas screenshots - of placeholder vs stage-1 vs stage-3 differ in pixel hashes, and the three - stages are visually distinct cubes/frames/house. Evidence lives in a - scratch directory and is not committed. +- Browser acceptance driver (see above), 45/45 checks green on desktop and + narrow viewports: framing asserted numerically from the projected scene + bounding box (not by trusting whole-canvas hashes alone), frozen + screenshots byte-deterministic, GLB hash equality, failure surfacing, + zero console errors. The frontend refits the camera to the loaded model + (`THREE.Box3`-based framing) whenever the canvas resizes, and a resize + never reverts the viewport to replay placeholder geometry. - NOT verified: production deployment of the old site remains untouched; `--render` PNG renders; replay button flow against a historical run. -- cgit v1.2.3