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"; 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, }; } export function createWp407CanvasFixture(resourceVersion = redResourceVersion) { const text = textTemplateIds.map((templateId, offset) => ({ ...common(offset + 1, "text_template", templateId, resourceVersion), 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")}`, resourceVersion), 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, resourceVersion), 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, resourceVersion), 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() { const canvas = createWp407CanvasFixture(); 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() { return createHash("sha256").update(JSON.stringify(wp407FixtureContract())).digest("hex").toUpperCase(); } export function assertWp407Fixture() { const fixture = wp407FixtureContract(); 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() }; } export const WP4_07_RED_RESOURCE_VERSION = redResourceVersion; export const WP4_07_REQUIRED_FONT_IDS = Object.freeze([ "FONT005", "FONT008", "FONT011", "FONT022", "FONT027", "FONT039", "FONT043", "FONT046", "FONT052", "FONT081", "15974853bc3294ef68e7e6d58fe74fd7", "46f8336813e4c48d06a1aef294fdccf6", "53ca6b704728520da50c145eabb2e635", "cca5efc0e02fb1bf62349bd68ef30fc1", "dd25b35dcb7ba4476cbaa9a9592e39e2", "e4210c9872f0c279b35273f230809821", "f4bfd4132df2d6be97ceabadf3853505", ]);