ShiboSoftwareDev/linux-gameboy-advance

Multilayer PCB carrier board integrating USB‑C, battery charging/power management, buck‑boost regulation, MCU/SoC, LCD/SD interfaces, audio controls, status LEDs, backlight, and UART/I²C/SPI service connectors with reviewed multilayer copper and vias.

Version
1.0.134
License
unset
Stars
0

mechanical/GbaEnclosure.tsx

import { Fragment } from "react"
import { assembly, jscad } from "tscircuit"

type Point = { x: number, y: number }

/** Mechanical datum: PCB centre, PCB mid-plane Z=0, millimetres. */
export const BOARD_OUTLINE: Point[] = [
  { x: -66, y: -41 }, { x: 66, y: -41 }, { x: 69.5, y: -39.5 }, { x: 71.5, y: -36.5 }, { x: 72.25, y: -32 },
  { x: 72.25, y: 30.25 }, { x: 70.5, y: 33 }, { x: 65, y: 37 }, { x: 61, y: 38 }, { x: 53, y: 43.27 },
  { x: -53, y: 43.27 }, { x: -61, y: 38 }, { x: -65, y: 37 }, { x: -70.5, y: 33 }, { x: -72.25, y: 30.25 },
  { x: -72.25, y: -32 }, { x: -71.5, y: -36.5 }, { x: -69.5, y: -39.5 },
]

/** Product-control datums shared by the PCB and enclosure.  Start/Select form
 * a central pair below the display instead of being mixed into either hand's
 * primary control cluster. */
export const PRODUCT_CONTROL_LAYOUT = {
  // Keep the validated central secondary-control spacing.  The LCD carrier
  // must route around this ergonomic datum rather than displacing the switch.
  select: { x: -14, y: -26 },
  start: { x: 1, y: -26 },
  shoulderLeftX: -45,
  shoulderRightX: 45,
  shoulderY: 39.32595785,
} as const

/** Shoulder controls move toward the PCB, normal to the top wall.  The
 * exterior key is a close-fitting rounded rectangle that is allowed to slide
 * into its matching wall guide.  Two narrow rails straddle the switch stem and
 * end in independent hidden retaining tabs; there is no large internal flange
 * wrapped around the switch.  A blind pocket in the back of the key receives
 * the round actuator while leaving nearly 2 mm of solid material on the finger
 * face, so the switch can never show through the printed part. */
export const SHOULDER_MECHANISM = {
  wallOuterY: 45.47,
  wallInnerY: 44.07,
  apertureCenterY: 44.67,
  apertureDepth: 2.4,
  apertureWidth: 23.6,
  apertureHeight: 6.8,
  // The key rests 0.15 mm off the case. Its 23.0 x 6.2 mm body fits through
  // the 23.6 x 6.8 mm guide with 0.30 mm clearance on every side,
  // allowing the full switch stroke without the key bottoming on the shell.
  visibleCenterY: 47.17,
  visibleDepth: 3.1,
  visibleWidth: 23.0,
  visibleHeight: 6.2,
  // Keep both rails inside the straight section of the rear wall.  The old
  // outer rail/tab reached into the chamfered corner at |X| > 53.65 mm and
  // physically intersected the shell even though the nominal flat-wall
  // clearance calculation passed.
  // Pull both guide rails into the flat part of the upper wall.  At 6.4 mm
  // offset, the outside edge of the left retaining tab landed exactly on the
  // polygon transition at X=-53 mm; the evaluated mesh then crossed the
  // chamfer during both rest and full-stroke positions.  A 4.5 mm offset
  // preserves 5.8 mm between rails around the actuator and keeps the complete
  // tabs on the straight wall section with 1.9 mm additional corner margin.
  railOffsetX: 4.5,
  railCenterY: 44.675,
  railDepth: 3.15,
  railWidth: 2.8,
  railHeight: 3.4,
  tabCenterY: 43.17,
  tabDepth: 0.8,
  // Retention is vertical, not lateral: each tab overhangs the 6.8 mm guide
  // by 0.40 mm at the top and bottom while staying clear of the chamfered
  // enclosure corner.  The previous 5.2 mm-high tabs were smaller than the
  // opening and therefore were neither valid retainers nor collision-free.
  tabWidth: 3.2,
  tabHeight: 7.6,
  // The real evaluated TS6695 actuator ends at Y=46.315 mm.  The blind pocket
  // closes at 46.565 mm for a deliberate 0.25 mm rest gap.  The pocket opens
  // only through the rear of the key and leaves 1.835 mm of solid face stock.
  actuatorPocketRadius: 1.8,
  actuatorPocketCenterY: 46.0575,
  actuatorPocketDepth: 1.015,
  actuatorContactY: 46.565,
  verifiedInwardTravel: 0.65,
} as const

/** The wheel follows the true bisector of the now-mirrored upper corner. */
export const VOLUME_CONTROL_LAYOUT = {
  // Set 1.80 mm inward from the cosmetic wall aperture so both plated
  // mounting slots retain at least 0.55 mm copper-to-board-edge clearance.
  x: -63.1291306297,
  y: 26.0729869550,
  rotationDeg: 46.7780905471,
} as const

/** Direct-drive volume extension.  The previous remote O-ring roller added a
 * second axle, two shell bearings and a tolerance-sensitive friction drive.
 * This split PA12 collar is concentric with the real C231765 thumbwheel and
 * grips its serrated rim directly.  The shell opening is concentric too, so
 * every angular position has the same running clearance and the nearby case
 * screw remains untouched. */
export const VOLUME_EXTENSION = {
  supplierWheelCenterX: -65.2241694089,
  supplierWheelCenterY: 28.0418674526,
  outerRadius: 10.2,
  nominalWheelRadius: 7.0,
  collarInnerRadius: 7.05,
  gripRibInnerRadius: 6.85,
  bottomZ: 1.30,
  topZ: 2.90,
  apertureRadius: 10.60,
  apertureBottomZ: 0.95,
  apertureTopZ: 3.25,
  serviceRadius: 11.10,
  splitAngleDeg: 32,
  splitWidth: 1.0,
} as const

/** Front-panel status-light datum.  The PCB LED sits between the display and
 * Start/Select, outside every control travel envelope.  A separate clear,
 * rear-flanged light pipe crosses the air gap to the front shell; the printed
 * enclosure never relies on a long unsupported LED lead or a translucent wall. */
export const STATUS_INDICATOR_LAYOUT = {
  x: -6.5,
  y: -18.5,
  apertureRadius: 1.25,
  lightPipeRadius: 1.05,
  lightPipeBottomZ: 1.55,
  lightPipeTopZ: 13.15,
  flangeRadius: 1.8,
  flangeHeight: 0.8,
  flangeCenterZ: 11.2,
} as const

export const SCREEN_CARRIER_HOLES = [
  { name: "MH_LCD_LT", x: -48.75, y: 32.75 },
  { name: "MH_LCD_RT", x: 51, y: 33 },
  // The front speaker and D-pad occupy the old lower-left route. This mount
  // sits in the clear lower service strip so its straight load path neither
  // snakes around controls nor crosses the speaker cradle.
  { name: "MH_LCD_LB", x: -20, y: -35.5 },
  { name: "MH_LCD_RB", x: 66, y: -31 },
] as const

/** Four rear M3 screws pass through the base and PCB and terminate in
 * heat-set inserts in the front shell. Every PCB hole therefore has a job. */
export const PCB_MOUNT_HOLES = [
  { name: "MH_TOP_LEFT", x: -59.5, y: -34 },
  { name: "MH_TOP_RIGHT", x: 55.5, y: -38 },
  { name: "MH_BOTTOM_LEFT", x: -66, y: 14 },
  { name: "MH_BOTTOM_RIGHT", x: 62, y: 34.5 },
] as const

/** Qualified protected 1S pouch envelope.  The Jauch LP906090JH pack is
 * 93.5 x 60.5 x 9.9 mm after cycling; the pocket adds one millimetre on every
 * side and does not hard-clamp the cell. */
export const BATTERY_HOLDER_CLEARANCE = {
  x: -2, y: 0, width: 95.5, height: 62.5, depth: 10.9, topZ: -4.5,
} as const

const BATTERY_DOOR = {
  openingWidth: 97.0, openingHeight: 64.0, rebateWidth: 100.0, rebateHeight: 67.0,
  panelWidth: 99.4, panelHeight: 66.4, outsideZ: -17.35,
  // The 0.90 mm perimeter flange remains within the shallow shell rebate;
  // a 0.90 mm central doubler produces a useful 1.80 mm service panel only
  // inside the through-opening.  The
  // 1.0 mm tongue is printable with a 0.4 mm nozzle and retains three full
  // extrusion lines after dimensional compensation.
  panelThickness: 0.9, innerReinforcement: 0.9, tongueThickness: 1.0,
} as const

/** Door installation and latch design inputs.  The upper tongue is inserted
 * first, then the lower edge pivots closed until the PA12 cantilever clears
 * the shell lip.  The strain estimate is checked again from the exported
 * assembly so an apparently harmless geometry edit cannot create a brittle
 * latch. */
export const BATTERY_DOOR_INSTALLATION = {
  tonguePivotY: 32.15,
  tonguePivotZ: -16.02,
  maximumOpenAngleDeg: 8,
  latchBeamLength: 16.0,
  latchBeamThickness: 1.2,
  requiredLatchDeflection: 0.55,
  maximumDesignStrain: 0.01,
} as const

// The switch's own return spring supports the released cap. Do not invent a
// compressed foam washer while simultaneously modelling an air gap at the
// actuator. The switch supplies return and its own end-of-travel stop; a second
// solid flange above the lid would make these rear-inserted caps impossible
// to assemble. Verify the switch's force/travel on the physical prototype.
const FACE_BUTTON_CONTACT_Z = 15.8

export const DPAD_MECHANISM = {
  pivot: [-58, 0, 4.3] as const,
  pivotRadius: 2.5,
  contactZ: 15.8,
  flangeTopZ: 10.95,
  ratedTiltDeg: 2.5,
} as const

const FACE_BUTTONS = [
  { name: "A", x: 58, y: -9, radius: 4.15 },
  { name: "B", x: 66.8, y: 0, radius: 4.15 },
  { name: "X", x: 49, y: 0, radius: 4.15 },
  { name: "Y", x: 58, y: 9, radius: 4.15 },
  { name: "SELECT", ...PRODUCT_CONTROL_LAYOUT.select, radius: 3.55 },
  { name: "START", ...PRODUCT_CONTROL_LAYOUT.start, radius: 3.55 },
] as const
const DPAD_SWITCHES = [
  { x: -58, y: 9 }, { x: -58, y: -9 }, { x: -66.8, y: 0 }, { x: -49, y: 0 },
] as const

/** Nineteen 2 mm ports provide 59.7 mm² of acoustic opening (19.0% of the
 * nominal 20 mm speaker face) while retaining at least 1.5 mm webs. */
const SPEAKER_GRILLE_HOLES = [
  { x: 0, y: 0 },
  ...Array.from({ length: 6 }, (_, index) => {
    const angle = index * Math.PI / 3
    return { x: 3.5 * Math.cos(angle), y: 3.5 * Math.sin(angle) }
  }),
  ...Array.from({ length: 12 }, (_, index) => {
    const angle = index * Math.PI / 6
    return { x: 7 * Math.cos(angle), y: 7 * Math.sin(angle) }
  }),
] as const

/** Front-firing 20 mm speaker.  It occupies the component-free lower-left
 * lid volume, seals directly to the front grille and is retained from inside
 * with real M2 hardware and heat-set inserts. */
const SPEAKER_LAYOUT = {
  x: -36,
  y: -20,
  // The production stack is referenced from the 11.60 mm lid underside:
  // 0.50 mm installed gasket, 0.10 mm gasket interference, a true 4.00 mm
  // CMS-2004-18SP envelope, then rigid retainer contact at Z=7.20.
  // Only the foam gasket compresses; rigid parts must not interpenetrate.
  z: 9.2,
  gasketCenterZ: 11.35,
  retainerCenterZ: 6.40,
  screwOffsetX: 13.2,
  terminalY: -28.65,
  terminalPositiveX: -37.1,
  terminalNegativeX: -34.9,
  terminalZ: 7.62,
} as const

/** Broad interior tongues register the two shells before the four structural
 * screws are tightened.  These replace the previous 0.4-0.5 mm cosmetic
 * slivers with printable 0.8 mm x 1.8 mm locating members. */
const SHELL_REGISTRATION_KEYS = [
  { x: -35, y: -41.2, width: 20, height: 0.8 },
  { x: 35, y: -41.2, width: 20, height: 0.8 },
  { x: -72.4, y: 18, width: 0.8, height: 12 },
  { x: 72.4, y: -18, width: 0.8, height: 12 },
] as const

const SHELL_PROFILE = {
  centerX: 0,
  centerY: 1.135,
  outerWidth: 148.9,
  outerHeight: 88.67,
  outerRadius: 13.5,
  innerWidth: 146.1,
  innerHeight: 85.87,
  // The hidden cavity is slightly squarer than the exterior so the PCB's
  // tapered bottom corners clear without weakening the straight side walls.
  innerRadius: 10.0,
} as const

const RoundedRectPrism = ({ x, y, bottom, width, height, depth, radius }: {
  x: number, y: number, bottom: number, width: number, height: number, depth: number, radius: number,
}) => (
  <jscad.union>
    <jscad.cuboid size={[width - 2 * radius, height, depth]} center={[x, y, bottom + depth / 2]} />
    <jscad.cuboid size={[width, height - 2 * radius, depth]} center={[x, y, bottom + depth / 2]} />
    {([-1, 1] as const).flatMap((xs) => ([-1, 1] as const).map((ys) => (
      <jscad.cylinder key={`${xs}_${ys}`} radius={radius} height={depth}
        center={[x + xs * (width / 2 - radius), y + ys * (height / 2 - radius), bottom + depth / 2]} />
    )))}
  </jscad.union>
)

/** Capsule-shaped side-wall opening.  `width` runs along the wall tangent,
 * `height` is vertical, and `depth` passes through the wall normal. */
const RotatedPillPrism = ({ x, y, z, width, height, depth, rotationDeg }: {
  x: number, y: number, z: number, width: number, height: number, depth: number, rotationDeg: number,
}) => {
  const angle = rotationDeg * Math.PI / 180
  const halfStraight = (width - height) / 2
  return (
    <jscad.union>
      <jscad.cuboid size={[width - height, depth, height]} center={[x, y, z]}
        rotation={[0, 0, angle]} />
      {([-1, 1] as const).map((sign) => (
        <jscad.cylinder key={`VOLUME_SLOT_${sign}`} radius={height / 2} height={depth}
          center={[x + sign * halfStraight * Math.cos(angle), y + sign * halfStraight * Math.sin(angle), z]}
          rotation={[Math.PI / 2, 0, angle]} />
      ))}
    </jscad.union>
  )
}

const CrossPrism = ({ x, y, bottom, armLength, armWidth, depth }: {
  x: number, y: number, bottom: number, armLength: number, armWidth: number, depth: number,
}) => (
  <jscad.union>
    <jscad.cuboid size={[armLength, armWidth, depth]} center={[x, y, bottom + depth / 2]} />
    <jscad.cuboid size={[armWidth, armLength, depth]} center={[x, y, bottom + depth / 2]} />
  </jscad.union>
)

/** Rounded rectangle in the enclosure X/Z plane, extruded along Y.  This is
 * used for the shoulder guides so the visible aperture and paddle share the
 * same radiused profile without relying on a rotated capsule approximation. */
const RoundedShoulderPrism = ({ x, y, z, width, height, depth, radius }: {
  x: number, y: number, z: number, width: number, height: number, depth: number, radius: number,
}) => (
  <jscad.roundedCuboid size={[width, depth, height]} roundRadius={radius} center={[x, y, z]} />
)

const Ring = ({ x, y, z, outerRadius, innerRadius, height }: {
  x: number, y: number, z: number, outerRadius: number, innerRadius: number, height: number,
}) => (
  <jscad.subtract>
    <jscad.cylinder radius={outerRadius} height={height} center={[x, y, z]} />
    <jscad.cylinder radius={innerRadius} height={height + 0.4} center={[x, y, z]} />
  </jscad.subtract>
)

const RectRing = ({ x, y, z, outerWidth, outerHeight, innerWidth, innerHeight, depth }: {
  x: number, y: number, z: number, outerWidth: number, outerHeight: number,
  innerWidth: number, innerHeight: number, depth: number,
}) => (
  <jscad.subtract>
    <jscad.cuboid size={[outerWidth, outerHeight, depth]} center={[x, y, z]} />
    <jscad.cuboid size={[innerWidth, innerHeight, depth + 0.4]} center={[x, y, z]} />
  </jscad.subtract>
)

const RingY = ({ x, y, z, outerRadius, innerRadius, height }: {
  x: number, y: number, z: number, outerRadius: number, innerRadius: number, height: number,
}) => (
  <jscad.subtract>
    <jscad.cylinder radius={outerRadius} height={height} center={[x, y, z]}
      rotation={[Math.PI / 2, 0, 0]} />
    <jscad.cylinder radius={innerRadius} height={height + 0.4} center={[x, y, z]}
      rotation={[Math.PI / 2, 0, 0]} />
  </jscad.subtract>
)

/** Constant-width rounded structural member between two XY datums. */
const RoundedBeam = ({ from, to, z, width, depth }: {
  from: [number, number], to: [number, number], z: number, width: number, depth: number,
}) => {
  const dx = to[0] - from[0]
  const dy = to[1] - from[1]
  // Extend one millimetre beyond both datums.  This creates a real printable
  // overlap at rail junctions and screw bosses instead of relying on tangent
  // mesh contact, which slicers correctly treat as disconnected islands.
  const length = Math.hypot(dx, dy) + 2.0
  return <jscad.translate x={(from[0] + to[0]) / 2} y={(from[1] + to[1]) / 2} z={z}>
    <jscad.rotate rotation={[0, 0, Math.atan2(dy, dx)]}>
      <jscad.roundedCuboid size={[length, width, depth]}
        roundRadius={Math.min(0.8, width / 2 - 0.1, depth / 2 - 0.1)} />
    </jscad.rotate>
  </jscad.translate>
}

type CadOp = Record<string, any>
const opCylinder = (radius: number, height: number, center: [number, number, number], rotation?: [number, number, number]): CadOp => {
  let shape: CadOp = { type: "cylinder", radius, height }
  if (rotation) shape = { type: "rotate", angles: rotation.map((degrees) => degrees * Math.PI / 180), shape }
  return { type: "translate", vector: center, shape }
}
const opCuboid = (size: [number, number, number], center: [number, number, number]): CadOp => ({
  type: "cuboid", size, center,
})
const opSphere = (radius: number, center: [number, number, number]): CadOp => ({
  type: "sphere", radius, center, segments: 12,
})
const opUnion = (...shapes: CadOp[]): CadOp => ({ type: "union", shapes })
const opSubtract = (...shapes: CadOp[]): CadOp => ({ type: "subtract", shapes })
const opHull = (...shapes: CadOp[]): CadOp => ({ type: "hull", shapes })
const opColor = (color: [number, number, number], shape: CadOp): CadOp => ({ type: "colorize", color, shape })
const opRing = (x: number, y: number, z: number, outerRadius: number, innerRadius: number, height: number): CadOp =>
  opSubtract(opCylinder(outerRadius, height, [x, y, z]), opCylinder(innerRadius, height + 0.4, [x, y, z]))
const opRingY = (x: number, y: number, z: number, outerRadius: number, innerRadius: number, height: number): CadOp =>
  opSubtract(
    opCylinder(outerRadius, height, [x, y, z], [90, 0, 0]),
    opCylinder(innerRadius, height + 0.4, [x, y, z], [90, 0, 0]),
  )
const opRectRing = (x: number, y: number, z: number, outerWidth: number, outerHeight: number,
  innerWidth: number, innerHeight: number, depth: number): CadOp => opSubtract(
  opCuboid([outerWidth, outerHeight, depth], [x, y, z]),
  opCuboid([innerWidth, innerHeight, depth + 0.4], [x, y, z]),
)
const cadModel = (color: [number, number, number], shape: CadOp) => ({ jscad: opColor(color, shape) })
const cableShape = (points: [number, number, number][], radius = 0.38) =>
  opUnion(...points.slice(1).map((point, index) =>
    opHull(opSphere(radius, points[index]), opSphere(radius, point))))
const cableModel = (color: [number, number, number], points: [number, number, number][], radius = 0.38) =>
  cadModel(color, cableShape(points, radius))

/** The catalog Jauch pack has two factory leads.  A separately bonded 10 kΩ
 * NTC and a three-circuit locking service connector form a qualified adapter
 * harness.  The user can unplug that connector through the open battery door;
 * the fixed three-wire branch remains routed through BAT_CABLE_SLOT to J_BAT. */
const BAT_WIRE_NEG_POINTS: [number, number, number][] = [
  [-57.0, -6.0, -12.8], [-57.0, -12.0, -12.8], [-54.5, -17.0, -3.9], [-54.5, -30.8, -3.9],
  [-54.0, -34.0, -3.9], [-54.0, -37.0, -3.8], [-54.0, -37.0, 1.70],
  [-49.0, -38.2, 1.70], [-43.0, -39.1, 1.70],
  [-37.2, -39.1, 1.70],
]
const BAT_WIRE_POS_POINTS: [number, number, number][] = [
  [-54.0, -6.0, -12.8], [-54.0, -12.0, -12.8], [-52.0, -17.0, -2.7], [-52.0, -28.5, -2.7],
  [-52.0, -32.5, -2.7], [-52.0, -35.0, -2.7], [-52.0, -35.0, 1.70],
  [-48.0, -34.8, 1.70], [-42.5, -35.1, 1.70],
  [-37.2, -35.1, 1.70],
]
const BAT_WIRE_NTC_POINTS: [number, number, number][] = [
  [-55.5, -6.0, -12.8], [-55.5, -12.0, -12.8], [-53.2, -17.0, -2.3], [-53.2, -26.0, -2.3],
  [-53.0, -32.5, -2.3], [-53.0, -36.0, -2.3], [-53.0, -36.0, 1.70],
  [-48.5, -36.5, 1.70], [-42.8, -37.1, 1.70],
  [-37.2, -37.1, 1.70],
] 

const BAT_PACK_LEAD_NEG_POINTS: [number, number, number][] = [
  [-49.14, -1.2, -9.95], [-51.0, -1.2, -10.2], [-51.0, 10.0, -11.8],
  [-57.0, 10.0, -12.8], [-57.0, 6.0, -12.8],
]
const BAT_PACK_LEAD_POS_POINTS: [number, number, number][] = [
  [-49.14, 1.2, -9.95], [-49.5, 2.0, -9.0], [-49.5, 12.0, -9.0],
  [-54.0, 12.0, -12.8], [-54.0, 6.0, -12.8],
]
const BAT_NTC_SENSE_POINTS: [number, number, number][] = [
  [-3.2, 0, -3.63], [-18, 0, -4.1], [-45, 10, -4.1], [-51.0, 12.0, -5.0],
  [-55.5, 11.0, -12.8], [-55.5, 6.0, -12.8],
]

/** The front speaker pair stays above the PCB in the lower service corridor,
 * then descends immediately beside J_SPK.  Both long runs sit above the
 * bottom registration tongue; their separate Z lanes maintain conductor
 * clearance while both pass outside the LCD carrier's lower-left boss. */
const SPK_WIRE_POS_POINTS: [number, number, number][] = [
  [SPEAKER_LAYOUT.terminalPositiveX, SPEAKER_LAYOUT.terminalY, SPEAKER_LAYOUT.terminalZ],
  [-37.1, -29.25, 7.0], [-37.1, -33.25, 7.0], [-30.0, -40.1, 6.2], [0, -40.1, 6.2],
  [35.0, -40.1, 6.2], [42.0, -36.0, 4.0], [43.5, -33.0, 1.55], [45.8, -33.0, 1.55],
]
const SPK_WIRE_NEG_POINTS: [number, number, number][] = [
  [SPEAKER_LAYOUT.terminalNegativeX, SPEAKER_LAYOUT.terminalY, SPEAKER_LAYOUT.terminalZ],
  [-34.9, -29.25, 7.0], [-34.9, -33.25, 7.0], [-26.0, -40.1, 5.3], [2.0, -40.1, 5.3],
  [36.0, -40.1, 5.3], [43.5, -37.5, 3.8], [44.8, -35.0, 1.55], [45.8, -35.0, 1.55],
]

const CableTunnel = ({ points, radius }: {
  points: [number, number, number][], radius: number,
}) => (
  <jscad.union>
    {points.slice(1).map((point, index) => (
      <jscad.hull key={`CABLE_TUNNEL_${index}`}>
        <jscad.sphere radius={radius} center={points[index]} />
        <jscad.sphere radius={radius} center={point} />
      </jscad.hull>
    ))}
  </jscad.union>
)

const batteryCoil = (x: number, ys: number[], z: number, radii: number[]): CadOp => {
  const rings = ys.map((y, index) => opRingY(x, y, z, radii[index], Math.max(0.9, radii[index] - 0.42), 0.24))
  const braces = [0, 2 * Math.PI / 3, 4 * Math.PI / 3].map((angle) => cableShape(
    ys.map((y, index) => [
      x + Math.cos(angle + index * 0.6) * (radii[index] - 0.22),
      y,
      z + Math.sin(angle + index * 0.6) * (radii[index] - 0.22),
    ] as [number, number, number]),
    0.16,
  ))
  return opUnion(...rings, ...braces)
}

const keystone209Model = () => cadModel([0.70, 0.72, 0.75], opUnion(
  opCuboid([8.2, 0.45, 9.0], [-32.1, -23.95, -8.55]),
  opCuboid([9.4, 1.6, 1.0], [-32.1, -24.75, -4.7]),
  opCuboid([9.4, 1.6, 1.0], [-32.1, -24.75, -12.4]),
  opCuboid([3.0, 1.4, 2.0], [-35.0, -24.7, -13.1]),
  batteryCoil(-32.1, [-23.55, -22.7, -21.9, -21.25, -20.75], -8.55, [3.45, 3.2, 2.9, 2.55, 2.15]),
))

const keystone228Model = () => cadModel([0.70, 0.72, 0.75], opUnion(
  opCuboid([8.2, 0.45, 9.0], [-15.9, -23.95, -8.55]),
  opCuboid([9.4, 1.6, 1.0], [-15.9, -24.75, -4.7]),
  opCuboid([9.4, 1.6, 1.0], [-15.9, -24.75, -12.4]),
  opCuboid([3.0, 1.4, 2.0], [-13.0, -24.7, -13.1]),
  opCylinder(3.15, 2.9, [-15.9, -22.3, -8.55], [90, 0, 0]),
  opCylinder(1.75, 0.8, [-15.9, -20.45, -8.55], [90, 0, 0]),
))

const keystone210Model = () => cadModel([0.70, 0.72, 0.75], opUnion(
  opCuboid([28.0, 0.45, 9.0], [-24, 33.95, -8.55]),
  opCuboid([29.2, 1.6, 1.0], [-24, 34.75, -4.7]),
  opCuboid([29.2, 1.6, 1.0], [-24, 34.75, -12.4]),
  opCylinder(3.15, 2.6, [-32.1, 32.45, -8.55], [90, 0, 0]),
  opCylinder(1.75, 0.8, [-32.1, 30.75, -8.55], [90, 0, 0]),
  batteryCoil(-15.9, [33.55, 32.75, 31.95, 31.25, 30.75], -8.55, [3.45, 3.2, 2.9, 2.55, 2.15]),
))

/** Same Sky CMS-2004-18SP production speaker envelope.  The model follows the
 * manufacturer's 20.0 mm diameter and 4.0 mm nominal height drawing and, unlike
 * the former anonymous cylinder, includes the terminal carrier and both real
 * solder pads used by the harness geometry. */
const cms2004SpeakerModel = () => ({ jscad: opUnion(
  opColor([0.12, 0.13, 0.14], opCylinder(10.0, 1.15,
    [SPEAKER_LAYOUT.x, SPEAKER_LAYOUT.y, 10.625])),
  opColor([0.18, 0.19, 0.20], opCylinder(9.55, 2.90,
    [SPEAKER_LAYOUT.x, SPEAKER_LAYOUT.y, 8.65])),
  opColor([0.10, 0.11, 0.12], opCylinder(6.10, 0.20,
    [SPEAKER_LAYOUT.x, SPEAKER_LAYOUT.y, 7.30])),
  opColor([0.04, 0.04, 0.045], opRing(
    SPEAKER_LAYOUT.x, SPEAKER_LAYOUT.y, 11.12, 9.1, 6.2, 0.16)),
  opColor([0.32, 0.22, 0.10], opCuboid([5.0, 2.7, 0.60],
    [SPEAKER_LAYOUT.x, SPEAKER_LAYOUT.terminalY, 7.50])),
  opColor([0.76, 0.58, 0.16], opCuboid([1.55, 1.65, 0.12],
    [SPEAKER_LAYOUT.terminalPositiveX, SPEAKER_LAYOUT.terminalY, SPEAKER_LAYOUT.terminalZ])),
  opColor([0.76, 0.58, 0.16], opCuboid([1.55, 1.65, 0.12],
    [SPEAKER_LAYOUT.terminalNegativeX, SPEAKER_LAYOUT.terminalY, SPEAKER_LAYOUT.terminalZ])),
) })

const BottomEdgePill = ({ x }: { x: number }) => (
  <jscad.union>
    <jscad.cuboid size={[6, 9, 3.9]} center={[x, -41, 2.7]} />
    <jscad.cylinder radius={1.95} height={9} center={[x - 3, -41, 2.7]}
      rotation={[Math.PI / 2, 0, 0]} />
    <jscad.cylinder radius={1.95} height={9} center={[x + 3, -41, 2.7]}
      rotation={[Math.PI / 2, 0, 0]} />
  </jscad.union>
)

const BatteryCradle = () => (
  <jscad.union>
    {/* Eight low edge stops locate the protected pouch with 1.0 mm XY
        clearance.  Thin orthogonal members replace the earlier 8 mm corner
        blocks, whose inner corners intruded into the qualified cell envelope.
        The rear foam pad carries normal load and leaves 4.2 mm above the
        fully cycled 9.9 mm envelope for swelling. */}
    {([-1, 1] as const).flatMap((xs) => ([-1, 1] as const).map((ys) => (
      <Fragment key={`PACK_KEEPER_${xs}_${ys}`}>
        <jscad.roundedCuboid size={[1.2, 8, 3.0]} roundRadius={0.35} center={[
          BATTERY_HOLDER_CLEARANCE.x + xs * (BATTERY_HOLDER_CLEARANCE.width / 2 + 0.6),
          BATTERY_HOLDER_CLEARANCE.y + ys * 25,
          -14.8,
        ]} />
        <jscad.roundedCuboid size={[8, 1.2, 3.0]} roundRadius={0.35} center={[
          BATTERY_HOLDER_CLEARANCE.x + xs * 40,
          BATTERY_HOLDER_CLEARANCE.y + ys * (BATTERY_HOLDER_CLEARANCE.height / 2 + 0.6),
          -14.8,
        ]} />
      </Fragment>
    )))}
    {/* The door opening removes the rear floor below the pouch.  These four
        shallow ribs therefore carry each edge-stop into the continuous side
        wall; without them the evaluated print contains loose keeper islands.
        All ribs remain outside the 93.5 x 60.5 mm cycled pack envelope. */}
    {([-1, 1] as const).flatMap((xs) => ([-1, 1] as const).map((ys) => (
      <jscad.cuboid key={`PACK_KEEPER_RIB_${xs}_${ys}`}
        size={[xs < 0 ? 22.4 : 26.4, 1.2, 3.0]}
        center={[
          xs < 0 ? -61.15 : 59.15,
          BATTERY_HOLDER_CLEARANCE.y + ys * 25,
          -14.8,
        ]} />
    )))}
    {/* Harness strain-relief bridge: all three members overlap each other and
        the lower pouch keeper.  This is a real load path into the shell, not
        two floating cable-guide blocks. */}
    <jscad.cuboid size={[18, 2.4, 5.6]} center={[-42, -31.4, -12.2]} />
    <jscad.cuboid size={[6.4, 2.4, 2.4]} center={[-31.0, -32.0, -10.5]} />
    <jscad.cuboid size={[2.4, 9, 5.6]} center={[-28, -34.5, -9.0]} />
  </jscad.union>
)

const SpeakerCradle = () => (
  <jscad.union>
    {/* 0.35 mm radial print clearance around the nominal 20 mm speaker. */}
    {/* The guide ends 0.30 mm above the retainer, not inside it.  Its top
        joins the lid underside, so it is one printable body with the lid. */}
    <Ring x={SPEAKER_LAYOUT.x} y={SPEAKER_LAYOUT.y} z={9.60}
      outerRadius={11.55} innerRadius={10.35} height={4.2} />
    {[-1, 1].map((sign) => (
      <jscad.cylinder key={`SPK_BOSS_${sign}`} radius={3.15} height={4.5}
        center={[SPEAKER_LAYOUT.x + sign * SPEAKER_LAYOUT.screwOffsetX, SPEAKER_LAYOUT.y, 9.45]} />
    ))}
  </jscad.union>
)

/** Integral open two-channel wire guide, not a claimed snap strain relief.
 * It sits 0.40 mm behind the terminal carrier and joins the lid directly.
 * Leads install from the open interior side before the speaker retainer. */
const SpeakerWireGuide = () => (
  <jscad.subtract>
    <jscad.cuboid size={[6.6, 3.2, 5.7]} center={[SPEAKER_LAYOUT.x, -32.0, 8.85]} />
    <CableTunnel points={SPK_WIRE_POS_POINTS.slice(0, 4)} radius={0.52} />
    <CableTunnel points={SPK_WIRE_NEG_POINTS.slice(0, 4)} radius={0.52} />
    {[SPEAKER_LAYOUT.terminalPositiveX, SPEAKER_LAYOUT.terminalNegativeX].map((x) => (
      <jscad.cuboid key={`SPK_GUIDE_ENTRY_${x}`} size={[1.04, 3.6, 1.5]}
        center={[x, -32.0, 6.5]} />
    ))}
  </jscad.subtract>
)

const BaseCutShell = () => (
  <jscad.subtract>
    <jscad.union>
      <jscad.subtract>
        <RoundedRectPrism x={SHELL_PROFILE.centerX} y={SHELL_PROFILE.centerY}
          bottom={-17.4} width={SHELL_PROFILE.outerWidth} height={SHELL_PROFILE.outerHeight}
          depth={29} radius={SHELL_PROFILE.outerRadius} />
        <RoundedRectPrism x={SHELL_PROFILE.centerX} y={SHELL_PROFILE.centerY}
          bottom={-15.8} width={SHELL_PROFILE.innerWidth} height={SHELL_PROFILE.innerHeight}
          depth={27.8} radius={SHELL_PROFILE.innerRadius} />
      </jscad.subtract>
      {PCB_MOUNT_HOLES.map(({ name, x, y }) => (
        <jscad.cylinder key={name} radius={3.5} height={15} center={[x, y, -8.3]} />
      ))}
      <BatteryCradle />
      {/* Four elastomer-foot lands keep the broad rear door off a table and
          prevent rocking without being part of the acoustic path. */}
      {[
        [-60, -28], [60, -14], [-60, 28], [60, 24],
      ].map(([x, y], index) => (
        <jscad.cylinder key={`CASE_FOOT_${index}`} radius={4} height={2.0}
          center={[x, y, -18.0]} />
      ))}
      {/* Raised shoulders guard the power slider against palm presses. */}
      <jscad.cuboid size={[2.2, 6, 6]} center={[-74.1, -29.3, 4.3]} />
      <jscad.cuboid size={[2.2, 6, 6]} center={[-74.1, -18.7, 4.3]} />
    </jscad.union>

    {/* Rear-access M3 clearance and pan-head counterbores. */}
    {PCB_MOUNT_HOLES.map(({ name, x, y }) => (
      <Fragment key={`${name}_FASTENER`}>
        <jscad.cylinder radius={1.7} height={21} center={[x, y, -7.2]} />
        {/* ISO 7045 M3 head is 5.6 x 2.4 mm.  The 6.1 x 2.7 mm recess
            leaves 0.25 mm radial and 0.15 mm axial installation clearance
            while keeping the head flush with the rear datum. */}
        <jscad.cylinder radius={3.05} height={2.7} center={[x, y, -16.15]} />
      </Fragment>
    ))}
    {/* Local internal relief at the two tapered PCB corners.  The evaluated
        board mesh previously touched the rear cavity by 0.09 mm^3 here; the
        shallow pockets preserve the exterior radius and at least 1 mm of
        corner wall while adding 0.4 mm assembly clearance. */}
    {[-1, 1].map((sign) => (
      <jscad.cuboid key={`PCB_BOTTOM_CORNER_RELIEF_${sign}`} size={[1.8, 0.9, 2.4]}
        center={[sign * 69.7, -39.25, 0]} />
    ))}

    <RoundedRectPrism x={BATTERY_HOLDER_CLEARANCE.x} y={BATTERY_HOLDER_CLEARANCE.y}
      bottom={-17.8} width={BATTERY_DOOR.openingWidth} height={BATTERY_DOOR.openingHeight}
      depth={2.4} radius={3.2} />
    <RoundedRectPrism x={BATTERY_HOLDER_CLEARANCE.x} y={BATTERY_HOLDER_CLEARANCE.y}
      bottom={-17.6} width={BATTERY_DOOR.rebateWidth} height={BATTERY_DOOR.rebateHeight}
      depth={1.25} radius={4} />
    {/* Side service bay exposes the inline disconnect without first lifting
        the pouch. Its opening and the matching door overlap the main panel. */}
    <RoundedRectPrism x={-55.5} y={0} bottom={-17.8}
      width={14} height={24} depth={2.4} radius={2.5} />
    <RoundedRectPrism x={-55.5} y={0} bottom={-17.6}
      width={16.6} height={26.6} depth={1.25} radius={3.2} />
    <jscad.cuboid size={[22.8, 3, 2]} center={[
      BATTERY_HOLDER_CLEARANCE.x, BATTERY_HOLDER_CLEARANCE.y + 32.5, -16.1,
    ]} />
    <jscad.cuboid size={[16, 4, 2.4]} center={[
      BATTERY_HOLDER_CLEARANCE.x, BATTERY_HOLDER_CLEARANCE.y - 32.5, -15.9,
    ]} />

    <BottomEdgePill x={0} />
    <jscad.cuboid size={[3.6, 15.1, 3.1]} center={[73, 22, 2.25]} />
    <jscad.cuboid size={[9, 6.8, 4.2]} center={[-72, -24, 4.28]} />
    {/* A concentric circular relief exposes only the outer sector of the
        direct-drive collar.  Unlike the old diagonal slot, it cannot bind as
        the control turns and it leaves a constant 0.40 mm radial gap. */}
    <jscad.cylinder radius={VOLUME_EXTENSION.apertureRadius}
      height={VOLUME_EXTENSION.apertureTopZ - VOLUME_EXTENSION.apertureBottomZ}
      center={[VOLUME_EXTENSION.supplierWheelCenterX, VOLUME_EXTENSION.supplierWheelCenterY,
        (VOLUME_EXTENSION.apertureTopZ + VOLUME_EXTENSION.apertureBottomZ) / 2]} />
    {/* Remove the upper wall above the wheel too. A matching skirt on the
        removable lid closes this service opening after PCB installation. */}
    <jscad.cylinder radius={VOLUME_EXTENSION.serviceRadius} height={11.2}
      center={[VOLUME_EXTENSION.supplierWheelCenterX, VOLUME_EXTENSION.supplierWheelCenterY, 6.55]} />

    {/* Internal clearance for the integral battery-door latch beam. */}
    <jscad.cuboid size={[11.0, 17.0, 3.4]} center={[
      BATTERY_HOLDER_CLEARANCE.x, BATTERY_HOLDER_CLEARANCE.y - 27.8, -14.8,
    ]} />

    {/* The battery adapter harness is retained in free cavity space and then
        passes through the PCB's existing BAT_CABLE_SLOT.  Do not subtract its
        complete swept path from the shell: the former grooves grazed the
        pouch keeper and created an unprintable detached sliver. */}

    {/* Close-fitting shoulder guides.  The complete exterior key can move into
        this opening; the two internal rails merely prevent yaw and carry the
        independent retaining tabs. */}
    <RoundedShoulderPrism x={PRODUCT_CONTROL_LAYOUT.shoulderLeftX}
      y={SHOULDER_MECHANISM.apertureCenterY} z={4.98}
      width={SHOULDER_MECHANISM.apertureWidth} height={SHOULDER_MECHANISM.apertureHeight}
      depth={SHOULDER_MECHANISM.apertureDepth} radius={1.0} />
    <RoundedShoulderPrism x={PRODUCT_CONTROL_LAYOUT.shoulderRightX}
      y={SHOULDER_MECHANISM.apertureCenterY} z={4.98}
      width={SHOULDER_MECHANISM.apertureWidth} height={SHOULDER_MECHANISM.apertureHeight}
      depth={SHOULDER_MECHANISM.apertureDepth} radius={1.0} />

  </jscad.subtract>
)

const Base = () => <BaseCutShell />

const BatteryDoor = () => (
  <jscad.subtract>
    <jscad.union>
      <RoundedRectPrism x={BATTERY_HOLDER_CLEARANCE.x} y={BATTERY_HOLDER_CLEARANCE.y}
        bottom={BATTERY_DOOR.outsideZ} width={BATTERY_DOOR.panelWidth} height={BATTERY_DOOR.panelHeight}
        depth={BATTERY_DOOR.panelThickness} radius={3.7} />
      <RoundedRectPrism x={-55.5} y={0} bottom={BATTERY_DOOR.outsideZ}
        width={16.0} height={26.0} depth={BATTERY_DOOR.panelThickness} radius={2.9} />
      <RoundedRectPrism x={-55.5} y={0}
        bottom={BATTERY_DOOR.outsideZ + BATTERY_DOOR.panelThickness - 0.02}
        width={13.4} height={23.4} depth={0.92} radius={2.2} />
      {/* Broad inner doubler spreads latch and handling loads without touching
          the protected pouch or its swelling allowance. */}
      <RoundedRectPrism x={BATTERY_HOLDER_CLEARANCE.x} y={BATTERY_HOLDER_CLEARANCE.y}
        bottom={BATTERY_DOOR.outsideZ + BATTERY_DOOR.panelThickness - 0.02}
        width={94.0} height={61.0} depth={BATTERY_DOOR.innerReinforcement + 0.02} radius={4.0} />
      {/* Perimeter-only registration tongue never crosses the pouch face. */}
      <jscad.subtract>
        <RoundedRectPrism x={BATTERY_HOLDER_CLEARANCE.x} y={BATTERY_HOLDER_CLEARANCE.y}
          bottom={BATTERY_DOOR.outsideZ + BATTERY_DOOR.panelThickness}
          width={BATTERY_DOOR.openingWidth - 0.6} height={BATTERY_DOOR.openingHeight - 0.6}
          depth={BATTERY_DOOR.tongueThickness} radius={2.9} />
        <RoundedRectPrism x={BATTERY_HOLDER_CLEARANCE.x} y={BATTERY_HOLDER_CLEARANCE.y}
          bottom={BATTERY_DOOR.outsideZ + BATTERY_DOOR.panelThickness - 0.1}
          width={94.0} height={61.0} depth={BATTERY_DOOR.tongueThickness + 0.2} radius={4.0} />
      </jscad.subtract>
      {/* Overlap the panel by 0.02 mm so the insertion tongue is one printable
          solid after boolean evaluation. */}
      <jscad.cuboid size={[22, 2.6, 0.9]} center={[
        BATTERY_HOLDER_CLEARANCE.x, BATTERY_HOLDER_CLEARANCE.y + 32.15, -16.02,
      ]} />
      {/* Integral cantilever latch.  The beam is fixed into the central door
          reinforcement and terminates in a positive hook inside the base
          relief; it is no longer a loose, unretained printed part. */}
      <jscad.cuboid size={[8.0, BATTERY_DOOR_INSTALLATION.latchBeamLength,
        BATTERY_DOOR_INSTALLATION.latchBeamThickness]}
        center={[BATTERY_HOLDER_CLEARANCE.x, BATTERY_HOLDER_CLEARANCE.y - 28.0, -15.72]} />
      {/* The hook reaches beyond the shell-slot edge at Y=-26 while remaining
          0.10 mm above the inner floor.  This gives the cantilever a real
          retaining shoulder instead of the old flush-ended cosmetic bump. */}
      <jscad.cuboid size={[8.2, 2.0, 1.4]} center={[
        BATTERY_HOLDER_CLEARANCE.x, BATTERY_HOLDER_CLEARANCE.y - 34.2, -14.5,
      ]} />
      <jscad.cuboid size={[7.2, 1.5, 0.4]} center={[
        BATTERY_HOLDER_CLEARANCE.x, BATTERY_HOLDER_CLEARANCE.y - 33.6, -15.25,
      ]} />
    </jscad.union>
    {/* Relieve the inner upper edge, outside the insertion tongue. Without
        this bevel-equivalent rebate the door hits the base when pivoted at
        its actual tongue datum rather than a fictitious displaced hinge. */}
    {[-1, 1].map((sign) => (
      <jscad.cuboid key={`DOOR_TILT_RELIEF_${sign}`} size={[40.2, 3, 1.6]}
        center={[-2 + sign * 31.2, 32.3, -15.85]} />
    ))}
    {/* Parallel relief slots define the compliant beam while preserving its
        fixed end and hooked free end as one printable solid. */}
    <jscad.cuboid size={[1.0, BATTERY_DOOR_INSTALLATION.latchBeamLength, 2.6]} center={[
      BATTERY_HOLDER_CLEARANCE.x - 4.5, BATTERY_HOLDER_CLEARANCE.y - 28.0, -15.65,
    ]} />
    <jscad.cuboid size={[1.0, BATTERY_DOOR_INSTALLATION.latchBeamLength, 2.6]} center={[
      BATTERY_HOLDER_CLEARANCE.x + 4.5, BATTERY_HOLDER_CLEARANCE.y - 28.0, -15.65,
    ]} />
    <jscad.cylinder radius={5.5} height={0.7} center={[
      BATTERY_HOLDER_CLEARANCE.x, BATTERY_HOLDER_CLEARANCE.y - 27.3, -17.2,
    ]} />
  </jscad.subtract>
)

const Lid = () => (
  <jscad.subtract>
    <jscad.union>
      <RoundedRectPrism x={SHELL_PROFILE.centerX} y={SHELL_PROFILE.centerY}
        bottom={11.6} width={SHELL_PROFILE.outerWidth} height={SHELL_PROFILE.outerHeight}
        depth={1.6} radius={SHELL_PROFILE.outerRadius} />
      {/* Removable upper wall sector: an intersection expressed as two
          subtractions for the native JSCAD serializer. The skirt overlaps
          the lid by 0.1 mm and has a 0.3 mm seam to the base on both ends. */}
      <jscad.subtract>
        <RoundedRectPrism x={SHELL_PROFILE.centerX} y={SHELL_PROFILE.centerY}
          bottom={VOLUME_EXTENSION.apertureTopZ} width={SHELL_PROFILE.outerWidth}
          height={SHELL_PROFILE.outerHeight} depth={11.7 - VOLUME_EXTENSION.apertureTopZ}
          radius={SHELL_PROFILE.outerRadius} />
        <RoundedRectPrism x={SHELL_PROFILE.centerX} y={SHELL_PROFILE.centerY}
          bottom={3.0} width={SHELL_PROFILE.innerWidth} height={SHELL_PROFILE.innerHeight}
          depth={9.0} radius={SHELL_PROFILE.innerRadius} />
        <jscad.subtract>
          <jscad.cuboid size={[300, 300, 30]} center={[0, 0, 10]} />
          <jscad.cylinder radius={VOLUME_EXTENSION.serviceRadius - 0.3} height={32}
            center={[VOLUME_EXTENSION.supplierWheelCenterX, VOLUME_EXTENSION.supplierWheelCenterY, 10]} />
        </jscad.subtract>
      </jscad.subtract>
      {/* Four broad internal tongues register the lid to the rear-shell
          cavity before the screws are tightened. */}
      {SHELL_REGISTRATION_KEYS.map(({ x, y, width, height }, index) => (
        <jscad.cuboid key={`SHELL_KEY_${index}`} size={[width, height, 1.8]}
          center={[x, y, 10.7]} />
      ))}
      {PCB_MOUNT_HOLES.map(({ name, x, y }) => (
        /* 3.5 mm matches the rear tower radius and leaves at least 0.30 mm
           radial clearance to the base cavity at the two right-edge mounts.
           The former 4.0 mm pillars physically crossed the rear sidewall. */
        <jscad.cylinder key={`${name}_PILLAR`} radius={3.8} height={10.3} center={[x, y, 6.45]} />
      ))}
      <SpeakerCradle />
      <SpeakerWireGuide />
    </jscad.union>
    {/* The relief leaves a 0.45 mm rigid-panel-to-lid gap.  A nominal 0.50 mm
        closed-cell front gasket therefore receives 0.05 mm total compression
        instead of floating below the bezel. */}
    <jscad.cuboid size={[86.46, 57.04, 0.9]} center={[-2.18, 15, 11.65]} />
    <jscad.cuboid size={[74.24, 49.76, 4]} center={[-2.68, 15, 12]} />
    {FACE_BUTTONS.map(({ name, x, y, radius }) => (
      <jscad.cylinder key={`BUTTON_${name}`} radius={radius + 0.30} height={4} center={[x, y, 12]} />
    ))}
    {/* 0.20 mm radial clearance around a separate clear light pipe.  Its
        underside flange is larger than this aperture, so it is captive after
        the PCB is installed and cannot pull through the front face. */}
    <jscad.cylinder radius={STATUS_INDICATOR_LAYOUT.apertureRadius} height={4}
      center={[STATUS_INDICATOR_LAYOUT.x, STATUS_INDICATOR_LAYOUT.y, 12]} />
    <CrossPrism x={-58} y={0} bottom={10.5} armLength={27} armWidth={8.4} depth={4} />
    {/* Local wheel clearance at the foot of the adjacent M3 pillar. This
        leaves over 1.1 mm polymer outside the 2.3 mm insert radius while the
        full-width pillar remains above the collar. */}
    <jscad.cylinder radius={VOLUME_EXTENSION.outerRadius + 0.4} height={2.3}
      center={[VOLUME_EXTENSION.supplierWheelCenterX, VOLUME_EXTENSION.supplierWheelCenterY, 2.1]} />
    {/* Front acoustic outlet: unobstructed in the user's normal grip and
        sealed directly to the speaker gasket. */}
    {SPEAKER_GRILLE_HOLES.map(({ x, y }, index) => (
      <jscad.cylinder key={`SPK_GRILLE_${index}`} radius={1.0} height={2.4}
        center={[SPEAKER_LAYOUT.x + x, SPEAKER_LAYOUT.y + y, 12.4]} />
    ))}
    {/* Continuous lead passages cross both the cradle rim and the integral
        strain-relief block.  Subtracting them from the complete lid avoids
        the earlier hidden collision where the block was relieved but the
        adjacent annular cradle was not. */}
    <CableTunnel points={SPK_WIRE_POS_POINTS.slice(0, 3)} radius={0.52} />
    <CableTunnel points={SPK_WIRE_NEG_POINTS.slice(0, 3)} radius={0.52} />
    {/* Blind M3 pockets end at Z=9.80. The seated M3x24 tip is Z=9.20:
        0.60 mm bottoming clearance and 1.80 mm of pillar roof below the lid.
        Heat-set inserts remain at Z=1.30..7.00, with full thread engagement. */}
    {PCB_MOUNT_HOLES.map(({ name, x, y }) => (
      <jscad.cylinder key={`${name}_INSERT`} radius={2.0} height={8.5} center={[x, y, 5.55]} />
    ))}
    {/* M2 short heat-set inserts retain the speaker.  The 3.15 mm bosses
        leave a 1.35 mm radial PA12 envelope outside the real 3.6 mm insert. */}
    {[-1, 1].map((sign) => (
      // Insert installs from the exposed Z=7.20 bearing face before the
      // retainer. A small entry overrun opens the pilot bore; the tip stops
      // at Z=10.60, 0.60 mm before the blind end.
      <jscad.cylinder key={`SPK_INSERT_${sign}`} radius={1.55} height={4.1}
        center={[SPEAKER_LAYOUT.x + sign * SPEAKER_LAYOUT.screwOffsetX, SPEAKER_LAYOUT.y, 9.15]} />
    ))}
  </jscad.subtract>
)

const FaceButton = ({ x, y, radius }: { x: number, y: number, radius: number }) => (
  <jscad.union>
    {/* Keep the actuator socket entirely in the hidden lower body.  The old
        single subtractive solid included the fingertip crown, which the
        hosted evaluator rendered as a partly buried crescent even though the
        exported GLB was manifold.  A separately unioned, overlapping crown
        gives both evaluators the same full circular exterior. */}
    <jscad.subtract>
      <jscad.union>
        <jscad.cylinder radius={radius} height={5.0} center={[x, y, 13.75]} />
        <jscad.cylinder radius={radius + 1.10} height={0.8} center={[x, y, 10.85]} />
      </jscad.union>
      {/* The actual top of this blind pocket is the switch-contact plane.  It
          is validated against the evaluated actuator mesh; no nominal crown
          datum is used as a proxy for contact. */}
      <jscad.cylinder radius={1.90} height={5.6}
        center={[x, y, FACE_BUTTON_CONTACT_Z - 2.8]} />
    </jscad.subtract>
    {/* Overlap the lower body by 0.60 mm so this cannot become a tangent or
        disconnected top skin after cloud-side boolean evaluation. */}
    <jscad.cylinder radius={radius} height={1.05} center={[x, y, 16.325]} />
  </jscad.union>
)

/** Split direct-drive collar for the real 14 mm supplier wheel.  Twelve small
 * inward ribs bite the catalog wheel's serrations; a radial relief opposite
 * the user-facing wall lets PA12 expand during installation.  Shallow outer
 * grooves give the fingertip useful traction without creating fragile teeth. */
const VolumeWheelExtension = () => {
  const centerX = VOLUME_EXTENSION.supplierWheelCenterX
  const centerY = VOLUME_EXTENSION.supplierWheelCenterY
  const height = VOLUME_EXTENSION.topZ - VOLUME_EXTENSION.bottomZ
  const centerZ = (VOLUME_EXTENSION.topZ + VOLUME_EXTENSION.bottomZ) / 2
  const splitAngle = VOLUME_EXTENSION.splitAngleDeg * Math.PI / 180
  const gripAngles = Array.from({ length: 12 }, (_, index) => index * Math.PI / 6)
    .filter((angle) => Math.abs(Math.atan2(
      Math.sin(angle - splitAngle), Math.cos(angle - splitAngle),
    )) > Math.PI / 8)
  const grooveAngles = Array.from({ length: 36 }, (_, index) => index * Math.PI / 18)
  return <jscad.union>
    <jscad.subtract>
      <jscad.cylinder radius={VOLUME_EXTENSION.outerRadius} height={height}
        center={[centerX, centerY, centerZ]} />
      <jscad.cylinder radius={VOLUME_EXTENSION.collarInnerRadius} height={height + 0.4}
        center={[centerX, centerY, centerZ]} />
      {/* The split opens toward the protected interior, not the visible wall. */}
      <jscad.cuboid size={[4.4, VOLUME_EXTENSION.splitWidth, height + 0.5]}
        center={[
          centerX + Math.cos(splitAngle) * 8.6,
          centerY + Math.sin(splitAngle) * 8.6,
          centerZ,
        ]} rotation={[0, 0, splitAngle]} />
      {grooveAngles.map((angle, index) => (
        /* A square cutter is deliberate: the cloud JSCAD rotation path used
           to leave 0.01 mm tangent caps at 90/270 degrees when the cutter had
           a shallow axis.  The 0.78 mm through-cut is comfortably above the
           PA12 detail limit and cannot create loose printable slivers. */
        <jscad.cuboid key={`VOLUME_GRIP_GROOVE_${index}`} size={[0.78, 0.78, height + 0.3]}
          center={[
            centerX + Math.cos(angle) * (VOLUME_EXTENSION.outerRadius - 0.22),
            centerY + Math.sin(angle) * (VOLUME_EXTENSION.outerRadius - 0.22),
            centerZ,
          ]} rotation={[0, 0, angle]} />
      ))}
    </jscad.subtract>
    {gripAngles.map((angle, index) => (
      <jscad.cuboid key={`VOLUME_INNER_GRIP_${index}`} size={[0.50, 0.70, height - 0.12]}
        center={[
          centerX + Math.cos(angle) * 7.10,
          centerY + Math.sin(angle) * 7.10,
          centerZ,
        ]} rotation={[0, 0, angle]} />
    ))}
  </jscad.union>
}

const Dpad = () => (
  <jscad.union>
    {/* The socket/pivot carrier is evaluated separately from the visible
        cross.  This avoids the cloud renderer clipping the front face at the
        four switch sockets while preserving the same captive mechanism. */}
    <jscad.subtract>
      <jscad.union>
        <CrossPrism x={-58} y={0} bottom={10.15} armLength={24.4} armWidth={5.8} depth={6.0} />
        <CrossPrism x={-58} y={0} bottom={10.15} armLength={28.2} armWidth={9.2} depth={0.8} />
        {/* A spherical foot defines the rocking centre. The upper flange
            stops excessive tilt; narrow guides and broad open sockets clear
            the full 2.5-degree cardinal and diagonal operating sweeps. */}
        <jscad.sphere radius={DPAD_MECHANISM.pivotRadius} center={[...DPAD_MECHANISM.pivot]} />
        <jscad.cylinder radius={2.5} height={6.5} center={[-58, 0, 7.55]} />
      </jscad.union>
      {DPAD_SWITCHES.map(({ x, y }, index) => (
        <jscad.cylinder key={`DPAD_SOCKET_${index}`} radius={3.0} height={6.0}
          center={[x, y, DPAD_MECHANISM.contactZ - 3.0]} />
      ))}
      {/* The upper-left M3 lid pillar approaches the wide captive flange at a
          rounded corner.  This service notch stops below the visible face. */}
      <jscad.cylinder radius={4.3} height={3.0} center={[-66, 14, 10.7]} />
    </jscad.subtract>
    {/* A full, overlapping front cross guarantees a clean, continuous
        control surface in both the local and hosted viewers. */}
    <CrossPrism x={-58} y={0} bottom={DPAD_MECHANISM.contactZ} armLength={26.2} armWidth={7.8} depth={1.35} />
  </jscad.union>
)

const ShoulderPaddle = ({ x }: { x: number }) => (
  <jscad.subtract>
    <jscad.union>
      <RoundedShoulderPrism x={x} y={SHOULDER_MECHANISM.visibleCenterY} z={4.98}
        width={SHOULDER_MECHANISM.visibleWidth} height={SHOULDER_MECHANISM.visibleHeight}
        depth={SHOULDER_MECHANISM.visibleDepth} radius={1.0} />
      {[-1, 1].map((sign) => <Fragment key={`SHOULDER_GUIDE_${sign}`}>
        <jscad.cuboid size={[SHOULDER_MECHANISM.railWidth, SHOULDER_MECHANISM.railDepth,
          SHOULDER_MECHANISM.railHeight]}
          center={[x + sign * SHOULDER_MECHANISM.railOffsetX,
            SHOULDER_MECHANISM.railCenterY, 4.98]} />
        <jscad.cuboid size={[SHOULDER_MECHANISM.tabWidth, SHOULDER_MECHANISM.tabDepth,
          SHOULDER_MECHANISM.tabHeight]}
          center={[x + sign * SHOULDER_MECHANISM.railOffsetX,
            SHOULDER_MECHANISM.tabCenterY, 4.98]} />
      </Fragment>)}
    </jscad.union>
    <jscad.cylinder radius={SHOULDER_MECHANISM.actuatorPocketRadius}
      height={SHOULDER_MECHANISM.actuatorPocketDepth}
      center={[x, SHOULDER_MECHANISM.actuatorPocketCenterY, 4.98]}
      rotation={[Math.PI / 2, 0, 0]} />
  </jscad.subtract>
)

const ScreenCarrier = () => (
  <jscad.subtract>
    <jscad.union>
      {SCREEN_CARRIER_HOLES.map(({ name, x, y }) => (
        <jscad.cylinder key={name} radius={3.6} height={6.35} center={[x, y, 3.975]} />
      ))}
      {/* Two broad rear-bezel rails carry the rigid panel through the rear
          gasket.  The lower-left mount first runs across the unobstructed
          bottom service strip, then rises at X=9: outside both Start/Select
          envelopes and outside the front-speaker cradle.  Control datums are
          never moved to make room for structure. */}
      <RoundedBeam from={[9, -11.2]} to={[36.5, -11.2]} z={7.00} width={5} depth={2.4} />
      <RoundedBeam from={[-20, -35.5]} to={[9, -35.5]} z={7.00} width={5} depth={2.4} />
      <RoundedBeam from={[9, -35.5]} to={[9, -11.2]} z={7.00} width={5} depth={2.4} />
      <RoundedBeam from={[-37.5, 41.2]} to={[36.5, 41.2]} z={7.00} width={5} depth={2.4} />
      {/* No centre spine: the two bearing rails are joined by the right-side
          load path below.  Removing the non-contact bar keeps the component
          field visually and physically open while retaining one connected
          four-point carrier. */}

      {/* Uniform rounded load paths replace the former asymmetric collection
          of rectangular blocks.  Deliberate gaps remain around SW_RIGHT and
          the upper-left shoulder switch envelope. */}
      <RoundedBeam from={[-42.8, 8.0]} to={[-42.8, 31.8]} z={7.00} width={6} depth={2.4} />
      <RoundedBeam from={[-42.8, 31.8]} to={[-35.0, 36.4]} z={7.00} width={6} depth={2.4} />
      <RoundedBeam from={[-35.0, 36.4]} to={[-35.0, 41.2]} z={7.00} width={6} depth={2.4} />
      <RoundedBeam from={[-48.75, 32.75]} to={[-42.8, 31.8]} z={7.00} width={6} depth={2.4} />

      <RoundedBeam from={[66, -31]} to={[66, -20]} z={7.00} width={7} depth={2.4} />
      <RoundedBeam from={[66, -20]} to={[50, -20]} z={7.00} width={6} depth={2.4} />
      <RoundedBeam from={[50, -20]} to={[39, -11.2]} z={7.00} width={6} depth={2.4} />
      <RoundedBeam from={[39, -11.2]} to={[39, 33]} z={7.00} width={6} depth={2.4} />
      <RoundedBeam from={[39, 33]} to={[36.5, 33]} z={7.00} width={6} depth={2.4} />
      <RoundedBeam from={[36.5, 33]} to={[51, 33]} z={7.00} width={6} depth={2.4} />
      <RoundedBeam from={[36.5, 33]} to={[36.5, 41.2]} z={7.00} width={6} depth={2.4} />
    </jscad.union>
    {SCREEN_CARRIER_HOLES.map(({ name, x, y }) => (
      /* A 4.0 mm pilot is intentionally smaller than the insert's 4.6 mm
         knurled OD.  The old 4.64 mm bore was a loose clearance hole and
         could not retain a heat-set insert. */
      <jscad.cylinder key={`${name}_INSERT_POCKET`} radius={2.0} height={5.0} center={[x, y, 3.3]} />
    ))}
  </jscad.subtract>
)

const SpeakerRetainer = () => (
  <jscad.subtract>
    <jscad.union>
      <Ring x={SPEAKER_LAYOUT.x} y={SPEAKER_LAYOUT.y} z={SPEAKER_LAYOUT.retainerCenterZ}
        outerRadius={11.1} innerRadius={8.7} height={1.6} />
      <jscad.cuboid size={[32.4, 5.2, 1.6]}
        center={[SPEAKER_LAYOUT.x, SPEAKER_LAYOUT.y, SPEAKER_LAYOUT.retainerCenterZ]} />
      {/* Closed bearing ears leave 1.9 mm of material outside each M2
          clearance hole.  The former holes broke through the bar ends. */}
      {[-1, 1].map((sign) => (
        <jscad.cylinder key={`SPK_RETAINER_EAR_${sign}`} radius={3.0} height={1.6}
          center={[SPEAKER_LAYOUT.x + sign * SPEAKER_LAYOUT.screwOffsetX,
            SPEAKER_LAYOUT.y, SPEAKER_LAYOUT.retainerCenterZ]} />
      ))}
    </jscad.union>
    {/* One terminal service window clears the real CMS-2004-18SP tab and both
        insulated leads.  A single opening leaves the U-shaped ring joined to
        its screw-bearing crossbar; two separate tunnels split the evaluated
        printable mesh into disconnected islands. */}
    <jscad.cuboid size={[7.0, 5.2, 2.0]} center={[
      SPEAKER_LAYOUT.x, SPEAKER_LAYOUT.terminalY, SPEAKER_LAYOUT.retainerCenterZ,
    ]} />
    {[-1, 1].map((sign) => (
      <jscad.cylinder key={`SPK_RETAINER_SCREW_${sign}`} radius={1.10} height={2.2}
        center={[SPEAKER_LAYOUT.x + sign * SPEAKER_LAYOUT.screwOffsetX,
          SPEAKER_LAYOUT.y, SPEAKER_LAYOUT.retainerCenterZ]} />
    ))}
  </jscad.subtract>
)

const BatteryPack = () => (
  <assembly.part name="BATTERY_PACK_LP906090JH"
    displayName="Jauch LP906090JH protected 1S 6000 mAh two-wire pouch, 93.5 x 60.5 x 9.9 mm cycled envelope"
    cadModel={cadModel([0.18, 0.36, 0.68],
      opCuboid([93.5, 60.5, 9.9], [BATTERY_HOLDER_CLEARANCE.x, BATTERY_HOLDER_CLEARANCE.y, -9.95]))} />
)

const MechanicalHardware = () => (
  <Fragment>
    {PCB_MOUNT_HOLES.map(({ name, x, y }) => (
      <Fragment key={`${name}_HARDWARE`}>
        <assembly.part name={`${name}_M3X24`} displayName="ISO 7045 M3 x 24 PH1 pan-head rear service screw"
          cadModel={cadModel([0.55, 0.57, 0.60], opUnion(
            // ISO 7045:2011 M3 envelope: 3.0 mm thread, 5.6 mm head,
            // 2.4 mm head height and 24 mm under-head length.
            // Bearing face seats on the base counterbore at Z=-14.80.
            opCylinder(1.50, 24, [x, y, -2.8]),
            opCylinder(2.80, 2.4, [x, y, -16.0]),
          ))} />
        <assembly.part name={`${name}_M3_INSERT`} displayName="CNC Kitchen M3 Standard heat-set insert, 5.7 x 4.6 mm"
          cadModel={cadModel([0.72, 0.48, 0.12], opRing(x, y, 4.15, 2.30, 1.50, 5.7))} />
        <assembly.part name={`${name}_M3_WASHER`} displayName="M3 nylon PCB washer"
          cadModel={cadModel([0.86, 0.86, 0.80], opRing(x, y, 1.05, 3.1, 1.6, 0.5))} />
      </Fragment>
    ))}

    {SCREEN_CARRIER_HOLES.map(({ name, x, y }) => (
      <Fragment key={`${name}_LCD_HARDWARE`}>
        <assembly.part name={`${name}_M2P5X6`} displayName="ISO 7045 M2.5 x 6 PH1 pan-head LCD-carrier screw"
          cadModel={cadModel([0.55, 0.57, 0.60], opUnion(
            // A 0.5 mm nylon washer spreads the pan-head load on the PCB.
            // The screw's under-head datum is the washer underside at Z=-1.3.
            opCylinder(1.25, 6, [x, y, 1.7]),
            opCylinder(2.35, 2.1, [x, y, -2.35]),
          ))} />
        <assembly.part name={`${name}_M2P5_INSERT`}
          displayName="CNC Kitchen M2.5 Short heat-set insert, 4.0 x 4.6 mm"
          cadModel={cadModel([0.72, 0.48, 0.12], opRing(x, y, 2.8, 2.30, 1.25, 4.0))} />
        <assembly.part name={`${name}_M2P5_WASHER`} displayName="M2.5 nylon LCD-carrier washer, 5.5 x 2.7 x 0.5 mm"
          cadModel={cadModel([0.86, 0.86, 0.80], opRing(x, y, -1.05, 2.75, 1.35, 0.5))} />
      </Fragment>
    ))}

    <BatteryPack />
    <assembly.part name="BATTERY_REAR_PAD" displayName="0.8 mm closed-cell pouch support pad, compressed to 0.65 mm"
      cadModel={cadModel([0.12, 0.12, 0.13], opSubtract(
        opCuboid([89, 56, 0.65], [-2, 0, -15.225]),
        opCuboid([10.8, 18, 0.30], [20, -21, -15.10]),
      ))} />
    <assembly.part name="BATTERY_PULL_RIBBON"
      displayName="10 mm wide, 0.15 mm nylon pull ribbon bonded to PCB underside; free end under door"
      cadModel={cadModel([0.80, 0.36, 0.08], opUnion(
        opCuboid([10, 47.75, 0.15], [20, -6.875, -4.80]),
        // Turn up through the gap between foam pads, then bond a 10 x 12 mm
        // lap to the bare PCB underside. No load is carried by a foam edge.
        opCuboid([10, 0.15, 3.83], [20, 17, -2.885]),
        opCuboid([10, 12, 0.15], [20, 23, -1.045]),
        opCuboid([10, 0.15, 10.45], [20, -30.75, -9.95]),
        opCuboid([10, 16, 0.15], [20, -22.75, -15.10]),
      ))} />
    <assembly.part name="BATTERY_RIBBON_ANCHOR"
      displayName="10 x 12 x 0.17 mm adhesive lap between pull ribbon and PCB underside; qualify peel strength"
      cadModel={cadModel([0.62, 0.56, 0.30], opCuboid([10, 12, 0.17], [20, 23, -0.885]))} />
    {([-1, 1] as const).flatMap((xs) => ([-1, 1] as const).map((ys) => (
      <assembly.part key={`BATTERY_TOP_PAD_${xs}_${ys}`} name={`BATTERY_TOP_PAD_${xs}_${ys}`}
        displayName="4.5 mm closed-cell pouch-to-PCB pad, compressed to the 4.2 mm installed gap"
        cadModel={cadModel([0.12, 0.12, 0.13], opCuboid(
          [12, 12, 4.2], [-2 + xs * 35, ys * 21, -2.9],
        ))} />
    )))}
    <assembly.part name="BATTERY_NTC_10K"
      displayName="10 kΩ B=3435 epoxy bead NTC on thermally conductive adhesive at pouch centre"
      cadModel={cadModel([0.82, 0.64, 0.04], opSphere(1.25, [-2, 0, -3.63]))} />
    <assembly.part name="BATTERY_NTC_BOND_PAD"
      displayName="12 x 10 x 0.12 mm thermally conductive adhesive layer on pouch surface"
      cadModel={cadModel([0.62, 0.56, 0.30], opCuboid([12, 10, 0.12], [-2, 0, -4.94]))} />
    <assembly.part name="BATTERY_SERVICE_CONNECTOR"
      displayName="JST ZOM 3-circuit inline pair: ZOMP-03V-A-S + ZOMR-03VF-A-S"
      cadModel={cadModel([0.82, 0.82, 0.78], opUnion(
        // Conservative mated envelope from JST's ZOM drawings: 1.5 mm pitch,
        // 6.5 mm three-circuit width, 2.8 mm housing thickness.  The two
        // keyed housings are divided at the mating plane for assembly clarity.
        opCuboid([6.5, 6.65, 2.8], [-55.5, -3.325, -12.8]),
        opCuboid([6.5, 6.65, 2.8], [-55.5, 3.325, -12.8]),
        opCuboid([1.8, 2.8, 0.45], [-55.5, 0, -14.425]),
      ))} />
    <assembly.part name="BAT_PACK_LEAD_NEG" displayName="Black factory battery lead to service connector"
      cadModel={cableModel([0.035, 0.035, 0.04], BAT_PACK_LEAD_NEG_POINTS)} />
    <assembly.part name="BAT_PACK_LEAD_POS" displayName="Red factory battery lead to service connector"
      cadModel={cableModel([0.72, 0.035, 0.025], BAT_PACK_LEAD_POS_POINTS)} />
    <assembly.part name="BAT_NTC_SENSE_LEAD" displayName="Yellow bonded-NTC lead to service connector"
      cadModel={cableModel([0.82, 0.64, 0.04], BAT_NTC_SENSE_POINTS, 0.28)} />
    <assembly.part name="BAT_WIRE_NEG" displayName="Black 26 AWG battery harness lead"
      cadModel={cableModel([0.035, 0.035, 0.04], BAT_WIRE_NEG_POINTS)} />
    <assembly.part name="BAT_WIRE_POS" displayName="Red 26 AWG battery harness lead"
      cadModel={cableModel([0.72, 0.035, 0.025], BAT_WIRE_POS_POINTS)} />
    <assembly.part name="BAT_WIRE_NTC" displayName="Yellow 26 AWG 10 kΩ pack-NTC harness lead"
      cadModel={cableModel([0.82, 0.64, 0.04], BAT_WIRE_NTC_POINTS)} />

    <assembly.part name="DPAD_PIVOT_PAD" displayName="1.0 mm self-adhesive Poron D-pad pivot disc"
      cadModel={cadModel([0.18, 0.18, 0.18],
        opCylinder(3.0, 1.0, [-58, 0, 1.30]))} />
    {/* Clear acrylic/polycarbonate light pipe.  Its lower face stops 0.15 mm
        above the LED package and its top is recessed 0.05 mm below the lid.
        The 3.6 mm flange bears against the lid underside and is secured with
        a small ring of optically clear adhesive after insertion from below. */}
    <assembly.part name="STATUS_LIGHT_PIPE" displayName="Clear flanged RGB status light pipe"
      cadModel={cadModel([0.70, 0.84, 0.96], opUnion(
        opCylinder(
          STATUS_INDICATOR_LAYOUT.lightPipeRadius,
          STATUS_INDICATOR_LAYOUT.lightPipeTopZ - STATUS_INDICATOR_LAYOUT.lightPipeBottomZ,
          [STATUS_INDICATOR_LAYOUT.x, STATUS_INDICATOR_LAYOUT.y,
            (STATUS_INDICATOR_LAYOUT.lightPipeTopZ + STATUS_INDICATOR_LAYOUT.lightPipeBottomZ) / 2],
        ),
        opCylinder(
          STATUS_INDICATOR_LAYOUT.flangeRadius,
          STATUS_INDICATOR_LAYOUT.flangeHeight,
          [STATUS_INDICATOR_LAYOUT.x, STATUS_INDICATOR_LAYOUT.y, STATUS_INDICATOR_LAYOUT.flangeCenterZ],
        ),
      ))} />
    <assembly.part name="LCD_FRONT_GASKET" displayName="0.5 mm closed-cell LCD bezel gasket"
      cadModel={cadModel([0.05, 0.05, 0.05], opRectRing(-2.18, 15, 11.875, 85.8, 56.4, 74.4, 49.9, 0.5))} />
    <assembly.part name="LCD_REAR_GASKET" displayName="0.5 mm closed-cell LCD carrier gasket"
      cadModel={cadModel([0.05, 0.05, 0.05], opRectRing(-2.18, 15, 8.45, 84.8, 55.4, 77.8, 49.4, 0.5))} />
    <assembly.part name="SPK_20MM" displayName="Same Sky CMS-2004-18SP 20 mm, 8 ohm speaker"
      cadModel={cms2004SpeakerModel()} />
    <assembly.part name="SPK_FOAM_GASKET" displayName="0.6 mm speaker gasket, installed at 0.5 mm"
      cadModel={cadModel([0.05, 0.05, 0.05], opRing(
        SPEAKER_LAYOUT.x, SPEAKER_LAYOUT.y, SPEAKER_LAYOUT.gasketCenterZ, 10.2, 8.5, 0.5))} />
    {[-1, 1].map((sign) => (
      <Fragment key={`SPK_M2_${sign}`}>
        <assembly.part name={`SPK_M2X5_${sign < 0 ? "L" : "R"}`}
          displayName="ISO 7045 M2 x 5 PH0 pan-head speaker-retainer screw"
          cadModel={cadModel([0.55, 0.57, 0.60], opUnion(
            opCylinder(0.95, 5, [SPEAKER_LAYOUT.x + sign * SPEAKER_LAYOUT.screwOffsetX,
              SPEAKER_LAYOUT.y, 8.10]),
            opCylinder(1.9, 1.7, [SPEAKER_LAYOUT.x + sign * SPEAKER_LAYOUT.screwOffsetX,
              SPEAKER_LAYOUT.y, 4.75]),
          ))} />
        <assembly.part name={`SPK_M2_INSERT_${sign < 0 ? "L" : "R"}`}
          displayName="CNC Kitchen M2 Short heat-set insert, 3.0 x 3.6 mm"
          cadModel={cadModel([0.72, 0.48, 0.12], opRing(
            SPEAKER_LAYOUT.x + sign * SPEAKER_LAYOUT.screwOffsetX,
            SPEAKER_LAYOUT.y, 8.7, 1.8, 1.0, 3.0,
          ))} />
      </Fragment>
    ))}
    <assembly.part name="SPK_WIRE_POS" displayName="Red speaker harness lead"
      cadModel={cableModel([0.72, 0.035, 0.025], SPK_WIRE_POS_POINTS, 0.32)} />
    <assembly.part name="SPK_WIRE_NEG" displayName="Black speaker harness lead"
      cadModel={cableModel([0.035, 0.035, 0.04], SPK_WIRE_NEG_POINTS, 0.32)} />
  </Fragment>
)

export const GbaEnclosure = () => (
  <Fragment>
    <assembly.printedpart name="GBA_CASE_BASE" displayName="Rear shell with protected pouch, service door, ports and screw retention"
      material="nylon" color="#30343b" jscad={<Base />} />
    <assembly.printedpart name="GBA_CASE_LID" displayName="Front shell with captive controls, display bezel and insert pillars"
      material="nylon" color="#343943" jscad={<Lid />} />
    <assembly.printedpart name="GBA_BATTERY_DOOR" displayName="Flush protected-pouch battery service door"
      material="nylon" color="#282d34" jscad={<BatteryDoor />} />
    <assembly.printedpart name="LCD_CARRIER" displayName="Four-point M2.5 LCD bezel carrier"
      material="nylon" color="#20242a" jscad={<ScreenCarrier />} />
    <assembly.printedpart name="SPK_RETAINER" displayName="Screw-retained 20 mm speaker ring"
      material="nylon" color="#20242a" jscad={<SpeakerRetainer />} />
    <assembly.printedpart name="DPAD_CAP" displayName="Captive rocking D-pad with central pivot"
      material="nylon" color="#171a1f" jscad={<Dpad />} />
    {FACE_BUTTONS.map(({ name, x, y, radius }) => (
      <assembly.printedpart key={name} name={`BUTTON_CAP_${name}`} displayName={`${name} captive button cap`}
        material="nylon" color={name === "SELECT" || name === "START" ? "#252a30" : "#b35a78"}
        jscad={<FaceButton x={x} y={y} radius={radius} />} />
    ))}
    <assembly.printedpart name="SHOULDER_PADDLE_L" displayName="Captive left shoulder paddle"
      material="nylon" color="#20242a" jscad={<ShoulderPaddle x={PRODUCT_CONTROL_LAYOUT.shoulderLeftX} />} />
    <assembly.printedpart name="SHOULDER_PADDLE_R" displayName="Captive right shoulder paddle"
      material="nylon" color="#20242a" jscad={<ShoulderPaddle x={PRODUCT_CONTROL_LAYOUT.shoulderRightX} />} />
    <assembly.printedpart name="VOLUME_WHEEL_EXTENSION"
      displayName="Split PA12 direct-drive volume thumbwheel collar"
      material="nylon" color="#14171b" jscad={<VolumeWheelExtension />} />
    <MechanicalHardware />
  </Fragment>
)