import type { MachineDefinition } from '../types'; import { groupNode, hierarchy, makeMachine, makePart, primitive, standardCameraPresets, thumbnail, tourStep, } from './catalogueHelpers'; const fourStrokeParts = [ makePart({ id: 'cylinder-block', name: 'Cylinder block', group: 'Structure', description: 'Rigid casting that supports the cylinder bore, crank bearings, coolant passages, and the head joint.', primitive: primitive('housing', { width: 2.1, height: 2.4, depth: 1.35, cutaway: true }), position: [0, 0, 0], material: 'cast-iron', explodedDirection: [-0.25, -0.2, 0], notes: ['Shown as a cutaway so the piston and crank motion remain visible.'], }), makePart({ id: 'cylinder-head', name: 'Cylinder head', group: 'Structure', description: 'Upper casting that closes the combustion chamber and carries the valves, spark plug, and ports.', primitive: primitive('box', { width: 2.2, height: 0.42, depth: 1.4, bevel: 0.08 }), position: [0, 1.42, 0], material: 'dark-anodized', explodedDirection: [0, 1, 0], explode: 1.2, }), makePart({ id: 'piston', name: 'Piston', group: 'Reciprocating assembly', description: 'Sliding aluminium piston that converts cylinder gas pressure into force on the connecting rod.', primitive: primitive('cylinder', { radius: 0.43, depth: 0.46, axis: 'y', ringGrooves: 3 }), position: [0, 0.55, 0], material: 'machined-steel', explodedDirection: [0.2, 0.85, 0], notes: ['Animated with sinusoidal secondary motion to approximate connecting-rod geometry.'], }), makePart({ id: 'connecting-rod', name: 'Connecting rod', group: 'Reciprocating assembly', description: 'Pinned link between piston and crank throw that transmits alternating tensile and compressive loads.', primitive: primitive('box', { width: 0.18, height: 1.18, depth: 0.16, rounded: true }), position: [0, -0.25, 0], material: 'brushed-steel', explodedDirection: [0.55, 0.1, 0], }), makePart({ id: 'crankshaft', name: 'Crankshaft', group: 'Rotating assembly', description: 'Offset crank throw turns reciprocating piston force into continuous rotary output torque.', primitive: primitive('custom', { profile: 'single-throw-crankshaft', mainRadius: 0.14, throw: 0.42 }), position: [0, -1.05, 0], rotation: [1.5708, 0, 0], material: 'machined-steel', explodedDirection: [0, -0.65, 0], }), makePart({ id: 'camshaft', name: 'Camshaft', group: 'Valve train', description: 'Half-speed shaft with lobes that open the intake and exhaust valves at precise crank angles.', primitive: primitive('cam', { shaftRadius: 0.08, lobeRadius: 0.18, lobes: 2, length: 1.25, axis: 'z' }), position: [0, 1.88, 0], material: 'brushed-steel', explodedDirection: [0, 0.95, 0.15], }), makePart({ id: 'intake-valve', name: 'Intake valve', group: 'Valve train', description: 'Poppet valve that opens during the intake stroke to admit the air-fuel charge.', primitive: primitive('valve', { stemRadius: 0.035, headRadius: 0.18, length: 0.72, axis: 'y' }), position: [-0.34, 1.05, 0], material: 'titanium', explodedDirection: [-0.55, 0.55, 0], }), makePart({ id: 'exhaust-valve', name: 'Exhaust valve', group: 'Valve train', description: 'Heat-resistant poppet valve that opens after combustion to release burned gases.', primitive: primitive('valve', { stemRadius: 0.035, headRadius: 0.18, length: 0.72, axis: 'y' }), position: [0.34, 1.05, 0], material: 'hot-metal', explodedDirection: [0.55, 0.55, 0], }), makePart({ id: 'spark-plug', name: 'Spark plug', group: 'Ignition', description: 'Insulated electrode that initiates petrol combustion near top dead centre.', primitive: primitive('custom', { profile: 'spark-plug', length: 0.62, hexRadius: 0.11 }), position: [0, 1.72, 0.28], rotation: [0.35, 0, 0], material: 'ceramic', explodedDirection: [0, 1, 0.45], }), makePart({ id: 'flywheel', name: 'Flywheel', group: 'Rotating assembly', description: 'High-inertia wheel that smooths torque pulses between the four strokes.', primitive: primitive('torus', { majorRadius: 0.56, tubeRadius: 0.08, axis: 'z' }), position: [0, -1.05, -0.88], material: 'machined-steel', explodedDirection: [0, 0, -0.9], }), makePart({ id: 'combustion-volume', name: 'Combustion volume', group: 'Fluids & energy', description: 'Transparent chamber volume that glows during the combustion stroke and shrinks during compression.', primitive: primitive('cylinder', { radius: 0.38, depth: 0.26, axis: 'y' }), position: [0, 1.02, 0], material: 'combustion-orange', explodedDirection: [0, 0.55, 0.35], crossSectionBehavior: 'fluid', }), makePart({ id: 'intake-manifold', name: 'Intake manifold', group: 'Fluids & energy', description: 'Duct routing metered air-fuel mixture from the throttle side to the intake port.', primitive: primitive('pipe', { radius: 0.12, length: 1.1, axis: 'x', elbow: true }), position: [-1.25, 1.05, 0], material: 'cool-air', explodedDirection: [-1, 0.15, 0], crossSectionBehavior: 'fluid', }), makePart({ id: 'exhaust-manifold', name: 'Exhaust manifold', group: 'Fluids & energy', description: 'Hot gas duct collecting burned products after the exhaust valve opens.', primitive: primitive('pipe', { radius: 0.13, length: 1.15, axis: 'x', elbow: true }), position: [1.25, 1.0, 0], material: 'exhaust-gray', explodedDirection: [1, 0.15, 0], crossSectionBehavior: 'fluid', }), ]; const twoStrokeParts = [ makePart({ id: 'crankcase', name: 'Sealed crankcase', group: 'Structure', description: 'Lower crank enclosure doubles as a pre-compression pump for the fresh fuel-air charge.', primitive: primitive('housing', { width: 1.9, height: 1.2, depth: 1.25, cutaway: true }), position: [0, -0.72, 0], material: 'cast-iron', explodedDirection: [0, -0.45, 0], }), makePart({ id: 'cylinder-barrel', name: 'Ported cylinder barrel', group: 'Structure', description: 'Cylinder liner with intake, transfer, and exhaust windows uncovered by piston motion.', primitive: primitive('cylinder', { radius: 0.58, depth: 1.85, axis: 'y', cutaway: true, ports: 3 }), position: [0, 0.62, 0], material: 'dark-anodized', explodedDirection: [0, 0.65, 0], }), makePart({ id: 'piston', name: 'Deflector piston', group: 'Reciprocating assembly', description: 'Light piston controls all port timing by exposing or covering cylinder windows.', primitive: primitive('cylinder', { radius: 0.44, depth: 0.42, axis: 'y', crown: 'deflector' }), position: [0, 0.52, 0], material: 'machined-steel', explodedDirection: [0.25, 0.85, 0], }), makePart({ id: 'connecting-rod', name: 'Connecting rod', group: 'Reciprocating assembly', description: 'Rod linking the piston pin to the crank throw for one power stroke every crank revolution.', primitive: primitive('box', { width: 0.16, height: 1.05, depth: 0.14, rounded: true }), position: [0, -0.24, 0], material: 'brushed-steel', explodedDirection: [0.5, 0.05, 0], }), makePart({ id: 'crankshaft', name: 'Crankshaft', group: 'Rotating assembly', description: 'Single-throw crankshaft also pumps the crankcase charge as the piston descends.', primitive: primitive('custom', { profile: 'single-throw-crankshaft', mainRadius: 0.13, throw: 0.38 }), position: [0, -1.15, 0], rotation: [1.5708, 0, 0], material: 'machined-steel', explodedDirection: [0, -0.85, 0], }), makePart({ id: 'spark-plug', name: 'Spark plug', group: 'Ignition', description: 'Ignition source firing once per revolution after the ports have closed and compression peaks.', primitive: primitive('custom', { profile: 'spark-plug', length: 0.52 }), position: [0, 1.72, 0.23], rotation: [0.28, 0, 0], material: 'ceramic', explodedDirection: [0, 0.9, 0.4], }), makePart({ id: 'intake-port', name: 'Intake port', group: 'Ports', description: 'Crankcase inlet controlled by reed valve or piston skirt timing.', primitive: primitive('box', { width: 0.58, height: 0.28, depth: 0.08, bevel: 0.03 }), position: [-0.58, -0.62, 0], material: 'cool-air', explodedDirection: [-0.7, 0, 0], crossSectionBehavior: 'fluid', }), makePart({ id: 'transfer-port', name: 'Transfer port', group: 'Ports', description: 'Passage that routes pre-compressed charge from crankcase into the cylinder.', primitive: primitive('pipe', { radius: 0.1, length: 1.1, axis: 'y', curved: true }), position: [-0.54, 0.22, 0], material: 'fluid-blue', explodedDirection: [-0.65, 0.2, 0], crossSectionBehavior: 'fluid', }), makePart({ id: 'exhaust-port', name: 'Exhaust port', group: 'Ports', description: 'Cylinder window opened by the descending piston to begin blowdown.', primitive: primitive('box', { width: 0.64, height: 0.32, depth: 0.08, bevel: 0.03 }), position: [0.58, 0.45, 0], material: 'exhaust-gray', explodedDirection: [0.7, 0.1, 0], crossSectionBehavior: 'fluid', }), makePart({ id: 'reed-valve', name: 'Reed valve', group: 'Ports', description: 'Flexible one-way valve preventing crankcase backflow at rising pressure.', primitive: primitive('valve', { profile: 'reed-pack', width: 0.42, height: 0.24, leaves: 4 }), position: [-0.96, -0.62, 0], material: 'brass', explodedDirection: [-0.95, 0, 0], }), makePart({ id: 'expansion-chamber', name: 'Expansion chamber', group: 'Exhaust tuning', description: 'Tuned exhaust cone reflects pressure waves to improve scavenging over a narrow RPM band.', primitive: primitive('pipe', { radius: 0.18, length: 1.65, axis: 'x', coneSections: 3 }), position: [1.46, 0.42, 0], material: 'hot-metal', explodedDirection: [1.1, 0, 0], }), makePart({ id: 'fuel-air-flow', name: 'Fuel-air charge', group: 'Fluids & energy', description: 'Animated flow volume showing intake, crankcase compression, transfer, and scavenging.', primitive: primitive('flow-path', { segments: ['intake', 'crankcase', 'transfer', 'cylinder'], arrowCount: 16 }), position: [-0.34, 0.05, 0.02], material: 'fluid-amber', explodedDirection: [-0.35, 0.4, 0.4], crossSectionBehavior: 'fluid', }), ]; const dieselParts = [ makePart({ id: 'cylinder-block', name: 'Reinforced cylinder block', group: 'Structure', description: 'Heavy casting designed for high compression ratio and diesel peak cylinder pressure.', primitive: primitive('housing', { width: 2.1, height: 2.55, depth: 1.38, cutaway: true }), position: [0, 0, 0], material: 'cast-iron', explodedDirection: [-0.25, -0.2, 0], }), makePart({ id: 'high-compression-piston', name: 'Bowl piston', group: 'Reciprocating assembly', description: 'Piston with recessed bowl that promotes swirl and mixture formation for compression ignition.', primitive: primitive('cylinder', { radius: 0.45, depth: 0.5, axis: 'y', bowl: true, ringGrooves: 3 }), position: [0, 0.5, 0], material: 'machined-steel', explodedDirection: [0.2, 0.85, 0], }), makePart({ id: 'connecting-rod', name: 'Heavy connecting rod', group: 'Reciprocating assembly', description: 'Stiff rod sized for higher compression and combustion loads than a petrol engine.', primitive: primitive('box', { width: 0.2, height: 1.18, depth: 0.17, rounded: true }), position: [0, -0.27, 0], material: 'brushed-steel', explodedDirection: [0.55, 0, 0], }), makePart({ id: 'crankshaft', name: 'Crankshaft', group: 'Rotating assembly', description: 'Converts pressure on the piston crown into shaft torque through a robust crank throw.', primitive: primitive('custom', { profile: 'single-throw-crankshaft', mainRadius: 0.15, throw: 0.42 }), position: [0, -1.12, 0], rotation: [1.5708, 0, 0], material: 'machined-steel', explodedDirection: [0, -0.75, 0], }), makePart({ id: 'camshaft', name: 'Camshaft', group: 'Valve train', description: 'Half-speed valve timing shaft; diesel breathing resembles a four-stroke petrol engine but without a spark ignition event.', primitive: primitive('cam', { shaftRadius: 0.08, lobeRadius: 0.18, lobes: 2, length: 1.25, axis: 'z' }), position: [0, 1.9, 0], material: 'brushed-steel', explodedDirection: [0, 0.95, 0], }), makePart({ id: 'intake-valve', name: 'Intake valve', group: 'Valve train', description: 'Admits only air; fuel is injected directly near the end of the compression stroke.', primitive: primitive('valve', { stemRadius: 0.035, headRadius: 0.18, length: 0.72, axis: 'y' }), position: [-0.34, 1.05, 0], material: 'titanium', explodedDirection: [-0.55, 0.55, 0], }), makePart({ id: 'exhaust-valve', name: 'Exhaust valve', group: 'Valve train', description: 'Opens to release burned gases after the power stroke.', primitive: primitive('valve', { stemRadius: 0.035, headRadius: 0.18, length: 0.72, axis: 'y' }), position: [0.34, 1.05, 0], material: 'hot-metal', explodedDirection: [0.55, 0.55, 0], }), makePart({ id: 'injector', name: 'Common-rail injector', group: 'Fuel system', description: 'Precisely meters atomised diesel into hot compressed air, initiating auto-ignition.', primitive: primitive('custom', { profile: 'injector', length: 0.86, nozzleRadius: 0.035 }), position: [0, 1.78, 0], material: 'brass', explodedDirection: [0, 1, 0.25], }), makePart({ id: 'glow-plug', name: 'Glow plug', group: 'Fuel system', description: 'Starting aid that heats the chamber in cold conditions; not the normal ignition source.', primitive: primitive('custom', { profile: 'glow-plug', length: 0.55 }), position: [0.23, 1.62, 0.24], rotation: [0.25, 0.12, 0], material: 'ceramic', explodedDirection: [0.25, 0.8, 0.45], }), makePart({ id: 'fuel-rail', name: 'High-pressure fuel rail', group: 'Fuel system', description: 'Accumulator supplying injectors with stable high pressure independent of each injection event.', primitive: primitive('cylinder', { radius: 0.07, depth: 1.55, axis: 'x' }), position: [0, 2.08, 0.38], material: 'brushed-steel', explodedDirection: [0, 0.85, 0.55], }), makePart({ id: 'combustion-bowl', name: 'Diesel combustion bowl', group: 'Fluids & energy', description: 'Glowing bowl volume visualises spray, swirl, compression heat, and diffusion flame.', primitive: primitive('cylinder', { radius: 0.3, depth: 0.18, axis: 'y', bowlVolume: true }), position: [0, 1.02, 0], material: 'combustion-orange', explodedDirection: [0, 0.55, 0.35], crossSectionBehavior: 'fluid', }), makePart({ id: 'flywheel', name: 'Flywheel', group: 'Rotating assembly', description: 'Stores rotational energy to smooth diesel torque pulses and carry the engine through compression.', primitive: primitive('torus', { majorRadius: 0.58, tubeRadius: 0.09, axis: 'z' }), position: [0, -1.1, -0.9], material: 'machined-steel', explodedDirection: [0, 0, -0.9], }), ]; const v8Parts = [ makePart({ id: 'crankcase', name: 'V8 crankcase', group: 'Structure', description: 'Central lower casting holding five main bearings and tying the two cylinder banks together.', primitive: primitive('housing', { width: 2.5, height: 1.3, depth: 2.1, vAngleDegrees: 90, cutaway: true }), position: [0, -0.25, 0], material: 'cast-iron', explodedDirection: [0, -0.45, 0], }), makePart({ id: 'left-cylinder-bank', name: 'Left cylinder bank', group: 'Structure', description: 'Four cylinders arranged on one side of the V for compact packaging and balance.', primitive: primitive('custom', { profile: 'inline-four-bank', cylinders: 4, boreRadius: 0.28, angleDegrees: -45 }), position: [-0.65, 0.72, 0], rotation: [0, 0, -0.7854], material: 'dark-anodized', explodedDirection: [-0.75, 0.45, 0], }), makePart({ id: 'right-cylinder-bank', name: 'Right cylinder bank', group: 'Structure', description: 'Opposite four-cylinder bank; bank angle and crankpin layout define firing character.', primitive: primitive('custom', { profile: 'inline-four-bank', cylinders: 4, boreRadius: 0.28, angleDegrees: 45 }), position: [0.65, 0.72, 0], rotation: [0, 0, 0.7854], material: 'dark-anodized', explodedDirection: [0.75, 0.45, 0], }), makePart({ id: 'crankshaft', name: 'Cross-plane crankshaft', group: 'Rotating assembly', description: 'Four-throw crank with 90-degree phasing for smooth V8 firing intervals.', primitive: primitive('custom', { profile: 'v8-crossplane-crankshaft', throws: 4, length: 2.35 }), position: [0, -0.75, 0], rotation: [1.5708, 0, 0], material: 'machined-steel', explodedDirection: [0, -0.8, 0], }), makePart({ id: 'camshaft', name: 'Camshaft set', group: 'Valve train', description: 'Simplified camshaft pair opening valves at half crank speed across both banks.', primitive: primitive('cam', { shaftRadius: 0.07, lobeRadius: 0.15, lobes: 16, length: 2.1, axis: 'z' }), position: [0, 1.46, 0], material: 'brushed-steel', explodedDirection: [0, 0.9, 0], }), makePart({ id: 'pistons-left-bank', name: 'Left bank pistons', group: 'Reciprocating assembly', description: 'Four grouped pistons animated with correct alternating phase offsets for the firing-order demonstration.', primitive: primitive('custom', { profile: 'piston-array', count: 4, boreRadius: 0.24, spacing: 0.48 }), position: [-0.66, 0.54, 0], rotation: [0, 0, -0.7854], material: 'machined-steel', explodedDirection: [-0.5, 0.75, 0], }), makePart({ id: 'pistons-right-bank', name: 'Right bank pistons', group: 'Reciprocating assembly', description: 'Opposite piston group with staggered phase offsets relative to the left bank.', primitive: primitive('custom', { profile: 'piston-array', count: 4, boreRadius: 0.24, spacing: 0.48 }), position: [0.66, 0.54, 0], rotation: [0, 0, 0.7854], material: 'machined-steel', explodedDirection: [0.5, 0.75, 0], }), makePart({ id: 'connecting-rods', name: 'Connecting rods', group: 'Reciprocating assembly', description: 'Eight rods share paired crank throws in this simplified cross-plane visualisation.', primitive: primitive('custom', { profile: 'v8-rod-array', count: 8, length: 0.88 }), position: [0, -0.05, 0], material: 'brushed-steel', explodedDirection: [0, 0.2, 0.55], }), makePart({ id: 'intake-manifold', name: 'Intake manifold', group: 'Air and exhaust', description: 'Plenum and runners distribute intake charge evenly to all eight cylinders.', primitive: primitive('custom', { profile: 'v8-intake-manifold', runners: 8, plenumRadius: 0.22 }), position: [0, 1.34, 0], material: 'cool-air', explodedDirection: [0, 1, 0], crossSectionBehavior: 'fluid', }), makePart({ id: 'exhaust-headers', name: 'Exhaust headers', group: 'Air and exhaust', description: 'Branching tubes collect hot gases from each bank and visualise pulse timing.', primitive: primitive('custom', { profile: 'v8-headers', primaries: 8, collectorRadius: 0.18 }), position: [0, 0.72, -0.95], material: 'hot-metal', explodedDirection: [0, 0.2, -0.95], }), makePart({ id: 'ignition-coils', name: 'Ignition coils', group: 'Ignition', description: 'Eight coil markers pulse in firing order to connect crank phase with combustion events.', primitive: primitive('custom', { profile: 'coil-array', count: 8, spacing: 0.48 }), position: [0, 1.78, 0.12], material: 'ceramic', explodedDirection: [0, 1.15, 0.25], }), makePart({ id: 'flywheel', name: 'Flywheel', group: 'Rotating assembly', description: 'Output inertia wheel smoothing the eight combustion impulses across two crank revolutions.', primitive: primitive('torus', { majorRadius: 0.62, tubeRadius: 0.08, axis: 'z' }), position: [0, -0.75, -1.36], material: 'machined-steel', explodedDirection: [0, 0, -0.95], }), makePart({ id: 'timing-chain', name: 'Timing chain', group: 'Valve train', description: 'Diagrammatic chain connecting crankshaft to camshafts at a two-to-one speed ratio.', primitive: primitive('belt', { width: 0.08, thickness: 0.03, path: 'front-timing-loop' }), position: [0, 0.35, 1.08], material: 'brushed-steel', explodedDirection: [0, 0.2, 0.9], }), makePart({ id: 'firing-order-indicator', name: 'Firing order indicator', group: 'Learning overlays', description: 'Numbered overlay showing a common V8 firing order; it is intentionally separate from physical geometry.', primitive: primitive('annotation-plane', { width: 1.55, height: 0.28, text: '1-8-4-3-6-5-7-2' }), position: [0, 2.15, 0], material: 'warm-highlight', explodedDirection: [0, 1.2, 0], crossSectionBehavior: 'hidden-when-clipped', }), ]; const wankelParts = [ makePart({ id: 'epitrochoid-housing', name: 'Epitrochoid housing', group: 'Structure', description: 'Rounded triangular chamber profile that keeps rotor apex seals in contact throughout the cycle.', primitive: primitive('housing', { profile: 'epitrochoid', width: 2.45, height: 1.68, depth: 0.74, cutaway: true }), position: [0, 0, 0], material: 'cast-iron', explodedDirection: [0, 0, -0.55], }), makePart({ id: 'triangular-rotor', name: 'Triangular rotor', group: 'Rotating assembly', description: 'Reuleaux-triangle rotor with three working faces, orbiting on an eccentric shaft.', primitive: primitive('rotor', { profile: 'wankel-triangle', radius: 0.72, depth: 0.52, roundedApex: true }), position: [0, 0, 0], material: 'machined-steel', explodedDirection: [0, 0, 0.7], }), makePart({ id: 'eccentric-shaft', name: 'Eccentric shaft', group: 'Rotating assembly', description: 'Output shaft with eccentric journal that converts rotor orbital motion into shaft rotation.', primitive: primitive('custom', { profile: 'eccentric-shaft', radius: 0.12, eccentricity: 0.22, length: 0.96 }), position: [0, 0, 0], rotation: [1.5708, 0, 0], material: 'brushed-steel', explodedDirection: [0, -0.1, 1.05], }), makePart({ id: 'apex-seals', name: 'Apex seals', group: 'Sealing', description: 'Spring-loaded seal strips at each rotor apex separating intake, compression, combustion, and exhaust zones.', primitive: primitive('custom', { profile: 'wankel-apex-seals', count: 3, length: 0.35 }), position: [0, 0, 0.03], material: 'graphite', explodedDirection: [0, 0.55, 0.8], }), makePart({ id: 'intake-port', name: 'Intake port', group: 'Ports', description: 'Side or peripheral opening admitting air-fuel charge as rotor volume expands.', primitive: primitive('box', { width: 0.18, height: 0.46, depth: 0.08, bevel: 0.03 }), position: [-0.96, -0.42, 0.42], material: 'cool-air', explodedDirection: [-0.85, -0.25, 0.25], crossSectionBehavior: 'fluid', }), makePart({ id: 'exhaust-port', name: 'Exhaust port', group: 'Ports', description: 'Opening uncovered near the end of expansion to release high-temperature gases.', primitive: primitive('box', { width: 0.2, height: 0.5, depth: 0.08, bevel: 0.03 }), position: [0.98, -0.35, 0.42], material: 'exhaust-gray', explodedDirection: [0.85, -0.25, 0.25], crossSectionBehavior: 'fluid', }), makePart({ id: 'spark-plugs', name: 'Twin spark plugs', group: 'Ignition', description: 'Two plugs improve flame travel across the long Wankel chamber.', primitive: primitive('custom', { profile: 'spark-plug-pair', count: 2, spacing: 0.22 }), position: [0.55, 0.55, 0.46], rotation: [0.2, 0, -0.45], material: 'ceramic', explodedDirection: [0.55, 0.65, 0.6], }), makePart({ id: 'combustion-chambers', name: 'Three chamber volumes', group: 'Fluids & energy', description: 'Transparent lobed volumes show the three simultaneous working chambers around the rotor.', primitive: primitive('custom', { profile: 'wankel-chamber-volumes', count: 3, opacityGradient: true }), position: [0, 0, 0.08], material: 'combustion-orange', explodedDirection: [0, 0.25, 0.9], crossSectionBehavior: 'fluid', }), makePart({ id: 'oil-metering-nozzle', name: 'Oil metering nozzle', group: 'Lubrication', description: 'Small feed that introduces lubricant for rotor and apex seal life.', primitive: primitive('pipe', { radius: 0.025, length: 0.62, axis: 'x' }), position: [-0.58, 0.72, 0.44], material: 'oil-green', explodedDirection: [-0.35, 0.8, 0.5], }), makePart({ id: 'output-gear', name: 'Stationary/output gear set', group: 'Rotating assembly', description: 'Internal gear relationship constrains the rotor so the eccentric shaft turns three times per rotor revolution.', primitive: primitive('gear', { radius: 0.28, thickness: 0.08, teeth: 30, boreRadius: 0.08, axis: 'z' }), position: [0, 0, 0.44], material: 'brass', explodedDirection: [0, 0, 1.05], }), ]; const steamParts = [ makePart({ id: 'boiler-steam-chest', name: 'Steam chest', group: 'Steam system', description: 'Pressurised chamber supplying steam to the slide valve and cylinder ports.', primitive: primitive('box', { width: 1.35, height: 0.42, depth: 0.55, bevel: 0.06 }), position: [0, 0.78, 0], material: 'brass', explodedDirection: [0, 0.9, 0], }), makePart({ id: 'cylinder', name: 'Double-acting cylinder', group: 'Structure', description: 'Cylinder receives steam alternately on each side of the piston for power in both directions.', primitive: primitive('cylinder', { radius: 0.36, depth: 1.35, axis: 'x', cutaway: true }), position: [-0.48, 0, 0], rotation: [0, 0, 1.5708], material: 'cast-iron', explodedDirection: [-0.6, 0, 0], }), makePart({ id: 'piston', name: 'Steam piston', group: 'Reciprocating assembly', description: 'Disk piston sealed against the cylinder bore and driven by alternating steam pressure.', primitive: primitive('cylinder', { radius: 0.31, depth: 0.18, axis: 'x' }), position: [-0.48, 0, 0], rotation: [0, 0, 1.5708], material: 'machined-steel', explodedDirection: [-0.35, 0.45, 0], }), makePart({ id: 'piston-rod', name: 'Piston rod', group: 'Reciprocating assembly', description: 'Straight rod transmitting piston motion through the gland to the crosshead.', primitive: primitive('cylinder', { radius: 0.045, depth: 1.1, axis: 'x' }), position: [0.13, 0, 0], rotation: [0, 0, 1.5708], material: 'brushed-steel', explodedDirection: [0.25, 0.4, 0], }), makePart({ id: 'connecting-rod', name: 'Connecting rod', group: 'Motion conversion', description: 'Long rod connecting crosshead motion to the crank pin.', primitive: primitive('box', { width: 0.14, height: 0.12, depth: 1.18, rounded: true }), position: [0.78, -0.06, 0], rotation: [0, 1.5708, 0], material: 'brushed-steel', explodedDirection: [0.35, 0.35, 0], }), makePart({ id: 'crankshaft', name: 'Crankshaft', group: 'Motion conversion', description: 'Crank converts reciprocating rod force into flywheel rotation.', primitive: primitive('custom', { profile: 'steam-crank', throw: 0.32, shaftRadius: 0.08 }), position: [1.35, 0, 0], rotation: [1.5708, 0, 0], material: 'machined-steel', explodedDirection: [0.5, -0.3, 0], }), makePart({ id: 'slide-valve', name: 'Slide valve', group: 'Valve gear', description: 'Sliding valve alternately opens steam admission and exhaust passages.', primitive: primitive('box', { width: 0.42, height: 0.16, depth: 0.36, bevel: 0.03 }), position: [-0.48, 0.52, 0], material: 'bronze', explodedDirection: [0, 0.72, 0], }), makePart({ id: 'eccentric', name: 'Eccentric drive', group: 'Valve gear', description: 'Off-centre sheave on the crankshaft drives the valve at the correct phase lead.', primitive: primitive('cam', { shaftRadius: 0.08, lobeRadius: 0.2, lobes: 1, axis: 'z' }), position: [1.35, 0, 0.28], material: 'brass', explodedDirection: [0.45, 0, 0.55], }), makePart({ id: 'flywheel', name: 'Flywheel', group: 'Motion conversion', description: 'Large inertia wheel carries the engine through dead centre and smooths cyclic torque.', primitive: primitive('torus', { majorRadius: 0.65, tubeRadius: 0.07, spokes: 6, axis: 'z' }), position: [1.35, 0, -0.38], material: 'machined-steel', explodedDirection: [0.45, 0, -0.75], }), makePart({ id: 'centrifugal-governor', name: 'Centrifugal governor', group: 'Controls', description: 'Rotating balls rise with speed to throttle steam and stabilise engine RPM.', primitive: primitive('custom', { profile: 'watt-governor', ballRadius: 0.08, armLength: 0.38 }), position: [0.45, 1.13, 0], material: 'brass', explodedDirection: [0.2, 1.1, 0], }), makePart({ id: 'exhaust-stack', name: 'Exhaust stack', group: 'Steam system', description: 'Low-pressure path releasing spent steam after expansion.', primitive: primitive('pipe', { radius: 0.1, length: 0.92, axis: 'y' }), position: [-1.0, 0.98, 0], material: 'exhaust-gray', explodedDirection: [-0.4, 0.85, 0], crossSectionBehavior: 'fluid', }), makePart({ id: 'valve-gear-linkage', name: 'Valve gear linkage', group: 'Valve gear', description: 'Simplified linkage transmitting eccentric motion to the slide valve.', primitive: primitive('custom', { profile: 'steam-valve-linkage', links: 3 }), position: [0.46, 0.48, 0.28], material: 'brushed-steel', explodedDirection: [0.25, 0.45, 0.45], }), ]; const turbojetParts = [ makePart({ id: 'intake-lip', name: 'Intake lip', group: 'Casing', description: 'Aerodynamic inlet that slows and straightens incoming air before compression.', primitive: primitive('torus', { majorRadius: 0.68, tubeRadius: 0.055, axis: 'x' }), position: [-1.78, 0, 0], rotation: [0, 0, 1.5708], material: 'titanium', explodedDirection: [-0.95, 0, 0], }), makePart({ id: 'compressor-spool', name: 'Axial compressor rotor', group: 'Compressor', description: 'Multi-stage rotating blade stack adding pressure and energy to the incoming air.', primitive: primitive('blade-disk', { stages: 7, radius: 0.58, hubRadius: 0.16, bladeTwist: 28, axis: 'x' }), position: [-0.92, 0, 0], rotation: [0, 0, 1.5708], material: 'machined-steel', explodedDirection: [-0.45, 0.2, 0], }), makePart({ id: 'compressor-stators', name: 'Compressor stators', group: 'Compressor', description: 'Stationary vanes between rotor rows convert swirl into pressure and set incidence for the next stage.', primitive: primitive('blade-disk', { stages: 6, radius: 0.6, hubRadius: 0.18, stator: true, axis: 'x' }), position: [-0.8, 0, 0], rotation: [0, 0, 1.5708], material: 'dark-anodized', explodedDirection: [-0.25, 0.45, 0], }), makePart({ id: 'combustion-chamber', name: 'Annular combustor', group: 'Combustion', description: 'Fuel burns in a controlled annulus while only part of the compressed air participates directly in flame.', primitive: primitive('housing', { profile: 'annular-combustor', radius: 0.52, length: 0.72, linerHoles: true }), position: [0.05, 0, 0], rotation: [0, 0, 1.5708], material: 'hot-metal', explodedDirection: [0, 0.65, 0], }), makePart({ id: 'fuel-injector-ring', name: 'Fuel injector ring', group: 'Combustion', description: 'Array of nozzles atomising fuel into the primary combustor zone.', primitive: primitive('custom', { profile: 'injector-ring', nozzles: 12, radius: 0.38 }), position: [-0.22, 0, 0], rotation: [0, 0, 1.5708], material: 'brass', explodedDirection: [0, 0.85, 0], }), makePart({ id: 'turbine-spool', name: 'Turbine rotor', group: 'Turbine', description: 'High-temperature rotor extracts enough power from gas flow to drive the compressor.', primitive: primitive('blade-disk', { stages: 2, radius: 0.52, hubRadius: 0.18, bladeTwist: -34, axis: 'x' }), position: [0.82, 0, 0], rotation: [0, 0, 1.5708], material: 'titanium', explodedDirection: [0.45, 0.25, 0], }), makePart({ id: 'turbine-stators', name: 'Nozzle guide vanes', group: 'Turbine', description: 'Stationary hot-section vanes accelerate and aim combustion gas into turbine blades.', primitive: primitive('blade-disk', { stages: 2, radius: 0.55, hubRadius: 0.18, stator: true, axis: 'x' }), position: [0.62, 0, 0], rotation: [0, 0, 1.5708], material: 'hot-metal', explodedDirection: [0.25, 0.5, 0], }), makePart({ id: 'exhaust-nozzle', name: 'Converging nozzle', group: 'Casing', description: 'Nozzle accelerates remaining gas energy into a high-speed jet that produces thrust.', primitive: primitive('housing', { profile: 'converging-nozzle', inletRadius: 0.52, outletRadius: 0.32, length: 0.82 }), position: [1.55, 0, 0], rotation: [0, 0, 1.5708], material: 'hot-metal', explodedDirection: [0.9, 0, 0], }), makePart({ id: 'core-shaft', name: 'Core shaft', group: 'Shafting', description: 'Coaxial shaft connecting compressor and turbine into one rotating spool.', primitive: primitive('cylinder', { radius: 0.08, depth: 2.35, axis: 'x' }), position: [0, 0, 0], rotation: [0, 0, 1.5708], material: 'brushed-steel', explodedDirection: [0, -0.55, 0], }), makePart({ id: 'airflow-path', name: 'Core airflow path', group: 'Flow', description: 'Animated streamline volume follows air from intake through compression, combustion, turbine, and nozzle.', primitive: primitive('flow-path', { path: 'turbojet-core', arrowCount: 32, colorStages: ['cool', 'compressed', 'hot'] }), position: [0, 0, 0], material: 'cool-air', explodedDirection: [0, 0.35, 0.55], crossSectionBehavior: 'fluid', }), makePart({ id: 'casing', name: 'Cutaway engine casing', group: 'Casing', description: 'Transparent shell preserving the turbojet silhouette while exposing the working core.', primitive: primitive('housing', { profile: 'turbojet-casing', length: 3.7, radius: 0.74, cutaway: true }), position: [0, 0, 0], rotation: [0, 0, 1.5708], material: 'transparent-shell', explodedDirection: [0, 0, -0.75], crossSectionBehavior: 'shell', }), ]; const turbofanParts = [ makePart({ id: 'fan', name: 'Large bypass fan', group: 'Fan and bypass', description: 'Front fan accelerates a large bypass stream around the core, providing most turbofan thrust.', primitive: primitive('blade-disk', { stages: 1, blades: 24, radius: 0.92, hubRadius: 0.18, bladeTwist: 42, axis: 'x' }), position: [-1.75, 0, 0], rotation: [0, 0, 1.5708], material: 'titanium', explodedDirection: [-0.95, 0, 0], }), makePart({ id: 'bypass-duct', name: 'Bypass duct', group: 'Fan and bypass', description: 'Annular outer duct routes fan air around the hot core for propulsive efficiency and noise reduction.', primitive: primitive('housing', { profile: 'annular-duct', length: 3.2, outerRadius: 1.08, innerRadius: 0.58, cutaway: true }), position: [-0.15, 0, 0], rotation: [0, 0, 1.5708], material: 'transparent-shell', explodedDirection: [0, 0, -0.8], crossSectionBehavior: 'shell', }), makePart({ id: 'low-pressure-compressor', name: 'Low-pressure compressor', group: 'Core', description: 'Early compressor stages driven by the low-pressure turbine and fan shaft.', primitive: primitive('blade-disk', { stages: 3, radius: 0.5, hubRadius: 0.16, axis: 'x' }), position: [-0.85, 0, 0], rotation: [0, 0, 1.5708], material: 'machined-steel', explodedDirection: [-0.35, 0.25, 0], }), makePart({ id: 'high-pressure-compressor', name: 'High-pressure compressor', group: 'Core', description: 'Compact multi-stage compressor raising core pressure before combustion.', primitive: primitive('blade-disk', { stages: 6, radius: 0.43, hubRadius: 0.14, axis: 'x' }), position: [-0.28, 0, 0], rotation: [0, 0, 1.5708], material: 'brushed-steel', explodedDirection: [-0.05, 0.45, 0], }), makePart({ id: 'combustor', name: 'Annular combustor', group: 'Core', description: 'Burns fuel in the compressed core stream while maintaining stable flame in a compact liner.', primitive: primitive('housing', { profile: 'annular-combustor', radius: 0.43, length: 0.58, linerHoles: true }), position: [0.34, 0, 0], rotation: [0, 0, 1.5708], material: 'hot-metal', explodedDirection: [0.15, 0.65, 0], }), makePart({ id: 'high-pressure-turbine', name: 'High-pressure turbine', group: 'Core', description: 'Hot turbine stages powering the high-pressure compressor through the inner shaft.', primitive: primitive('blade-disk', { stages: 1, radius: 0.43, hubRadius: 0.17, bladeTwist: -34, axis: 'x' }), position: [0.82, 0, 0], rotation: [0, 0, 1.5708], material: 'titanium', explodedDirection: [0.35, 0.45, 0], }), makePart({ id: 'low-pressure-turbine', name: 'Low-pressure turbine', group: 'Core', description: 'Downstream turbine extracts energy for fan and low-pressure compressor.', primitive: primitive('blade-disk', { stages: 3, radius: 0.5, hubRadius: 0.18, bladeTwist: -28, axis: 'x' }), position: [1.28, 0, 0], rotation: [0, 0, 1.5708], material: 'titanium', explodedDirection: [0.65, 0.25, 0], }), makePart({ id: 'core-shaft', name: 'High-pressure shaft', group: 'Shafting', description: 'Inner shaft couples high-pressure turbine to high-pressure compressor.', primitive: primitive('cylinder', { radius: 0.055, depth: 1.55, axis: 'x' }), position: [0.22, 0, 0], rotation: [0, 0, 1.5708], material: 'brushed-steel', explodedDirection: [0, -0.45, 0], }), makePart({ id: 'fan-shaft', name: 'Fan / LP shaft', group: 'Shafting', description: 'Coaxial outer shaft couples fan, booster, and low-pressure turbine at a different speed.', primitive: primitive('cylinder', { radius: 0.09, depth: 3.15, axis: 'x', hollow: true }), position: [-0.25, 0, 0], rotation: [0, 0, 1.5708], material: 'machined-steel', explodedDirection: [0, -0.75, 0], }), makePart({ id: 'mixer-nozzle', name: 'Mixer and nozzle', group: 'Nozzle', description: 'Final ducts combine or separately exhaust bypass and core streams depending on engine design.', primitive: primitive('housing', { profile: 'mixed-flow-nozzle', inletRadius: 1.02, coreRadius: 0.48, outletRadius: 0.78 }), position: [1.92, 0, 0], rotation: [0, 0, 1.5708], material: 'dark-anodized', explodedDirection: [0.95, 0, 0], }), makePart({ id: 'nacelle', name: 'Nacelle cutaway', group: 'Casing', description: 'Aerodynamic outer shell around fan and bypass duct, shown transparent for education.', primitive: primitive('housing', { profile: 'turbofan-nacelle', length: 3.95, radius: 1.16, cutaway: true }), position: [-0.15, 0, 0], rotation: [0, 0, 1.5708], material: 'transparent-shell', explodedDirection: [0, 0, -1.05], crossSectionBehavior: 'shell', }), makePart({ id: 'bypass-flow', name: 'Bypass airflow', group: 'Flow', description: 'Cool annular flow path representing the high mass of air accelerated by the fan around the core.', primitive: primitive('flow-path', { path: 'bypass-annulus', arrowCount: 36, annular: true }), position: [-0.15, 0, 0], material: 'cool-air', explodedDirection: [0, 0.5, 0.7], crossSectionBehavior: 'fluid', }), makePart({ id: 'core-flow', name: 'Core airflow', group: 'Flow', description: 'Hot core stream through compressor, combustor, turbines, and final nozzle.', primitive: primitive('flow-path', { path: 'turbofan-core', arrowCount: 28, colorStages: ['cool', 'hot'] }), position: [0.15, 0, 0], material: 'combustion-orange', explodedDirection: [0, 0.25, 0.85], crossSectionBehavior: 'fluid', }), ]; export const ENGINE_MACHINES: readonly MachineDefinition[] = [ makeMachine({ id: 'four-stroke-petrol-engine', slug: 'four-stroke-petrol-engine', title: 'Four Stroke Petrol Engine', shortTitle: 'Four Stroke Engine', category: 'engines', difficulty: 'Beginner', complexityScore: 42, sortOrder: 1, releasedAt: '2025-02-01', summary: 'Classic spark-ignition engine showing intake, compression, combustion, and exhaust strokes.', description: 'A cutaway single-cylinder spark-ignition engine that exposes the piston, crankshaft, camshaft, poppet valves, and gas-exchange paths. The animation loops over two crank revolutions so learners can see why the camshaft turns at half speed.', keywords: ['petrol', 'gasoline', 'otto cycle', 'piston', 'crankshaft', 'camshaft', 'valves'], facts: [ { label: 'Cycle', value: '4 strokes over 720° crank rotation', kind: 'note' }, { label: 'Typical efficiency', value: '25-35', unit: '% brake thermal', kind: 'efficiency' }, { label: 'Typical RPM range', value: '800-6500', unit: 'rpm', kind: 'rpm' }, { label: 'Invention milestone', value: '1876 Otto four-stroke engine', kind: 'date' }, { label: 'Common applications', value: 'Cars, motorcycles, generators, small aircraft', kind: 'application' }, ], relatedMachineIds: ['diesel-engine', 'two-stroke-engine', 'slider-crank', 'v8-engine'], thumbnail: thumbnail('Four Stroke Petrol Engine', ['piston', 'crankshaft', 'camshaft'], 'combustion-volume'), parts: fourStrokeParts, hierarchy: hierarchy([ groupNode('structure', 'Structure', ['cylinder-block', 'cylinder-head']), groupNode('rotating', 'Rotating assembly', ['crankshaft', 'flywheel']), groupNode('reciprocating', 'Reciprocating assembly', ['piston', 'connecting-rod']), groupNode('valvetrain', 'Valve train', ['camshaft', 'intake-valve', 'exhaust-valve']), groupNode('ignition-flow', 'Ignition and flow', ['spark-plug', 'combustion-volume', 'intake-manifold', 'exhaust-manifold']), ]), cameraPresets: standardCameraPresets(5.7), guidedTour: [ tourStep('overview', 'Cutaway layout', 'The block is transparent enough to reveal the piston, crank, valves, and the gas paths on both sides.', 'isometric', ['cylinder-block', 'piston', 'crankshaft'], { explodeDistance: 0.15 }), tourStep('intake', 'Intake stroke', 'The intake valve opens while the piston moves down, drawing fresh mixture through the blue manifold.', 'left', ['intake-valve', 'intake-manifold', 'piston'], { playback: 'play', rpm: 900 }), tourStep('compression', 'Compression stroke', 'Both valves close as the piston rises, shrinking the trapped charge before ignition.', 'front', ['piston', 'combustion-volume', 'spark-plug'], { playback: 'play', rpm: 700 }), tourStep('power', 'Combustion and torque', 'A spark ignites the compressed charge near top dead centre and pressure drives the crank through the power stroke.', 'isometric', ['spark-plug', 'combustion-volume', 'crankshaft'], { playback: 'play', rpm: 900 }), tourStep('exhaust', 'Exhaust stroke', 'The exhaust valve opens and the piston pushes burned gases into the hot manifold.', 'right', ['exhaust-valve', 'exhaust-manifold', 'piston'], { playback: 'play', rpm: 900 }), ], animationModuleId: 'anim-four-stroke-petrol-engine', labelsEnabledByDefault: true, }), makeMachine({ id: 'two-stroke-engine', slug: 'two-stroke-engine', title: 'Two Stroke Engine', category: 'engines', difficulty: 'Intermediate', complexityScore: 45, sortOrder: 2, releasedAt: '2025-02-01', summary: 'Port-timed piston engine with one power event per crank revolution.', description: 'A simplified two-stroke engine that uses crankcase pre-compression and cylinder ports rather than a conventional cam-operated valve train. The procedural cutaway emphasises scavenging, transfer-port timing, and the tuned expansion chamber.', keywords: ['two stroke', 'scavenging', 'ports', 'crankcase compression', 'expansion chamber'], facts: [ { label: 'Cycle', value: '2 strokes over 360° crank rotation', kind: 'note' }, { label: 'Typical efficiency', value: '15-30', unit: '% brake thermal', kind: 'efficiency' }, { label: 'Typical RPM range', value: '3000-12000', unit: 'rpm', kind: 'rpm' }, { label: 'Invention milestone', value: 'Late 1870s practical two-stroke concepts', kind: 'date' }, { label: 'Common applications', value: 'Chainsaws, scooters, outboards, model engines', kind: 'application' }, ], relatedMachineIds: ['four-stroke-petrol-engine', 'wankel-rotary-engine', 'piston-pump'], thumbnail: thumbnail('Two Stroke Engine', ['piston', 'transfer-port', 'expansion-chamber'], 'fuel-air-flow'), parts: twoStrokeParts, hierarchy: hierarchy([ groupNode('structure', 'Structure', ['crankcase', 'cylinder-barrel']), groupNode('motion', 'Motion conversion', ['piston', 'connecting-rod', 'crankshaft']), groupNode('ports', 'Port timing', ['intake-port', 'transfer-port', 'exhaust-port', 'reed-valve']), groupNode('combustion-exhaust', 'Combustion and exhaust', ['spark-plug', 'expansion-chamber', 'fuel-air-flow']), ]), cameraPresets: standardCameraPresets(5.4), guidedTour: [ tourStep('overview', 'No camshaft required', 'Piston position opens and closes the ports, replacing much of the four-stroke valve train.', 'isometric', ['cylinder-barrel', 'piston', 'transfer-port']), tourStep('crankcase-pump', 'Crankcase pre-compression', 'As the piston descends it compresses fresh mixture in the sealed crankcase.', 'front', ['crankcase', 'piston', 'fuel-air-flow'], { playback: 'play', rpm: 1800 }), tourStep('scavenging', 'Transfer and scavenging', 'Transfer ports send mixture into the cylinder while exhaust exits through the opposite window.', 'left', ['transfer-port', 'exhaust-port', 'fuel-air-flow'], { playback: 'play', rpm: 1800 }), tourStep('tuned-exhaust', 'Expansion chamber tuning', 'Pressure waves in the cone can push mixture back before the exhaust port closes.', 'right', ['expansion-chamber', 'exhaust-port'], { explodeDistance: 0.25 }), ], animationModuleId: 'anim-two-stroke-engine', labelsEnabledByDefault: true, }), makeMachine({ id: 'diesel-engine', slug: 'diesel-engine', title: 'Diesel Engine', category: 'engines', difficulty: 'Intermediate', complexityScore: 48, sortOrder: 3, releasedAt: '2025-02-01', summary: 'Compression-ignition piston engine with injector timing and high-pressure fuel rail.', description: 'This cutaway diesel variant highlights the higher compression structure, bowl piston, common-rail injector, glow plug starting aid, and air-only intake path. The animation contrasts compression ignition with spark ignition while preserving the familiar four-stroke valve timing.', keywords: ['diesel', 'compression ignition', 'injector', 'common rail', 'piston bowl'], facts: [ { label: 'Cycle', value: '4 strokes over 720° crank rotation', kind: 'note' }, { label: 'Typical efficiency', value: '35-45', unit: '% brake thermal', kind: 'efficiency' }, { label: 'Typical RPM range', value: '600-4500', unit: 'rpm', kind: 'rpm' }, { label: 'Invention milestone', value: '1890s Rudolf Diesel prototypes', kind: 'date' }, { label: 'Common applications', value: 'Trucks, ships, generators, heavy equipment', kind: 'application' }, ], relatedMachineIds: ['four-stroke-petrol-engine', 'piston-pump', 'turbocharger'], thumbnail: thumbnail('Diesel Engine', ['injector', 'high-compression-piston', 'fuel-rail'], 'combustion-bowl'), parts: dieselParts, hierarchy: hierarchy([ groupNode('structure', 'Structure', ['cylinder-block']), groupNode('motion', 'Motion conversion', ['high-compression-piston', 'connecting-rod', 'crankshaft', 'flywheel']), groupNode('valvetrain', 'Valve train', ['camshaft', 'intake-valve', 'exhaust-valve']), groupNode('fuel', 'Fuel and ignition aids', ['injector', 'glow-plug', 'fuel-rail', 'combustion-bowl']), ]), cameraPresets: standardCameraPresets(5.8), guidedTour: [ tourStep('overview', 'Built for pressure', 'The diesel block, rod, and piston bowl are shown larger to communicate high compression loads.', 'isometric', ['cylinder-block', 'high-compression-piston']), tourStep('air-only', 'Air intake only', 'Unlike a petrol engine, the intake stroke draws air without fuel.', 'left', ['intake-valve', 'high-compression-piston'], { playback: 'play', rpm: 800 }), tourStep('compression-heat', 'Compression heat', 'A high compression ratio raises air temperature enough to ignite injected fuel.', 'front', ['high-compression-piston', 'combustion-bowl'], { playback: 'play', rpm: 700 }), tourStep('injection', 'Fuel injection event', 'The injector sprays near top dead centre; combustion begins without a spark plug.', 'top', ['injector', 'fuel-rail', 'combustion-bowl'], { playback: 'play', rpm: 850 }), ], animationModuleId: 'anim-diesel-engine', labelsEnabledByDefault: true, }), makeMachine({ id: 'v8-engine', slug: 'v8-engine', title: 'V8 Engine', category: 'engines', difficulty: 'Advanced', complexityScore: 78, sortOrder: 4, releasedAt: '2025-02-01', summary: 'Two banks of four cylinders with cross-plane crank and firing-order visualisation.', description: 'A diagrammatic V8 assembly showing bank angle, grouped pistons, crankshaft phasing, timing drive, intake manifold, exhaust headers, and a firing-order overlay. The procedural grouping is designed for performance while still exposing the relationships that make a V8 smooth and compact.', keywords: ['v8', 'cross-plane', 'firing order', 'bank angle', 'crankshaft', 'intake manifold'], facts: [ { label: 'Cylinder layout', value: 'Two banks of four cylinders', kind: 'note' }, { label: 'Typical efficiency', value: '25-38', unit: '% brake thermal', kind: 'efficiency' }, { label: 'Typical RPM range', value: '600-7500', unit: 'rpm', kind: 'rpm' }, { label: 'Invention milestone', value: 'Early 1900s production V8s', kind: 'date' }, { label: 'Common applications', value: 'Performance cars, trucks, marine engines', kind: 'application' }, ], relatedMachineIds: ['four-stroke-petrol-engine', 'diesel-engine', 'manual-gearbox-5-speed'], thumbnail: thumbnail('V8 Engine', ['left-cylinder-bank', 'right-cylinder-bank', 'crankshaft'], 'firing-order-indicator'), parts: v8Parts, hierarchy: hierarchy([ groupNode('structure', 'Structure', ['crankcase', 'left-cylinder-bank', 'right-cylinder-bank']), groupNode('rotating', 'Rotating assembly', ['crankshaft', 'flywheel']), groupNode('reciprocating', 'Reciprocating assembly', ['pistons-left-bank', 'pistons-right-bank', 'connecting-rods']), groupNode('air-exhaust', 'Air and exhaust', ['intake-manifold', 'exhaust-headers']), groupNode('ignition-timing', 'Ignition and timing', ['camshaft', 'ignition-coils', 'timing-chain', 'firing-order-indicator']), ]), cameraPresets: standardCameraPresets(6.8), guidedTour: [ tourStep('overview', 'Why a V layout?', 'Two cylinder banks shorten the engine compared with an inline eight while preserving eight power events.', 'isometric', ['left-cylinder-bank', 'right-cylinder-bank', 'crankcase']), tourStep('crank-phasing', 'Cross-plane crankshaft', 'Crank throws are phased to distribute combustion pulses and reduce vibration.', 'front', ['crankshaft', 'pistons-left-bank', 'pistons-right-bank'], { playback: 'play', rpm: 700 }), tourStep('firing-order', 'Firing order overlay', 'Coil markers pulse in the displayed sequence to show how torque impulses move between banks.', 'top', ['ignition-coils', 'firing-order-indicator'], { playback: 'play', rpm: 900 }), tourStep('breathing', 'Intake and headers', 'The manifold feeds all cylinders while headers carry alternating hot pulses away.', 'back', ['intake-manifold', 'exhaust-headers'], { explodeDistance: 0.2 }), ], animationModuleId: 'anim-v8-engine', labelsEnabledByDefault: true, }), makeMachine({ id: 'wankel-rotary-engine', slug: 'wankel-rotary-engine', title: 'Wankel Rotary Engine', category: 'engines', difficulty: 'Advanced', complexityScore: 72, sortOrder: 5, releasedAt: '2025-02-01', summary: 'Eccentric triangular rotor engine with simultaneous chamber events and port timing.', description: 'The Wankel model presents the epitrochoid housing, triangular rotor, eccentric shaft, apex seals, ports, twin spark plugs, and three chamber volumes. Its guided tour explains how rotor orbit and gear ratio produce intake, compression, combustion, and exhaust without reciprocating pistons.', keywords: ['wankel', 'rotary engine', 'epitrochoid', 'apex seals', 'eccentric shaft'], facts: [ { label: 'Cycle', value: 'Three chambers per rotor, one shaft turn per 120° rotor orbit equivalent', kind: 'note' }, { label: 'Typical efficiency', value: '20-30', unit: '% brake thermal', kind: 'efficiency' }, { label: 'Typical RPM range', value: '1000-9000', unit: 'rpm', kind: 'rpm' }, { label: 'Invention milestone', value: '1950s Felix Wankel development', kind: 'date' }, { label: 'Common applications', value: 'Sports cars, UAVs, compact generators', kind: 'application' }, ], relatedMachineIds: ['two-stroke-engine', 'four-stroke-petrol-engine', 'gear-pump'], thumbnail: thumbnail('Wankel Rotary Engine', ['epitrochoid-housing', 'triangular-rotor'], 'combustion-chambers'), parts: wankelParts, hierarchy: hierarchy([ groupNode('structure', 'Structure', ['epitrochoid-housing']), groupNode('rotor', 'Rotor and shaft', ['triangular-rotor', 'eccentric-shaft', 'output-gear']), groupNode('sealing', 'Sealing and lubrication', ['apex-seals', 'oil-metering-nozzle']), groupNode('ports-energy', 'Ports and energy', ['intake-port', 'exhaust-port', 'spark-plugs', 'combustion-chambers']), ]), cameraPresets: standardCameraPresets(4.8), guidedTour: [ tourStep('overview', 'Rotary architecture', 'The rotor orbits inside a fixed epitrochoid housing rather than sliding in a cylinder.', 'isometric', ['epitrochoid-housing', 'triangular-rotor']), tourStep('gear-constraint', 'Eccentric shaft ratio', 'The internal gear relationship makes the shaft spin faster than the rotor while maintaining apex contact.', 'front', ['eccentric-shaft', 'output-gear', 'triangular-rotor'], { playback: 'play', rpm: 900 }), tourStep('three-chambers', 'Three simultaneous chambers', 'Intake, compression, combustion, and exhaust occur in different volumes at the same time.', 'front', ['combustion-chambers', 'intake-port', 'exhaust-port'], { playback: 'play', rpm: 900 }), tourStep('seals', 'Apex seal challenge', 'The seal strips must survive sliding contact, heat, and pressure differences around the rotor.', 'isometric', ['apex-seals', 'epitrochoid-housing'], { explodeDistance: 0.35 }), ], animationModuleId: 'anim-wankel-rotary-engine', labelsEnabledByDefault: true, }), makeMachine({ id: 'steam-engine', slug: 'steam-engine', title: 'Steam Engine', category: 'engines', difficulty: 'Intermediate', complexityScore: 58, sortOrder: 6, releasedAt: '2025-02-01', summary: 'Double-acting reciprocating steam engine with slide valve, flywheel, and governor.', description: 'A classic stationary steam engine assembly showing the steam chest, double-acting cylinder, slide valve, eccentric valve gear, crank, flywheel, and centrifugal governor. The animation emphasises valve lead, alternating steam admission, and flywheel carry-through at dead centre.', keywords: ['steam', 'slide valve', 'flywheel', 'governor', 'double acting', 'industrial revolution'], facts: [ { label: 'Working fluid', value: 'Pressurised steam expanding in a cylinder', kind: 'note' }, { label: 'Typical efficiency', value: '5-15', unit: '% for historic simple engines', kind: 'efficiency' }, { label: 'Typical RPM range', value: '60-300', unit: 'rpm', kind: 'rpm' }, { label: 'Invention milestone', value: '18th-19th century practical development', kind: 'date' }, { label: 'Common applications', value: 'Historic mills, locomotives, pumps, generators', kind: 'application' }, ], relatedMachineIds: ['slider-crank', 'piston-pump', 'scotch-yoke'], thumbnail: thumbnail('Steam Engine', ['cylinder', 'flywheel', 'slide-valve'], 'centrifugal-governor'), parts: steamParts, hierarchy: hierarchy([ groupNode('steam', 'Steam system', ['boiler-steam-chest', 'exhaust-stack']), groupNode('cylinder', 'Cylinder assembly', ['cylinder', 'piston', 'piston-rod']), groupNode('motion', 'Motion conversion', ['connecting-rod', 'crankshaft', 'flywheel']), groupNode('valve-gear', 'Valve gear and control', ['slide-valve', 'eccentric', 'valve-gear-linkage', 'centrifugal-governor']), ]), cameraPresets: standardCameraPresets(5.8), guidedTour: [ tourStep('overview', 'Steam power path', 'Steam from the chest is valved into a double-acting cylinder and exhausted after expansion.', 'isometric', ['boiler-steam-chest', 'cylinder', 'exhaust-stack']), tourStep('slide-valve', 'Slide valve timing', 'The valve shifts before the piston reaches stroke end so pressure is ready on the next side.', 'front', ['slide-valve', 'valve-gear-linkage', 'eccentric'], { playback: 'play', rpm: 110 }), tourStep('crank', 'Reciprocating to rotary', 'The piston rod and connecting rod drive the crank while the flywheel smooths speed.', 'right', ['piston', 'connecting-rod', 'crankshaft', 'flywheel'], { playback: 'play', rpm: 120 }), tourStep('governor', 'Speed regulation', 'Governor balls rise with RPM, representing the feedback mechanism that throttles steam admission.', 'isometric', ['centrifugal-governor'], { playback: 'play', rpm: 150, explodeDistance: 0.2 }), ], animationModuleId: 'anim-steam-engine', labelsEnabledByDefault: true, }), makeMachine({ id: 'turbojet-engine', slug: 'turbojet-engine', title: 'Jet Engine (Turbojet)', shortTitle: 'Turbojet Engine', category: 'engines', difficulty: 'Advanced', complexityScore: 82, sortOrder: 7, releasedAt: '2025-02-01', summary: 'Single-spool turbojet showing intake, compressor, combustor, turbine, and nozzle flow.', description: 'The turbojet cutaway arranges compressor stages, combustor, turbine stages, core shaft, casing, and a coloured airflow path along one axis. It is intentionally diagrammatic so learners can see pressure rise, heat addition, turbine work extraction, and nozzle acceleration at once.', keywords: ['turbojet', 'gas turbine', 'compressor', 'combustor', 'turbine', 'nozzle', 'thrust'], facts: [ { label: 'Thermodynamic cycle', value: 'Open Brayton cycle', kind: 'note' }, { label: 'Typical thermal efficiency', value: '20-35', unit: '% depending on era and pressure ratio', kind: 'efficiency' }, { label: 'Spool speed', value: '8000-20000', unit: 'rpm small/medium engines', kind: 'rpm' }, { label: 'Invention milestone', value: '1930s Whittle and von Ohain gas turbines', kind: 'date' }, { label: 'Common applications', value: 'Historic jets, missiles, test rigs', kind: 'application' }, ], relatedMachineIds: ['turbofan-engine', 'turbocharger', 'centrifugal-pump'], thumbnail: thumbnail('Jet Engine (Turbojet)', ['compressor-spool', 'combustion-chamber', 'turbine-spool'], 'airflow-path'), parts: turbojetParts, hierarchy: hierarchy([ groupNode('casing', 'Casing and nozzle', ['intake-lip', 'casing', 'exhaust-nozzle']), groupNode('compressor', 'Compressor', ['compressor-spool', 'compressor-stators']), groupNode('combustion', 'Combustion', ['combustion-chamber', 'fuel-injector-ring']), groupNode('turbine', 'Turbine and shaft', ['turbine-stators', 'turbine-spool', 'core-shaft']), groupNode('flow', 'Flow path', ['airflow-path']), ]), cameraPresets: standardCameraPresets(6.3), guidedTour: [ tourStep('overview', 'Gas path overview', 'Air travels left to right through intake, compressor, combustor, turbine, and nozzle.', 'isometric', ['airflow-path', 'casing']), tourStep('compression', 'Compressor pressure rise', 'Rotor and stator rows progressively raise pressure before combustion.', 'left', ['compressor-spool', 'compressor-stators'], { playback: 'play', rpm: 9000 }), tourStep('combustion', 'Heat addition', 'Fuel nozzles feed an annular combustor; flow remains continuous rather than pulsed.', 'front', ['combustion-chamber', 'fuel-injector-ring', 'airflow-path'], { playback: 'play', rpm: 9000 }), tourStep('turbine-nozzle', 'Turbine and thrust', 'The turbine powers the compressor and the nozzle converts remaining gas energy into jet velocity.', 'right', ['turbine-spool', 'core-shaft', 'exhaust-nozzle'], { playback: 'play', rpm: 9000 }), ], animationModuleId: 'anim-turbojet-engine', labelsEnabledByDefault: true, }), makeMachine({ id: 'turbofan-engine', slug: 'turbofan-engine', title: 'Turbofan Engine', category: 'engines', difficulty: 'Advanced', complexityScore: 88, sortOrder: 8, releasedAt: '2025-02-01', summary: 'High-bypass turbofan with fan, bypass duct, core flow split, and twin-spool behaviour.', description: 'A cutaway high-bypass turbofan model showing fan, bypass duct, low- and high-pressure compressors, combustor, turbine sections, coaxial shafts, nacelle, and separate bypass/core flow streams. The animation highlights why modern turbofans move more air at lower jet velocity than pure turbojets.', keywords: ['turbofan', 'bypass ratio', 'fan', 'gas turbine', 'compressor', 'turbine', 'nacelle'], facts: [ { label: 'Cycle', value: 'Brayton core plus bypass propulsor', kind: 'note' }, { label: 'Propulsive efficiency', value: 'High at subsonic flight due to large mass flow', kind: 'efficiency' }, { label: 'Fan speed', value: '2000-5000', unit: 'rpm large engines', kind: 'rpm' }, { label: 'Invention milestone', value: '1950s-1960s practical turbofan adoption', kind: 'date' }, { label: 'Common applications', value: 'Commercial aircraft, business jets, military transports', kind: 'application' }, ], relatedMachineIds: ['turbojet-engine', 'turbocharger', 'centrifugal-pump'], thumbnail: thumbnail('Turbofan Engine', ['fan', 'bypass-duct', 'core-flow'], 'bypass-flow'), parts: turbofanParts, hierarchy: hierarchy([ groupNode('fan-bypass', 'Fan and bypass', ['fan', 'bypass-duct', 'bypass-flow']), groupNode('core', 'Gas generator core', ['low-pressure-compressor', 'high-pressure-compressor', 'combustor', 'high-pressure-turbine', 'low-pressure-turbine', 'core-flow']), groupNode('shafts', 'Coaxial shafts', ['core-shaft', 'fan-shaft']), groupNode('casing-nozzle', 'Casing and nozzle', ['nacelle', 'mixer-nozzle']), ]), cameraPresets: standardCameraPresets(7.1), guidedTour: [ tourStep('overview', 'Bypass versus core', 'The large fan splits air into a cool bypass stream and a smaller hot core stream.', 'isometric', ['fan', 'bypass-flow', 'core-flow']), tourStep('fan', 'Fan thrust', 'Most thrust comes from accelerating bypass air through the annular duct.', 'front', ['fan', 'bypass-duct', 'bypass-flow'], { playback: 'play', rpm: 2500 }), tourStep('core', 'Gas generator', 'The core compresses, burns, and expands air to power both turbine spools.', 'left', ['high-pressure-compressor', 'combustor', 'high-pressure-turbine'], { playback: 'play', rpm: 7000 }), tourStep('twin-spool', 'Different shaft speeds', 'The HP and LP shaft groups rotate at different multipliers, matching real turbofan architecture.', 'right', ['core-shaft', 'fan-shaft', 'low-pressure-turbine'], { playback: 'play', rpm: 4500, explodeDistance: 0.22 }), ], animationModuleId: 'anim-turbofan-engine', labelsEnabledByDefault: true, }), ];