# REPORT — 03-flat-vector · “Popwise — 灵感，即刻发生”

## Concept (two sentences)
A one-continuous-shot flat-vector explainer: a single sunflower circle is already mid wind-up on
the very first frame, then squashes, stretches and falls out of an empty ultramarine field,
becomes the rising sun, and a city pops up around it; the camera pushes into one wide office
window where a geometric character at a desk is interrupted by a phone notification. That
notification bubble expands into the whole frame as a shape wipe, four feature cards burst in on
a 120 BPM rhythm and then keep breathing while the music holds, and everything collapses into the
“Popwise” logo lockup — so the circle from the first frame returns as the mark, bookending the film.

## Beat sheet (cue sheet — picture *and* sound read from these numbers)
| time | beat |
|---|---|
| 0.00–0.24 | HOOK. The circle is **already in frame on frame 0** (the film never opens on an empty field); it is mid wind-up — lifts 26 px, squashes to 0.925 wide / stretches to 1.11 tall. |
| 0.24–0.62 | Fall under gravity from the lifted position, stretching ~20 % vertically on the way down. |
| 0.62–1.35 | IMPACT: squash to 0.80 / stretch to 1.18, expanding impact ring, then a damped elastic settle that is **still ringing at 1.1 s** (deliberately, so the ball never freezes before the sun rise). |
| 0.90–1.52 | The circle becomes the SUN — rises up-left, shrinks 132→104, 8 rotating ray ticks fade in. |
| 1.12–1.42 | Ground band rises from the bottom of frame. |
| 1.42–2.29 | CITY: 7 buildings pop up from the ground, staggered 0.085 s (~2.5 frames), each with its own overshoot + counter-squash; their windows pop 2–4 frames later, individually staggered. |
| 1.50–2.50 | Clouds slide right→left and wrap; 3 birds flap across the sky. |
| 2.85–3.00 | Anticipation — the camera pulls *back* to 0.975× before the push. |
| 3.00–3.60 | CAMERA PUSH: zoom about the window centre, 1× → 5× (inOut); the window's corner radius animates to 0. |
| 3.60–4.30 | INSIDE. Character types (two-bone arm oscillates), head bobs, breathes, blinks at 3.90. |
| 4.35 | PHONE PING — screen lights mint, a coral notification bubble pops above it. |
| 4.45–4.95 | Character reacts: head tilts toward the notification, the front hand lifts. |
| 5.28–5.62 | Anticipation for the wipe — both hands come up. |
| 5.50–5.80 | SHAPE WIPE: the notification bubble expands from the phone out to the full frame, decelerating (easeOutQuart) so the coral field only covers the frame on the last few frames, becoming the coral field. |
| 5.68–6.12 | Headline 「让灵感，随时在线。」 slides up with elastic — it starts **while** the wipe is still landing, so there is no flat coral plateau between the two beats. |
| 5.80–6.71 | Four cream feature cards pop in from below, staggered 0.13 s (~4 frames). |
| 6.35–7.20 | ICONS burst in a choreographed rhythm: bar chart grows (3 bars, 0.08 s stagger) → check circle pops and the check draws on → heart pops and beats → coin pops and starts spinning. |
| **7.20–8.00** | **LIVE HOLD (see below).** The choreography resolves but nothing stops: cards float/breathe/rock out of phase, the bars keep pumping, the check has a second rebound then breathes, the heart beats a continuous double-thump every 1.15 s, the coin keeps spinning, the headline drifts and its tracking pushes, and four flat light-spots drift behind everything. |
| 8.00–8.45 | Cards collapse inward to the logo centre, rotating, with motion blur. |
| 8.16–8.62 | The coral field itself collapses into the logo mark (shape wipe), revealing ultramarine. |
| 8.42 | Burst: mint ring expands and fades. |
| 8.55–9.05 | Logo mark pops (elastic settle). |
| 8.74 | Mint notification badge pops on the mark's corner. |
| 8.80–9.20 | Wordmark “Popwise” — each letter rises with a 2-frame stagger. |
| 9.15–9.65 | Tagline 「灵感，即刻发生。」 slides in. |
| 9.30–10.00 | Hold with living micro-motion: the whole lockup (squircle + mark + badge) bobs as one body, badge pulses, wordmark breathes, tagline drifts, shimmer sweep, drifting confetti, film grain. |

## Rhythm check — no dead spans (measured with harness/rhythm.mjs, not asserted)
`node harness/rhythm.mjs out/03-flat-vector/video.mp4 0.5` decodes the **encoded deliverable** to
240×135 grey and reports, per 0.5 s window, the mean adjacent-frame difference and **moved%** — the
percentage of pixels that change by more than 6 grey levels (a window under ~0.25 % moved is
flagged DEAD). The tail of the current table:

| window | meanΔ | minΔ | moved% | flag |
|---|---|---|---|---|
| 6.5–7.0 s | 0.737 | 0.356 | 1.83 % | |
| 7.0–7.5 s | 0.429 | 0.279 | 0.76 % | (calm) |
| 7.5–8.0 s | 0.366 | 0.268 | 0.63 % | (calm) |
| 8.0–8.5 s | 7.800 | 1.118 | 14.90 % | |
| 8.5–9.0 s | 0.883 | 0.177 | 2.26 % | |
| 9.0–9.5 s | 0.522 | 0.287 | 1.34 % | (calm) |
| **9.5–10.0 s** | **0.220** | **0.137** | **1.17 %** | (calm) |

**DEAD SPANS: none.** No stuck-frame warning. Whole film: overall mean Δ 3.374, **global min Δ 0.064**
(@ f156, t=5.20 s — the quietest single frame pair in the film, still non-zero).

Two earlier defects were found by these measurements and fixed:

1. **The 9.5–10.0 s end hold used to move only 0.17 % of pixels** — an order of magnitude quieter
   than anything else in the film, and effectively a freeze; the coordinator flagged it as DEAD. The
   lockup micro-motion was raised until it is measurable: the whole lockup (squircle + mark + badge)
   bobs/sways as one body, the badge pulses, the wordmark breathes, the tagline drifts, 12 confetti
   drift on two out-of-phase sine paths, and a narrow constant-velocity cream shine sweeps the
   wordmark. Pure bob/sway/confetti averaged 0.65 % but dipped to 0.17 % at its motion apexes; the
   constant-velocity shine supplies the floor that keeps every frame alive. **Result: 0.17 % → 1.17 %**
   — comparable to the 7–8 s card hold (0.63–0.76 %) and far above the DEAD threshold, while the hold
   is still the calmest passage in the film.
2. **f170/f171 (t≈5.70 s) were nearly identical** (meanΔ 0.017, moved 0.00 %), reported as “a stuck
   frame?”. Root cause was **not** a double-drawn frame — it was a timing gap: the coral shape wipe
   reached full coverage while the field was still *accelerating* (linear/ease-in growth covers the
   frame early), then sat on a flat coral plateau for ~1.6 frames before the headline entered at
   5.72. Fixed by growing the field with `easeOutQuart` (decelerating, so coverage happens at the
   *end* of the wipe), extending the wipe to `T.expand = [5.50, 5.80]`, and starting the headline at
   5.68 and the light-spot drift at 5.62 so the next beat is already moving when the wipe lands.
   **Result: f170→f171 now meanΔ 5.441, moved 6.56 %.** The audio bogie moved to 5.73 to match.

The card hold (6.5–8.0 s) moves ~1.8 % of pixels per frame — well above the interior typing passage
it replaced and above the DEAD floor — i.e. it is a live hold, not a freeze. A third, smaller stall
was found the same way in the first pass: the ball froze at t≈0.95 because the landing settle had
decayed before the inOut sun-rise had picked up speed (fixed by letting the settle ring to 1.35 s).

## How each signature feature is realised
- **纯色大色块 / 无渐变 / 高饱和品牌色板.** Six colours only — ultramarine #2B2BFF, coral #FF5A4E,
  sunflower #FFC62B, mint #2EE6A8, cream #FFF6E9, ink #151433. There is not one
  createLinearGradient in the file; every fill is a flat colour, including the slow background
  drift (four plain discs of a slightly lighter/darker coral). The only texture is a very subtle
  (alpha 0.045) tiled film grain so flat digital fields don't look dead.
- **几何化角色 + 锚点旋转 rig.** The office character's arms are a real **two-bone rig**: a shoulder
  anchor, two segment lengths and a target; the elbow is solved in closed form (twoBone()), so the
  arm rotates about the shoulder and folds naturally instead of being a stretched rectangle. It also
  has head bob, breathing scale, head tilt, a blink timer and a second (far) arm drawn behind the
  torso for depth.
- **弹性缓动是灵魂 (overshoot).** Two curves do most of the work: the canonical
  cubic-bezier(0.34, 1.56, 0.64, 1) (EASE.pop) for every scale/position entrance, and a hand-built
  damped settle 0.20·e^(−3.6u)·cos(4.2·pi·u) for the ball's landing. The hook uses a strict
  **three-phase entrance**: anticipation lift + stretch → stretched fall → squash, rebound,
  overshoot, settle.
- **元素错峰入场 (stagger 2–4 帧).** Buildings 0.085 s (2.5 f), building windows 0.035 s within a
  building, feature cards 0.13 s (4 f), icon sub-parts 0.08 s (2.4 f), wordmark letters 0.066 s (2 f).
  The card idle is staggered too, so no two cards are ever in the same phase.
- **同一时刻只有一个主体动.** Each beat has exactly one hero: the ball → the city rising → the
  character → the cards → the lockup. Secondary motion (sun rays, clouds, birds, blinks, the card
  idle) is deliberately sub-threshold so it never competes — the whole point of the 7.2–8.0 s idle
  is that it reads as *alive*, not as *busy*.
- **squash & stretch ~10 %.** Ball: 20 % vertical stretch in the fall, then 0.80 squash / 1.18
  stretch at impact. Buildings counter-squash 5 % horizontally as they rise.
- **元素飞出带动转场 (not a crossfade).** Both transitions are element-driven: the phone's
  notification bubble grows into the full-frame coral field, and later that same coral field
  collapses into the logo mark while the cards fly into it. Nothing ever cross-dissolves.
- **30 fps, motion blur on fast moves.** Rendered at 30 fps; the fast card collapse is drawn with
  3 motion-blur samples along its own delta.

## Technical route
- index.html sets up a 1920×1080 canvas and exposes window.renderAt(t) / window.__ready per the
  harness contract. All film logic lives in parts/film.mjs.
- **Canvas 2D, closed-form per t.** No state, no requestAnimationFrame loop, no Date.now(), no
  Math.random() (only seeded mulberry32). Every frame is recomputed from t; the renderer may ask
  for frames in any order.
- **Camera** is a zoom about the window's world centre (920, 498): screen(p) = C + (p − WC)·s with
  s: 1 → 5. The interior is drawn inside the window's on-screen rect, so at s = 5 the window *is*
  the frame and the city→interior transition is seamless. The whole city is skipped once the window
  covers the frame.
- **Grain** is a 512×512 seeded tile blitted with drawImage + globalCompositeOperation 'overlay'.
  (`shared/ease.mjs` now ships the same tiled approach; this demo keeps its own instance, which is
  equivalent.)
- **Sound** is audio.py → shared/audio.py (stdlib only), 48 kHz stereo, 120 BPM, all hits on the cue
  sheet; the renderer then masters it with loudnorm to −14 LUFS.
- Render: node harness/render.mjs demos/03-flat-vector — ~0.24 s/frame, ~1.2 min total.

## Check numbers (from out/03-flat-vector/qa.json)
| check | required | measured |
|---|---|---|
| duration | 10.000 s | **10.000 s** |
| resolution | 1920×1080 | **1920×1080** |
| fps | 30 | **30** (300 frames) |
| video codec | H.264 MP4 | **h264, yuv420p, crf 15** |
| audio | stereo AAC 48 kHz | **aac, 48000 Hz, 2 ch, 192 kbps** |
| integrated loudness | ~ −14 LUFS | **−13.7 LUFS** (LRA 1.2) |
| true peak | ≤ −1 dBTP | **−2.8 dBTP** |
| black frames | 0 | **0** |
| frozen frames | — | **0 / 298** |
| console errors | 0 | **0** |

File: out/03-flat-vector/video.mp4, 8.3 MB (6.6 Mbps).

## Honest top-3 weaknesses
1. **The character is stylised-abstract and its acting is thin.** It reads as a clean geometric
   figure (correct for the style) but its performance is limited to typing, a head tilt, a blink and
   one raised hand — no real facial range, and the hair crescent is a simple shape rather than a
   designed silhouette. A top-tier explainer character would have more authored posing and a
   stronger side profile.
2. **The camera work is a single zoom.** “One continuous shot” is promised and the push into the
   window is clean, but there is no lateral dolly, parallax or foreground pass, and no true
   zoom-blur on the push — so it feels flatter than the Buck/ManvsMachine benchmark. A small
   lateral drift during the city build, or a foreground element passing the lens, would sell the
   shot much harder.
3. **Motion blur is sparse.** Only the card collapse is blurred; the two fastest moves — the
   notification wipe and the ball's fall — are hard-edged. At 30 fps these read as discrete steps.
   It is most visible if you step the wipe frame by frame.

## Self-assessment (1–10; 6 = template, 8 = high-end agency, 9 = award shortlist)
| dimension | score | note |
|---|---|---|
| Style fidelity | **9** | Instantly nameable as flat/vector explainer; every listed signature technique is present and correctly executed. |
| Concept / wow | **8** | Clean, charming, and the sun→logo callback gives it a real idea; but it is a canonical form, not a new one. |
| Motion craft | **9** | Easing, stagger, anticipation, follow-through, squash, and a hold that is now *measured* alive (every 0.5 s window moves ≥0.62 % of pixels, no dead span) rather than asserted; the camera and blur are still the weaker half. |
| Design & typography | **8.5** | Strong palette and grid, disciplined negative space, well-set CJK with correct full-width punctuation; a little airy in the card scene. |
| Finish & texture | **9** | Verified: no dead spans, no frozen frames, 0 black frames, no banding, no tofu, no clipping; the live end hold is the finishing touch. |
| Sound & sync | **8** | Genre-correct 120 BPM pop with every entry hit on the beat; the mix is straightforward (no sidechain pump, simple pad). |
| Technical | **9** | Fully deterministic, console-clean, exactly on spec, 0.24 s/frame, 0 black frames. |
