Video2Code
Faithfully replicate a webpage demonstrated in a reference video. Match layout + visual style + interactions + animations, not just a static lookalike. Every animation must exist, react to the same driver (time / scroll / hover / click / pointer position), and read the same way as the source.
The visibility baseline — one rule, all four phases
Fidelity is bounded by what a viewer sees. The replica is judged on a same-scale composite at 1×, zoomable to ~3×; that, and nothing finer, is the resolution of every fact this task deals in. The floor cuts both ways, and the second direction is the one that gets missed:
- Nothing sub-visible can fail. A difference you cannot distinguish at ≤3× is within tolerance by definition — pass it, don't fix it, don't spend budget on it.
- Nothing sub-visible may be produced. Not observed, not written into the plan, not carried in
measured. If establishing a value takes an instrument, that value is not part of this task — in any phase, on the source side exactly as much as the replica side.
The second clause is not a restatement of the first. A value read off a single source frame in Phase 1 is not yet a difference, so a floor written only about differences never reaches it — and by the time Phase 4 arrives, the whole run has been steered by numbers no viewer could see, with a plan full of targets no composite can settle.
"Measure" in this document always means "read a value off the artifact the tool just showed you, at the granularity your eye resolves on it." It never means "compute it from the pixel array." Reading a heading's width against the frame is measuring; np.nonzero on its ink is not.
Concretely, either side, any phase:
| Carry these | Never produce these |
|---|---|
| positions and sizes to the nearest few px, read off a 1×–3× view | any length claimed to ±1px; glyph ink bounding boxes; per-character advance |
| type size in px at ~2px granularity (14 / 16 / 20 / 32 / 48) | stroke or hairline thickness in px; weight inferred from stroke width |
| color as a nameable, reproducible token (one-shot sample, §4.4) | channel deltas, luminance levels, region mean/std, Δ≈7-style reads |
| durations to ~0.1s off timestamped grid cells | ink coverage %, energy ratios, per-pixel row/column profiles, sub-pixel amplitude |
| counts, gaps, fractions of the viewport | any quantity whose two sides differ by less than you can see |
A right-hand-column number appearing in out/plan.md or out/verify.jsonl is a defect in its own right, independent of how good the replica is: it sets a target no viewer asked for, and it makes that id's pass/fail unfalsifiable by eye — the only way to re-check it is to re-run the same script, which is not evidence. Each phase below restates this floor in its own terms (Phase 1–2 escalation cap, Phase 3 weight tags, §4.3 fix budgets, §4.4 Parity standard). They are all this one rule.
Stack
Always uses web-replicate (React + TS + Vite + Tailwind + shadcn/ui). Pick the lightest implementation that fits:
| Use case | Tool |
|---|---|
| Hover/fade, simple slide | CSS transition / keyframes |
| Sticky / pinned / scroll-snap / position coupled to scroll | plain CSS (position: sticky, scroll-snap) — no library |
| Simple entrance | Tailwind animate-* / tailwindcss-animate |
| Orchestrated multi-element, scroll-triggered reveal | framer-motion |
| True 3D / WebGL — depth, particles, volumetric light/glow, shader bg, camera flythrough | load video2code-3d, scaffold default-3d (raw three.js) |
Reach for the lightest tool that fits — don't escalate to a library for a 200ms fade.
This table only decides how to BUILD an effect. It does NOT decide whether the effect is
[S]or[D](Phase 3). Those are different questions: implementation is "CSS vs library", classification is "can one frozen frame prove it's correct". A pure-CSS effect (sticky, scroll-snap) can absolutely be[D]. Never reason "no library → static."
File contract — the three artifacts that define "done"
All contract state lives in files under out/ (relative to the project directory). Conversation text carries zero contract status — nothing counts as planned/verified/finished until it is in these files:
| File | What | When |
|---|---|---|
out/plan.md |
The design spec. Every observable concern is one line starting with [S<#>] or [D<#>], carrying measured numbers and (for [D]) the source clip time range that proves it. |
Written at end of Phase 3, before any component code is written. |
out/verify.jsonl |
Append-only verification log. One JSON object per check, status field named result — exact schema in Exact line formats below. measured values are read off the evidence artifact (composite scale, grid timestamps, tool receipts) at the tolerance's granularity — approximate (~) values are fine (see §4.4 Where the numbers come from). Never edit or delete earlier lines — a fix is recorded by appending a new line for the same id. The last line for an id is its current state. |
Appended live during Phase 4 verification. |
out/report.md |
Close-out: what shipped, deployed URL, deferred list with reasons. | Skeleton written in Phase 3 (carries a V2C_REPORT_SKELETON marker); filled as the last act of the task (marker removed). |
The task is complete only when every [S#]/[D#] id in out/plan.md has a final pass or defer line in out/verify.jsonl and out/report.md exists. A defer is only legitimate after the check was actually attempted to its cap (see §4.3) — never defer an effect you never tried to verify.
Exact line formats — copy these literally
The close-out audit reads these two files by literal field name and literal line shape. A field renamed to a synonym, a value outside the allowed set, or an id moved off the start of its line all read as "never planned / never verified": the work is done and scores zero, with no warning and no tolerance layer. Getting these literals right costs nothing; getting them wrong voids the run.
out/plan.md — every tagged concern begins its line with the bracketed id and ends with a {core}/{detail} weight; lines covering a moving region carry a {render}/{footage} source tag too:
[S4] Card grid: 3 columns at 1440px, 24px gutters, card 384×512 (still @2.1s) {core}
[D2] Card hover: lifts 6px, shadow blooms, ~180ms ease-out (source clip 5.2–6.0s; testid=card) {detail} {render}
[D5] Hero reel: full-bleed film, hard cuts ~1s apart (source clip 0.0–3.5s; <video autoplay muted loop>; testid=hero-video; asset=/assets/hero.mp4) {core} {footage}
The id is the first thing on the line, in square brackets. Not a table row (| S4 | … |), not a heading (### [S4] …), not S4. or **S4** — those forms are invisible to the audit, which then sees a plan with zero tagged concerns.
Source tags obey the same literalism: exactly {render} or {footage}, and a {footage} line's asset= path must match the file you actually ship. A shipped video the plan never declared, or an asset= on a {render} line, is a close-out gap — the audit reconciles the two lists.
out/verify.jsonl — one JSON object per line; the status field is named exactly result, and its value is exactly one of pass / fail / defer:
{"id": "S4", "result": "pass", "evidence": "out/cmp/S4_grid.png", "diffs": [{"what": "gutter reads 26px vs source 24px", "severity": "minor", "disposition": "negligible: within the 1.5× gap tolerance"}], "measured": {"columns": "3 both", "card_w": "src 384 / rep 386", "gutter": "src 24 / rep 26"}, "reason": ""}
{"id": "D2", "result": "fail", "evidence": "out/cmp/D2_hover.png", "measured": {"lift": "src ~6px / rep 0px", "duration": "src ~180ms / rep n/a"}, "reason": "hover handler never fires — lift measures 0px at every beat"}
- The key is
result. Notverdict, notstatus, notoutcome. Verdict is this document's English word for the judgment you reach; the JSON key that records it isresult. Extra keys of your own (kind,tier,method, …) are harmless — a missing or renamedresultis fatal. - The value is
pass,fail, ordefer. Nothing else. Notpartial, notn/a, notok, notskipped. An id you could not settle is adefercarrying areason(and only after §4.3's cap has actually been spent) — never a fourth state you invent for it. idmatches the plan's bracketed id exactly —"S4", not"S4-anchorB"or"S4 grid". Need to record several judgments against one id? Append several lines with the sameid; the last one is its state.
Evidence tools
Two MCP tools make and SHOW you evidence images in one round (inline in the tool result — no follow-up Read); parameter details live in the tool schemas:
still_crops— full-res frames at exact timestamps from the source video or your own recordings, optional crop/scale zoom. Batch every instant you currently want to see into one call.composite_view— same-scale SRC|REP composite, the standard Parity evidence form; crop+scale makes a zoomed regional composite — prefer it over any per-pixel measurement script.
Equivalent CLI scripts (still.py, composite.py under <plugin_root>/skills/video2code/scripts/, plugin root announced at session start; fallback cat .v2c/plugin_root) remain for Bash pipelines, but a script run costs an extra Read round — default to the tools.
Workflow — strict order, nothing collapsible
Replication runs through four phases in this exact order. Complete each before the next — no merging, no reordering, no skipping.
〈ingest the video〉 → 〈observation + ambiguities〉 → clip_video the ambiguities and [D] candidates → write out/plan.md → init webapp → build (assets → components) → npm run build → deploy_website → ZCode Browser Use IAB → verify sweep (append out/verify.jsonl) → batch-fix + re-verify → out/report.md
Phase 1 — Observe
Ingest. If the task input already provides pre-extracted, timestamped frames or contact sheets of the video, Read them directly. Otherwise call ingest_video on the video file — one call covers the whole duration at layout-level sampling and inlines timestamped overview sheets (do not substitute a few coarse clip_video segments: that path caps at 180s total and its detail-level sampling wastes budget on an overview). Either way, study the entire video end-to-end before anything else.
Browser chrome in the recording. Some captures (annotation-platform screen recordings) carry the browser's own chrome — a tab/address-bar strip along the top edge. It is not page content: read every layout fact, measurement and framing against the page region below it, and do not reproduce the chrome in the replica.
Then write a brief observation in your response:
## Video observation
<3–6 sentence free-form summary: what kind of page, visual style/mood, core interactions and notable animations, anything that will shape the implementation>
**Ambiguities to clip in Phase 2** (or "- (none)"):
- <thing you couldn't tell from one watch>
- ...
Keep the summary tight — Phase 3 will re-structure layout / tokens / animations in detail. The Ambiguities list is load-bearing: every item drives a clip_video call in Phase 2.
Layout stills — one batched extraction, driven by the sheet. The contact sheet is your index: every cell is timestamped. Pick each major section's settled moment (section fully in place, entrance motion finished) off the sheet, then pull those full-resolution frames in ONE still_crops(video, times=[...]) call — all sections' timestamps in the same call. One settled frame per section is the complete layout evidence — pull an extra offset only for a section-to-section spacing measurement; do not Read frame files one by one or re-pull near-duplicate moments. Measure, don't recall — read off these stills at the visibility baseline's granularity (values your eye resolves on the frame the tool showed you; never a pixel-array computation): the heading's width as a fraction of the viewport; column widths; margin/whitespace rhythm; type scale; each section's complete element inventory (side rails, badges, secondary copy — the pieces most easily lost). For repeated elements (card grids, logo walls, list rows): items fully visible per viewport, item width as a viewport fraction, gaps, stagger rhythm — and per item: aspect ratio, media-area width fraction, corner rounding, any curvature or tilt with its sign (bows toward or away from center; leans which way) — signs are binary facts, write them down explicitly; a remembered impression routinely flips them. For each text role (display heading, section heading, body, caption): serif vs sans, case, italic/outline, letter-spacing — and the display size in absolute px off the frame (to the nearest ~2px, read against the frame — not derived from a glyph bounding box), not by feel. These stills are the evidence the Phase 3 [S] lines must cite. Animated-in content (cards, captions, overlays that fade/slide in): locate the settled frame before you crop — a coarse sheet cell routinely catches these mid-entrance, and guessing tight-crop timestamps against a half-faded element turns one extraction call into three. Spend one call on a short dense series around the appearance (4–6 closely spaced times, full frames), pick the first complete-and-static frame, then take every tight crop at that exact timestamp.
Site tours (multi-page). When the video visits multiple distinct pages (full-viewport transitions / URL-bar changes), build a multi-page site (client-side routing, one route per page). List the pages in the observation; each page gets its own layout in the plan and its own [S]/[D] concerns.
WebGL / 3D pages. While observing, decide whether this is a WebGL page. Signals (any one is enough, judged from what's on screen — not from guessing the tech): true 3D perspective/depth (occlusion changes with viewpoint, perspective zoom) · a particle field (thousands of independently moving points) · volumetric light / glow / fog · a continuous free camera flythrough or orbit (not explainable by a CSS transform) · infinite-detail fractal zoom · a full-bleed animated shader background. If so, say so in the observation, load video2code-3d now (a third Skill call) and scaffold with the default-3d template in Phase 4. A card flip / slight tilt / parallax that a single CSS perspective() covers is not a WebGL page — stay 2D.
Rendered or footage? — classify every moving region. A page can move because it renders (WebGL, canvas, CSS/JS animation, a scroll-driven camera) or because it plays a video file (a hero reel, a project hover preview, a background film). The two demand opposite builds: rendered motion you must write; a <video> element you must ship as a video asset. Hand-drawing a film reel is as wrong as replaying a rendered scene. Decide this per moving region while observing; Phase 3 records the verdict as a {render}/{footage} tag, and the close-out audit checks the shipped assets against it.
Reads as {footage} |
Reads as {render} |
|---|---|
| live-action or pre-rendered film (people, cities, product shots) | particle fields, geometry, shader patterns |
| hard cuts, an editor's rhythm (tunnel → skyline → arch) | continuous camera move, no cut points |
| fixed-length loop with a visible seam | motion that never repeats |
| no coupling at all to scroll or pointer — it runs on its own clock | scroll drives the camera or the progress |
| play/mute chrome | frame rate wobbles under load; vector-crisp at the page's DPR |
Three rules settle it:
{render}is the default, and doubt resolves to{render}— the same shape as the[S]/[D]test below.{footage}is the cheap answer, so it needs positive evidence, not merely the absence of contrary evidence.- Interaction that changes the content of the picture — not merely its playback position — forces
{render}, no exceptions. Drag a handle and watch the scene's glow spread and its background hue flip: that is a running program, whatever it looks like. A<video>cannot do it. - A page you just called WebGL is
{render}across its whole scene layer. There is no footage exception for a rendered scene, however photoreal (seevideo2code-3d).
Phase 2 — Clip ambiguous moments
Call clip_video with the exact ranges. Batch ALL the windows you currently want into one call — back-to-back calls with 1–2 segments each return the same information for extra rounds; split only when the caps force it. Limits: ≤8 segments, ≤60s each, ≤180s total. Keep a window ledger: track the ranges you have already clipped; before each new call, subtract covered ranges — new windows should target uncovered time or a tighter micro-window inside a covered range (the motion-ROI crop zooms in harder on a short window; that is the only reason to re-clip). The tool annotates overlaps with what a previous call already covered. Prioritize: animation > motion-/scroll-coupled effects > hover states > one-off transitions.
Clip triggers — two independent obligations:
- Ambiguity-driven: clip at least one segment per Phase 1 ambiguity. An ambiguity list of
- (none)skips only this obligation. [D]-driven (not skippable): every effect you expect to tag[D]in Phase 3 must have a source clip. You need it twice — as the Phase 3 evidence rule (a clip showing the effect mid-progress forbids an[S]tag) and as the Phase 4 side-by-side comparison against your own recording. "I have no ambiguities" does not excuse skipping these: confidence is not evidence.
You may only reach Phase 3 with zero clips when the page has no motion, scroll-coupling, or hover/click animation at all.
Evidence-escalation cap (Phases 1–2): chasing one detail in the source video is capped at three inspection attempts total — crop/zoom stills, a clip_video segment, and any analysis script all count against the same cap. Still unresolved? Write it into the Ambiguities list with your best hypothesis and move on — Phase 4 verifies against your own deploy at full resolution, which usually settles it for free; blind-guessing crop coordinates over and over on a ~2px detail is the signature failure here. The visibility baseline governs observation, not just verification: a value you can only establish with a measurement script is a value no viewer can see — it needs no number, no plan constant, and no further probe. When eyeball reads keep disagreeing at ≤3× zoom, that instability IS the answer: write the mechanism with a confidence marker (Phase 3) and stop measuring. Rephrasing the question ("now I'm checking the cursor, not the color") does not reset the cap — attempts count against the underlying detail, whatever the probe is named. Negative findings generalize: once two representative elements of a family show no entrance animation / no fade-in, extend that conclusion to the whole family.
Determine each effect's DRIVER by tracking the visible cursor (the recording shows a visible cursor marker):
- deformation/response follows the cursor path (angle/offset varies with cursor position) → pointer-driven (
mousemove), even if the effect also runs ambiently — some pages layer both; when ambiguous, implement both layers; - changes only during a drag → drag-gated;
- motion with the cursor parked or absent → time-driven autoplay;
- motion locked to scroll position (advances and reverses with scroll) → scroll-driven. The driver decides both the implementation and how Phase 4 must trigger the recording; getting it wrong burns the whole 3-attempt recording budget on a mechanism that can't fire.
Read the frames, then write the motion spec yourself. For every clipped effect, derive its spec from the frames alone before implementing: which element, which transform (translate / scale / rotate — and around which axis / 3D perspective flip / curvature bend / parallax), start and end states, approximate duration and easing. Frame timestamps give you the duration; the intermediate frames give you the transform type. Task-provided hints about motion are coarse pointers at best — the frames are the only ground truth.
Phase 1+2 round budget: for a 30–60s video, observation + clipping should land within ~15 rounds total. Past that, stop investigating — write what's left into the Ambiguities list with your best hypothesis and move on: Phase 4 verifies against your own deploy at full resolution, where those questions settle for free.
Phase 3 — Write out/plan.md
Spell out everything you intend to build, then Write it to out/plan.md. This file is the design baseline that Phase 4 verification will hold the replica against. Tag every observable concern with [S<#>] (static) or [D<#>] (dynamic):
# Plan — <site>
## Layout strategy
[S1] Three-column grid at 1440px; heading spans 0.42 of viewport width (still @12.0s) {core}
...
## Component breakdown (untagged — architectural)
- ...
## Design tokens
[S2] Colors: primary #FACC15, bg #1e293b, ... (still @3.5s) {core}
...
## Interactions & animations
[S3] Trash icon → red on hover (instant — the end state is the whole story) {detail}
[D1] Modal entrance: 250ms fade+scale (source clip 8.2–10.0s; framer-motion; testid=modal-panel) {detail} {render}
[D2] Gallery: left heading pins while images scroll under (source clip 18.3–20.0s; CSS sticky; testid=gallery-pin) {core} {render}
...
Tag rules:
- The bracketed id opens the line, literally (see Exact line formats). Prose lines, bullets and sub-sections around them are free-form — but a tagged concern that starts with anything else (a table cell, a
###heading,S4.) is invisible to the close-out audit, which then reads the plan as having no tagged concerns at all. - Visibility is the only test for whether to tag. If it shows up on screen, tag it — regardless of what drives it (
useState, props, CSS, a library). Skip a line only when it produces no visible result. "It's just transientuseState" is not grounds to skip a visible state-change. - Coverage — close the loop. Every interaction/animation named in the Phase 1 observation, and every moment clipped in Phase 2, must map to at least one tagged line. Silently dropping an effect — no tag, no verify entry, no check — is the same failure as misclassifying it, and harder to catch.
- Number
[S<#>]and[D<#>]as two independent sequences, continuous, no gaps, no duplicates. - Weight-tag every line at plan time: end each
[S#]/[D#]line with{core}or{detail}.{core}— structure-critical: the page-level layout skeleton and scroll→chapter mapping; any entity whose share of the frame reaches ~15–20%+ at ANY anchor state (judge by its maximum across states); and attention anchors regardless of area — the brand mark, display-level headings, any element the video dwells on or zooms into. A core entity's structure, look, and signature motion are all core concerns.{detail}— every other observable concern (smaller subjects, visible details). The tag is fixed at plan time and decides the fix budget in §4.3. Deltas invisible at ≤3× zoom carry NO weight tag — they sit below the visibility baseline: zero fix rounds, no scripted probing, and no plan constant of their own. The plan's entity list is a build guide, not a bound on verification: the §4.2 difference scan judges whatever is on the composite, listed or not. - Every
[D]line also names thedata-testidits element will carry (testid=...). Phase 4 builds the attribute straight from the plan and verification selects by it — naming it once here is what prevents guessed selectors, grep hunts for your own naming, and wasted recordings later. - Source-tag every line that covers a moving region: add
{render}or{footage}next to the weight tag (... {core} {render}). This is the Phase-1 rendered-or-footage call, written down where the audit can read it.{render}— the source generates these pixels at run time, so your code must generate them too: no mp4/webm/gif/APNG/animated-WebP asset, no numbered frame sequence, no blitting source frames into a canvas. All the same thing; all barred.{footage}— the source page is itself playing a video file, so shipping one is the faithful build. A{footage}line must carry two things or it does not count: the source clip range that shows the footage signals, andasset=/assets/<name>.mp4naming the file you will ship. Untagged lines are read as{render}; doubt resolves to{render}. Static lines over still regions need no source tag. - Every
[S]line carries the measured numbers read off the Phase 1 stills; every[D]line carries the source-measured numbers from the Phase 2 clip (duration and amplitude always; direction/axis/origin, stagger order and interval, easing character where the effect has them) plus the clip time range and the implementation approach. A line written from memory is a guess, not a design decision. Every number here must be one you read, not one you computed — a constant distilled by scripting the source frame (ink bbox, stroke thickness, per-character advance, channel delta) is barred by the visibility baseline even when it is arithmetically correct: it becomes a Phase-4 target no composite can settle, so the id can then only be closed by re-running the same script. When the source measurement itself is low-confidence (compression noise, sub-pixel amplitude, ambiguous readings), write the mechanism — what couples to what, in which direction — with a confidence marker, not a derived constant: a precise number distilled from weak data becomes a false verification target in Phase 4.
Static vs Dynamic — a verification question, not an implementation question.
The only test: Can you name a single screenshot — one frozen frame, at a state you can navigate to and hold still — that on its own proves this line is correct?
- Yes →
[S]. The truth is a fixed configuration; one still frame is ground truth.- No →
[D]. The truth lives in how two things change together as a continuous input advances — elapsed time, scroll position, or pointer/drag position. No single frame can prove a coupling.
Worked examples:
- Final layout, colours, typography, an instant modal pop, a hover state you can hold open →
[S]. - 300ms scale+fade modal entrance →
[D](the truth is the timing/easing between frames). - Sticky header pinning; parallax; scroll-snap; scroll-progress reveal; drag-coupled carousel →
[D], even though every one of these is plain CSS.
Tie-break — default toward rigor: if you cannot name the one proving screenshot, it is [D]. "It's only CSS", "there's no easing", "it has a trigger so it must be simple" are not reasons to call something [S].
Evidence rule: if a Phase 2 clip shows the effect mid-progress across two or more frames, it cannot be [S]. Reconcile the tag with what the clip actually shows — don't assert "static" against your own footage.
out/plan.md must exist before any component code is written. If you find yourself editing src/ without the plan file on disk, stop and write it first.
Write the out/report.md skeleton in the same turn as the plan — issue both Write calls in one message, the plan first. The deliverable then exists from the very start; a run cut short later leaves a fileable report on disk instead of nothing. The skeleton is a placeholder carrying a marker you remove only when you finalize it at close-out:
<!-- V2C_REPORT_SKELETON -->
# Report
**Shipped:** (tbd)
**Deployed URL:** (tbd)
**Deferred (id → reason):** (tbd)
Grading treats a report still carrying the V2C_REPORT_SKELETON marker as unfilled (no credit). At close-out you replace the placeholders with the real content and delete the marker line.
Phase 4 — Build, deploy, verify
4.1 Build
- Initialize the project per
web-replicate(init script → project atapp/). WebGL/3D page (per Phase 1): scaffold with thedefault-3dtemplate (init-webapp <title> default-3d) and followvideo2code-3dfor the scene, recipes, software-render budget, and 3D verify rules. - Assemble before polishing: first put up every section as a placeholder component wired into a page that builds and deploys — coverage first — then flesh out one component/effect system at a time. Never leave a section unstubbed while polishing another. Cap pre-verify polish: before the first
out/verify.jsonlline lands, do not iterate the same visual detail more than twice — get coverage and run the first sweep, then fix against evidence. Polishing a detail you have not yet judged against the source burns rounds blind; this is a separate, earlier failure from the post-sweep tiered fix-round regime (§4.3), which only starts once verification is underway. Batch the writes: independent new files (components, hooks, SVG assets) go as parallelWritecalls, 3–4 per turn; collect every planned change to one file into a singleEditper turn — one hunk per round doubles the build's round count (the build is the sync point, not each file). - Ship image assets and real copy as you build each component (see Image assets below) — wire real
/assets/...paths before writing the JSX that references them, and read the real text (headings, body copy, captions, button labels) off the frames; never lorem ipsum. - While implementing interactive/animated elements, add stable
data-testidattributes — they are the selectors verification will need. Give every text-less interactive control (icon buttons, arrow buttons, dot navs) anaria-labeltoo: the browser element list shows only tag+text, so unlabeled buttons all read asbutton: ''and cannot be told apart when you need to click them during your own verification. npm run build→deploy_website(local_dir="app/dist", type="static")→ **first deploy only:**按 ZCode 官方control-browserskill 用 IAB 打开 URL,等待domcontentloaded,再用domSnapshot()/tab.screenshot()确认可达与视觉状态。- Any
src/edit invalidates the current build: edit(s) 和deploy_website可在同一轮完成;部署保留相同 URL。随后恢复同一个 IAB tab 并reload(),等待具体页面状态或tab.playwright.waitForTimeout(settleMs),再截图/录像。部署工具不再持有浏览器,也不返回截图。Never capture, record, or judge a build older than your latest edit. 长加载动画可放进tab.recording.start({settleMs,...}),其最终 WebM 再由still_crops(recording,[t_end])取稳定帧。
4.2 Verify — sweep first, fix in batches
Adversarial mindset: you are trying to prove the replica wrong. Hunt specifically for blank/flat sections where the source has content or atmosphere, and for the signature effects most likely to have shipped broken (3D / perspective / parallax / scroll-pinning). Sweep [D] ids in that risk order; simple fades last.
The verdict instrument is a difference scan on the image — not a checklist fill-in. A composite is judged by finding differences, the way a viewer plays find-the-differences. "Does it match what I planned?" is the wrong question and produces blind verdicts. For every SRC|REP artifact, before any verdict on the ids it covers:
- Scan the whole composite, block by block — every distinct visual block on either side (subjects, background layers, patterns/textures, shadows/reflections, cards/text, overlays). List every visible difference, ranked by prominence at 1×. Differences of shape and structure — how many of something, what geometry, what arrangement, regular vs irregular, crisp vs diffuse — weigh exactly as much as dimensions and colors.
- Full-frame anchor composites: name at least 3 candidate differences before any verdict. You may argue a candidate negligible afterwards; you may not skip the naming. Genuinely fewer than 3? Then say per block why the two sides are indistinguishable — when they truly are, that justification is easy; the floor only bites when you are about to gloss. Regional/zoomed composites: the scan still comes first, but may return fewer (or zero, with a one-line why).
- Pixels are the only admissible source. You know what you built, and the source may print its own labels/readouts on screen; neither is evidence. Matching config values, mode numbers, or intent does not make two sides look alike — and your builder's knowledge actively pulls you toward seeing them as alike; treat it as interference, not information. In one audited run all four chapters' particle figures passed as "(m,n) … both", read off on-screen mode readouts, while the composites showed a sparse irregular figure on the SRC side and a regular ring-and-spoke web on the REP side — four false passes from one shortcut.
- The scan lands in the ledger. Rows judged from a composite carry a
diffsarray:{"what": "<one line>", "severity": "prominent"|"minor", "disposition": "fix"|"defer"|"negligible: <why>"}.passis legal only while nothing is left at"fix". A prominent difference needs no pre-existing plan row to be actionable — it rides the (anchor) id it surfaced under into the §4.3 fix loop or an honest defer; "the plan never listed it" is how it got missed, not a reason to skip it. The diff lists are themselves evidence: scans that come back empty near close-out, after rounds of non-empty scans, read as fatigue, not fidelity.
The first capture pass IS the sweep. Judge and ledger from the very first artifacts captured after deploy — including the fails. Do not take an unledgered "reconnaissance" lap (capture every section, eyeball it, fix by impression, then re-capture everything for the formal sweep): that acquires the same evidence twice for one verdict. A capture round that produces no verify line for any id should make you stop and judge what you already hold.
Sweep — evidence by artifact, judgment by id, fixes deferred:
- Acquire evidence efficiently up front — composites batched, parallel
Reads, recordings of unrelated regions in one turn. The evidence unit is the artifact; one artifact usually covers several ids. - Judge: run the difference scan (above) over the artifact, then hold each covered id's plan numbers against it per the Parity standard (§4.4).
- Record one line per id for
out/verify.jsonl—{"id": …, "result": "pass"|"fail", …}in the literal schema above — carrying the scan'sdiffs(composite-judged ids) andmeasuredsource→replica pairs (forfail, the measured delta inreason). Do NOT edit source code yet. Never stamp several ids off evidence you haven't examined per-id.
Append at the moment of judgment. The bookkeeping unit is the evidence artifact: the moment you finish judging an artifact's ids, append those lines in one cat >> out/verify.jsonl call, and let that bookkeeping ride in the same turn as the next acquisition (append calls first, then the next capture call — pairing them halves the sweep's round count). A fail is written when it is discovered, never reconstructed later; do NOT hold judged lines for an end-of-run ledger dump — the file's append timeline is part of the evidence. One artifact usually settles several ids; write them together. Never write a line for a check you haven't actually run.
Chaining the audit onto an append: use --progress. Running the contract audit in the same Bash call as an append (cat >> … <<EOF … EOF then python3 …/contract_audit.py) is good round economy — keep doing it, but pass --progress. The shell returns the last command's exit code, so a bare audit that still has gaps (normally just "report.md is still the skeleton", which is the expected state until close-out) makes the whole call come back as a tool error even though your append landed fine. --progress prints the identical gap list and exits 0, so a successful append reads as successful. Drop the flag only for the final self-audit in §4.6, where a non-zero exit is the point.
Discipline (each a hard rule):
- One id, one line, one examined verdict. Every line cites the artifact it was actually judged from — ids covered by one artifact are judged one by one against it, never stamped in a batch.
- No
passwithout a tool result that actually shows the effect. Scrolling past the target, or two identical captures, proves nothing. - No
passwithout source-paired evidence. For layout[S]ids: the same-scale SRC|REP composite (§4.4). For[D]ids: the recording proves the motion happened, but the closing line must cite a matched-beat SRC|REP artifact — source-clip frame vs recording frame at the same beats, one composite strip (recording path as supporting note). Build it in ONE call:composite_viewaccepts video paths on both sides —composite_view(source=<Phase-2 clip>, replica=<your recording>, beats=[[t_src, t_rep], ...], out_path="out/cmp/<ID>_....png")extracts and pairs the beats itself; no per-sidestill_cropsneeded. An artifact showing only the replica is a working shot: it can find bugs, it can never close an id — you would be certifying against your memory of the source, and memory certifies "a plate with patterns", not this plate. Save paired artifacts underout/cmp/named after the id(s) they certify (out/cmp/D3_hover.png; a shared artifact carries every id it covers,out/cmp/D4_D12_reveal.png) — the close-out audit keys on both the path and the name.measuredcarries both sides as values read off the artifact; a bare"match"is an impression, not a measurement — and a shared label or config value written once for both sides ("(3,5) both") is a label, not a read; for visually-judged classes the verdict is "indistinguishable on the composite", citing the composite. Theevidencefield is a bare artifact path (a shared artifact: its one merged-name path) — viewport/beat annotations and+-joined path lists belong inmeasured/reason, never inevidence; the close-out audit parses that field as a path. - A
fail→passflip requires fresh evidence from THIS fix round — the newpassline must cite a new capture, not the pre-fix one. - One capture per clean id. Re-shooting a region you already judged adds zero evidence.
After the sweep: batch-edit files for all failed ids and append one deploy_website(...) in the same turn (auto-build, stable URL). Then recover the same IAB tab, reload once, and capture fresh evidence with Browser Use. Re-verify only the failed ids plus any passed id the fix plausibly touched (same file/section); give the fresh capture one glance for collateral regressions and stop — every other passed id stays passed, its evidence line stays valid. Exception — global-system edits. A fix that touches a shared visual system (layout skeleton / scroll mapping / camera / lighting / post-processing / a shared sim or animation core) invalidates evidence page-wide, not per-section. After that fix batch deploys, re-shoot the anchor set — the capture views your current evidence lines cite — in one batched capture round, build a fresh full-frame SRC|REP composite per anchor, and re-judge every anchor id through the difference scan (the 3-candidate floor applies), appending a fresh line per id. No anchor keeps its old line on a glance — "looks unchanged" is not a verdict.
4.3 Caps
- Fix budget follows the plan row's weight tag (Phase 3).
{detail}: ≤2 fix rounds, then adeferline with the measured delta ("reason": "duration 2.1× source after 2 fix rounds").{core}: ≤4 fix rounds — a structure-critical miss is worth twic
…(truncated)