Files
workinf_Blender_Wasm/tools/web/generate-blender-next-task-plan.mjs
mes123456 10640aeb3c
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Govern task context and advance execution pointer
2026-08-20 06:02:43 -04:00

71 lines
4.5 KiB
JavaScript

import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const output = path.resolve(process.argv[2] ?? path.join(root, "tests/golden/M15-03A/next-task-plan.json"));
const mapPath = path.join(root, "tests/golden/M15-02A/blender-parity-map.json");
const gapPath = path.join(root, "tests/golden/M15-02B/blender-gap-audit.json");
const map = JSON.parse(fs.readFileSync(mapPath, "utf8"));
const gaps = JSON.parse(fs.readFileSync(gapPath, "utf8"));
const completionPath = path.join(root, "tests/golden/M15-03A/completed-gap-tasks.json");
const completedIds = fs.existsSync(completionPath) ? new Set(JSON.parse(fs.readFileSync(completionPath, "utf8"))) : new Set();
const byId = new Map(map.entries.map((entry) => [entry.id, entry]));
const waveFor = (owner) => {
if (["Main", "Mesh", "MODIFIER", "RNA_DATABLOCK", "OPERATOR"].includes(owner)) return "M16";
if (owner === "CONSTRAINT") return "M17";
if (["SHADER_NODE", "GEOMETRY_NODE", "COMPOSITOR_NODE"].includes(owner)) return "M18";
if (owner === "Core") return "M19";
if (["PHYSICS", "SEQUENCER"].includes(owner)) return "M20";
if (["IMPORT_EXPORT", "EDITOR", "REGION", "SPACE", "WORKSPACE", "KEYMAP"].includes(owner)) return "M21";
return "M22";
};
const safeName = (value) => value.replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96) || "gap";
const sourceGaps = [...gaps.gaps.unmapped, ...gaps.gaps.summaryOnly, ...gaps.gaps.proxyOnly, ...gaps.gaps.routeOnly];
const uniqueGaps = [...new Set(sourceGaps)];
const work = uniqueGaps.map((gapId) => {
const mapping = byId.get(gapId);
if (!mapping) throw new Error(`gap has no parity map entry: ${gapId}`);
return { gapId, mapping, wave: waveFor(mapping.ownerFamily) };
});
work.sort((a, b) => a.wave.localeCompare(b.wave) || (a.gapId < b.gapId ? -1 : a.gapId > b.gapId ? 1 : 0));
const waveCounts = new Map();
const activeIndex = work.findIndex((value) => !completedIds.has(value.gapId));
const tasks = work.map((value, index) => {
const serial = (waveCounts.get(value.wave) ?? 0) + 1;
waveCounts.set(value.wave, serial);
const id = `${value.wave}-GAP-${String(serial).padStart(5, "0")}`;
const fixturePath = `tests/files/web/generated/${id}-${safeName(value.gapId)}.blend`;
return {
id,
gapId: value.gapId,
ownerFamily: value.mapping.ownerFamily,
sourceTask: value.mapping.task,
state: completedIds.has(value.gapId) ? "completed" : index === activeIndex ? "active" : "pending",
dependencies: [],
targetImplementationClass: "LOCAL_EXACT",
fixture: { path: fixturePath, state: "REQUIRED" },
desktopCommand: `build_blender_5.2.0/bin/blender -b --factory-startup --python tools/web/generated/${id}.py -- ${fixturePath}`,
webCommand: `npm --prefix web run test:generated-gap -- --task ${id}`,
comparator: `node tools/web/check-generated-gap.mjs --task ${id}`,
exitCriteria: ["desktop fixture evidence exists", "WASM uses the same fixture", "comparator passes", "save/reopen preserves Main", "manifest hashes all artifacts"],
};
});
const sha256File = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
const completionBytes = fs.readFileSync(completionPath);
const plan = {
schemaVersion: 1,
task: "M15-03A",
operation: "BLENDER_NEXT_TASK_PLAN",
sources: { parityMap: { path: "tests/golden/M15-02A/blender-parity-map.json", sha256: sha256File(mapPath) }, gapAudit: { path: "tests/golden/M15-02B/blender-gap-audit.json", sha256: sha256File(gapPath) }, completions: { path: "tests/golden/M15-03A/completed-gap-tasks.json", sha256: crypto.createHash("sha256").update(completionBytes).digest("hex") } },
summary: { taskCount: tasks.length, active: tasks.filter((task) => task.state === "active").length, pending: tasks.filter((task) => task.state === "pending").length, completed: tasks.filter((task) => task.state === "completed").length, blocked: tasks.filter((task) => task.state === "blocked").length, byWave: Object.fromEntries([...waveCounts.entries()]) },
tasks,
firstTask: tasks.find((task) => task.state === "active")?.id ?? null,
closureGate: "M15-03E",
nextTask: "M15-03B",
};
fs.mkdirSync(path.dirname(output), { recursive: true });
fs.writeFileSync(output, `${JSON.stringify(plan, null, 2)}\n`);
process.stdout.write(`blender-next-task-plan-generated tasks=${tasks.length} active=${plan.summary.active} first=${plan.firstTask} output=${output}\n`);