export const MACHINE_CATEGORIES = [ 'engines', 'gearboxes-drives', 'pumps-fluid', 'mechanisms', 'structural-other', ] as const; export type MachineCategory = (typeof MACHINE_CATEGORIES)[number]; export const MACHINE_CATEGORY_META: Readonly< Record > = { engines: { label: 'Engines', description: 'Heat engines and propulsion systems that convert fuel or steam energy into mechanical work.', }, 'gearboxes-drives': { label: 'Gearboxes & Drives', description: 'Power transmission assemblies, ratio-changing drives, and torque-splitting mechanisms.', }, 'pumps-fluid': { label: 'Pumps & Fluid Systems', description: 'Machines that move, pressurise, meter, or control liquids and gases.', }, mechanisms: { label: 'Mechanisms', description: 'Kinematic linkages and motion converters used throughout machinery and automation.', }, 'structural-other': { label: 'Structural / Other', description: 'Bearings, brakes, and auxiliary mechanical assemblies that support or control machines.', }, }; export const DIFFICULTY_LEVELS = ['Beginner', 'Intermediate', 'Advanced'] as const; export type DifficultyLevel = (typeof DIFFICULTY_LEVELS)[number]; export type Axis3D = 'x' | 'y' | 'z'; export type SignedAxis3D = Axis3D | '-x' | '-y' | '-z'; export type Vector3Tuple = readonly [number, number, number]; export type EulerTuple = readonly [number, number, number]; export type PrimitiveParameter = | string | number | boolean | null | readonly PrimitiveParameter[] | { readonly [key: string]: PrimitiveParameter }; export type ProceduralPrimitiveKind = | 'box' | 'cylinder' | 'sphere' | 'torus' | 'gear' | 'bevel-gear' | 'worm' | 'rack' | 'pulley' | 'belt' | 'spring' | 'blade-disk' | 'cam' | 'rotor' | 'housing' | 'pipe' | 'flow-path' | 'valve' | 'caliper' | 'pad' | 'seal' | 'annotation-plane' | 'custom'; export interface ProceduralPrimitiveDefinition { kind: ProceduralPrimitiveKind; parameters: Readonly>; detail?: 'low' | 'medium' | 'high'; /** * Human-readable modelling notes for the procedural generator and for * artists replacing the diagram with GLB geometry. */ modellingHint?: string; } export type MaterialTokenName = | 'cast-iron' | 'machined-steel' | 'brushed-steel' | 'bearing-steel' | 'dark-anodized' | 'matte-black' | 'blue-accent' | 'warm-highlight' | 'copper' | 'brass' | 'bronze' | 'rubber' | 'ceramic' | 'glass' | 'transparent-shell' | 'fluid-blue' | 'fluid-amber' | 'combustion-orange' | 'exhaust-gray' | 'cool-air' | 'hot-metal' | 'oil-green' | 'gasket' | 'carbon' | 'paint-red' | 'paint-yellow' | 'paint-green' | 'graphite' | 'titanium'; export interface MaterialDefinition { token: MaterialTokenName; label: string; color: string; opacity: number; metalness: number; roughness: number; emissive?: string; emissiveIntensity?: number; transparent?: boolean; } export interface TransformDefinition { position: Vector3Tuple; rotation: EulerTuple; scale: Vector3Tuple; } export interface MachineFactDefinition { label: string; value: string; unit?: string; kind: | 'efficiency' | 'rpm' | 'date' | 'application' | 'ratio' | 'pressure' | 'temperature' | 'load' | 'flow' | 'power' | 'note'; sourceNote?: string; } export interface ExplodedPartDefinition { direction: Vector3Tuple; distanceMultiplier: number; order: number; } export interface PartLabelDefinition { anchor: Vector3Tuple; offset: Vector3Tuple; text?: string; visibleByDefault: boolean; } export interface MachinePartDefinition { id: string; name: string; group: string; description: string; engineeringNotes: readonly string[]; primitive: ProceduralPrimitiveDefinition; transform: TransformDefinition; material: MaterialTokenName; exploded: ExplodedPartDefinition; label: PartLabelDefinition; defaultVisible: boolean; defaultOpacity: number; selectable: boolean; crossSectionBehavior: 'solid' | 'shell' | 'fluid' | 'hidden-when-clipped'; tags: readonly string[]; specs?: readonly MachineFactDefinition[]; } export interface ComponentHierarchyNode { id: string; name: string; partIds: readonly string[]; children?: readonly ComponentHierarchyNode[]; } export interface CameraPresetDefinition { id: 'front' | 'back' | 'left' | 'right' | 'top' | 'bottom' | 'isometric' | string; label: string; position: Vector3Tuple; target: Vector3Tuple; up: Vector3Tuple; focalLength: number; description?: string; } export interface GuidedTourStepDefinition { id: string; title: string; body: string; durationSeconds: number; cameraPresetId: string; focusPartIds: readonly string[]; highlightPartIds: readonly string[]; isolatePartIds?: readonly string[]; explodeDistance?: number; playback?: 'play' | 'pause' | 'restart' | 'step'; rpm?: number; captionPlacement?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; } export interface ThumbnailStrategy { kind: 'procedural-snapshot' | 'static-image' | 'generated-svg'; cameraPresetId: string; heroPartIds: readonly string[]; accentPartId?: string; background: 'dark-gradient' | 'transparent' | 'blueprint'; imagePath?: string; alt: string; notes: readonly string[]; } export interface AssetReplacementPoint { strategy: 'procedural-first-glb-replaceable'; proceduralAssembly: 'registry-primitives'; glbPath: string; previewImagePath: string; scaleMetersPerUnit: number; upAxis: 'Y' | 'Z'; rootNodeName: string; partNodeMap: Readonly>; requiredPartNodeIds: readonly string[]; optionalNodeIds: readonly string[]; notes: readonly string[]; } export interface MachineViewStatePreset { cameraPresetId: string; explodeDistance: number; displayMode: 'solid' | 'wireframe' | 'cross-section'; crossSectionAxis: Axis3D; crossSectionOffset: number; showLabels: boolean; } export interface MachineDefinition { id: string; slug: string; title: string; shortTitle?: string; category: MachineCategory; difficulty: DifficultyLevel; complexityScore: number; sortOrder: number; releasedAt: string; summary: string; description: string; keywords: readonly string[]; facts: readonly MachineFactDefinition[]; relatedMachineIds: readonly string[]; thumbnail: ThumbnailStrategy; asset: AssetReplacementPoint; cameraPresets: readonly CameraPresetDefinition[]; parts: readonly MachinePartDefinition[]; hierarchy: readonly ComponentHierarchyNode[]; guidedTour: readonly GuidedTourStepDefinition[]; animationModuleId: string; defaultViewState: MachineViewStatePreset; labelsEnabledByDefault: boolean; } export type AnimationLoopMode = 'continuous' | 'stepped-indexed' | 'reciprocating'; export type AnimationParameter = | string | number | boolean | null | readonly AnimationParameter[] | { readonly [key: string]: AnimationParameter }; export type AnimationTrackKind = | 'rotation' | 'linear-oscillation' | 'angular-oscillation' | 'orbit' | 'flow' | 'scale-pulse' | 'material-pulse' | 'visibility-window' | 'ratio-morph' | 'indexed-step' | 'camera-safe-focus'; export interface AnimationTrackDefinition { id: string; kind: AnimationTrackKind; partId?: string; partIds?: readonly string[]; axis?: Axis3D; phaseDegrees?: number; rpmMultiplier?: number; amplitude?: number; amplitudeDegrees?: number; offset?: number; easing?: 'linear' | 'sine-in-out' | 'sine-in' | 'sine-out' | 'step' | 'hold'; parameters?: Readonly>; } export interface CycleStepDefinition { id: string; label: string; atNormalizedTime: number; durationNormalized: number; description: string; activePartIds: readonly string[]; } export interface MachineAnimationDefinition { id: string; machineId: string; title: string; cycleSeconds: number; defaultRpm: number; rpmRange: readonly [number, number]; timeScaleRange: readonly [number, number]; loopMode: AnimationLoopMode; physicallyBelievableNotes: readonly string[]; cycleSteps: readonly CycleStepDefinition[]; tracks: readonly AnimationTrackDefinition[]; } export interface RegistryValidationResult { ok: boolean; errors: readonly string[]; warnings: readonly string[]; }