218 lines
8.4 KiB
JavaScript
218 lines
8.4 KiB
JavaScript
import { createHash } from "node:crypto";
|
|
|
|
export const WP4_07_SOURCE_HASHES = Object.freeze({
|
|
"DevelopmentPlan.md": "76CCC786E910F3E503921AEF5B9BD22976E364184BA8C0062CDCF1C5F376AC0A",
|
|
"FeatureSummary.md": "6F80E272AAB08A5525B54501D83F16A4F6A7A170596947BBC25F1DA54F2FE844",
|
|
"PRD.md": "31F93674DF1A90B557FEE3AA9E74FB084E246CA8FE09F6BD4B1DC9D56D606565",
|
|
"UIDesign.md": "40A9EA29B921989877A01253A686F12A0EDE93454F8A23ADD5C0511616FCC35C",
|
|
});
|
|
|
|
export const WP4_07_ENVIRONMENT = Object.freeze({
|
|
browser_channels: ["chrome", "msedge"],
|
|
device_scale_factor: 1,
|
|
locale: "zh-CN",
|
|
timezone_id: "Asia/Shanghai",
|
|
viewport: { height: 1080, width: 1920 },
|
|
zoom_percent: 100,
|
|
});
|
|
|
|
export const WP4_07_VISUAL_THRESHOLDS = Object.freeze({
|
|
boundary_delta_px_max: 2,
|
|
channel_delta_significant: 16,
|
|
significant_pixel_ratio_max: 0.01,
|
|
});
|
|
|
|
export const WP4_07_PERFORMANCE_BUDGETS = Object.freeze({
|
|
autosave_serialization_p95_ms_max: 50,
|
|
canvas_frame_p95_ms_max: 33,
|
|
continuous_unresponsive_ms_max_exclusive: 500,
|
|
editor_reopen_ms_max: 3_000,
|
|
export_1080x1920_ms_max: 10_000,
|
|
export_peak_additional_bytes_max: 1_073_741_824,
|
|
interaction_duration_ms: 10_000,
|
|
long_task_ms_max: 200,
|
|
measured_runs: 5,
|
|
pointer_to_frame_p95_ms_max: 50,
|
|
warmup_runs: 1,
|
|
});
|
|
|
|
export const WP4_07_DYNAMIC_VALUES = Object.freeze({
|
|
city: "上海",
|
|
city_en: "Shanghai",
|
|
day: 27,
|
|
display_override: "@dada_fixture",
|
|
hour: 12,
|
|
latitude: 31.2304,
|
|
longitude: 121.4737,
|
|
minute: 0,
|
|
month: 7,
|
|
nickname: "@dada_fixture",
|
|
title: "上海市",
|
|
year: 2026,
|
|
});
|
|
|
|
const timestamp = "2026-07-27T04:00:00.000Z";
|
|
const redResourceVersion = "wp4-07-red-contract-v1";
|
|
export const WP4_07_REAL_RESOURCE_VERSIONS = Object.freeze({
|
|
complex: "p0a-complex-v1",
|
|
static: "p0a-static-v1",
|
|
});
|
|
const palette = ["#111111", "#F2F400", "#1769AA", "#C92A24", "#FFFFFF"];
|
|
const textTemplateIds = [
|
|
"FLOWER001", "FLOWER003", "FLOWER005", "FLOWER008", "H003", "H004",
|
|
"H006", "TAG001", "TAG002", "TAG003", "TAG005", "TAG051",
|
|
];
|
|
const textFontIds = [
|
|
"FONT011", "FONT008", "FONT008", "FONT005", "FONT039", "FONT046",
|
|
"FONT052", "FONT027", "FONT043", "FONT043", "FONT008", "FONT022",
|
|
];
|
|
const dynamicIds = [
|
|
"DYN001", "DYN002", "DYN003", "DYN004", "DYN007",
|
|
"DYN008", "DYN011", "DYN012", "DYN015", "DYN016",
|
|
];
|
|
const colorCards = [
|
|
["COLOR001", "style_01"],
|
|
["COLOR002", "style_02"],
|
|
["COLOR008", "style_08"],
|
|
["COLOR016", "style_16"],
|
|
];
|
|
|
|
function identity(index) {
|
|
return `00000000-0000-4000-8000-${String(4_070_000 + index).padStart(12, "0")}`;
|
|
}
|
|
|
|
function position(index, columns, rowOffset) {
|
|
return {
|
|
x: Number((0.1 + (index % columns) * (0.8 / Math.max(1, columns - 1))).toFixed(4)),
|
|
y: Number((rowOffset + Math.floor(index / columns) * 0.105).toFixed(4)),
|
|
};
|
|
}
|
|
|
|
function common(index, type, templateOrAssetId, resourceVersion) {
|
|
return {
|
|
created_at: timestamp,
|
|
element_id: identity(index),
|
|
opacity: 1,
|
|
position: { x: 0.5, y: 0.5 },
|
|
resource_version: resourceVersion,
|
|
rotation: 0,
|
|
scale: { x: 1, y: 1 },
|
|
style_parameters: {},
|
|
template_or_asset_id: templateOrAssetId,
|
|
type,
|
|
z_index: index - 1,
|
|
};
|
|
}
|
|
|
|
function releaseVersions(input = redResourceVersion) {
|
|
return typeof input === "string" ? { complex: input, static: input } : input;
|
|
}
|
|
|
|
export function createWp407CanvasFixture(resourceVersions = redResourceVersion) {
|
|
const versions = releaseVersions(resourceVersions);
|
|
const text = textTemplateIds.map((templateId, offset) => ({
|
|
...common(offset + 1, "text_template", templateId, versions.complex),
|
|
content: offset === 0 ? "DADA\n视觉预算" : `固定文字 ${String(offset + 1).padStart(2, "0")}`,
|
|
font_size: 48,
|
|
position: offset === 0 ? { x: 0.5, y: 0.5 } : position(offset, 4, 0.09),
|
|
rotation: (offset % 3 - 1) * 4,
|
|
scale: { x: 0.72, y: 0.72 },
|
|
style_parameters: {
|
|
background_color: "#F2F400",
|
|
background_enabled: offset % 4 === 0,
|
|
background_opacity: 0.9,
|
|
default_font_id: textFontIds[offset],
|
|
fill_color: offset % 2 === 0 ? "#111111" : "#1769AA",
|
|
letter_spacing: 1,
|
|
line_height: 1.2,
|
|
stroke_color: "#FFFFFF",
|
|
stroke_enabled: offset % 5 === 0,
|
|
stroke_width: offset % 5 === 0 ? 2 : 0,
|
|
text_align: "center",
|
|
},
|
|
}));
|
|
const stickers = Array.from({ length: 24 }, (_, offset) => ({
|
|
...common(offset + 13, "static_sticker", `STK${String(offset + 1).padStart(3, "0")}`, versions.static),
|
|
opacity: 0.84 + (offset % 4) * 0.04,
|
|
position: position(offset, 6, 0.39),
|
|
rotation: (offset % 5 - 2) * 6,
|
|
scale: { x: 0.58 + (offset % 3) * 0.06, y: 0.58 + (offset % 3) * 0.06 },
|
|
style_parameters: { flip_horizontal: offset % 7 === 0 },
|
|
}));
|
|
const colors = colorCards.map(([cardId, styleId], offset) => ({
|
|
...common(offset + 37, "color_card", cardId, versions.complex),
|
|
colors: [...palette],
|
|
position: { x: 0.16 + offset * 0.22, y: 0.83 },
|
|
scale: { x: 1.25, y: 1.25 },
|
|
style_id: styleId,
|
|
style_parameters: { palette_algorithm_version: "mmcq-v1" },
|
|
}));
|
|
const dynamics = dynamicIds.map((dynamicId, offset) => ({
|
|
...common(offset + 41, "dynamic_sticker", dynamicId, versions.complex),
|
|
dynamic_fields: { ...WP4_07_DYNAMIC_VALUES },
|
|
formatted_value: dynamicId === "DYN012" ? "12:00 PM" : "@dada_fixture",
|
|
position: { x: 0.09 + (offset % 5) * 0.205, y: 0.9 + Math.floor(offset / 5) * 0.06 },
|
|
scale: { x: 0.55, y: 0.55 },
|
|
style_parameters: dynamicId === "DYN012" ? { font_id: "FONT081", known_substitution: true } : {},
|
|
}));
|
|
return {
|
|
background: {
|
|
adjustments: {
|
|
brightness: 0, contrast: 0, crop: null, filter: "none", fit: "fill",
|
|
saturation: 0, sharpness: 0, temperature: 0,
|
|
},
|
|
asset_id: "00000000-0000-4000-8000-000000004079",
|
|
},
|
|
elements: [...text, ...stickers, ...colors, ...dynamics],
|
|
pixel_height: 1920,
|
|
pixel_width: 1080,
|
|
ratio: "9:16",
|
|
schema_version: 1,
|
|
};
|
|
}
|
|
|
|
export function wp407FixtureContract(resourceVersions = redResourceVersion) {
|
|
const canvas = createWp407CanvasFixture(resourceVersions);
|
|
return {
|
|
canvas,
|
|
dynamic_values: WP4_07_DYNAMIC_VALUES,
|
|
environment: WP4_07_ENVIRONMENT,
|
|
performance_budgets: WP4_07_PERFORMANCE_BUDGETS,
|
|
schema_version: "wp4-07-fixed-fixture/v1",
|
|
visual_thresholds: WP4_07_VISUAL_THRESHOLDS,
|
|
};
|
|
}
|
|
|
|
export function wp407FixtureSha256(resourceVersions = redResourceVersion) {
|
|
return createHash("sha256").update(JSON.stringify(wp407FixtureContract(resourceVersions))).digest("hex").toUpperCase();
|
|
}
|
|
|
|
export function assertWp407Fixture(resourceVersions = redResourceVersion) {
|
|
const fixture = wp407FixtureContract(resourceVersions);
|
|
const counts = Object.fromEntries(["text_template", "static_sticker", "color_card", "dynamic_sticker"].map((type) => [
|
|
type,
|
|
fixture.canvas.elements.filter((element) => element.type === type).length,
|
|
]));
|
|
if (fixture.canvas.elements.length !== 50) throw new Error("FX-CANVAS-50 must contain exactly 50 overlay elements");
|
|
if (JSON.stringify(counts) !== JSON.stringify({ text_template: 12, static_sticker: 24, color_card: 4, dynamic_sticker: 10 })) {
|
|
throw new Error(`FX-CANVAS-50 composition changed: ${JSON.stringify(counts)}`);
|
|
}
|
|
if (fixture.canvas.pixel_width !== 1080 || fixture.canvas.pixel_height !== 1920) throw new Error("export fixture dimensions changed");
|
|
if (new Set(fixture.canvas.elements.map((element) => element.element_id)).size !== 50) throw new Error("fixture element IDs are not unique");
|
|
if (fixture.performance_budgets.warmup_runs !== 1 || fixture.performance_budgets.measured_runs !== 5) throw new Error("measurement count changed");
|
|
if (fixture.performance_budgets.interaction_duration_ms !== 10_000) throw new Error("interaction duration changed");
|
|
if (fixture.environment.viewport.width !== 1920 || fixture.environment.viewport.height !== 1080 || fixture.environment.device_scale_factor !== 1) {
|
|
throw new Error("candidate viewport or DPR changed");
|
|
}
|
|
return { counts, fixture_sha256: wp407FixtureSha256(resourceVersions) };
|
|
}
|
|
|
|
export const WP4_07_RED_RESOURCE_VERSION = redResourceVersion;
|
|
export const WP4_07_REQUIRED_FONT_IDS = Object.freeze([
|
|
"FONT005", "FONT008", "FONT011", "FONT021", "FONT022", "FONT027", "FONT039", "FONT043", "FONT046", "FONT052", "FONT081",
|
|
"15974853bc3294ef68e7e6d58fe74fd7", "46f8336813e4c48d06a1aef294fdccf6",
|
|
"53ca6b704728520da50c145eabb2e635", "cca5efc0e02fb1bf62349bd68ef30fc1",
|
|
"dd25b35dcb7ba4476cbaa9a9592e39e2", "e4210c9872f0c279b35273f230809821",
|
|
"f4bfd4132df2d6be97ceabadf3853505",
|
|
]);
|