import { ENGINE_MACHINES } from './catalogue/engines'; import { GEARBOX_AND_DRIVE_MACHINES } from './catalogue/gearboxesAndDrives'; import { MECHANISM_MACHINES } from './catalogue/mechanisms'; import { PUMP_AND_FLUID_MACHINES } from './catalogue/pumpsAndFluid'; import { STRUCTURAL_AND_OTHER_MACHINES } from './catalogue/structuralOther'; import type { MachineCategory, MachineDefinition, RegistryValidationResult } from './types'; export const PHASE_1_CORE_MACHINE_COUNT = 28; export const CORE_MACHINE_DEFINITIONS: readonly MachineDefinition[] = [ ...ENGINE_MACHINES, ...GEARBOX_AND_DRIVE_MACHINES, ...PUMP_AND_FLUID_MACHINES, ...MECHANISM_MACHINES, ...STRUCTURAL_AND_OTHER_MACHINES, ]; export const CORE_MACHINE_IDS: readonly string[] = CORE_MACHINE_DEFINITIONS.map((machine) => machine.id); export const CORE_MACHINE_BY_ID: Readonly> = Object.fromEntries( CORE_MACHINE_DEFINITIONS.map((machine) => [machine.id, machine]), ) as Record; export const CORE_MACHINE_BY_SLUG: Readonly> = Object.fromEntries( CORE_MACHINE_DEFINITIONS.map((machine) => [machine.slug, machine]), ) as Record; export function getCoreMachineById(machineId: string): MachineDefinition | undefined { return CORE_MACHINE_BY_ID[machineId]; } export function getCoreMachineBySlug(slug: string): MachineDefinition | undefined { return CORE_MACHINE_BY_SLUG[slug]; } export function getCoreMachinesByCategory(category: MachineCategory): readonly MachineDefinition[] { return CORE_MACHINE_DEFINITIONS.filter((machine) => machine.category === category); } export function getRelatedCoreMachines(machineId: string): readonly MachineDefinition[] { const machine = getCoreMachineById(machineId); if (!machine) { return []; } return machine.relatedMachineIds .map((relatedId) => getCoreMachineById(relatedId)) .filter((relatedMachine): relatedMachine is MachineDefinition => Boolean(relatedMachine)); } export function validateCoreMachineRegistry( machines: readonly MachineDefinition[] = CORE_MACHINE_DEFINITIONS, ): RegistryValidationResult { const errors: string[] = []; const warnings: string[] = []; const ids = new Set(); const slugs = new Set(); for (const machine of machines) { if (ids.has(machine.id)) { errors.push(`Duplicate machine id "${machine.id}".`); } ids.add(machine.id); if (slugs.has(machine.slug)) { errors.push(`Duplicate machine slug "${machine.slug}".`); } slugs.add(machine.slug); if (!machine.parts.length) { errors.push(`Machine "${machine.id}" has no parts.`); } if (!machine.guidedTour.length) { errors.push(`Machine "${machine.id}" has no guided-tour steps.`); } if (!machine.cameraPresets.some((preset) => preset.id === machine.defaultViewState.cameraPresetId)) { errors.push( `Machine "${machine.id}" default camera "${machine.defaultViewState.cameraPresetId}" does not exist.`, ); } const partIds = new Set(); for (const part of machine.parts) { if (partIds.has(part.id)) { errors.push(`Machine "${machine.id}" has duplicate part id "${part.id}".`); } partIds.add(part.id); if (part.defaultOpacity < 0 || part.defaultOpacity > 1) { errors.push(`Machine "${machine.id}" part "${part.id}" has opacity outside 0..1.`); } } for (const node of machine.hierarchy) { for (const partId of node.partIds) { if (!partIds.has(partId)) { errors.push(`Machine "${machine.id}" hierarchy node "${node.id}" references missing part "${partId}".`); } } } for (const step of machine.guidedTour) { if (!machine.cameraPresets.some((preset) => preset.id === step.cameraPresetId)) { errors.push(`Machine "${machine.id}" tour step "${step.id}" references missing camera "${step.cameraPresetId}".`); } for (const partId of [...step.focusPartIds, ...step.highlightPartIds, ...(step.isolatePartIds ?? [])]) { if (!partIds.has(partId)) { errors.push(`Machine "${machine.id}" tour step "${step.id}" references missing part "${partId}".`); } } } for (const relatedId of machine.relatedMachineIds) { if (!machines.some((candidate) => candidate.id === relatedId)) { warnings.push(`Machine "${machine.id}" references related machine "${relatedId}" outside this registry.`); } } for (const requiredNodeId of machine.asset.requiredPartNodeIds) { if (!partIds.has(requiredNodeId)) { errors.push(`Machine "${machine.id}" GLB replacement requires missing part node "${requiredNodeId}".`); } } } if (machines.length !== PHASE_1_CORE_MACHINE_COUNT) { errors.push( `Expected ${PHASE_1_CORE_MACHINE_COUNT} Phase 1 machines, but registry contains ${machines.length}.`, ); } return { ok: errors.length === 0, errors, warnings, }; }