chore: import upstream snapshot with attribution
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:35 +08:00
commit 85453da49f
4031 changed files with 710987 additions and 0 deletions
@@ -0,0 +1,173 @@
#!/usr/bin/env node
/* check-timing.cjs — verify plan.json word timings vs transcript.json (1:1 port of check-timing.py).
* node check-timing.cjs <project-dir> [--strict]
*/
const path = require("path");
const fs = require("fs");
const DRIFT_TOL = 0.08;
const CREATIVE_SUBS = {
"15%": ["fifteen", "percent"],
"1/3": ["one", "third"],
"2x": ["two", "times"],
};
const norm = (s) =>
String(s)
.replace(/^[ .,?!"']+|[ .,?!"']+$/g, "")
.toLowerCase();
const splitPacked = (t) =>
String(t)
.split(/<br>|\s+/)
.filter(Boolean);
function estimateVerticalBand(css, words, fw, fh) {
const top = css.match(/top:\s*(-?[\d.]+)%/);
if (!top) return null;
const topPct = parseFloat(top[1]);
const sizeM = css.match(/font-size:\s*calc\(\s*([\d.]+)\s*\*\s*var\(--h\)\s*\)/);
const fontPx = sizeM ? parseFloat(sizeM[1]) * fh : 0.05 * fh;
const lhM = css.match(/line-height:\s*([\d.]+)/);
const lh = lhM ? parseFloat(lhM[1]) : 1.1;
const charW = /uppercase/i.test(css) ? 0.72 : 0.6;
let lineCount = 1,
cur = 0;
const spaceW = fontPx * 0.3;
for (const w of words) {
const parts = String(w.text).split(/<br>/);
parts.forEach((part, i) => {
const pw = part.length * fontPx * charW;
if (i > 0) {
lineCount++;
cur = pw;
} else {
const test = cur + (cur > 0 ? spaceW : 0) + pw;
if (test > fw && cur > 0) {
lineCount++;
cur = pw;
} else cur = test;
}
});
}
return [topPct, topPct + (100 * (lineCount * fontPx * lh)) / fh];
}
function check(project, strict) {
const plan = JSON.parse(fs.readFileSync(path.join(project, "plan.json"), "utf8"));
const t = JSON.parse(fs.readFileSync(path.join(project, "transcript.json"), "utf8"));
const seq = (t.words || [])
.filter((w) => w.type === "word")
.map((w) => [norm(w.text), w.start, w.end]);
const issues = [];
const fw = plan.width || 720,
fh = plan.height || 1290;
const groups = plan.groups || [];
const bands = groups.map((g) =>
g.plane
? [g.id || "?", g.in, g.out, null, true]
: [
g.id || "?",
g.in,
g.out,
estimateVerticalBand(g.css || "", g.words || [], fw, fh),
g.allow_overlap || false,
],
);
for (let i = 0; i < bands.length; i++) {
const [gi, ai, bi, bbi, oki] = bands[i];
if (!bbi) continue;
for (let j = i + 1; j < bands.length; j++) {
const [gj, aj, bj, bbj, okj] = bands[j];
if (!bbj || oki || okj) continue;
if (Math.min(bi, bj) - Math.max(ai, aj) <= 0.05) continue;
if (Math.min(bbi[1], bbj[1]) - Math.max(bbi[0], bbj[0]) > 2.0)
issues.push(
`[${gi}${gj}] groups overlap in time (${Math.max(ai, aj).toFixed(2)}${Math.min(bi, bj).toFixed(2)}s) AND vertically (band ${Math.max(bbi[0], bbj[0]).toFixed(0)}%${Math.min(bbi[1], bbj[1]).toFixed(0)}%) — reposition one or add "allow_overlap": true if deliberate.`,
);
}
}
let ti = 0;
for (const g of groups) {
const gid = g.id || "?",
gin = g.in,
gout = g.out;
const ws = (g.words || []).map((w) => w.start),
we = (g.words || []).map((w) => w.end);
if (ws.length && gin != null) {
const e = Math.min(...ws);
if (e < gin - 0.01)
issues.push(
`[${gid}] group.in=${gin.toFixed(2)} but earliest word starts at ${e.toFixed(2)} — word delayed by ${(gin - e >= 0 ? "+" : "") + (gin - e).toFixed(2)}s. Lower group.in.`,
);
}
if (we.length && gout != null) {
const l = Math.max(...we);
if (l > gout + 0.01)
issues.push(
`[${gid}] group.out=${gout.toFixed(2)} but latest word ends at ${l.toFixed(2)} — word clipped. Raise group.out.`,
);
}
for (const w of g.words || []) {
// Exact pairing: a compiler that KNOWS the transcript index emits `ti` —
// duplicate words then pair by POSITION, not text-search (which grabs the
// first occurrence and reports a phantom drift). Sanity-check the text; on
// mismatch fall back to the text matcher below.
if (
Number.isInteger(w.ti) &&
seq[w.ti] &&
seq[w.ti][0] === norm(splitPacked(w.text)[0] || "")
) {
const drift = w.start - seq[w.ti][1];
if (Math.abs(drift) > DRIFT_TOL)
issues.push(
`[${gid}] '${norm(w.text)}': plan=${w.start.toFixed(3)} transcript=${seq[w.ti][1].toFixed(3)} drift ${(drift >= 0 ? "+" : "") + drift.toFixed(3)}s`,
);
ti = w.ti + 1;
continue;
}
const parts = splitPacked(w.text).map(norm);
parts.forEach((part, pi) => {
if (!part) return;
if (part in CREATIVE_SUBS) {
for (const sub of CREATIVE_SUBS[part]) {
const j = seq.findIndex((s, k) => k >= ti && s[0] === sub);
if (j >= 0) ti = j + 1;
}
return;
}
let found = seq.findIndex((s, k) => k >= ti && s[0] === part);
if (found < 0) found = seq.findIndex((s) => s[0] === part);
if (found < 0) {
issues.push(`[${gid}] '${part}': NOT IN TRANSCRIPT (plan start=${w.start.toFixed(3)})`);
return;
}
const ts = seq[found][1];
ti = found + 1;
if (pi === 0) {
const drift = w.start - ts;
if (Math.abs(drift) > DRIFT_TOL)
issues.push(
`[${gid}] '${part}': plan=${w.start.toFixed(3)} transcript=${ts.toFixed(3)} drift ${(drift >= 0 ? "+" : "") + drift.toFixed(3)}s`,
);
} else
issues.push(
`[${gid}] '${part}' packed with '${parts[0]}': transcript=${ts.toFixed(3)} but entry has no distinct timing — split into separate word entries`,
);
});
}
}
const name = path.basename(project);
if (!issues.length) {
console.log(`${name}: timing OK ✓ (${seq.length} transcript words)`);
return 0;
}
console.log(`${name}: ${issues.length} timing issue(s):`);
for (const i of issues) console.log(` ${i}`);
return strict ? 1 : 0;
}
const args = process.argv.slice(2).filter((a) => a !== "--strict");
if (!args.length) {
console.error("usage: check-timing.cjs <project-dir> [--strict]");
process.exit(2);
}
process.exit(check(path.resolve(args[0]), process.argv.includes("--strict")));