// Original educational geometry. All units are normalized, not machining dimensions.
export const CONFIG = Object.freeze({ radius: 0.72, rodLength: 2.65, boreRadius: 0.67, bankAngle: 45, journalPitch: 1.62, maxLift: 0.24 });
export const FIRING_ORDER = Object.freeze([1,8,4,3,6,5,7,2]);
export const JOURNAL_PHASES = Object.freeze([45,135,315,225]);
export const STROKES = Object.freeze(['Power','Exhaust','Intake','Compression']);
export const mod = (a, n) => ((a % n) + n) % n;
export const radians = a => a * Math.PI / 180;
export function cylinderState(angle, cylinder, config=CONFIG) {
  if (!Number.isFinite(angle) || !Number.isInteger(cylinder) || cylinder<1 || cylinder>8) throw new RangeError('Finite angle and cylinder 1–8 required');
  const bank = cylinder % 2 ? 1 : -1;
  const beta = radians(bank * config.bankAngle);
  const journal = Math.floor((cylinder-1)/2);
  const a = radians(angle + JOURNAL_PHASES[journal]);
  const alpha = a-beta;
  const s = config.radius*Math.cos(alpha)+Math.sqrt(config.rodLength**2-(config.radius*Math.sin(alpha))**2);
  const z = (1.5-journal)*config.journalPitch + bank*0.16;
  const phase = mod(angle-FIRING_ORDER.indexOf(cylinder)*90,720);
  const lift = (start) => phase>=start && phase<start+180 ? config.maxLift * Math.sin(Math.PI*(phase-start)/180)**2 : 0;
  return { cylinder, bank, beta, journal, phase, stroke:STROKES[Math.floor(phase/180)], strokeIndex:Math.floor(phase/180), s, z,
    normal:[Math.sin(beta),Math.cos(beta),0],
    pin:[config.radius*Math.sin(a),config.radius*Math.cos(a),z],
    wrist:[s*Math.sin(beta),s*Math.cos(beta),z],
    intakeLift:lift(360), exhaustLift:lift(180),
    camAngle: angle/2, travel:config.rodLength+config.radius-s };
}
export const smooth = x => { x=Math.max(0,Math.min(1,x));return x*x*(3-2*x); };
// Reproducible 18-second route through the same engine; capture and interactive tour share it.
export function tourState(t) {
  t=Math.max(0,Math.min(18,t));
  const cut=smooth((t-2.4)/1.6)*(1-smooth((t-15.2)/1.7));
  const explode=smooth((t-11.7)/1.2)*(1-smooth((t-14.7)/1.2));
  const angle=t<6 ? t*54 : t<12 ? 324+(t-6)*120 : 1044+(t-12)*54;
  const stage=t<2.4?0:t<6?1:t<11.7?2:t<15.2?3:4;
  return {time:t,angle:mod(angle,720),cut,explode,stage,view:explode>.3?'exploded':cut>.5?'section':'assembled',azimuth:0.72+0.22*Math.sin(t/18*Math.PI*2),zoom:1-0.09*smooth((t-6)/1.3)+0.09*smooth((t-11)/.7)};
}
