Skip to content
RPRichard PruittTech & Cyber
Navigation
repo://poppyup/viewer/components.js

viewer/components.js

55 KB · 484 lines · javascript · Download from the repository page

viewer/components.js
// PoppyUp 3D mode — component-aware assembly model.// Pure data: no rendering here. Consumed by geometry.js (browser) and validate.mjs (Node).//// COORDINATE CONVENTION (document frame, millimetres, matches docs/08-mechanical.md and drawings/)://   X  0 → 190  left → right, viewed from the front//   Y  0 → 96   top edge (slots) → bottom edge (ports)          (Y grows DOWN, as in the docs)//   Z  0 → 38   front face (frame / battery door) → rear face (magnesium skin)// geometry.js converts to the renderer's Y-up frame; every value below is in the document frame.//// Geometry spec types://   { type:'box',  x:[x0,x1], y:[y0,y1], z:[z0,z1] }//   { type:'cyl',  axis:'x'|'y'|'z', c:[cx,cy,cz], r, len }//   { type:'plate', normal:'z'|'y', x:[..], y:[..], z:[..], r, holes:[{x:[..],y:[..],r}] }  // rounded plate with holes; for normal 'y' the hole/outline "y" fields are Z ranges//   { type:'ring', normal:'z'|'y', x:[..], y:[..], z:[..], r, t }  // rounded-rect ring of section t//// explode: [dx,dy,dz] in the document frame, applied as  position(t) = assembled + ease(t) * explode//   (top-level assemblies move in the whole-object explosion; children move only when their//   parent is the explosion scope or "expand subassemblies" is on). Illustrative layout, not a//   verified disassembly sequence (assembly order is in 08-mechanical.md §12).//// confidence: 'verified' (dimension stated in a doc/drawing) | 'derived' (computed from stated//   values) | 'approximate' (placed for visualization only) | 'unknown'. export const MODEL = {  title: 'PoppyUp — Phase 1 hardware concept',  revision: 'ADR v0.3 (2026-09-27) · drawings rev C · viewer model rev 1',  units: 'mm',  envelope: { x: [0, 190], y: [0, 96], z: [0, 38] },  status: 'Concept visualization. Envelope, slot row, shutter, Z-stack and board outlines follow the Phase 1 documents; component placements on the boards are approximate. Not a manufacturing model.',  sources: [    'docs/08-mechanical.md §2.1, §3.1–3.3, §5, §6, §7, §10, §12',    'docs/09-pcb-architecture.md §1, §1.1, §1.2',    'docs/10-bom.md (part numbers)',    'drawings/generate_drawings.py (slot X positions, shutter, exploded parts list rows 1–15)',  ],}; // Slot table — 08-mechanical.md §5 and drawings/generate_drawings.py `slots`.export const SLOTS = [  { key: 'cfast', name: 'CFast 2.0',   x0: 13.5,  w: 45, open: [44, 6],   depth: 34, card: [42.8, 36.4, 3.6], conn: 'Amphenol CFast receptacle', ctrl: 'ASM1064 (SATA/AHCI)' },  { key: 'cfeb',  name: 'CFexpress B', x0: 62.5,  w: 40, open: [40, 6],   depth: 27, card: [38.5, 29.6, 3.8], conn: 'Molex CFexpress Type B push-push', ctrl: 'direct PCIe Gen3 ×2 root port via PI3DBS16412 mux' },  { key: 'cfea',  name: 'CFexpress A', x0: 106.5, w: 22, open: [21.5, 5], depth: 25, card: [20.0, 28.0, 2.8], conn: 'Molex CFexpress Type A push-push', ctrl: 'ASM2812 downstream port' },  { key: 'sd',    name: 'SD UHS-II',   x0: 132.5, w: 27, open: [25, 4],   depth: 26, card: [24.0, 32.0, 2.1], conn: 'SD UHS-II push-push', ctrl: 'GL9755' },  { key: 'usd',   name: 'microSD',     x0: 163.5, w: 13, open: [12, 3],   depth: 13, card: [11.0, 15.0, 1.0], conn: 'microSD push-push', ctrl: 'GL9750' },];const SLOT_ZC = 15; // slot centreline in Z on the top face (top-view drawing) // Part definitions shared by repeated instances.export const DEFINITIONS = {  'def-21700-cell':  { name: '21700 Li-ion cell', partNumber: 'Samsung INR21700-50E', material: 'Li-ion, steel can', dims: 'Ø21 × 70 mm', qty: 4, source: '10-bom.md CELLS; 08-mechanical.md §3.2', confidence: 'verified' },  'def-m25-frame-screw': { name: 'M2.5 frame screw', partNumber: 'M2.5, Zn-Ni-plated steel or Al (no stainless on Mg)', material: 'Zn-Ni steel / Al', dims: 'M2.5 (length not stated)', qty: 14, source: '08-mechanical.md §12 step 7; exploded parts list row 14', confidence: 'approximate' },  'def-m25-door-screw':  { name: 'M2.5 captive door screw', partNumber: 'M2.5 captive', material: 'Zn-Ni steel / Al', dims: 'M2.5 captive (length not stated)', qty: 6, source: 'front-view drawing (6 positions); 08-mechanical.md §11', confidence: 'verified' },  'def-slide-pin':   { name: 'Shutter slide pin', partNumber: 'Ø2.0 h6 pin, 304/316 stainless', material: 'stainless steel (runs in POM only)', dims: 'Ø2.0 × 3 mm projection', qty: 8, source: '08-mechanical.md §6.1', confidence: 'verified' },  'def-n52-magnet':  { name: 'N52 magnet', partNumber: 'N52 NdFeB Ø4 × 2, Ni-Cu-Ni', material: 'NdFeB', dims: 'Ø4 × 2 mm', qty: 2, source: '08-mechanical.md §4, §6.2; top-view drawing (X 55, X 150)', confidence: 'verified' },  'def-tpu-corner':  { name: 'TPU corner bumper', partNumber: 'TPU Shore 80A 2-shot overmold', material: 'TPU Shore 80A', dims: '24 mm arms × 7 mm, full depth', qty: 4, source: 'front-view drawing; 08-mechanical.md §4', confidence: 'approximate' },  'def-usbc':        { name: 'USB-C receptacle, IP67 mid-mount', partNumber: 'Amphenol IP67 mid-mount USB-C 24-pin (Port C: 40 Gbps-rated variant)', material: '—', dims: '≈ 9 × 3.3 mm opening', qty: 3, source: '10-bom.md USBC; 08-mechanical.md §7', confidence: 'verified' },  'def-efuse':       { name: 'Slot eFuse', partNumber: 'TPS2596-class', material: '—', dims: '—', qty: 5, source: '10-bom.md eFuse', confidence: 'approximate' },  'def-tps25750':    { name: 'USB PD controller', partNumber: 'TI TPS25750', material: '—', dims: '—', qty: 3, source: '10-bom.md PD / PD-C', confidence: 'approximate' },}; // ---------- helpers for repetitive geometry ----------const box = (x0, x1, y0, y1, z0, z1) => ({ type: 'box', x: [x0, x1], y: [y0, y1], z: [z0, z1] });const cyl = (axis, cx, cy, cz, r, len) => ({ type: 'cyl', axis, c: [cx, cy, cz], r, len }); const C = []; // componentsconst add = (c) => { C.push(c); return c; }; // =====================================================================// 1. SHUTTER (exploded parts list row 1)// =====================================================================add({ id: 'shutter', name: 'Shutter assembly (two-slat roll-top)', parent: null, kind: 'assembly', major: true,  description: 'Two 2.0 × 20 × 170 mm hard-anodized 6061-T6 slats joined by a full-length piano-hinge pin; slides front→rear on end-cap T-tracks and parks on the rear face. Approved unchanged (D11).',  function: 'Covers the five card slots (IP54 closed); exposes the 22 mm slot channel when open.',  source: '08-mechanical.md §6; exploded parts list row 1', confidence: 'verified', material: '6061-T6, Type III hard anodize 40 µm, PTFE-impregnated',  explode: [0, -48, 0], anchor: [95, -2, 19] });add({ id: 'slat-1', name: 'Slat 1 (front, thumb ridge)', parent: 'shutter', kind: 'part', major: true, sku: 'both',  description: 'Front slat, Z 0–20 when closed; carries the wiper lip, the 1.5 mm thumb ridge and laser-etched slot icons.',  material: '6061-T6, hard anodized', dims: '2.0 × 20 × 170 mm', partNumber: 'SHUTTER (BOM Mechanical)', source: '08-mechanical.md §6.1; top-view drawing', confidence: 'verified',  geometry: [box(10, 180, -2, 0, 0, 20), box(10, 180, -3.5, -2, 0.5, 2.0)], explode: [0, 0, -14] });add({ id: 'slat-2', name: 'Slat 2 (rear, magnets)', parent: 'shutter', kind: 'part', major: true,  description: 'Rear slat, Z 20–38 when closed plus a 2 mm wrap onto the R10 top-rear radius; carries two potted N52 magnets for the Hall sensor.',  material: '6061-T6, hard anodized', dims: '2.0 × 20 × 170 mm', source: '08-mechanical.md §6.1', confidence: 'verified',  geometry: [box(10, 180, -2, 0, 20, 38), box(10, 180, 0, 2, 38, 40)], explode: [0, 0, 14] });add({ id: 'hinge-pin', name: 'Hinge pin', parent: 'shutter', kind: 'part',  description: 'Full-length Ø2.0 stainless piano-hinge pin in staked bushings along the slat-to-slat joint.',  material: '304/316 stainless', dims: 'Ø2.0 × 170 mm', source: '08-mechanical.md §6.1', confidence: 'verified',  geometry: [cyl('x', 95, -1, 20, 1.0, 170)], explode: [0, -8, 0] });[[55, 'magnet-1'], [150, 'magnet-2']].forEach(([x, id], i) => add({ id, name: `N52 magnet ${i + 1}`, parent: 'shutter', kind: 'instance', def: 'def-n52-magnet',  description: 'Potted in slat 2; pulls onto a 0.8 mm steel keeper behind the recess wall (≈ 3 N each) and gives the Hall sensor a > 10 mT step.',  source: '08-mechanical.md §6.2; top-view drawing', confidence: 'verified',  geometry: [cyl('y', x, -1, 33, 2.0, 2.0)], explode: [0, -6, 0] }));let pinN = 0;for (const [xs, side] of [[8.5, 'L'], [181.5, 'R']]) for (const z of [3, 17, 23, 37]) {  pinN++; add({ id: `slide-pin-${pinN}`, name: `Slide pin ${pinN} (${side}, Z ${z})`, parent: 'shutter', kind: 'instance', def: 'def-slide-pin',    description: 'Projects 3 mm from the slat end into the POM T-track in the end cap.', source: '08-mechanical.md §6.1', confidence: 'approximate',    geometry: [cyl('x', xs, -1, z, 1.0, 3)], explode: [xs < 95 ? -10 : 10, 0, 0] });} // =====================================================================// 2. RAILS + DETENTS (row 2)// =====================================================================add({ id: 'rails', name: 'Shutter rails and detents', parent: null, kind: 'assembly', major: true,  description: 'Two POM-C T-profile tracks moulded into the inner 3 mm of each PC end cap: flat along the top face, R14 (pin-centre) quarter circle at the top-rear edge, 40 mm down the rear face. One ball-spring detent cartridge per track, brush seals at the track entries.',  function: 'Guides the eight slat pins; detents hold the shutter at closed and open (8–12 N break-out).',  source: '08-mechanical.md §6.1–6.2; exploded parts list row 2', confidence: 'verified', material: 'POM-C (Delrin 500P at EVT)',  explode: [0, -30, 0] });for (const [x0, x1, id, side, dx] of [[7, 10, 'rail-left', 'left', -24], [180, 183, 'rail-right', 'right', 24]]) {  add({ id, name: `POM T-track rail, ${side}`, parent: 'rails', kind: 'part', major: true,    description: 'T-track in the end cap: top run Z 0–38, corner, 40 mm down the rear face. Corner arc simplified to a straight step in this model.',    material: 'POM-C', dims: '3 mm wide groove, 40 mm rear leg', source: '08-mechanical.md §6.1; top-view drawing (red dashed)', confidence: 'derived',    geometry: [box(x0, x1, -0.3, 3, 0, 38), box(x0, x1, 0, 40, 35, 38.3)], explode: [dx, 0, 0] });  add({ id: id.replace('rail', 'detent'), name: `Detent cartridge, ${side}`, parent: 'rails', kind: 'part',    description: 'Ø3 stainless ball on a 0.5 N/mm spring (6 N preload) in a press-in POM cartridge; engages dimples in slat 1\'s rear pin carrier at closed and open.',    material: '304 stainless ball, spring; POM body', dims: 'Ø3 ball', source: '08-mechanical.md §6.2', confidence: 'approximate',    geometry: [box(x0, x1, 0, 4, 1, 7), cyl('x', (x0 + x1) / 2, -0.5, 3, 1.5, 3)], explode: [dx * 1.3, -6, 0] });} // =====================================================================// 3. FRONT FRAME + END CAPS (row 3), 15 battery door, 11 TPU (children)// =====================================================================add({ id: 'front-frame-asm', name: 'Front frame assembly (PC+GF)', parent: null, kind: 'assembly', major: true,  description: 'PC + 30 % GF moulding: 2.5 mm front wall with the OLED lens aperture, battery-door aperture, button seat, BLE window and Wi-Fi window band; the two 10 mm end caps are part of the same moulding. TPU bumpers are 2-shot overmolded onto it.',  function: 'User-facing face; the only RF-transparent band (Wi-Fi upper band, BLE bottom-left); carries the shutter rails in its end caps.',  material: 'PC + 30 % GF (Sabic Lexan 3413 / Covestro Makrolon 9415)', source: '08-mechanical.md §3.1, §4; exploded parts list row 3', confidence: 'verified',  explode: [0, 0, -62] });add({ id: 'front-frame', name: 'Front frame wall', parent: 'front-frame-asm', kind: 'part', major: true,  description: 'Front wall 190 × 96 × 2.5, R10 corners, apertures for the lens (X 64–126, Y 7–43), battery door (X 18–172, Y 42–92), power button (Ø12 at X 30, Y 12) and BLE window (X 11–18, Y 74–88). Strap slots and texture omitted.',  material: 'PC + 30 % GF, MT-11020 texture', dims: '190 × 96 × 2.5 mm', partNumber: 'FRAME (BOM Mechanical)', source: '08-mechanical.md §3.1, §3.3; front-view drawing', confidence: 'verified',  geometry: [{ type: 'plate', normal: 'z', x: [0, 190], y: [0, 96], z: [0, 2.5], r: 10,    holes: [{ x: [64, 126], y: [7, 42], r: 3 }, { x: [18, 172], y: [42, 92], r: 6 }, { x: [24, 36], y: [6, 18], r: 6 }, { x: [11, 18], y: [74, 88], r: 1.5 }] }],  explode: [0, 0, 0] });add({ id: 'end-cap-left', name: 'End cap, left (X 0–10)', parent: 'front-frame-asm', kind: 'part',  description: 'Plain PC end cap: rail groove in its inner 3 mm and a drain exit below the rail. No RF requirement (cellular antennas deleted, D1). EVT option: cast in magnesium as part of the chassis.',  material: 'PC + 30 % GF', dims: '10 × 96 × 35.5 mm', source: '08-mechanical.md §3.1; top-view drawing', confidence: 'derived',  geometry: [box(0, 10, 0, 96, 2.5, 38)], explode: [-20, 0, 0] });add({ id: 'end-cap-right', name: 'End cap, right (X 180–190)', parent: 'front-frame-asm', kind: 'part',  description: 'Plain PC end cap with rail groove, drain exit, lanyard hole Ø3 and strap slot (holes omitted in this model).',  material: 'PC + 30 % GF', dims: '10 × 96 × 35.5 mm', source: '08-mechanical.md §3.1; side-view drawing', confidence: 'derived',  geometry: [box(180, 190, 0, 96, 2.5, 38)], explode: [20, 0, 0] });add({ id: 'wifi-window-1', name: 'Wi-Fi window zone 1 (frame band)', parent: 'front-frame-asm', kind: 'reference',  description: 'Zone of the frame wall (same PC+GF at 1.8 mm) behind which Wi-Fi antenna flex 1 is bonded. Shown as a separate translucent panel for identification only — it is not a separate part.',  material: 'PC + 30 % GF, 1.8 mm (Dk ≈ 3.2)', dims: '40 × 8 mm zone', source: '07-rf-antenna.md; 08-mechanical.md §3.1', confidence: 'verified',  geometry: [box(12, 52, 30, 38, 0.2, 2.3)], explode: [0, 0, -10] });add({ id: 'wifi-window-2', name: 'Wi-Fi window zone 2 (frame band)', parent: 'front-frame-asm', kind: 'reference',  description: 'Zone behind which Wi-Fi antenna flex 2 is bonded (118 mm from zone 1). Identification aid, not a separate part.',  material: 'PC + 30 % GF, 1.8 mm', dims: '40 × 8 mm zone', source: '07-rf-antenna.md; 08-mechanical.md §3.1', confidence: 'verified',  geometry: [box(130, 170, 30, 38, 0.2, 2.3)], explode: [0, 0, -10] });add({ id: 'ble-window', name: 'BLE chip-antenna window', parent: 'front-frame-asm', kind: 'part',  description: '7 × 14 mm PC window between the left end cap and the battery door, over the EC\'s chip antenna keep-out (6 × 12).',  material: 'PC + 30 % GF, 1.8 mm', dims: '7 × 14 mm', source: '08-mechanical.md §3.1; front-view drawing', confidence: 'verified',  geometry: [box(11, 18, 74, 88, 0.3, 2.1)], explode: [0, 0, -12] });add({ id: 'oled-lens', name: 'OLED lens', parent: 'front-frame-asm', kind: 'part', major: true,  description: 'PC lens, hard-coat ≥ 3H + AR both sides, recessed 0.5 mm below the frame surface; OCA-bonded to the OLED.',  material: 'PC 1.5 mm, hard-coat + AR', dims: '62 × 36 × 1.5 mm (modelled 62 × 35: the documented outline overlaps the door aperture by 1 mm)', partNumber: 'LENS (BOM Mechanical)', source: '08-mechanical.md §4, §3.3; front-view drawing', confidence: 'verified',  geometry: [box(64, 126, 7, 42, 0.5, 2.0)], explode: [0, 0, -24] });add({ id: 'power-button-cap', name: 'Power button cap (TPU)', parent: 'front-frame-asm', kind: 'part',  description: 'TPU cap over an IP67 sealed tactile switch; 1.5 N actuation, 0.5 mm travel, illuminated ring via light pipe. Cap is part of the overmold.',  material: 'TPU Shore 80A', dims: 'Ø12 mm', source: '08-mechanical.md §7; front-view drawing', confidence: 'verified',  geometry: [cyl('z', 30, 12, 1.25, 6, 2.5), cyl('z', 30, 12, -0.4, 4.5, 0.8)], explode: [0, 0, -16] });add({ id: 'led-light-guide', name: 'LED bar light guide (5 segments)', parent: 'front-frame-asm', kind: 'part',  description: 'Five 12 × 3 mm light-guide windows in the frame just below the top edge, one over each slot; RGB segments on the LED bar flex behind them.',  material: 'PC light guide', dims: '5 × (12 × 3) mm', source: 'front-view drawing; 10-bom.md LEDs', confidence: 'derived',  geometry: SLOTS.map((s) => box(s.x0 + s.w / 2 - 6, s.x0 + s.w / 2 + 6, 3, 6, 0.2, 2.3)), explode: [0, 0, -9] });// battery door (row 15)add({ id: 'battery-door', name: 'Battery door', parent: 'front-frame-asm', kind: 'part', major: true,  description: 'Screw-down PC+GF panel with its own perimeter gasket and six captive M2.5 screws; service access to the pack (EU Battery Regulation 2023/1542). No antenna may sit on the door (cells 2.4 mm behind it).',  material: 'PC + 30 % GF', dims: '154 × 50 × 2.5 mm', source: '08-mechanical.md §3.1, §11; exploded parts list row 15', confidence: 'verified',  geometry: [{ type: 'plate', normal: 'z', x: [18, 172], y: [42, 92], z: [0, 2.5], r: 6, holes: [] }], explode: [0, 0, -42] });add({ id: 'door-gasket', name: 'Battery-door gasket', parent: 'front-frame-asm', kind: 'part',  description: 'Moulded silicone compression gasket around the door perimeter; a new gasket is fitted at every pack replacement.',  material: 'Silicone 40 Shore A', dims: '1.5 mm section', source: '08-mechanical.md §4, §11; exploded parts list row 13', confidence: 'approximate',  geometry: [{ type: 'ring', normal: 'z', x: [18.5, 171.5], y: [42.5, 91.5], z: [2.5, 3.5], r: 6, t: 1.5 }], explode: [0, 0, -30] });let ds = 0;for (const sx of [24, 95, 166]) for (const sy of [46, 88]) {  ds++; add({ id: `door-screw-${ds}`, name: `Door screw ${ds} (X ${sx}, Y ${sy})`, parent: 'front-frame-asm', kind: 'instance', def: 'def-m25-door-screw',    description: 'Captive M2.5, 0.35 N·m.', source: 'front-view drawing; 08-mechanical.md §12 step 8', confidence: 'verified',    geometry: [cyl('z', sx, sy, 0.6, 1.6, 1.2), cyl('z', sx, sy, 3.5, 1.0, 5)], explode: [0, 0, -62] });}// TPU overmold (row 11)add({ id: 'tpu-overmold', name: 'TPU overmold set', parent: 'front-frame-asm', kind: 'assembly', major: true,  description: 'Shore 80A TPU 2-shot overmolded onto the PC frame with mechanical interlocks: four corner bumpers (7 mm), edge grip, RJ45 door, button cap. Edge overmold and interlocks omitted; corners modelled as L-shaped blocks.',  material: 'TPU Shore 80A (BASF Elastollan / Lubrizol Estane), UV-stabilized black', source: '08-mechanical.md §4; exploded parts list row 11', confidence: 'approximate',  explode: [0, 0, -14] });[[0, 0, 'top-left', -1, -1], [190, 0, 'top-right', 1, -1], [0, 96, 'bottom-left', -1, 1], [190, 96, 'bottom-right', 1, 1]].forEach(([cx, cy, nm, sx, sy], i) => {  const xa = sx < 0 ? [0, 24] : [166, 190], ya = sy < 0 ? [0, 7] : [89, 96];  const xb = sx < 0 ? [0, 7] : [183, 190], yb = sy < 0 ? [0, 24] : [72, 96];  add({ id: `bumper-${i + 1}`, name: `Corner bumper, ${nm}`, parent: 'tpu-overmold', kind: 'instance', def: 'def-tpu-corner',    description: '7 mm TPU corner, 24 mm arms, takes the MIL-STD-810H 1.22 m corner drops.', source: 'front-view drawing; 08-mechanical.md §8', confidence: 'approximate',    geometry: [box(xa[0], xa[1], ya[0], ya[1], -0.5, 38.5), box(xb[0], xb[1], yb[0], yb[1], -0.5, 38.5)], explode: [sx * 22, sy * 16, 0] });});add({ id: 'rj45-door', name: 'RJ45 door (TPU, hinged)', parent: 'tpu-overmold', kind: 'part',  description: 'Hinged TPU door with over-center latch; living hinge in the overmold plus a stainless pin; gasket lip seals to a raised rim. Door open = not rated.',  material: 'TPU Shore 80A', dims: '≈ 22 × 8 mm', source: '08-mechanical.md §7; front-view drawing', confidence: 'approximate',  geometry: [box(165, 187, 96, 98, 8, 30)], explode: [0, 18, 0] }); // =====================================================================// 4. OLED + LED FLEX (row 4)// =====================================================================add({ id: 'display-asm', name: 'Display and LED bar', parent: null, kind: 'assembly', major: true,  description: '2.42" 128 × 64 OLED module (SSD1309) with FPC, OCA-bonded to the lens, and the five-segment addressable RGB LED bar on a flex behind the light guides.',  function: 'Status display (job, link, battery); per-slot LED status doubles as slot position marker in the dark.',  source: '08-mechanical.md §3.1, §3.3; exploded parts list row 4', confidence: 'verified', explode: [0, 0, -44] });add({ id: 'oled-module', name: 'OLED module 2.42" (SSD1309)', parent: 'display-asm', kind: 'part', major: true,  description: 'Module outline 62 × 36, active area 57 × 30 at X 66.5–123.5, Y 10–40; 1.8 mm thick + FPC. Modelled 1 mm shorter (Y 7–42) because the documented outline (Y 7–43) overlaps the battery-door aperture (Y 42–92).',  material: '—', dims: '62 × 36 × 1.8 mm', partNumber: 'OLED (BOM EC/UI) — SSD1309 controller', source: '08-mechanical.md §3.3; front-view drawing', confidence: 'verified',  geometry: [box(64, 126, 7, 42, 2.5, 4.3), box(66.5, 123.5, 10, 40, 2.3, 2.5)], explode: [0, 0, 0] });add({ id: 'led-bar-flex', name: 'LED bar flex (5 RGB segments)', parent: 'display-asm', kind: 'part',  description: 'Addressable RGB LED bar on a 0.5 mm flex behind the light guides; may alternatively live on the reader board (09 §1).',  material: 'Flex PCB', dims: '166 × 5 × 0.5 mm', partNumber: 'LEDs (BOM EC/UI)', source: '08-mechanical.md §3.3; exploded parts list row 4', confidence: 'approximate',  geometry: [box(12, 178, 2, 7, 2.5, 3.0)], explode: [0, -6, 0] }); // =====================================================================// 12. ANTENNAS (row 12)// =====================================================================add({ id: 'antennas', name: 'Antennas (2 × Wi-Fi flex)', parent: null, kind: 'assembly', major: true,  description: 'Two tri-band 2.4/5/6 GHz flex antennas bonded to the inner face of the PC frame in the upper band, centres 118 mm apart, each on an MHF4 pigtail to the M.2 Wi-Fi module. The BLE chip antenna is an SMD part on the EC (see main board). MHF4 cables omitted.',  function: 'Wi-Fi 7 (MT7925) 2 × 2 MIMO; the only RF path through the metal enclosure is the front-frame upper band.',  source: '07-rf-antenna.md; 08-mechanical.md §3.1; exploded parts list row 12', confidence: 'verified', explode: [0, 0, -52] });add({ id: 'wifi-ant-1', name: 'Wi-Fi antenna flex 1', parent: 'antennas', kind: 'part', major: true, partNumber: 'ANT-W (BOM Wi-Fi/BLE), 2 × MHF4',  material: 'Flex antenna', dims: '40 × 8 mm (thickness not stated)', source: '08-mechanical.md §3.1', confidence: 'verified',  geometry: [box(12, 52, 30, 38, 2.5, 2.8)], explode: [0, 0, 0] });add({ id: 'wifi-ant-2', name: 'Wi-Fi antenna flex 2', parent: 'antennas', kind: 'part', major: true, partNumber: 'ANT-W (BOM Wi-Fi/BLE), 2 × MHF4',  material: 'Flex antenna', dims: '40 × 8 mm', source: '08-mechanical.md §3.1', confidence: 'verified',  geometry: [box(130, 170, 30, 38, 2.5, 2.8)], explode: [0, 0, 0] }); // =====================================================================// 5. BATTERY PACK (row 5)// =====================================================================add({ id: 'battery-pack', name: 'Battery pack 4 × 21700, 2S2P, 72 Wh', parent: null, kind: 'assembly', major: true,  description: 'Four Samsung INR21700-50E cells in a PC+GF cradle (2 rows × 2 cells end-to-end, 144 × 44 footprint, X 23–167, Y 44–88), pack PCB with BQ40Z50-R2 gauge/protector, secondary protector, fuse, two NTCs, JST-GH connector, foam wrap. Certified as an independent pack (UN 38.3 / IEC 62133-2).',  function: '72 Wh energy store; 4–6 h continuous upload, ~3 weeks standby. Service-replaceable through the battery door.',  material: 'PC+GF cradle, Li-ion cells', dims: '144 × 44 × 22.6 mm (Z 3.3–25.9)', partNumber: 'PACK + CELLS (BOM Power)', source: '08-mechanical.md §3.1, §3.2; 09 §1; exploded parts list row 5', confidence: 'verified',  explode: [0, 0, -32] });add({ id: 'pack-cradle', name: 'Pack cradle (PC+GF)', parent: 'battery-pack', kind: 'part',  description: '0.8 mm front and rear walls; cells float on 0.8 mm closed-cell foam against the front frame. Modelled as an open frame so the cells stay visible.',  material: 'PC + GF', dims: '144 × 44, walls 0.8 mm', source: '08-mechanical.md §3.2', confidence: 'verified',  geometry: [box(23, 167, 44, 88, 3.3, 4.1), box(23, 167, 44, 88, 25.1, 25.9), box(23, 24, 44, 88, 4.1, 25.1), box(166, 167, 44, 88, 4.1, 25.1), box(24, 166, 44, 44.5, 4.1, 25.1), box(24, 166, 87.5, 88, 4.1, 25.1)],  explode: [0, 0, 0] });let cellN = 0;for (const [row, yc] of [[0, 55], [1, 77]]) for (const [col, xc] of [[0, 60], [1, 131]]) {  cellN++; add({ id: `cell-${cellN}`, name: `Cell ${cellN} (row ${row + 1}, ${col ? 'right' : 'left'})`, parent: 'battery-pack', kind: 'instance', def: 'def-21700-cell',    description: 'Ø21 × 70 mm cell, axis along X. 2S2P: two series groups of two parallel cells (grouping not specified in the docs).', source: '08-mechanical.md §3.2; exploded-view drawing', confidence: 'derived',    geometry: [cyl('x', xc, yc, 14.6, 10.5, 70)], explode: [col ? 10 : -10, row ? 14 : -14, 0] });}add({ id: 'pack-pcb', name: 'Pack PCB (BQ40Z50-R2, protector, fuse, NTC)', parent: 'battery-pack', kind: 'part',  description: '2-layer 60 × 25 mm pack PCB: BQ40Z50-R2 gauge/protector, BQ294xx secondary protector, fuse, 2 × NTC, pack connector, cell tabs. Position inside the pack is not documented; placed in the 1.0 mm clearance behind the cradle rear wall.',  material: 'FR-4, 2 oz Cu, 1.0 mm', dims: '60 × 25 × 1.0 mm', partNumber: 'PACK (BOM PCBs); GAUGE BQ40Z50-R2; PROT2 BQ294xx', source: '09-pcb-architecture.md §1, §2.3', confidence: 'approximate',  geometry: [box(100, 160, 60, 85, 25.9, 26.9)], explode: [0, 0, 10] }); // =====================================================================// 13. FRAME / SHUTTER GASKETS (row 13)// =====================================================================add({ id: 'gaskets', name: 'Sealing gaskets (frame, shutter)', parent: null, kind: 'assembly', major: false,  description: 'Frame-perimeter silicone gasket between the PC frame and the magnesium chassis, and the shutter-perimeter gasket in a groove around the top-face rim. Door gasket is with the door; EMI fabric-over-foam strip, wiper lip, shingle strip and brush seals are not modelled.',  function: 'IP54 (shutter closed). Open shutter: IP52.', material: 'Silicone 40 Shore A', source: '08-mechanical.md §6.3, §9; exploded parts list row 13', confidence: 'approximate',  explode: [0, 0, -38] });add({ id: 'frame-gasket', name: 'Frame perimeter gasket', parent: 'gaskets', kind: 'part',  material: 'Silicone 40 Shore A', dims: '1.5 mm section', source: '08-mechanical.md §4; exploded parts list row 13', confidence: 'approximate',  geometry: [{ type: 'ring', normal: 'z', x: [10, 180], y: [1, 95], z: [2.5, 4.0], r: 8, t: 1.5 }], explode: [0, 0, 0] });add({ id: 'shutter-gasket', name: 'Shutter perimeter gasket', parent: 'gaskets', kind: 'part',  description: 'Compressed 0.4 mm at closed by slat 1\'s front edge and slat 2\'s rear wrap (magnets + detent).',  material: 'Silicone 40 Shore A', dims: '1.5 mm section', source: '08-mechanical.md §6.3', confidence: 'approximate',  geometry: [{ type: 'ring', normal: 'y', x: [10.5, 179.5], y: [-1.0, 0.4], z: [1.5, 36.5], r: 4, t: 1.5 }], explode: [0, -22, 0] }); // =====================================================================// 6. READER DAUGHTERBOARD (row 6)// =====================================================================// Z position: 08 §3.3 places the board at Z 14.0–15.6 with ~4 mm of slack in the band; the top-view// drawing puts the slot centreline at Z 15. This model shifts the board to Z 17–18.6 so the card// connectors (front face) centre on Z 15. Recorded in the evidence register.const RB_Z0 = 17.0, RB_Z1 = 18.6;add({ id: 'reader-board-asm', name: 'Reader daughterboard assembly', parent: null, kind: 'assembly', major: true,  description: 'Vertical 172 × 38 mm 8-layer board (FR-4 Tg170) hanging on the main board\'s front face through the 80-pin mezzanine. Carries the five card connectors on its front face and their controllers, eFuses, card-detect and the Hall sensor on its rear face. Slot line-up is a mechanical part: no-CFast and dual-CFe-B variants are a board swap.',  function: 'Card interface: CFast (SATA via ASM1064), CFexpress B (direct PCIe Gen3 ×2 via mux), CFexpress A (switch port), SD UHS-II (GL9755), microSD (GL9750).',  dims: '172 × 38 × 1.6 mm, X 9–181, Y 8–46', partNumber: 'RDR (BOM PCBs)', source: '09-pcb-architecture.md §1, §1.2; 08-mechanical.md §3.1, §3.3; exploded parts list row 6', confidence: 'verified',  explode: [0, 0, -22] });add({ id: 'reader-board', name: 'Reader daughterboard PCB', parent: 'reader-board-asm', kind: 'part', major: true,  material: 'FR-4 Tg170, 8 layers', dims: '172 × 38 × 1.6 mm (modelled 172 × 36: at Y 8–46 the documented height overlaps the battery pack (Y 44–88, Z 3.3–25.9) by 2 mm)', source: '09-pcb-architecture.md §1, §2.2', confidence: 'verified',  geometry: [box(9, 181, 8, 44, RB_Z0, RB_Z1)], explode: [0, 0, 0] });for (const s of SLOTS) {  const y1 = Math.min(43.5, 7.6 + s.depth + 3);  add({ id: `conn-${s.key}`, name: `${s.name} connector`, parent: 'reader-board-asm', kind: 'part', major: true,    description: `Right-angle push-push connector on the reader board's front face; card enters downward through a ${s.open[0]} × ${s.open[1]} mm opening in the recess floor. Insertion depth ${s.depth} mm. Metal cage screwed to the board and pinned to a chassis rib so insertion force never loads the solder joints.`,    material: '—', dims: `housing ${s.w} mm wide, card ${s.card[0]} × ${s.card[1]} × ${s.card[2]} mm`, partNumber: s.conn, source: '08-mechanical.md §5', confidence: 'verified',    geometry: [box(s.x0 + 0.5, s.x0 + s.w - 0.5, 7.6, y1, 10, RB_Z0)], explode: [0, 0, -12] });}add({ id: 'card-cfeb-inserted', name: 'CFexpress B card (inserted, reference)', parent: 'reader-board-asm', kind: 'reference',  description: 'Reference geometry: a seated CFexpress Type B card protruding 3 mm above the recess floor, still ≥ 1 mm below the shutter plane. Not a product part.',  dims: '38.5 × 29.6 × 3.8 mm', source: '08-mechanical.md §5', confidence: 'verified',  geometry: [box(63.25, 101.75, 4.6, 34.2, 13.1, 16.9)], explode: [0, -30, 0] });// rear-face controllers on the reader boardconst RBC_Z0 = RB_Z1, RBC_Z1 = RB_Z1 + 1.5;add({ id: 'asm1064', name: 'ASM1064 (CFast SATA/AHCI)', parent: 'reader-board-asm', kind: 'part', partNumber: 'ASMedia ASM1064', source: '10-bom.md SATA; 09 §1.2', confidence: 'approximate', description: 'PCIe-to-SATA AHCI controller for the CFast slot; reader-board rear face.',  geometry: [box(28, 40, 26, 38, RBC_Z0, RBC_Z1)], explode: [0, 0, 8] });add({ id: 'gl9755', name: 'GL9755 (SD UHS-II)', parent: 'reader-board-asm', kind: 'part', partNumber: 'Genesys GL9755', source: '10-bom.md SDC; 09 §1.2', confidence: 'approximate', description: 'PCIe SD UHS-II host controller; reader-board rear face under the SD slot.',  geometry: [box(138, 148, 26, 36, RBC_Z0, RBC_Z1)], explode: [0, 0, 8] });add({ id: 'gl9750', name: 'GL9750 (microSD)', parent: 'reader-board-asm', kind: 'part', partNumber: 'Genesys GL9750', source: '10-bom.md uSDC; 09 §1.2', confidence: 'approximate', description: 'PCIe microSD host controller; reader-board rear face under the microSD slot.',  geometry: [box(164, 172, 28, 36, RBC_Z0, RBC_Z1)], explode: [0, 0, 8] });SLOTS.forEach((s, i) => add({ id: `efuse-${s.key}`, name: `eFuse, ${s.name} slot`, parent: 'reader-board-asm', kind: 'instance', def: 'def-efuse',  description: 'Per-slot current limit and interlock (slot rail < 0.3 V within 150 ms of PWR_EN low).', source: '10-bom.md eFuse; 09 §5.2', confidence: 'approximate',  geometry: [box(s.x0 + s.w / 2 - 2, s.x0 + s.w / 2 + 2, 40, 44, RBC_Z0, RBC_Z0 + 1.0)], explode: [0, 0, 6] }));add({ id: 'hall-sensor', name: 'Hall sensor (shutter closed detect)', parent: 'reader-board-asm', kind: 'part', partNumber: 'HALL (BOM EC/UI)',  description: 'Reads the two N52 magnets in slat 2 (> 10 mT step); "closed" = IP54 state. Docs place it at X 95 "under the rear rim"; the top-view drawing shows it at Z 33, which is behind the reader board — position here is approximate.',  source: '08-mechanical.md §6.2; top-view drawing', confidence: 'approximate',  geometry: [box(93.5, 96.5, 8.5, 11.5, RBC_Z0, RBC_Z0 + 1.2)], explode: [0, 0, 10] }); // =====================================================================// 7. MEZZANINE (row 7)// =====================================================================add({ id: 'mezzanine', name: 'Mezzanine pair (Samtec ADM6/ADF6, 80-pin)', parent: null, kind: 'part', major: true,  description: '0.635 mm pitch 80-pin (2 × 40) board-to-board pair, 5 mm stack; carries Gen3/Gen4 PCIe lanes, SATA, UHS-II I/O, slot power and control between the reader board (female) and the main board (male). Male and female halves shown as one block.',  dims: '≈ 30 × 6 mm, stack 5 mm (block spans the 9.3 mm board-to-board gap)', partNumber: 'MEZZ (BOM Connectors): Samtec ADM6 / ADF6', source: '09-pcb-architecture.md §8; exploded parts list row 7', confidence: 'approximate',  geometry: [box(80, 110, 30, 36, RBC_Z0, 27.9)], explode: [0, 0, -11] }); // =====================================================================// 8. MAIN BOARD (row 8) — the explosion anchor (offset 0)// =====================================================================// Board X: the docs give 165 × 80 spanning Y 12–92; ports at X 25/43/61 and the RJ45 at X 167–186// require the board to reach X ≈ 186, so it is placed at X 20–185 (the exploded-view drawing// draws it at X 12–177, which would leave the RJ45 off-board). Recorded in the evidence register.const MB_X0 = 20, MB_X1 = 185, MB_Z0 = 27.9, MB_Z1 = 29.5, R = MB_Z1; // R = rear-face component baseadd({ id: 'main-board-asm', name: 'Main board assembly', parent: null, kind: 'assembly', major: true,  description: '165 × 80 mm 10-layer hybrid board (Megtron 6 / I-Speed layers) pressed against the magnesium rear skin. Everything with a high-speed path to the SoC and everything that dissipates heat lives here: SoM carrier, PCIe switch, Gen4 mux, USB4 bridge, USB host, 5GbE PHY, two M.2 sockets, charger, PD controllers, EC, TPM, three USB-C and the RJ45. Component placements below follow the 09 §1.1 sketch and are approximate.',  function: 'Compute, PCIe fabric, network, power and host interfaces.',  dims: '165 × 80 × 1.6 mm, Y 12–92', partNumber: 'MAIN (BOM PCBs)', source: '09-pcb-architecture.md §1, §1.1, §2.1; 08-mechanical.md §3.1–3.2; exploded parts list row 8', confidence: 'verified',  explode: [0, 0, 0] });add({ id: 'main-board', name: 'Main board PCB (10-layer hybrid)', parent: 'main-board-asm', kind: 'part', major: true,  material: 'Megtron 6 / I-Speed hybrid laminate, 10 layers', dims: '165 × 80 × 1.6 mm', source: '09-pcb-architecture.md §2.1', confidence: 'verified',  geometry: [box(MB_X0, MB_X1, 12, 92, MB_Z0, MB_Z1)], explode: [0, 0, 0] });add({ id: 'som', name: 'RK3588 SoM (carrier placeholder)', parent: 'main-board-asm', kind: 'part', major: true, sku: 'both',  description: 'System-on-module, undecided (D12): PoC bake-off of Firefly Core-3588J (70 × 55, 3 × B2B), Forlinx FET3588-C and a board-down RK3588J on the ODM reference. Modelled at the Core-3588J outline: B2B connectors 1.5 + SoM PCB 1.2 + SoC 1.0 = 3.7 mm above the board. Position follows the 09 §1.1 sketch (left-centre).',  function: 'Rockchip RK3588J, 8 GB LPDDR4x, 64 GB eMMC; PCIe Gen3 ×4 to the switch and ×2 to the mux; USB 3 to Port A. 5.5 W largest heat source.',  material: '—', dims: '70 × 55 mm (Core-3588J)', partNumber: 'SoM (BOM Compute) — costing placeholder Firefly Core-3588J', source: '09-pcb-architecture.md §1, §1.1; 08-mechanical.md §3.2; 10-bom.md', confidence: 'approximate',  geometry: [box(26, 96, 22, 77, R, R + 1.5), box(26, 96, 22, 77, R + 1.5, R + 2.7), box(46, 76, 36, 62, R + 2.7, R + 3.7)], explode: [0, 0, 14] });add({ id: 'asm2812', name: 'ASM2812 PCIe Gen3 switch', parent: 'main-board-asm', kind: 'part', major: true, partNumber: 'ASMedia ASM2812 (SW1)',  description: 'PCIe Gen3 packet switch, ×2 upstream, 9 of 12 downstream lanes used: CFe A, SD, microSD, CFast controllers, NVMe, 5GbE, ASM3142. 1.8 W in ~1.2 cm² — padded to a chassis boss.',  source: '10-bom.md SW1; 09 §1.1; 06-thermal.md', confidence: 'approximate', dims: '≈ 17 × 17 mm package (assumed)',  geometry: [box(100, 117, 14, 31, R, R + 1.2)], explode: [0, 0, 10] });add({ id: 'pi3dbs16412', name: 'PI3DBS16412 Gen4 ×2 mux + REFCLK mux', parent: 'main-board-asm', kind: 'part', partNumber: 'Diodes PI3DBS16412 (MUX1) + REFCLK mux (MUX2)',  description: 'Steers the CFe B slot\'s PCIe Gen4 ×2 lanes to either the SoC root port (device mode) or the ASM2464PD (Port C host mode). Clustered at the top edge nearest the CFe B connector to keep the Gen4 path short.',  source: '10-bom.md MUX1/MUX2; 09 §1.1, §4.1', confidence: 'approximate',  geometry: [box(120, 132, 13, 21, R, R + 1.0)], explode: [0, 0, 8] });add({ id: 'asm2464pd', name: 'ASM2464PD USB4 bridge (Pro)', parent: 'main-board-asm', kind: 'part', major: true, sku: 'pro', partNumber: 'ASMedia ASM2464PD (BR1) + SPI flash',  description: 'USB4 / Thunderbolt-class PCIe bridge passing the CFexpress B card straight to Port C at ~3 GB/s (Pro SKU only, depopulated on Base, D7). Read-only enforcement: NVMe namespace write-protect → bridge write-protect → per-session consent (D8).',  source: '10-bom.md BR1; 09 §1.1; ADR D7/D8', confidence: 'approximate', dims: '≈ 14 × 14 mm (assumed)',  geometry: [box(120, 134, 24, 38, R, R + 1.2), box(136, 140, 30, 34, R, R + 0.8)], explode: [0, 0, 10] });add({ id: 'rtl8126', name: 'RTL8126 5GBASE-T PHY + magnetics', parent: 'main-board-asm', kind: 'part', major: true, partNumber: 'Realtek RTL8126 (ETH) + discrete magnetics',  description: '5GbE MAC/PHY (final, D6 — no 10GbE option) with discrete magnetics and shield; padded to a chassis boss. The ADR ceiling numbers assume a 5GbE burst of ~17 min before throttling at 25 °C.',  source: '10-bom.md ETH; 09 §1.1, §3.5', confidence: 'approximate',  geometry: [box(100, 112, 40, 52, R, R + 1.0), box(114, 128, 40, 52, R, R + 2.5)], explode: [0, 0, 10] });add({ id: 'm2-wifi', name: 'Wi-Fi 7 module M.2 2230 (MT7925)', parent: 'main-board-asm', kind: 'part', major: true, partNumber: 'AzureWave AW-XB591NF (WIFI) in M.2 Key-E 2230 socket',  description: 'MediaTek MT7925 Wi-Fi 7 module in an M.2 Key-E socket (2.25 mm stand-off), rear-facing so its pad presses the magnesium skin. Two MHF4 cables to the front-frame antennas (~30 mating cycles; module/antenna pairing is part of the radio grant).',  dims: '22 × 30 mm, socket 2.25 + module 1.5 mm', source: '08-mechanical.md §3.1; 10-bom.md WIFI, M.2 E', confidence: 'verified',  geometry: [box(140, 162, 40, 46, R, R + 2.25), box(140, 162, 14, 44, R + 2.25, R + 3.75)], explode: [0, 0, 12] });add({ id: 'm2-nvme', name: 'NVMe SSD M.2 2280, 1 TB (Pro)', parent: 'main-board-asm', kind: 'part', major: true, sku: 'pro', partNumber: 'NVME (BOM Mechanical) in M.2 M-key 2280 socket',  description: 'Internal cache/buffer SSD, Pro SKU only; socket fitted but empty on Base. Stack 2.25 socket + 1.5 module (+1.0 pad) = 4.7 mm, the tallest rear-face part with the SoM stack.',  dims: '22 × 80 mm', source: '08-mechanical.md §3.1, §3.2; 10-bom.md NVME', confidence: 'verified',  geometry: [box(163, 185, 86, 92, R, R + 2.25), box(163, 185, 12, 90, R + 2.25, R + 3.75)], explode: [0, 0, 12] });add({ id: 'm2-nvme-socket', name: 'M.2 M-key 2280 socket (empty on Base)', parent: 'main-board-asm', kind: 'part', sku: 'base',  description: 'Socket and chassis-pressed standoff present on both SKUs; shown on Base where the module is depopulated.',  partNumber: 'M.2 M (BOM Connectors)', source: '10-bom.md M.2 M; 08 §3.1', confidence: 'approximate',  geometry: [box(163, 185, 86, 92, R, R + 2.25)], explode: [0, 0, 12] });add({ id: 'power-spread-zone', name: 'Power spread / bulk capacitance zone (ex-modem area)', parent: 'main-board-asm', kind: 'reference',  description: 'Reference zone: the ~30 × 52 mm area freed when the v0.1 M.2 Key-B modem slot was deleted, now power-section spread, 5 V / VSYS bulk capacitance and the spare PCIe Gen2 test header. Docs give X 104–134; drawn narrower here to clear the ASM2812/RTL8126 cluster.',  source: '09-pcb-architecture.md §1.1; 08-mechanical.md §3.1; ADR §9', confidence: 'approximate',  geometry: [box(104, 136, 56, 86, R, R + 1.5)], explode: [0, 0, 6] });add({ id: 'asm3142', name: 'ASM3142 USB 3.2 Gen2 host (Port B)', parent: 'main-board-asm', kind: 'part', partNumber: 'ASMedia ASM3142 (XHCI)',  description: 'USB host controller for Port B: external SSD ingest, USB-tethered phones and USB cellular adapters (ADR §4.3). 0.8 W in tethered mode, padded.',  source: '10-bom.md XHCI; 09 §1.1', confidence: 'approximate',  geometry: [box(39, 47, 78, 86, R, R + 1.0)], explode: [0, 0, 8] });add({ id: 'tpm', name: 'TPM 2.0 (SLB9672)', parent: 'main-board-asm', kind: 'part', partNumber: 'Infineon OPTIGA SLB9672',  source: '10-bom.md TPM; 09 §1.1', confidence: 'approximate', description: 'Discrete TPM 2.0 for measured boot and job signing keys.',  geometry: [box(20, 25, 74, 79, R, R + 0.9)], explode: [0, 0, 8] });add({ id: 'ec-nrf5340', name: 'Embedded controller nRF5340 + BLE', parent: 'main-board-asm', kind: 'part', major: true, partNumber: 'Nordic nRF5340 (EC)',  description: 'Always-on EC with BLE 5.4: power-state machine, shutter/Hall, LEDs, button, thermal safety rule, phone location over BLE/NCM into job metadata (D13). Bottom-left corner behind the BLE window.',  source: '10-bom.md EC; 09 §1.1', confidence: 'approximate',  geometry: [box(30, 38, 80, 88, R, R + 1.0)], explode: [0, 0, 8] });add({ id: 'ble-chip-antenna', name: 'BLE chip antenna (on EC)', parent: 'main-board-asm', kind: 'part', partNumber: 'chip antenna + matching (in EC line)',  description: 'SMD chip antenna at the board\'s lower-left corner with a 6 × 12 mm copper-free keep-out to the board edge, behind the 7 × 14 PC window. Docs place the keep-out at X 12–18, Y 75–87; with the board edge at X 20 in this model it sits at X 20–26, Y 80–92.',  source: '08-mechanical.md §3.1; 07-rf-antenna.md §5', confidence: 'approximate',  geometry: [box(20, 26, 80, 92, R, R + 1.0)], explode: [0, 0, 8] });add({ id: 'bq25792', name: 'BQ25792 buck-boost charger', parent: 'main-board-asm', kind: 'part', major: true, partNumber: 'TI BQ25792 (CHG) + inductor + FETs',  description: 'Buck-boost charger from 65 W USB-C PD (Port A; also Port C on Pro via the priority mux). NVDC path to VSYS.',  source: '10-bom.md CHG; 09 §1.1', confidence: 'approximate',  geometry: [box(48, 72, 78, 88, R, R + 2.0)], explode: [0, 0, 9] });add({ id: 'bucks', name: 'Buck converters (5 V, 3.3 V ×2, 1.8 V)', parent: 'main-board-asm', kind: 'part', partNumber: 'BUCK set (BOM Power)',  description: '5 V/6 A SYS, 3.3 V/4 A slot, 3.3 V/4 A logic, 1.8 V. Clustered bottom-left away from the Wi-Fi module and antenna exits.',  source: '10-bom.md BUCK; 09 §1.1', confidence: 'approximate',  geometry: [box(76, 98, 80, 88, R, R + 1.6)], explode: [0, 0, 9] });add({ id: 'buck-portb', name: 'Port B 5 V/3 A buck (TPS62933 class)', parent: 'main-board-asm', kind: 'part', partNumber: 'BUCK-B (BOM Power)',  description: 'Dedicated tether/adapter supply kept off the SoM\'s 5 V SYS rail; EC-programmable 0.5 / 1.5 / 3 A limit via the Port B eFuse (1.5 A default, 3 A only with external PD, D13).',  source: '10-bom.md BUCK-B; 05-power-budget.md §3.3', confidence: 'approximate',  geometry: [box(104, 116, 88, 92, R, R + 1.4)], explode: [0, 0, 9] });[['pd-a', 31, 'Port A sink/source', 'both'], ['pd-b', 49, 'Port B source', 'both'], ['pd-c', 67, 'Port C sink (Pro)', 'pro']].forEach(([id, x, role, sku]) => add({  id, name: `PD controller, ${role}`, parent: 'main-board-asm', kind: 'instance', def: 'def-tps25750', sku,  description: role.includes('Pro') ? 'Third TPS25750 for Port C PD sink (Mac powers the device in reader mode, D7). Pro population only.' : `TPS25750, ${role}.`,  source: '10-bom.md PD / PD-C; 09 §1.1', confidence: 'approximate',  geometry: [box(x, x + 5, 89, 94, R, R + 0.9)], explode: [0, 0, 8] }));add({ id: 'vbus-mux', name: 'VBUS priority mux (TPS2121 class, Pro)', parent: 'main-board-asm', kind: 'part', sku: 'pro', partNumber: 'MUX-C (BOM Power)',  description: 'ORs Port C VBUS with Port A VBUS into the charger input, Port A priority, break-before-make. Base fits a 0 Ω bypass.',  source: '10-bom.md MUX-C; 09 §1.1, §12 item 10', confidence: 'approximate',  geometry: [box(74, 80, 89, 93, R, R + 1.0)], explode: [0, 0, 8] });[['A', 25, 'PD / PHONE — USB-C 5 Gbps gadget (read-only mass storage) + 65 W PD sink', 'both'], ['B', 43, 'SSD / TETHER — USB-C host 10 Gbps, 5 V/3 A source', 'both'], ['C', 61, 'USB4 FAST (Pro) — USB4 pass-through + PD sink; TPU plug on Base', 'both']].forEach(([p, x, role, sku]) => add({  id: `usb-c-${p.toLowerCase()}`, name: `Port ${p} USB-C receptacle`, parent: 'main-board-asm', kind: 'instance', def: 'def-usbc', major: true, sku,  description: `${role}. IP67 mid-mount receptacle with integrated O-ring; shell screwed to a Zn-Ni steel / Al bracket in the chassis bottom wall (bracket not modelled). Ports 18 mm apart.`,  source: '08-mechanical.md §7; front-view drawing', confidence: 'verified',  geometry: [box(x - 4.5, x + 4.5, 88, 97, R, R + 3.3)], explode: [0, 12, 4] }));add({ id: 'rj45', name: 'RJ45 5GBASE-T jack (front face)', parent: 'main-board-asm', kind: 'part', major: true, partNumber: 'RJ45 (BOM Networking): shielded MagJack-class with metal frame',  description: 'Tall part on the main board\'s FRONT face at X 167–186 where there is no battery shadow; frame riveted to the chassis wall; behind the hinged TPU door.',  dims: '≈ 18 × 16 × 13.5 mm', source: '08-mechanical.md §3.1, §7', confidence: 'approximate',  geometry: [box(167, 185, 80, 96, 14.4, MB_Z0)], explode: [0, 12, -6] }); // =====================================================================// 9. THERMAL PADS (row 9)// =====================================================================add({ id: 'thermal-pads', name: 'Thermal gap pads (5)', parent: null, kind: 'assembly', major: false,  description: 'Bergquist/Laird gap pads on masked bare-conversion-coat bosses: SoM 2.0 mm (3 W/m·K), ASM2812 1.5 mm, RTL8126 1.5 mm, plus 1.5 mm pads bridging the CFe B and CFast cages to a chassis rib (D3 slack spent on boss height and pad thickness).',  function: 'Primary heat path from the hot parts into the magnesium rear skin.', material: 'Silicone gap pad 1.0–3.0 W/m·K',  source: '08-mechanical.md §10; 06-thermal.md §5; exploded parts list row 9', confidence: 'verified', explode: [0, 0, 24] });add({ id: 'pad-som', name: 'SoM gap pad 2.0 mm', parent: 'thermal-pads', kind: 'part', material: 'gap pad 3 W/m·K', dims: '≈ 62 × 47 × 2.0 mm (area assumed)', source: '08-mechanical.md §10', confidence: 'approximate',  geometry: [box(30, 92, 26, 73, R + 3.7, R + 5.7)], explode: [0, 0, 0] });add({ id: 'pad-asm2812', name: 'ASM2812 gap pad 1.5 mm', parent: 'thermal-pads', kind: 'part', dims: '17 × 17 × 1.5 mm', source: '08-mechanical.md §10', confidence: 'approximate',  geometry: [box(100, 117, 14, 31, R + 1.2, R + 2.7)], explode: [0, 0, 0] });add({ id: 'pad-rtl8126', name: 'RTL8126 gap pad 1.5 mm', parent: 'thermal-pads', kind: 'part', dims: '12 × 12 × 1.5 mm', source: '08-mechanical.md §10', confidence: 'approximate',  geometry: [box(100, 112, 40, 52, R + 1.0, R + 2.5)], explode: [0, 0, 0] });add({ id: 'pad-cfeb-cage', name: 'CFe B cage bridge pad 1.5 mm', parent: 'thermal-pads', kind: 'part', description: 'Bridges the CFexpress B connector cage to a chassis rib so card heat (3 W) leaves the reader band. Placement approximate.', source: '08-mechanical.md §10', confidence: 'approximate',  geometry: [box(63, 102, 8.5, 20, RBC_Z0, RBC_Z0 + 1.5)], explode: [0, 0, -10] });add({ id: 'pad-cfast-cage', name: 'CFast cage bridge pad 1.5 mm', parent: 'thermal-pads', kind: 'part', description: 'Bridges the CFast cage to a chassis rib. Placement approximate.', source: '08-mechanical.md §10', confidence: 'approximate',  geometry: [box(14, 58, 8.5, 24, RBC_Z0, RBC_Z0 + 1.5)], explode: [0, 0, -10] }); // =====================================================================// 10. MAGNESIUM CHASSIS (row 10)// =====================================================================add({ id: 'chassis', name: 'Magnesium AZ91D chassis-heatsink', parent: null, kind: 'assembly', major: true,  description: 'Hot-chamber die-cast AZ91D (EVT/DVT: machined AZ31B or thixomolded AZ91D): rear skin, bottom wall and top wall with the 22 × 6 mm slot channel, 1.6 mm walls, external longitudinal ribs, machined bosses under the SoM / ASM2812 / RTL8126, 2.5 mm rear pocket step for the parked shutter, vent, 1/4"-20 insert. Chromate-free conversion coat + light grey powder/e-coat; bosses and EMI lands masked bare. No RF window (all metal). ≈ 143 g.',  function: 'Structure, drop stiffness, primary heat spreader (G_eff ≈ 0.44 W/K shutter closed), shield.',  material: 'AZ91D magnesium, k ≈ 72 W/m·K (verify)', dims: '1.6 mm walls; ribs 1.5 mm tall at 20 mm pitch', partNumber: 'CHASSIS (BOM Mechanical)',  source: '08-mechanical.md §2.2, §3.2, §4, §4.1, §10; exploded parts list row 10', confidence: 'verified', explode: [0, 0, 48] });add({ id: 'rear-skin', name: 'Rear skin (Y 42–96)', parent: 'chassis', kind: 'part', major: true,  description: '1.6 mm cast wall + 0.4 mm rib root inside the 38 mm envelope (drawings keep a 2.0 mm section).', material: 'AZ91D', dims: '170 × 54 × 2.0 mm', source: '08-mechanical.md §3.2 row 10', confidence: 'verified',  geometry: [box(10, 180, 42, 96, 36, 38)], explode: [0, 0, 16] });add({ id: 'rear-pocket', name: 'Rear pocket (stepped skin, Y 0–42)', parent: 'chassis', kind: 'part', major: true,  description: '2.5 mm step in the rear skin over the top 42 mm where the two slats park when the shutter is open (behind the reader band, where rear-face parts are ≤ 1.5 mm tall).', material: 'AZ91D', dims: '170 × 42, step 2.5 mm', source: '08-mechanical.md §6.1; side-view drawing', confidence: 'verified',  geometry: [box(10, 180, 0, 42, 33.5, 35.5), box(10, 180, 42, 43.6, 33.5, 36)], explode: [0, 0, 16] });add({ id: 'bottom-wall', name: 'Bottom wall (port face)', parent: 'chassis', kind: 'part', major: true,  description: 'Bottom (port) face: cut-outs for Ports A/B/C, the RJ45 with its door rim, vent, 1/4"-20 insert and reset pinhole (cut-outs not modelled; ports pass through as separate meshes).', material: 'AZ91D', dims: '170 × 1.6 × 33.5 mm', source: '08-mechanical.md §7', confidence: 'derived',  geometry: [box(10, 180, 94.4, 96, 2.5, 36)], explode: [0, 16, 0] });add({ id: 'top-wall', name: 'Top wall with slot channel', parent: 'chassis', kind: 'part', major: true,  description: 'Top face between the end caps: front and rear rims, a 22 mm wide × 6 mm deep channel (Z 8–30) with 5° drafted walls, recess floor with the five slot openings, 3° drain slope to Ø2.5 drains at each end (draft and slope not modelled).',  material: 'AZ91D', dims: 'channel 166 × 22 × 6 mm', source: '08-mechanical.md §5; top-view drawing', confidence: 'verified',  geometry: [box(10, 180, 0, 1.6, 2.5, 8), box(10, 180, 0, 1.6, 30, 33.5), box(10, 180, 0, 7.6, 8, 9.2), box(10, 180, 0, 7.6, 28.8, 30),    { type: 'plate', normal: 'y', x: [9.2, 180.8], y: [6.0, 7.6], z: [9.2, 28.8], r: 0.5,      holes: SLOTS.map((s) => ({ x: [s.x0 + s.w / 2 - s.open[0] / 2, s.x0 + s.w / 2 + s.open[0] / 2], y: [SLOT_ZC - s.open[1] / 2, SLOT_ZC + s.open[1] / 2], r: 0.8 })) }],  explode: [0, -16, 0] });[50, 70, 90].forEach((y, i) => add({ id: `rib-${i + 1}`, name: `External rib ${i + 1} (Y ${y})`, parent: 'chassis', kind: 'part',  description: 'Longitudinal external rib, 1.5 mm tall, 20 mm pitch; adds ~18 % area and forms the rear grip texture. Sits outside the 38 mm envelope.', material: 'AZ91D', dims: '166 × 1.5 × 1.5 mm', source: '08-mechanical.md §10', confidence: 'verified',  geometry: [box(12, 178, y - 0.75, y + 0.75, 38, 39.5)], explode: [0, 0, 20] }));add({ id: 'boss-som', name: 'SoM boss (machined, masked bare)', parent: 'chassis', kind: 'part', description: 'Cast then machined to 0.10 mm flatness; +2 mm taller than the 06 §5 local boss (D3 slack). Bare conversion coat under the pad.', material: 'AZ91D', source: '08-mechanical.md §10', confidence: 'approximate',  geometry: [box(30, 92, 26, 73, R + 5.7, 36)], explode: [0, 0, 16] });add({ id: 'boss-asm2812', name: 'ASM2812 boss', parent: 'chassis', kind: 'part', material: 'AZ91D', source: '08-mechanical.md §10', confidence: 'approximate',  geometry: [box(100, 117, 14, 31, R + 2.7, 33.5)], explode: [0, 0, 16] });add({ id: 'boss-rtl8126', name: 'RTL8126 boss', parent: 'chassis', kind: 'part', material: 'AZ91D', source: '08-mechanical.md §10', confidence: 'approximate',  geometry: [box(100, 112, 40, 52, R + 2.5, 36)], explode: [0, 0, 16] });add({ id: 'vent', name: 'Vent membrane Ø8 (Gore PolyVent)', parent: 'chassis', kind: 'part', partNumber: 'Gore PolyVent / Nitto Temish Ø8', description: 'IP67-class pressure-equalisation membrane in the bottom face under a labyrinth cap; also the port for the sample leak test. X position approximate.', source: '08-mechanical.md §4, §7, §12', confidence: 'approximate',  geometry: [cyl('y', 110, 95.2, 19, 4, 1.6)], explode: [0, 20, 0] });add({ id: 'tripod-insert', name: '1/4"-20 insert', parent: 'chassis', kind: 'part', description: 'Tripod / cart-mount insert in the bottom face (aluminum or Zn-Ni insert, no stainless in Mg).', material: 'Al / Zn-Ni insert', source: 'front-view drawing; 08-mechanical.md §4.1', confidence: 'approximate',  geometry: [cyl('y', 95, 92.5, 19, 4.5, 7)], explode: [0, 20, 0] }); // =====================================================================// 14. HARDWARE (row 14) — frame screws only; standoff, SoM and pack screws not modelled// =====================================================================add({ id: 'hardware', name: 'Frame screws (14 × M2.5)', parent: null, kind: 'assembly', major: false,  description: 'Fourteen Zn-Ni-plated steel or aluminum M2.5 screws marry the frame to the chassis in a torque pattern (0.35 N·m). Positions are not documented; placed 6 along the bottom edge and 4 per side. Standoff (8 + 6), SoM (2–4), pack (4) screws, USB-C brackets and RJ45 rivets are not modelled.',  material: 'Zn-Ni steel / Al into Al / Zn-Ni inserts (galvanic rule D4)', source: '08-mechanical.md §4.1, §12 step 7; exploded parts list row 14', confidence: 'approximate', explode: [0, 0, -98] });let fs = 0;const framePos = [[14, 93], [47, 93], [80, 93], [113, 93], [146, 93], [176, 93], [5, 15], [5, 35], [5, 60], [5, 80], [185, 15], [185, 35], [185, 60], [185, 80]];for (const [x, y] of framePos) { fs++; add({ id: `frame-screw-${fs}`, name: `Frame screw ${fs} (X ${x}, Y ${y})`, parent: 'hardware', kind: 'instance', def: 'def-m25-frame-screw',  source: '08-mechanical.md §12 step 7', confidence: 'approximate', geometry: [cyl('z', x, y, 0.6, 2.2, 1.2), cyl('z', x, y, 4.0, 1.25, 6)], explode: [0, 0, 0] }); } export const COMPONENTS = C; // Assembly order from the exploded-view drawing footer (08 §12): illustrative, referenced by the UI legend.export const ASSEMBLY_ORDER = 'Assembly order (08-mechanical.md §12): chassis → pads → main board → antennas → mezzanine → reader board → frame (+ display, gaskets) → screws → pack → door → rails → shutter.';