# Drifting field / double underline

One existing treatment: `study-keyword-double-underline`, retained `review02`.
This is a portability package, not a new mechanism or an aesthetic approval.
The independent critic is separate. This lane has not been published.

The original core, recipe, two content JSONs and eight SVG assets are unchanged.
The first filled underline appears at local frame 13, while the keyword still
has seven characters. The first sweep remains under the opposing return and
the irregular retrace. Those three layers remain visible through frame 59.
The four background images and drafting marks move independently throughout.

## Package contents

- `project/`: exact selected members of `core-publication-v08.zip` and the
  treatment's `assets-001.zip`, including the OFL Geist font and license.
- `input-binding.json`: archive and member SHA-256s; unrelated recipes omitted.
- `controls/`: 60-frame JSON/CSV tables for each content variant and eight
  derived SVG path plates. The tables include masks, opacity, ordering,
  pivots, contain-fit padding and transformed corner coordinates.
- `prepare.mjs`, `prepare/Root.tsx`: actual transparent layer preparation using
  the retained `FontGate`/`MotionLayer`. It does not change the core renderer.
- `RESOLVE.md`: exact layer graph, frame controls, masking and import steps.
- `source-clock.json`, `replay-proof.json`: measured clocks and replay evidence.
- `tests/contract.test.mjs`: preservation, overlap, background, clock and geometry
  regressions. The self-contained command runs the upstream timing/assembly
  tests and local width-contract tests without retrieving unrelated recipes.

No source footage, replay movie, raster plate, dependency tree or private path
is in this ZIP. The separate source excerpt is reference evidence only. The
supplied code, original SVGs and permitted font are the production inputs.

## Reproduce without installing dependencies

Requirements: Node 22.18 or newer with type stripping, FFmpeg/FFprobe, the
existing pinned dependencies in `project/package-lock.json`, and full Chrome.
Actual replay/preparation used Node **24.13.1**, Remotion **4.0.520**, React
**19.2.8**, TypeScript **5.9.3** and full Chrome **152.0.7977.42**.
The retained review02 manifests record an earlier Node runtime; the new movies
still match their movie bytes and every decoded frame.

Bind the browser executable to SHA-256
`9a1fec86405041cdf16c9977d022130c4caf96da6d1a9a20b1767b4ae370e8f6`.
Same-version HeadlessShell is not an interchangeable rasterizer. Supply any
required browser library search path in the local environment, not in this kit.
`prepare.mjs` fails before output-directory creation on missing/mismatched
dependencies or browser. It never downloads a browser.

From the unpacked kit, set these environment variables to already-existing,
approved locations: `FIELDNOTES_NODE_MODULES`, `REMOTION_BROWSER_EXECUTABLE`,
`TMPDIR`, `STORAGE_GUARD`, `WORKER_NAME`, `ENCODER_LOCK`, and `OUT` (a fresh
output directory outside this kit). Do not install/copy dependencies or create
symlinks. `STORAGE_GUARD` is the local bounded-run Python guard; its allocation
must cover code, outputs and transient scratch. The builder used a 150 MB cap
and aborted monitored work at 138 MB, including all shared temporary files.

```bash
set -eu
export KIT="$(pwd)"
: "${FIELDNOTES_NODE_MODULES:?}" "${REMOTION_BROWSER_EXECUTABLE:?}" "${TMPDIR:?}"
: "${STORAGE_GUARD:?}" "${WORKER_NAME:?}" "${ENCODER_LOCK:?}" "${OUT:?}"
node --experimental-strip-types --input-type=module -e \
  'import {prerequisites} from "./prepare.mjs"; console.log(await prerequisites())'
python3 -B "$STORAGE_GUARD" --worker "$WORKER_NAME" --incoming-bytes 60000000
mkdir "$OUT"
(
  flock --exclusive 9
  cd "$KIT/project"
  node --experimental-loader "$KIT/shared-dependencies.mjs" \
    --experimental-strip-types scripts/render.mjs \
    --recipe study-keyword-double-underline --variant default \
    --out "$OUT/original.mp4"
  node --experimental-loader "$KIT/shared-dependencies.mjs" \
    --experimental-strip-types scripts/render.mjs \
    --recipe study-keyword-double-underline --variant adaptation \
    --out "$OUT/adaptation.mp4"
) 9>"$ENCODER_LOCK"
cd "$KIT/project"
node --experimental-strip-types scripts/verify.mjs "$OUT/original.mp4"
node --experimental-strip-types scripts/verify.mjs "$OUT/adaptation.mp4"
```

These are two actual full Remotion compositions, not re-encoded retained movies.
The external resolver only resolves otherwise-missing bare imports against the
existing dependency directory. It does not change webpack settings or the recipe.
Outputs cannot overwrite an existing movie or provenance manifest. The original
renderer limits asset loads to local resources, validates typography and binds
selected input, code and asset files before and after the render.

## Prepare Resolve layers and verify controls

Use the same environment and a new `OUT/resolve-prepared` directory. The PNGs
are generated in the output directory, not added to the editable ZIP.

```bash
cd "$KIT"
python3 -B "$STORAGE_GUARD" --worker "$WORKER_NAME" --incoming-bytes 60000000
(
  flock --exclusive 9
  node --experimental-loader "$KIT/shared-dependencies.mjs" \
    --experimental-strip-types prepare.mjs "$OUT/resolve-prepared"
) 9>"$ENCODER_LOCK"
node --experimental-strip-types verify-preparation.mjs "$OUT/resolve-prepared"
PREPARED_DIRECTORY="$OUT/resolve-prepared" \
node --experimental-strip-types --test tests/contract.test.mjs tests/preparation.test.mjs \
  project/tests/timing.test.mjs \
  project/tests/assembly.test.mjs
node typecheck.mjs
```

Use this test command for the single-example package. The unchanged core's
blanket `npm test` includes catalog/adapter suites requiring omitted unrelated
recipes. The builder also ran the upstream text-fit suite with its one unchanged
archive fixture temporarily supplied, then removed that unrelated fixture.

Preparation has actually produced **36 transparent PNG plates**, 18 per variant:
four material images, four separate path layers, two lead-word states and eight
keyword states. Images are at their native SVG viewport; text uses the retained
118 px/550-weight font, box and line height. Position, opacity, clip and scale
are deliberately removed from each isolated plate and reapplied from the frame
tables. This is layer preparation, not a flattened title animation.

The preparation verifier rejects incorrect/missing browser or font provenance,
duplicate/missing/unexpected PNG inventory paths, wrong dimensions or identity,
PNG byte changes, wrong adapter hashes and declared package-version drift. It
checks the actual bundled font and all 36 RGBA PNG byte hashes. Matching these
unsigned records is not independent runtime attestation or native Resolve proof.

`node --experimental-strip-types controls.mjs NEW_DIRECTORY` regenerates controls
without touching the core. Do not overwrite a reviewed package to change copy.
Make a separately labeled derivative and re-run its contract, render and review.

## Content contract

The preserved inputs are **Build new / pathways** and **Work with / drawings**.
Lead: exactly two words, 8–11 characters, one line, 586×140 px box at (404,475).
Keyword: exactly eight characters, one word, 555×140 px box at (990,475).
Font is Geist, weight 550, size 118 px, line height 1.1, zero extra tracking.
No centering, autofit, shrinking or altered reveal timing is allowed.

The recipe recommends a keyword occupying roughly 510–545 px. That is a
selection guideline, not a falsely claimed automatic minimum-width gate. The
actual browser gate measures copy and rejects overflow beyond its 555 px box
(with the original 0.5 px tolerance). Unsupported copy requires a new treatment
or shorter copy, not an altered underline width. The emphasis intentionally
does not extend under every glyph.

The three ribbons require original/permitted 640×700 transparent SVGs; the
diagonal field requires 2160×1200. `constructionMarks` has viewBox 1920×1080;
the three filled emphasis paths each have viewBox 550×34. Keep their irregular
curves and independent masks; neither a font underline nor a constant-width
write-on stroke is equivalent. Preserve rights declarations and the OFL license.
Do not substitute source equations, film frames, source branding or demo data.

## Evidence boundaries

Source: OpenAI, *Introducing ChatGPT Study Mode*, video `XDYilxy1dn8`, published
July 29, 2025 according to retained official metadata. Source frame range
`[153,213)` at declared 30/1 fps is nominally `[5.1,7.1)` seconds. The source
master is not present here. Its retained 60 PTS starts run 5.100–7.067 seconds
with 33/34 ms steps. The next-frame PTS/final native duration was not remeasured.

The separately retained source excerpt is **960×540**, 60 silent frames, 30 fps
CFR, PTS 0–59 in a 1/30 time base; exclusive endpoint 2 seconds. It is not a
native-resolution or native-PTS master. Both new **1920×1080** replays have 60
silent frames, PTS increments 512 in time base 1/15360 and endpoint 2 seconds.
Maximum relative replay-versus-inherited-ledger difference is **2/3 ms**.

Both replay files are byte-identical to retained review02. Each matches all 60
decoded RGB24 and YUV420P frames; RGB mean/max error and changed pixels are zero.
This is a replay reproducibility result, not a source-fidelity score. Source and
replacement fonts, words, field colors, fold shading and paths differ.

All 60 actual frame positions per movie were inspected as labeled full-frame
sheets, with the emphasis interval additionally inspected in title crops. This
is static all-frame inspection, not continuously perceived motion or a blind win.
Native Resolve is unavailable: the guide and prepared plates are **not a native
Resolve pass**. Its alpha, filtering, color interpretation and curve application
must be checked on a Resolve workstation. No completion/publication credit is
asserted for this lane.
