import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import ts from "../../web/node_modules/typescript/lib/typescript.js"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const sourcePath = path.join(root, "web/protocol/scene-ir.ts"); const source = ts.createSourceFile(sourcePath, fs.readFileSync(sourcePath, "utf8"), ts.ScriptTarget.Latest, true); const manifest = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-01/lighting-field-parity.json"), "utf8")); function interfaceDeclaration(name) { const declaration = source.statements.find((statement) => ts.isInterfaceDeclaration(statement) && statement.name.text === name); assert.ok(declaration, `missing interface ${name}`); return declaration; } function leafTypePaths(type, prefix) { if (!ts.isTypeLiteralNode(type)) return [prefix]; return type.members.filter(ts.isPropertySignature).flatMap((member) => leafTypePaths(member.type, `${prefix}.${member.name.getText(source)}`)); } function interfaceLeaves(name, include = () => true) { return interfaceDeclaration(name).members.filter((member) => ts.isPropertySignature(member) && include(member.name.getText(source))).flatMap((member) => leafTypePaths(member.type, member.name.getText(source))).sort(); } const expected = new Map([ ["CAMERA", interfaceLeaves("CameraIR")], ["LIGHT", interfaceLeaves("LightIR")], ["WORLD", interfaceLeaves("WorldIR")], ["SCENE_COLOR_MANAGEMENT", interfaceLeaves("SceneIR", (name) => name === "renderEngine" || name === "colorManagement")], ]); const stages = ["reader", "writer", "viewportMain", "viewportOffscreen"]; const stageStates = new Set(["VERIFIED", "PARTIAL", "METADATA_ONLY", "BLOCKED", "NOT_APPLICABLE"]); const parityStates = new Set(["COMPLETE", "PARTIAL", "BLOCKED"]); assert.equal(manifest.schemaVersion, 1); assert.equal(manifest.task, "M11-01"); assert.equal(manifest.blenderVersion, "5.2.0"); assert.deepEqual(manifest.domains.map((domain) => domain.domain), [...expected.keys()]); let total = 0; const parityCounts = { COMPLETE: 0, PARTIAL: 0, BLOCKED: 0 }; for (const domain of manifest.domains) { assert.deepEqual(domain.fields.map((field) => field.path).sort(), expected.get(domain.domain)); assert.equal(new Set(domain.fields.map((field) => field.path)).size, domain.fields.length); assert.ok(Array.isArray(domain.evidence) && domain.evidence.length >= 4); for (const evidence of domain.evidence) assert.ok(fs.existsSync(path.join(root, evidence)), `${domain.domain} evidence is missing: ${evidence}`); for (const field of domain.fields) { for (const stage of stages) assert.ok(stageStates.has(field[stage]), `${domain.domain}.${field.path}.${stage} is invalid`); assert.ok(parityStates.has(field.parity), `${domain.domain}.${field.path}.parity is invalid`); if (field.parity === "COMPLETE") { assert.ok(stages.every((stage) => field[stage] === "VERIFIED" || field[stage] === "NOT_APPLICABLE"), `${domain.domain}.${field.path} overclaims COMPLETE`); } parityCounts[field.parity] += 1; total += 1; } } const requiredBlocks = [ ["CAMERA", "orthoScale", "viewportMain"], ["CAMERA", "depthOfField.enabled", "viewportMain"], ["LIGHT", "areaSpread", "viewportMain"], ["LIGHT", "sunAngle", "viewportOffscreen"], ["WORLD", "environmentRotation", "reader"], ["SCENE_COLOR_MANAGEMENT", "colorManagement.displayDevice", "viewportMain"], ["SCENE_COLOR_MANAGEMENT", "colorManagement.gamma", "viewportOffscreen"], ]; for (const [domainName, fieldPath, stage] of requiredBlocks) { const field = manifest.domains.find((domain) => domain.domain === domainName)?.fields.find((candidate) => candidate.path === fieldPath); assert.equal(field?.[stage], "BLOCKED", `${domainName}.${fieldPath}.${stage} must remain BLOCKED`); } assert.equal(total, 60); process.stdout.write(`lighting-field-parity-ok fields=${total} complete=${parityCounts.COMPLETE} partial=${parityCounts.PARTIAL} blocked=${parityCounts.BLOCKED}\n`);