import * as THREE from './vendor/three.module.min.js';

const SKIN = 0xf2b781;
const SKIN_DARK = 0x8a4f29;
const GLOW = 0xffcc33;
const DEFAULT_MODEL_URL = './assets/six-seven-overkill-hands.glb';
const DEFAULT_ANATOMICAL_MODEL_URL = './assets/six-seven-open-empty-hand.glb';
const ANATOMICAL_BASE_X_DEG = 75;
const ANATOMICAL_PAIR_X_LOCK_DEG = 38;
const ANATOMICAL_FLAGPOLE_TRAVEL = 0.46;
const HAND_BODY_D =
  'M8 12.9v-7.4c0-1.34 1.04-2.38 2.38-2.38c.25 0 .5 .04 .72 .12v-.06' +
  'c0-1.46 1.16-2.62 2.62-2.62s2.62 1.16 2.62 2.62v.72c.24-.08 .5-.12 .76-.12' +
  'c1.34 0 2.38 1.04 2.38 2.38v.58c.24-.08 .49-.12 .76-.12' +
  'c1.34 0 2.38 1.04 2.38 2.38v7.1c0 4.17-3.33 7.5-7.5 7.5h-2.9' +
  'c-2.75 0-5.3-1.44-6.68-3.82l-3.45-6.0c-.75-1.31-.31-2.97 .98-3.74' +
  'c1.09-.65 2.49-.49 3.41 .38l1.52 1.5z';
const HAND_LINE_PATHS = [
  'M8 13v-7.5a1.5 1.5 0 0 1 3 0v6.5',
  'M11 5.5v-2a1.5 1.5 0 1 1 3 0v8.5',
  'M14 5.5a1.5 1.5 0 0 1 3 0v6.5',
];
const GLB_MAGIC = 0x46546c67;
const GLB_JSON = 0x4e4f534a;
const GLB_BIN = 0x004e4942;
const GLTF_COMPONENTS = {
  5120: { array: Int8Array, bytes: 1, read: 'getInt8' },
  5121: { array: Uint8Array, bytes: 1, read: 'getUint8' },
  5122: { array: Int16Array, bytes: 2, read: 'getInt16' },
  5123: { array: Uint16Array, bytes: 2, read: 'getUint16' },
  5125: { array: Uint32Array, bytes: 4, read: 'getUint32' },
  5126: { array: Float32Array, bytes: 4, read: 'getFloat32' },
};
const GLTF_TYPE_SIZES = {
  SCALAR: 1,
  VEC2: 2,
  VEC3: 3,
  VEC4: 4,
  MAT2: 4,
  MAT3: 9,
  MAT4: 16,
};

function normalizeOverkillMode(mode) {
  if (mode === 'ultimate' || mode === 'anatomical') return mode;
  return 'normal';
}

function normalizePropMode(mode) {
  return mode === 'splitJuggle' ? 'splitJuggle' : 'center67';
}

function wrapUnit(n) {
  return ((n % 1) + 1) % 1;
}

function clampNumber(value, min, max, fallback) {
  const n = Number(value);
  if (!Number.isFinite(n)) return fallback;
  return Math.max(min, Math.min(max, n));
}

function rangeMid(range, fallback) {
  if (!range || typeof range !== 'object') return fallback;
  const min = Number(range.min);
  const max = Number(range.max);
  if (!Number.isFinite(min) || !Number.isFinite(max)) return fallback;
  return (min + max) / 2;
}

function normalizeMotionConfig(config) {
  const read = (rangeKey, directKey, fallback) => {
    const direct = config && config[directKey];
    if (Number.isFinite(Number(direct))) return Number(direct);
    return rangeMid(config && config[rangeKey], fallback);
  };

  return {
    paceMs: clampNumber(read('pace', 'paceMs', 385), 180, 2000, 385),
    bobPx: clampNumber(read('bob', 'bobPx', 16.5), 0, 40, 16.5),
    driftPx: clampNumber(read('drift', 'driftPx', 10), 0, 30, 10),
    wobblePaceMs: clampNumber(read('wobblePace', 'wobblePaceMs', 1360), 360, 10000, 1360),
    wobbleAmpDeg: clampNumber(read('wobbleAmp', 'wobbleAmpDeg', 8.5), 0, 32, 8.5),
    wobbleJoltPx: clampNumber(read('wobbleJolt', 'wobbleJoltPx', 0.77), 0, 2.2, 0.77),
    tiltXDeg: clampNumber(read('tiltX', 'tiltXDeg', 4), 0, 14, 4),
    tiltSkewDeg: clampNumber(read('tiltSkew', 'tiltSkewDeg', 1.5), 0, 10, 1.5),
  };
}

function tremor(t, seed, motionConfig, mode) {
  const paceHz = 1000 / Math.max(360, motionConfig.wobblePaceMs);
  const freq = Math.max(9.5, Math.min(34, paceHz * 20));
  const ampScale = mode === 'ultimate' ? 0.95 : mode === 'anatomical' ? 0.82 : 0.68;
  const joltScale = mode === 'ultimate' ? 0.07 : mode === 'anatomical' ? 0.055 : 0.052;
  const amp = THREE.MathUtils.degToRad(motionConfig.wobbleAmpDeg) * ampScale;
  const jolt = motionConfig.wobbleJoltPx * joltScale;
  const a = t * freq + seed;
  const b = t * freq * 1.83 + seed * 1.7;
  const c = t * freq * 3.17 + seed * 2.3;
  const rough = Math.sin(a) * 0.62 + Math.sin(b) * 0.28 + Math.sin(c) * 0.14;
  const snap = Math.sign(Math.sin(t * freq * 0.72 + seed)) * 0.18;
  return {
    x: (rough + snap) * jolt,
    y: (Math.sin(b + 1.3) * 0.72 + snap * 0.34) * jolt,
    z: Math.sin(c + 2.1) * jolt * 1.35,
    rx: Math.sin(b + 0.4) * amp * 0.72,
    ry: Math.sin(c + 0.9) * amp * 0.88,
    rz: (rough + snap) * amp,
  };
}

function createMotionPhase(timeMs, mode, motionConfig = normalizeMotionConfig()) {
  const t = timeMs * 0.001;
  const rateFromPace = (Math.PI * 2) / Math.max(0.18, motionConfig.paceMs * 0.001);
  const modeRateScale = mode === 'ultimate' ? 1.12 : mode === 'anatomical' ? 0.92 : 1;
  const handRate = THREE.MathUtils.clamp(rateFromPace * modeRateScale, 7.2, 30);
  const rightOffset = mode === 'ultimate' ? Math.PI * 0.92 : Math.PI;
  const leftAngle = t * handRate;
  const rightAngle = leftAngle + rightOffset;
  const leftHandY = Math.sin(leftAngle);
  const rightHandY = Math.sin(rightAngle);
  const phaseFor = (angle, handY, offset = 0) => {
    const throwLift = Math.pow(Math.max(0, Math.sin(angle - 0.22 + offset)), 0.68);
    return {
      angle,
      handY,
      jab: Math.max(0, Math.sin(t * (mode === 'ultimate' ? 5.6 : 5.4) + offset)),
      contact: (handY + 1) / 2,
      throwLift,
      impact: 1 - throwLift,
    };
  };
  return {
    t,
    primaryPhase: wrapUnit(leftAngle / (Math.PI * 2)),
    left: phaseFor(leftAngle, leftHandY, 0),
    right: phaseFor(rightAngle, rightHandY, rightOffset * (mode === 'ultimate' ? 0.4 : 0.35)),
    intensity: mode === 'ultimate' ? 1.28 : mode === 'anatomical' ? 0.82 : 1,
    motionConfig,
  };
}

function mat(color, roughness = 0.58, metalness = 0.02) {
  return new THREE.MeshStandardMaterial({
    color,
    roughness,
    metalness,
  });
}

function addCapsule(group, name, radius, length, material, position, rotation = [0, 0, 0], scale = [1, 1, 1]) {
  const geo = new THREE.CapsuleGeometry(radius, length, 10, 18);
  const mesh = new THREE.Mesh(geo, material);
  mesh.name = name;
  mesh.position.set(...position);
  mesh.rotation.set(...rotation);
  mesh.scale.set(...scale);
  mesh.castShadow = true;
  mesh.receiveShadow = true;
  group.add(mesh);
  return mesh;
}

function addCapsuleBetween(group, name, radius, start, end, material, scale = [1, 1, 1]) {
  const a = new THREE.Vector3(...start);
  const b = new THREE.Vector3(...end);
  const mid = a.clone().lerp(b, 0.5);
  const dir = b.clone().sub(a);
  const length = Math.max(0.01, dir.length() - radius * 2);
  const mesh = new THREE.Mesh(new THREE.CapsuleGeometry(radius, length, 10, 18), material);
  mesh.name = name;
  mesh.position.copy(mid);
  mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.normalize());
  mesh.scale.set(...scale);
  mesh.castShadow = true;
  mesh.receiveShadow = true;
  group.add(mesh);
  return mesh;
}

function addPalm(group, side, material, outlineMaterial) {
  const palm = new THREE.Group();
  palm.name = `${side}-palm`;

  const palmShadow = new THREE.Mesh(new THREE.SphereGeometry(0.78, 30, 22), outlineMaterial);
  palmShadow.name = `${side}-palm-outline`;
  palmShadow.position.set(0.04, -0.02, -0.035);
  palmShadow.scale.set(0.92, 1.1, 0.18);
  palm.add(palmShadow);

  const palmBody = new THREE.Mesh(new THREE.SphereGeometry(0.72, 30, 22), material);
  palmBody.name = `${side}-palm-body`;
  palmBody.position.set(0, 0, 0.02);
  palmBody.scale.set(0.88, 1.04, 0.2);
  palmBody.castShadow = true;
  palmBody.receiveShadow = true;
  palm.add(palmBody);

  const fingerXs = [-0.39, -0.13, 0.13, 0.39];
  fingerXs.forEach((x, i) => {
    const len = 0.86 - i * 0.035;
    const finger = new THREE.Group();
    finger.name = `${side}-finger-${i + 1}`;
    finger.position.set(x, -0.63, 0.08);
    finger.rotation.z = (i - 1.5) * 0.045;
    addCapsule(finger, `${side}-finger-${i + 1}-skin`, 0.13, len, material, [0, -0.26, 0], [0, 0, 0], [1, 1, 0.78]);
    addCapsule(finger, `${side}-finger-${i + 1}-line`, 0.032, len * 0.7, outlineMaterial, [0, -0.25, 0.145], [0, 0, 0], [0.82, 1, 0.68]);
    palm.add(finger);
  });

  const thumb = new THREE.Group();
  thumb.name = `${side}-thumb`;
  thumb.position.set(-0.58, -0.28, 0.09);
  thumb.rotation.set(0.05, 0.04, 0.88);
  addCapsule(thumb, `${side}-thumb-outline`, 0.17, 0.7, outlineMaterial, [0.035, -0.18, -0.025], [0, 0, 0], [1, 1, 0.72]);
  addCapsule(thumb, `${side}-thumb-skin`, 0.15, 0.68, material, [0, -0.18, 0.02], [0, 0, 0], [1, 1, 0.72]);
  palm.add(thumb);

  group.add(palm);
  return palm;
}

function createHandTexture() {
  const canvas = document.createElement('canvas');
  canvas.width = 256;
  canvas.height = 256;
  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.scale(canvas.width / 28, canvas.height / 28);
  ctx.translate(2, 2);
  ctx.translate(12, 12);
  ctx.rotate(Math.PI);
  ctx.translate(-12, -12);

  const gradient = ctx.createLinearGradient(6, 0, 20, 24);
  gradient.addColorStop(0, '#ffe070');
  gradient.addColorStop(0.48, '#f6c84c');
  gradient.addColorStop(1, '#c98224');
  ctx.fillStyle = gradient;
  ctx.fill(new Path2D(HAND_BODY_D));

  ctx.strokeStyle = '#120c05';
  ctx.lineWidth = 1.62;
  ctx.lineCap = 'round';
  ctx.lineJoin = 'round';
  ctx.stroke(new Path2D(HAND_BODY_D));
  for (const d of HAND_LINE_PATHS) ctx.stroke(new Path2D(d));

  const texture = new THREE.CanvasTexture(canvas);
  texture.colorSpace = THREE.SRGBColorSpace;
  texture.minFilter = THREE.LinearFilter;
  texture.magFilter = THREE.LinearFilter;
  texture.generateMipmaps = false;
  return texture;
}

function tokenFont(fontSize) {
  return `italic 900 ${fontSize}px system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
}

function fitTokenFont(ctx, glyph, maxWidth) {
  let fontSize = glyph.length <= 2 ? 196 : 178;
  while (fontSize > 58) {
    ctx.font = tokenFont(fontSize);
    if (ctx.measureText(glyph).width <= maxWidth) break;
    fontSize -= 4;
  }
  return fontSize;
}

function tokenWidthForTexture(texture) {
  const aspect = texture && texture.userData && Number(texture.userData.aspect);
  return THREE.MathUtils.clamp(Number.isFinite(aspect) ? aspect * 1.06 : 2.42, 2.42, 5.2);
}

function createTokenTexture(text = '6/7') {
  const glyph = String(text || '6/7').trim() || '6/7';
  const measuringCanvas = document.createElement('canvas');
  const measuringCtx = measuringCanvas.getContext('2d');
  const maxTextWidth = glyph.length <= 2 ? 460 : 980;
  const fontSize = fitTokenFont(measuringCtx, glyph, maxTextWidth);
  measuringCtx.font = tokenFont(fontSize);
  const measuredWidth = Math.ceil(measuringCtx.measureText(glyph).width);
  const padding = Math.max(72, Math.round(fontSize * 0.52));
  const width = THREE.MathUtils.clamp(measuredWidth + padding, 512, 1152);
  const height = 256;
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.font = tokenFont(fontSize);
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.lineJoin = 'round';
  ctx.lineWidth = Math.max(5, Math.round(fontSize * 0.07));
  ctx.strokeStyle = 'rgba(0, 0, 0, 0.14)';
  ctx.fillStyle = '#ffcc33';
  ctx.strokeText(glyph, width / 2, 132);
  ctx.fillText(glyph, width / 2, 132);

  const texture = new THREE.CanvasTexture(canvas);
  texture.colorSpace = THREE.SRGBColorSpace;
  texture.minFilter = THREE.LinearFilter;
  texture.magFilter = THREE.LinearFilter;
  texture.generateMipmaps = false;
  texture.userData.aspect = width / height;
  texture.userData.text = glyph;
  return texture;
}

function createToonHandRig(sideSign, texture) {
  const rig = new THREE.Group();
  rig.name = sideSign < 0 ? 'left-toon-hand-rig' : 'right-toon-hand-rig';

  const handPivot = new THREE.Group();
  handPivot.name = sideSign < 0 ? 'left-hand-pivot' : 'right-hand-pivot';
  handPivot.position.set(sideSign * 1.28, -0.36, -0.85);
  handPivot.rotation.set(-0.34, sideSign * -0.18, sideSign * 0.06);

  const geometry = new THREE.PlaneGeometry(2.25, 2.25);
  const handMaterial = new THREE.MeshBasicMaterial({
    map: texture,
    transparent: true,
    alphaTest: 0.02,
    side: THREE.DoubleSide,
    depthWrite: false,
    toneMapped: false,
  });
  const shadowMaterial = new THREE.MeshBasicMaterial({
    map: texture,
    color: 0x000000,
    transparent: true,
    opacity: 0.24,
    alphaTest: 0.02,
    side: THREE.DoubleSide,
    depthWrite: false,
    toneMapped: false,
  });

  const shadow = new THREE.Mesh(geometry, shadowMaterial);
  shadow.name = sideSign < 0 ? 'left-hand-soft-shadow' : 'right-hand-soft-shadow';
  shadow.position.set(0.07, -0.09, -0.08);
  shadow.scale.setScalar(1.08);

  const hand = new THREE.Mesh(geometry, handMaterial);
  hand.name = sideSign < 0 ? 'left-hand-silhouette' : 'right-hand-silhouette';
  hand.position.z = 0.01;

  handPivot.add(shadow, hand);
  rig.add(handPivot);
  return { rig, handPivot };
}

function createToken(texture, text = '6/7') {
  const token = new THREE.Group();
  const isCenterToken = text === '6/7' || text === '67';
  token.name = isCenterToken ? 'floating-6-7-token' : `floating-${text}-token`;
  const material = new THREE.MeshBasicMaterial({
    map: texture,
    transparent: true,
    alphaTest: 0.02,
    side: THREE.DoubleSide,
    depthWrite: false,
    depthTest: false,
    toneMapped: false,
  });
  const width = tokenWidthForTexture(texture);
  const mesh = new THREE.Mesh(new THREE.PlaneGeometry(width, 1.06), material);
  mesh.name = isCenterToken ? 'floating-6-7-canvas-text' : `floating-${text}-canvas-text`;
  mesh.renderOrder = 4;
  token.userData.textMesh = mesh;
  token.add(mesh);
  token.position.set(0, 0.24, 0.28);
  return token;
}

function createTokenPropSystem(root, initialMode) {
  let mode = normalizePropMode(initialMode);
  const group = new THREE.Group();
  group.name = 'six-seven-prop-layer';
  root.add(group);

  const textures = {
    center: createTokenTexture('6/7'),
    six: createTokenTexture('6'),
    seven: createTokenTexture('7'),
  };
  const center = createToken(textures.center, '6/7');
  const six = createToken(textures.six, '6');
  const seven = createToken(textures.seven, '7');
  group.add(center, six, seven);
  let centerText = '6/7';

  function setMode(nextMode) {
    mode = normalizePropMode(nextMode);
  }

  function setCenterText(nextText) {
    const normalized = String(nextText || '6/7').trim() || '6/7';
    if (normalized === centerText) return;
    centerText = normalized;
    const previousTexture = textures.center;
    textures.center = createTokenTexture(centerText);
    const mesh = center.userData.textMesh;
    if (mesh) {
      mesh.material.map = textures.center;
      mesh.material.needsUpdate = true;
      mesh.geometry.dispose();
      mesh.geometry = new THREE.PlaneGeometry(tokenWidthForTexture(textures.center), 1.06);
    }
    previousTexture.dispose();
  }

  function centerPose(phase, overkillMode) {
    const t = phase.t;
    if (overkillMode === 'ultimate') {
      center.position.set(0, 1.22 + Math.sin(t * 10.5) * 0.04, 1.05);
      center.scale.setScalar(1.08 + Math.sin(t * 12.4) * 0.06);
      center.rotation.z = 0;
    } else if (overkillMode === 'anatomical') {
      center.position.set(0, 0.92 + Math.sin(t * 6.1) * 0.018, 1.08);
      center.scale.setScalar(0.96 + Math.sin(t * 14.2) * 0.045);
      center.rotation.z = 0;
    } else {
      center.position.set(0, 1.12, 0.42);
      center.scale.setScalar(0.94 + Math.sin(t * 9.7) * 0.045);
      center.rotation.z = 0;
    }
  }

  function splitPose(token, side, handPhase, overkillMode) {
    const lift = handPhase.throwLift;
    const impact = handPhase.impact;
    const depthBase = overkillMode === 'anatomical' ? 1.34 : overkillMode === 'ultimate' ? 1.12 : 0.38;
    const yBase = overkillMode === 'anatomical' ? -0.08 : overkillMode === 'ultimate' ? 0.0 : 0.12;
    const spread = overkillMode === 'anatomical' ? 0.74 : overkillMode === 'ultimate' ? 0.82 : 0.62;
    const palmLift = overkillMode === 'anatomical' ? 0.24 : overkillMode === 'ultimate' ? 0.42 : 0.3;
    const contactClearance = overkillMode === 'anatomical' ? 0.28 : overkillMode === 'ultimate' ? 0.2 : 0.24;
    const throwHeight = overkillMode === 'anatomical' ? 1.42 : overkillMode === 'ultimate' ? 1.14 : 1.02;
    const throwDepth = overkillMode === 'anatomical' ? 0.74 : overkillMode === 'ultimate' ? 0.68 : 0.62;
    const depthClearance = overkillMode === 'anatomical' ? 0.24 : overkillMode === 'ultimate' ? 0.16 : 0.18;
    const arc = Math.sin(handPhase.angle * 0.5) * 0.18;
    token.position.x = side * (spread + 0.06 + lift * (overkillMode === 'anatomical' ? 0.36 : 0.3) + arc);
    token.position.y = yBase + handPhase.handY * palmLift + contactClearance + lift * throwHeight - impact * 0.02;
    token.position.z = depthBase + depthClearance + lift * throwDepth;
    token.rotation.z = side * (handPhase.angle * -0.94 + lift * 0.88 + Math.sin(handPhase.angle * 2.3) * 0.16);
    token.rotation.y = side * (lift * 0.48 + Math.sin(handPhase.angle * 0.5) * 0.2);
    token.rotation.x = impact * -0.18 + lift * 0.32;
    token.scale.setScalar(0.88 + lift * 0.18 + impact * 0.06);
  }

  function update(phase, overkillMode, canvas) {
    const split = mode === 'splitJuggle';
    if (!split) setCenterText(canvas.dataset.selectedVariant || '6/7');
    center.visible = !split;
    six.visible = split;
    seven.visible = split;
    if (split) {
      splitPose(six, -1, phase.left, overkillMode);
      splitPose(seven, 1, phase.right, overkillMode);
      canvas.dataset.tokenSource = 'canvas-text-split';
    } else {
      centerPose(phase, overkillMode);
      canvas.dataset.tokenSource = 'canvas-text';
    }
    canvas.dataset.overkillPropMode = mode;
    canvas.dataset.tokenText = split ? '6,7' : centerText;
    canvas.dataset.tokenPlacement = split ? 'split-juggle-from-palms' : 'above-hands-y-axis-locked-low';
    canvas.dataset.motionClock = 'shared-phase-v1';
    canvas.dataset.motionPrimaryPhase = phase.primaryPhase.toFixed(3);
    canvas.dataset.motionLeftHandY = phase.left.handY.toFixed(3);
    canvas.dataset.motionRightHandY = phase.right.handY.toFixed(3);
    canvas.dataset.tokenBounceDirection = split ? 'up-from-palm' : 'above-palms-y-axis';
    canvas.dataset.motionLeftTokenLift = phase.left.throwLift.toFixed(3);
    canvas.dataset.motionRightTokenLift = phase.right.throwLift.toFixed(3);
  }

  function dispose() {
    Object.values(textures).forEach((texture) => texture.dispose());
    disposeObject(group);
  }

  setMode(mode);
  return { setMode, update, dispose, getMode: () => mode };
}

function disposeObject(obj) {
  obj.traverse((child) => {
    if (child.geometry) child.geometry.dispose();
    if (child.material) {
      const mats = Array.isArray(child.material) ? child.material : [child.material];
      mats.forEach((m) => m.dispose());
    }
  });
}

function parseGlb(buffer) {
  const view = new DataView(buffer);
  if (view.getUint32(0, true) !== GLB_MAGIC) throw new Error('Invalid GLB magic.');
  if (view.getUint32(4, true) !== 2) throw new Error('Unsupported GLB version.');
  const totalLength = view.getUint32(8, true);
  let offset = 12;
  let json = null;
  let bin = null;

  while (offset + 8 <= totalLength) {
    const chunkLength = view.getUint32(offset, true);
    const chunkType = view.getUint32(offset + 4, true);
    offset += 8;
    const chunk = buffer.slice(offset, offset + chunkLength);
    offset += chunkLength;
    if (chunkType === GLB_JSON) {
      const text = new TextDecoder().decode(chunk).replace(/\0+$/g, '').trim();
      json = JSON.parse(text);
    } else if (chunkType === GLB_BIN) {
      bin = chunk;
    }
  }

  if (!json || !bin) throw new Error('GLB is missing JSON or BIN chunks.');
  return { json, bin };
}

function readAccessor(gltf, bin, index) {
  const accessor = gltf.accessors[index];
  if (!accessor) throw new Error(`Missing GLB accessor ${index}.`);
  const bufferView = gltf.bufferViews[accessor.bufferView];
  const component = GLTF_COMPONENTS[accessor.componentType];
  const itemSize = GLTF_TYPE_SIZES[accessor.type];
  if (!bufferView || !component || !itemSize) {
    throw new Error(`Unsupported GLB accessor ${index}.`);
  }

  const count = accessor.count || 0;
  const elementBytes = component.bytes * itemSize;
  const stride = bufferView.byteStride || elementBytes;
  const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);

  if (stride === elementBytes && byteOffset % component.bytes === 0) {
    const source = new component.array(bin, byteOffset, count * itemSize);
    return { array: source.slice(), itemSize };
  }

  const data = new component.array(count * itemSize);
  const source = new DataView(bin, byteOffset, bufferView.byteLength - (accessor.byteOffset || 0));
  for (let i = 0; i < count; i += 1) {
    const row = i * stride;
    for (let j = 0; j < itemSize; j += 1) {
      data[i * itemSize + j] = source[component.read](row + j * component.bytes, true);
    }
  }
  return { array: data, itemSize };
}

function makeMaterial(def) {
  const pbr = def && def.pbrMetallicRoughness ? def.pbrMetallicRoughness : {};
  const rgba = pbr.baseColorFactor || [1, 1, 1, 1];
  const alpha = rgba[3] === undefined ? 1 : rgba[3];
  const material = new THREE.MeshStandardMaterial({
    color: new THREE.Color(rgba[0], rgba[1], rgba[2]),
    opacity: alpha,
    transparent: alpha < 1 || def?.alphaMode === 'BLEND',
    roughness: pbr.roughnessFactor === undefined ? 0.58 : pbr.roughnessFactor,
    metalness: pbr.metallicFactor === undefined ? 0.02 : pbr.metallicFactor,
  });
  material.name = def?.name || '';
  return material;
}

function buildGeometry(gltf, bin, primitive) {
  const geometry = new THREE.BufferGeometry();
  const attrs = primitive.attributes || {};
  const position = readAccessor(gltf, bin, attrs.POSITION);
  geometry.setAttribute('position', new THREE.BufferAttribute(position.array, position.itemSize));

  if (attrs.NORMAL !== undefined) {
    const normal = readAccessor(gltf, bin, attrs.NORMAL);
    geometry.setAttribute('normal', new THREE.BufferAttribute(normal.array, normal.itemSize));
  }
  if (attrs.TEXCOORD_0 !== undefined) {
    const uv = readAccessor(gltf, bin, attrs.TEXCOORD_0);
    geometry.setAttribute('uv', new THREE.BufferAttribute(uv.array, uv.itemSize));
  }
  if (primitive.indices !== undefined) {
    const indices = readAccessor(gltf, bin, primitive.indices);
    geometry.setIndex(new THREE.BufferAttribute(indices.array, 1));
  }
  if (!geometry.attributes.normal) geometry.computeVertexNormals();
  geometry.computeBoundingSphere();
  return geometry;
}

function buildGenericGlbScene(gltf, bin, fallbackName = 'six-seven-model') {
  const materials = (gltf.materials || []).map(makeMaterial);
  const named = new Map();

  function buildMesh(meshIndex) {
    const meshDef = gltf.meshes[meshIndex];
    const group = new THREE.Group();
    group.name = meshDef?.name || '';
    for (const primitive of meshDef?.primitives || []) {
      if (primitive.mode !== undefined && primitive.mode !== 4) continue;
      const material = materials[primitive.material] || new THREE.MeshStandardMaterial({ color: SKIN });
      const mesh = new THREE.Mesh(buildGeometry(gltf, bin, primitive), material);
      mesh.castShadow = true;
      mesh.receiveShadow = true;
      group.add(mesh);
    }
    return group;
  }

  function buildNode(index) {
    const node = gltf.nodes[index];
    const obj = new THREE.Group();
    obj.name = node.name || '';
    if (obj.name) named.set(obj.name, obj);

    if (node.matrix) {
      const matrix = new THREE.Matrix4();
      matrix.fromArray(node.matrix);
      matrix.decompose(obj.position, obj.quaternion, obj.scale);
    } else {
      if (node.translation) obj.position.fromArray(node.translation);
      if (node.rotation) obj.quaternion.fromArray(node.rotation);
      if (node.scale) obj.scale.fromArray(node.scale);
    }

    if (node.mesh !== undefined) obj.add(buildMesh(node.mesh));
    for (const childIndex of node.children || []) obj.add(buildNode(childIndex));
    return obj;
  }

  const sceneDef = gltf.scenes[gltf.scene || 0];
  const scene = new THREE.Group();
  scene.name = sceneDef?.name || fallbackName;
  for (const nodeIndex of sceneDef?.nodes || []) scene.add(buildNode(nodeIndex));
  return { scene, named };
}

function buildGlbScene(gltf, bin) {
  const { scene, named } = buildGenericGlbScene(gltf, bin, 'six-seven-overkill-model');

  const left = {
    rig: named.get('left-arm-hand-rig'),
    handPivot: named.get('left-hand-pivot'),
  };
  const right = {
    rig: named.get('right-arm-hand-rig'),
    handPivot: named.get('right-hand-pivot'),
  };
  const token = named.get('floating-67-token');
  if (!left.rig || !left.handPivot || !right.rig || !right.handPivot || !token) {
    disposeObject(scene);
    throw new Error('GLB is missing the named Six Seven animation controls.');
  }

  return { scene, left, right, token };
}

function buildAnatomicalGlbScene(gltf, bin) {
  const { scene, named } = buildGenericGlbScene(gltf, bin, 'six-seven-anatomical-open-hand-scene');
  let hand = named.get('anatomical-open-hand') || null;
  if (!hand) {
    scene.traverse((child) => {
      if (!hand && child.isMesh) hand = child.parent || child;
    });
  }
  if (!hand) {
    disposeObject(scene);
    throw new Error('Anatomical GLB is missing a usable hand mesh.');
  }
  return { scene, hand };
}

async function loadPublishedModel(modelUrl) {
  const buffer = await fetchModelBuffer(modelUrl);
  const { json, bin } = parseGlb(buffer);
  return buildGlbScene(json, bin);
}

async function loadAnatomicalModel(modelUrl) {
  const buffer = await fetchModelBuffer(modelUrl);
  const { json, bin } = parseGlb(buffer);
  return buildAnatomicalGlbScene(json, bin);
}

async function fetchModelBuffer(modelUrl) {
  try {
    const response = await fetch(modelUrl, { mode: 'cors', cache: 'force-cache' });
    if (!response.ok) throw new Error(`Model fetch failed: ${response.status}`);
    return await response.arrayBuffer();
  } catch (error) {
    throw error;
  }
}

function createArm(sideSign) {
  const skin = mat(SKIN);
  const line = mat(SKIN_DARK, 0.7, 0);
  const rig = new THREE.Group();
  rig.name = sideSign < 0 ? 'left-arm-hand-rig' : 'right-arm-hand-rig';

  const handPivot = new THREE.Group();
  handPivot.name = 'hand-pivot';
  handPivot.position.set(sideSign * 0.98, -0.52, -0.2);
  handPivot.rotation.set(-0.15, sideSign * 0.18, sideSign * -0.16);
  handPivot.scale.setScalar(1.1);
  if (sideSign > 0) handPivot.scale.x = -1.1;
  const palm = addPalm(handPivot, sideSign < 0 ? 'left' : 'right', skin, line);
  palm.position.y = -0.1;

  addCapsuleBetween(
    rig,
    'forearm',
    0.25,
    [sideSign * 2.35, -2.6, -2.2],
    [sideSign * 1.12, -1.2, -0.36],
    skin,
    [1.08, 1, 0.82]
  );
  addCapsuleBetween(
    rig,
    'forearm-shadow-line',
    0.045,
    [sideSign * 2.35, -2.5, -2.0],
    [sideSign * 1.1, -1.08, -0.2],
    line,
    [0.9, 1, 0.8]
  );
  addCapsuleBetween(
    rig,
    'wrist',
    0.18,
    [sideSign * 1.14, -1.0, -0.42],
    [sideSign * 0.96, -0.7, -0.14],
    skin,
    [1.05, 1, 0.8]
  );
  rig.add(handPivot);

  return { rig, handPivot };
}

function cloneNormalizedModel(source, targetHeight = 2.35) {
  const model = source.clone(true);
  trimAnatomicalWrist(model);
  const box = new THREE.Box3().setFromObject(model);
  const size = box.getSize(new THREE.Vector3());
  const center = box.getCenter(new THREE.Vector3());
  const maxAxis = Math.max(size.x, size.y, size.z, 0.001);
  model.position.sub(center);
  model.scale.multiplyScalar(targetHeight / maxAxis);
  model.traverse((child) => {
    if (!child.isMesh) return;
    child.castShadow = true;
    child.receiveShadow = true;
    if (child.material) {
      const mats = Array.isArray(child.material) ? child.material : [child.material];
      mats.forEach((m) => {
        m.roughness = Math.max(0.5, m.roughness ?? 0.58);
        m.metalness = Math.min(0.03, m.metalness ?? 0.02);
        m.side = THREE.DoubleSide;
      });
    }
  });
  return model;
}

function trimAnatomicalWrist(model, cutoffY = 0.42) {
  model.traverse((child) => {
    if (!child.isMesh || !child.geometry?.getAttribute('position')) return;
    const geometry = child.geometry;
    const position = geometry.getAttribute('position');
    const index = geometry.getIndex();
    if (!index) return;
    const kept = [];
    for (let i = 0; i < index.count; i += 3) {
      const a = index.getX(i);
      const b = index.getX(i + 1);
      const c = index.getX(i + 2);
      const minY = Math.min(position.getY(a), position.getY(b), position.getY(c));
      if (minY > cutoffY) continue;
      kept.push(a, b, c);
    }
    const IndexArray = position.count > 65535 ? Uint32Array : Uint16Array;
    geometry.setIndex(new THREE.BufferAttribute(new IndexArray(kept), 1));
    geometry.computeVertexNormals();
  });
}

function createAnatomicalRig(sideSign, handSource) {
  const rig = new THREE.Group();
  rig.name = sideSign < 0 ? 'left-anatomical-right-hand-rig' : 'right-anatomical-left-hand-rig';
  rig.position.set(sideSign * 1.08, 0.68, 0.18);

  const handPivot = new THREE.Group();
  handPivot.name = sideSign < 0 ? 'left-right-hand-pivot' : 'right-left-hand-pivot';
  handPivot.position.set(0, 0, 0.12);
  handPivot.rotation.set(
    THREE.MathUtils.degToRad(ANATOMICAL_BASE_X_DEG),
    sideSign * Math.PI,
    0
  );

  const hand = cloneNormalizedModel(handSource, 2.42);
  hand.name = sideSign < 0 ? 'left-screen-right-hand-stl' : 'right-screen-left-hand-stl';
  hand.position.set(0, -0.06, 0.02);
  hand.rotation.set(0.04, 0, sideSign * 0.03);
  if (sideSign > 0) hand.scale.x *= -1;
  handPivot.add(hand);
  rig.add(handPivot);

  return {
    rig,
    handPivot,
    hand,
    baseRigPosition: rig.position.clone(),
    basePivotPosition: handPivot.position.clone(),
    basePivotRotation: handPivot.rotation.clone(),
  };
}

function createScene(canvas, options = {}) {
  let mode = normalizeOverkillMode(options.mode);
  let propMode = normalizePropMode(options.propMode || canvas.dataset.overkillPropMode);
  const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true, powerPreference: 'high-performance' });
  renderer.setClearColor(0x000000, 0);
  renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1));
  renderer.outputColorSpace = THREE.SRGBColorSpace;

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(46, 1, 0.1, 100);
  camera.position.set(0, 0.2, 8.6);
  camera.lookAt(0, -0.18, 0);

  scene.add(new THREE.AmbientLight(0xffd6a8, 1.15));
  const key = new THREE.DirectionalLight(0xfff0c8, 3.2);
  key.position.set(-4, 5, 7);
  scene.add(key);
  const mag = new THREE.PointLight(0xff2fb1, 1.8, 12);
  mag.position.set(3.8, 1.4, 3.8);
  scene.add(mag);
  const cyan = new THREE.PointLight(0x22f8ff, 1.15, 12);
  cyan.position.set(-4, -1.2, 4.5);
  scene.add(cyan);

  const root = new THREE.Group();
  scene.add(root);

  const handTexture = createHandTexture();
  const toonGroup = new THREE.Group();
  toonGroup.name = 'normal-overkill-toon-hands';
  const left = createToonHandRig(-1, handTexture);
  const right = createToonHandRig(1, handTexture);
  toonGroup.add(left.rig, right.rig);
  root.add(toonGroup);

  const ultimateGroup = new THREE.Group();
  ultimateGroup.name = 'ultimate-overkill-depth-rigs';
  const ultimateLeft = createArm(-1);
  const ultimateRight = createArm(1);
  ultimateGroup.add(ultimateLeft.rig, ultimateRight.rig);
  root.add(ultimateGroup);

  const anatomicalGroup = new THREE.Group();
  anatomicalGroup.name = 'anatomical-overdrive-stl-hand-rigs';
  const anatomicalMotionRoot = new THREE.Group();
  anatomicalMotionRoot.name = 'anatomical-vertical-flagpole-motion-root';
  const anatomicalPairRoot = new THREE.Group();
  anatomicalPairRoot.name = 'anatomical-y180-z180-pair-root';
  anatomicalPairRoot.rotation.y = Math.PI;
  anatomicalPairRoot.rotation.z = Math.PI;
  anatomicalMotionRoot.add(anatomicalPairRoot);
  anatomicalGroup.add(anatomicalMotionRoot);
  root.add(anatomicalGroup);

  const propSystem = createTokenPropSystem(root, propMode);
  let motionConfig = normalizeMotionConfig(options.motionConfig);

  let runtimeSource = 'procedural';
  let anatomicalRuntimeSource = 'anatomical-loading';
  let anatomicalSource = null;
  let anatomicalLeft = null;
  let anatomicalRight = null;
  let disposed = false;
  canvas.dataset.runtimeSource = 'procedural-loading';
  canvas.dataset.tokenSource = 'canvas-text';
  canvas.dataset.overkillPropMode = propMode;
  canvas.dataset.geometrySource = 'toon-texture-planes';

  function applyMode() {
    toonGroup.visible = mode === 'normal';
    ultimateGroup.visible = mode === 'ultimate';
    anatomicalGroup.visible = mode === 'anatomical';
    canvas.dataset.overkillMode = mode;
    canvas.dataset.visualSource = mode === 'ultimate'
      ? 'depth-rig'
      : mode === 'anatomical'
        ? 'anatomical-stl'
        : 'toon-silhouette';
    canvas.dataset.geometrySource = mode === 'ultimate'
      ? 'capsule-hand-rigs'
      : mode === 'anatomical'
        ? 'cc-by-sa-open-empty-hand-stl-rigs'
        : 'toon-texture-planes';
  }

  applyMode();

  loadPublishedModel(options.modelUrl || canvas.dataset.modelUrl || DEFAULT_MODEL_URL).then((model) => {
    if (disposed) {
      disposeObject(model.scene);
      return;
    }
    model.scene.visible = false;
    root.add(model.scene);
    runtimeSource = 'glb';
    canvas.dataset.runtimeSource = runtimeSource;
  }).catch((error) => {
    runtimeSource = 'procedural-fallback';
    canvas.dataset.runtimeSource = runtimeSource;
    canvas.dataset.runtimeError = error && error.message ? error.message : String(error);
  });

  loadAnatomicalModel(options.anatomicalModelUrl || canvas.dataset.anatomicalModelUrl || DEFAULT_ANATOMICAL_MODEL_URL).then((model) => {
    if (disposed) {
      disposeObject(model.scene);
      return;
    }
    anatomicalSource = model.scene;
    anatomicalSource.visible = false;
    anatomicalGroup.add(anatomicalSource);
    anatomicalLeft = createAnatomicalRig(-1, model.hand);
    anatomicalRight = createAnatomicalRig(1, model.hand);
    anatomicalPairRoot.add(anatomicalLeft.rig, anatomicalRight.rig);
    anatomicalRuntimeSource = 'anatomical-glb';
  }).catch((error) => {
    anatomicalRuntimeSource = 'anatomical-error';
    canvas.dataset.anatomicalRuntimeError = error && error.message ? error.message : String(error);
  });

  function resize() {
    const width = Math.max(1, canvas.clientWidth || window.innerWidth);
    const height = Math.max(1, canvas.clientHeight || window.innerHeight);
    renderer.setSize(width, height, false);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }

  function setTargetRect(rect) {
    if (!rect) return;
    const cx = (rect.left + rect.right) / 2;
    const cy = (rect.top + rect.bottom) / 2;
    const nx = (cx / Math.max(1, window.innerWidth) - 0.5) * 1.2;
    const ny = (0.5 - cy / Math.max(1, window.innerHeight)) * 0.55;
    root.position.x = THREE.MathUtils.clamp(nx, -0.42, 0.42);
    root.position.y = THREE.MathUtils.clamp(ny, -0.22, 0.32);
  }

  function render(timeMs) {
    applyMode();
    canvas.dataset.runtimeSource = mode === 'ultimate'
      ? 'procedural-ultimate'
      : mode === 'anatomical'
        ? anatomicalRuntimeSource
        : runtimeSource;
    const phase = createMotionPhase(timeMs, mode, motionConfig);
    const t = phase.t;
    const manic = 1 + Math.sin(t * 5.3) * 0.18 + Math.sin(t * 12.7) * 0.08;
    const motionLiftScale = 0.7 + motionConfig.bobPx / 26;
    const motionDriftScale = 0.7 + motionConfig.driftPx / 18;
    const tiltScale = 0.72 + motionConfig.tiltXDeg / 14;
    propSystem.setMode(propMode);
    propSystem.update(phase, mode, canvas);
    canvas.dataset.handTremor = 'motion-config';
    canvas.dataset.motionPaceMs = motionConfig.paceMs.toFixed(1);
    canvas.dataset.motionWobbleAmpDeg = motionConfig.wobbleAmpDeg.toFixed(1);
    canvas.dataset.motionWobbleJoltPx = motionConfig.wobbleJoltPx.toFixed(2);

    if (mode === 'ultimate') {
      root.rotation.z = Math.sin(t * 9.8) * 0.045;
      root.position.z = 0.38 + Math.sin(t * 7.1) * 0.1;

      const rigs = [
        { side: -1, ref: ultimateLeft, handPhase: phase.left, offset: 0 },
        { side: 1, ref: ultimateRight, handPhase: phase.right, offset: Math.PI * 0.92 },
      ];
      for (const { side, ref, handPhase, offset } of rigs) {
        const slap = handPhase.handY;
        const jab = handPhase.jab;
        const shake = tremor(t, offset + side * 0.9, motionConfig, mode);
        ref.rig.position.x = side * (0.62 + slap * 0.18 * motionDriftScale) + shake.x;
        ref.rig.position.y = 0.28 + slap * 0.34 * motionLiftScale - jab * 0.16 + shake.y;
        ref.rig.position.z = 1.25 + jab * 2.35 + shake.z;
        ref.rig.rotation.z = side * (0.2 + slap * 0.12) + shake.rz;
        ref.rig.rotation.y = side * (0.2 + Math.sin(t * 7.4 + offset) * 0.13 * tiltScale) + shake.ry;
        ref.rig.rotation.x = -0.08 + jab * 0.22 + shake.rx * 0.5;
        ref.handPivot.rotation.x = -0.18 - jab * 0.32 + slap * 0.1 + shake.rx;
        ref.handPivot.rotation.y = side * (0.28 + Math.sin(t * 12.7 + offset) * 0.16 * tiltScale) + shake.ry;
        ref.handPivot.rotation.z = side * (-0.14 + Math.sin(t * 17.4 + offset) * 0.16) + shake.rz;
        const scale = 1.12 + jab * 0.34 + Math.abs(shake.rz) * 1.8;
        ref.handPivot.scale.set(scale * (side > 0 ? -1 : 1), scale * (0.95 + manic * 0.04), scale * (1.02 + jab * 0.16));
      }
    } else if (mode === 'anatomical') {
      root.rotation.z = 0;
      root.position.z = 0.34;

      const flagpoleWave = phase.left.handY;
      const leftYOffset = -flagpoleWave * ANATOMICAL_FLAGPOLE_TRAVEL;
      const rightYOffset = flagpoleWave * ANATOMICAL_FLAGPOLE_TRAVEL;
      anatomicalMotionRoot.rotation.x = THREE.MathUtils.degToRad(ANATOMICAL_PAIR_X_LOCK_DEG);
      anatomicalMotionRoot.position.y = -0.34;
      anatomicalMotionRoot.position.z = 0.1;
      canvas.dataset.anatomicalMotionAxis = 'rig.position.y';
      canvas.dataset.anatomicalXRange = `${ANATOMICAL_PAIR_X_LOCK_DEG},${ANATOMICAL_PAIR_X_LOCK_DEG}`;
      canvas.dataset.anatomicalXDeg = ANATOMICAL_PAIR_X_LOCK_DEG.toFixed(1);
      canvas.dataset.anatomicalHandsScreenOffset = 'down-0.30';
      canvas.dataset.anatomicalLeftYOffset = leftYOffset.toFixed(3);
      canvas.dataset.anatomicalRightYOffset = rightYOffset.toFixed(3);
      canvas.dataset.anatomicalTremor = 'motion-config';

      if (anatomicalLeft && anatomicalRight) {
        const rigs = [
          { side: -1, ref: anatomicalLeft, yOffset: leftYOffset, offset: 0 },
          { side: 1, ref: anatomicalRight, yOffset: rightYOffset, offset: Math.PI },
        ];
        for (const { side, ref, yOffset, offset } of rigs) {
          const shake = tremor(t, offset + side * 1.3, motionConfig, mode);
          ref.rig.position.copy(ref.baseRigPosition);
          ref.rig.position.x += shake.x * 0.72;
          ref.rig.position.y += yOffset + shake.y * 0.78;
          ref.rig.position.z += shake.z * 0.72;
          ref.rig.rotation.set(shake.rx * 0.18, shake.ry * 0.16, shake.rz * 0.32);
          ref.handPivot.position.copy(ref.basePivotPosition);
          ref.handPivot.rotation.copy(ref.basePivotRotation);
          ref.handPivot.rotation.x += shake.rx * 0.74;
          ref.handPivot.rotation.y += shake.ry * 0.54;
          ref.handPivot.rotation.z += shake.rz * 0.68;
          ref.handPivot.scale.set(1, 1, 1);
        }
      }
    } else {
      root.rotation.z = Math.sin(t * 8.1) * 0.035;
      root.position.z = Math.sin(t * 6.2) * 0.08;

      const rigs = [
        { side: -1, ref: left, handPhase: phase.left, offset: 0 },
        { side: 1, ref: right, handPhase: phase.right, offset: Math.PI },
      ];
      for (const { side, ref, handPhase, offset } of rigs) {
        const slap = handPhase.handY;
        const jab = handPhase.jab;
        const shake = tremor(t, offset + side * 0.5, motionConfig, mode);
        ref.rig.position.x = side * (0.22 + slap * 0.08 * motionDriftScale) + shake.x;
        ref.rig.position.y = slap * 0.28 * motionLiftScale - jab * 0.18 + shake.y;
        ref.rig.position.z = jab * 1.15 + shake.z;
        ref.rig.rotation.z = side * (0.04 + slap * 0.09) + shake.rz;
        ref.rig.rotation.y = side * (0.08 + Math.sin(t * 6.6 + offset) * 0.08 * tiltScale) + shake.ry;
        ref.rig.rotation.x = shake.rx * 0.38;
        ref.handPivot.rotation.x = -0.28 + slap * 0.14 - jab * 0.48 + shake.rx;
        ref.handPivot.rotation.y = side * (0.28 + Math.sin(t * 11.8 + offset) * 0.13 * tiltScale) + shake.ry;
        ref.handPivot.rotation.z = side * (-0.08 + Math.sin(t * 14.0 + offset) * 0.11) + shake.rz;
        ref.handPivot.scale.set(1.1 * (side < 0 ? -1 : 1), 1.1 * manic, 1.1 + jab * 0.18 + Math.abs(shake.rz) * 0.6);
      }
    }

    renderer.render(scene, camera);
  }

  function setMode(nextMode) {
    mode = normalizeOverkillMode(nextMode);
    applyMode();
  }

  function setPropMode(nextPropMode) {
    propMode = normalizePropMode(nextPropMode);
    propSystem.setMode(propMode);
    canvas.dataset.overkillPropMode = propMode;
  }

  function setMotionConfig(nextMotionConfig) {
    motionConfig = normalizeMotionConfig(nextMotionConfig);
  }

  function dispose() {
    disposed = true;
    handTexture.dispose();
    propSystem.dispose();
    disposeObject(scene);
    renderer.dispose();
  }

  resize();
  return {
    resize,
    render,
    setTargetRect,
    setMode,
    setPropMode,
    setMotionConfig,
    dispose,
    getRuntimeSource: () => runtimeSource,
    getMode: () => mode,
    getPropMode: () => propMode,
  };
}

export function createSixSevenOverkill(canvas, options) {
  return createScene(canvas, options);
}
