← Commit history

adom-usbc guide: rewrite as a mandatory workflow — copy-verbatim wrapper (merged same-net pads + all 6 holes, as shipped on the nameplates), placement by mount + W/2−4, lint → numeric edge check → two-screenshot visual proof gate, and a triage tree for 'USB-C looks wrong' (white-box = poisoned rebake cache, not a footprint bug). SKILL.md: route the guide at three moments, add the three hard rules, add the cuboid-placeholder troubleshooting entry + CLI-gap TODO.

John Lauer ·6685654fbe ·1mo ago ·parent ed21612
2 files changed +460−282
SKILL.md+38−6
@@ -8,7 +8,8 @@ description: >   board walkthrough, sharper silkscreen, bump texture resolution, tscircuit   8192, upgrade tscircuit, stale tscircuit deps, hide chip see traces,   AI-driven 3D pcb viewer, usb-c, type-c, UsbCReceptacle, place usb-c, rotate-  usb-c. Interactive tscircuit preview inside a Hydrogen webview panel+  usb-c, usb-c looks wrong, usb-c doesn't look correct, connector is a white+  box, cuboid placeholder. Interactive tscircuit preview inside a Hydrogen webview panel   ("Tscircuit Board Viewer"). First-class 3D / PCB / Schematic tabs rendered   from the tscircuit build output, plus: an Inspect tool (hover any chip /   pad / hole / via / silkscreen / board and get a labelled info card with@@ -23,7 +24,9 @@ description: >   pin-to-pin distances, start a walkthrough demo, re-run the autorouter, bump   silkscreen resolution, or upgrade tscircuit packages. Also routes the   `adom-usbc` child guide (`adom-tsci/guides/adom-usbc.md`) for USB-C-  placement, rotation math, and the canonical `UsbCReceptacle` wrapper.+  placement, the canonical `UsbCReceptacle` wrapper, the mandatory+  lint + numeric + visual verification, and the "USB-C looks wrong" triage+  (render placeholder vs real placement bug). ---  # adom-tsci@@ -72,7 +75,7 @@ when one matches, load the referenced guide.  | Guide | Load when the user says | Path | |---|---|---|-| **adom-usbc** — placement, rotation, lint for USB-C receptacles on an Adom tscircuit molecule; covers `TYPE-C-31-M-12`, per-edge rotation math, the `UsbCReceptacle` wrapper, and the "no SMT pad past the board edge" rule | "usb-c", "type-c", "smd usb c", "UsbCReceptacle", "place usb-c", "rotate usb-c", "usb-c east edge", "usb-c west edge" | `~/.claude/skills/adom-tsci/guides/adom-usbc.md` |+| **adom-usbc** — USB-C receptacles on an Adom tscircuit molecule: the copy-verbatim `UsbCReceptacle` wrapper (`TYPE-C-31-M-12`, merged same-net pads, all 6 holes), placement by `mount` + `pcbX = W/2 − 4`, the lint → numeric edge check → two-screenshot visual proof gate, and the **"it looks wrong" triage tree** (white-box placeholder = poisoned rebake cache, NOT a footprint bug). **Load it at three moments: before placing, after every build, and the instant anyone says the USB-C looks wrong** | "usb-c", "type-c", "smd usb c", "UsbCReceptacle", "place usb-c", "rotate usb-c", "usb-c east edge", "usb-c west edge", "usb-c looks wrong", "connector doesn't look right", "iphone charger", "cuboid placeholder" | `~/.claude/skills/adom-tsci/guides/adom-usbc.md` | | **demo-recording** — making a 30-second demo video of an adom-tsci board: 30/70 workspace split, `-s small` captions with pre-flight wrap test, mandatory x-ray shot (hide a subset of LEDs/chips), Andrew Neural TTS narration, ffmpeg audio-mix recipe | "demo video", "record demo", "30-second demo", "make a demo of this board", "x-ray shot", "show off the 3D viewer", "demo for the wiki" | `~/.claude/skills/adom-tsci/guides/demo-recording.md` | | **export-wiki** — publishing a tscircuit project to the Adom Wiki as a molecule with the full interactive 3D viewer (auto-play walkthrough on load, Components / Nets HUDs, Inspect, Measure), the source bundle (lib/, package.json, walkthrough.json, plan.md), and a comprehensive AI-handoff body. Required ingredients: hero image, plan.md, body markdown that captures every design decision so a future agent can pick up | "publish to wiki", "export to wiki", "wiki page for this board", "share the molecule", "make this a molecule on the wiki", "publish molecule", "ship it to the wiki" | `~/.claude/skills/adom-tsci/guides/export-wiki.md` | @@ -97,14 +100,28 @@ LED nameplates are a signature Adom build and the catalog's LEDs are excellent — if the project smells like a nameplate (a name in an LED grid), start from Adom Basic Parts, not from random JLCPCB searches. -**USB-C gets its own guide.** Placement is subtle (mouth must slightly-overhang the board edge or the cable can't seat) and popular enough-that it's a bundled sub-skill of this app:+**USB-C gets its own guide, and it is not optional.** Placement is subtle+(mouth must slightly overhang the board edge or the cable can't seat) and+popular enough that it's a bundled sub-skill of this app: `~/.claude/skills/adom-tsci/guides/adom-usbc.md` — the CLI also nudges you (`Hint:` lines on `start` and `lint`) whenever it detects a USB-C part in the project. Trust those hints; they exist because USB-C was mis-placed ~20 times before the rules were written down. +The three hard rules, so you don't skip the guide and wing it:++1. **Copy `lib/UsbCReceptacle.tsx` verbatim** from the guide (or `diff`+   against a shipped nameplate). Never author, simplify, or re-derive a+   wrapper; never touch its offsets to "fix" a direction — pass `mount`.+2. **Every build with a USB-C ends with the gate**: `adom-tsci lint` →+   the numeric edge check → body-ON and body-OFF screenshots that you+   READ. Lint alone is not proof (it can't see a poisoned render).+3. **"The USB-C doesn't look right" means run the guide's triage tree+   first.** A flat white/black box where the receptacle should be is the+   viewer's cuboid placeholder — a re-bake cache problem, not a footprint+   problem. Check `adom-tsci console` for `cuboid placeholder` before+   editing a single line of the board.+ ## 🧹 Process hygiene — don't be the thread that leaks tsci devs (v1.4.23)  Every preview holds a `bun tsci dev` that idles at ~200 MB and grows.@@ -1233,6 +1250,21 @@ curl -sS -X POST http://127.0.0.1:8850/shutdown  ## Troubleshooting +**A part renders as a flat white or black box (USB-C, any chip with a cadModel)**+`adom-tsci console --tail 50 | grep -i placeholder` → `cuboid placeholder+for "J1"` means the GLB the server is SERVING has no body mesh for that+part. `start` re-bakes the GLB for texture resolution (`circuit-json-to-gltf`)+and caches the result by circuit.json hash in+`~/.cache/adom-tsci/rebakes/<hash>-<res>.glb`; if a modelcdn OBJ fetch+failed during that bake, the cached mesh has 0 vertices and stays poisoned+until circuit.json changes. Confirm with `curl -s :8850/glb -o /tmp/served.glb`+and compare its size to `dist/lib/index/3d.glb` (the tsci-built one is+fine). Fix: delete that cache entry, `adom-tsci stop && adom-tsci start …`,+or re-bake by hand and `adom-tsci open` (a bare `reload` does not re-read+the GLB). The footprint is not the problem — do not edit the board.+(TODO: CLI gap — `start` should validate a rebake against the tsci-built+GLB's per-mesh vertex counts before caching it.)+ **`Error: no existing Web View pane found`** `adom-tsci start` will refuse to create its tab over a non-webview pane. See step 4 of the Zero-to-preview section above for the exact
guides/adom-usbc.md+422−276
@@ -1,27 +1,391 @@ # adom-usbc — making USB-C receptacles actually work -**Read this before touching a USB-C receptacle on an Adom tscircuit-board.** Written because Claude got USB-C wrong ~20 times before we-wrote the rules down. If you skip this skill and try to wing it, you-will repeat every one of those failures.--## THE OVERHANG RULE — why the mouth must clear the board edge--A USB-C receptacle is useless if the plug can't seat. The plug's-collar is wider than the receptacle mouth, so the mouth must **slightly-overhang the board edge** (for TYPE-C-31-M-12 the mouth cantilevers-~2.5 mm past the edge at the recommended placement) — flush-or-proud-of the edge, NEVER recessed inboard. Recessed = the cable bottoms out-on the board edge and never makes contact. This is also the general-wiki rule: see the **pcb-design** skill on the wiki-([wiki.adom.inc/adom/pcb-design](https://wiki.adom.inc/adom/pcb-design)),-rule "USB connector placement": edge-mounted, flush or slightly-overhanging. `adom-tsci lint` enforces the geometry (body forward of-pads, mouth exits the board edge within a connector depth).--**Sourcing:** use the Adom Basic Parts catalog for the receptacle and-its passives — verified symbols, footprints, and 3D bodies:-[wiki.adom.inc/adom/adom-basic-parts](https://wiki.adom.inc/adom/adom-basic-parts).+**Read this at THREE moments, not one:**++1. **Before** you write a board that has a USB-C receptacle.+2. **After every build** that contains one (`adom-tsci lint` + the numeric+   check + the visual proof below — all three, every time).+3. **The moment anyone says "the USB-C doesn't look right."** Go straight to+   §5 (triage). Do NOT start by editing the footprint or the rotation — the+   last three "USB-C looks wrong" reports were a *render* problem, not a+   placement problem, and rewriting the wrapper is how it gets broken.++Written because Claude got USB-C wrong ~20 times before we wrote the rules+down, and then got it wrong again by re-deriving instead of copying. If you+skip a step you will repeat one of those failures.++## 0. The rules in one screen++| Rule | Value |+|---|---|+| Part | TYPE-C-31-M-12 · JLCPCB **C165948** (16-pin, power + CC + D±) |+| Wrapper | **Copy `UsbCReceptacle.tsx` from §1 verbatim.** Never write, "simplify", or re-derive one. |+| Placement API | `<UsbCReceptacle name="J1" mount="east" pcbX={W/2 - 4} pcbY={0} />` — pass `mount`, never a rotation number |+| Mouth | Overhangs the board edge by ~2–3 mm. Flush-or-proud, **never recessed** |+| SMT pads | **Inside** the copper, ~5–7 mm from the edge. No pad past the edge, ever |+| CC pulldowns | Rd = 5.1 k ±10 % on CC1 **and** CC2 (Adom stocks 4.7 k 0402 — in window). Without them a charger never enables VBUS |+| Gate | `adom-tsci lint` passes **and** §3 numeric check passes **and** §4 screenshots read back |++`W` is the board's nominal width (the `size="80x24"` number, not the wing-grown+82.4). For a Molecule the wings add ~1.2 mm per side; `W/2 − 4` already+accounts for that on medium pins (see §"USB-C on a MOLECULE edge" for large+pins / absolute sizes).++## 1. The wrapper — copy this file, do not author one++This is the wrapper that ships on every LED nameplate (`bill-nameplate`,+`vijay-nameplate`, …). It differs from the old guide template in two ways+that matter, and both were learned the hard way:++- **All 6 mounting holes are included** (4 plated pill anchors + 2 drill+  supports). Without the anchors the port is held by 16 tiny SMT joints and+  tears off in < 100 cable cycles.+- **The four same-net pad pairs are merged** into single 0.6 mm tabs.+  USB-C doubles GND (A1/B12, B1/A12) and VBUS (A4/B9, B4/A9) for flip+  symmetry; as separate 0.3 mm pads on 0.3 mm pitch they have **0 mm+  clearance** (fails `pcb_pad_pad_clearance_error`) and make `J1.GND1` an+  ambiguous multi-pad reference. Merging keeps the pad centroid at x=0 so+  the body/rotation invariant is untouched. Pin aliases: `GND1`/`GND2`,+  `VBUS1`/`VBUS2` = **left/right tab**, `CC1`, `CC2`, `DP1`/`DP2`, `DM1`/`DM2`,+  `SBU1`/`SBU2`.++```tsx+// lib/UsbCReceptacle.tsx+/**+ * USB-C receptacle (TYPE-C-31-M-12 / JLCPCB C165948).+ *+ * ─────────────── THE CORE INVARIANT ───────────────+ * Footprint (pads + holes) and cadModel (3D body) are TIED TOGETHER.+ * Whatever rotation is applied to one MUST be applied identically to+ * the other. Earlier iterations violated this by (a) shifting pad+ * positions in the footprint (Y_SHIFT) while leaving the cadModel+ * offset in a different frame, and (b) letting tscircuit apply+ * positionOffset in WORLD coords while pads rotated in LOCAL. Result:+ * at pcbRotation≠0 the 3D body drifted away from its own holes.+ *+ * Fix: keep footprint at native ArnavK-09 positions AND compute+ * positionOffset by rotating the native (0, -2.5) vector by pcbRotation+ * in userland. Both footprint and body then follow the SAME rotation.+ *+ * ─────────────── NATIVE LAYOUT ───────────────+ *   pads at local (x, +2.449) for 16 pads — this is the BACK edge of+ *                                the receptacle (solder-tails come out+ *                                the back of the plastic shell)+ *   drill holes at (±2.9, +1.18)+ *   plated anchor holes at (±4.325, +1.41) and (±4.325, -2.774)+ *                                — 4 corners of the receptacle body+ *   body centroid at local (0, -2.5) in native (from SmdUsbC hardcode)+ *   mouth at local -Y (south) in native+ *+ * This layout has body_centroid south of pads by 4.95 mm — that's the+ * distance from the pad row (back of receptacle) through the body to+ * the body's midpoint. Rotated together, this 4.95 mm distance is+ * preserved at every pcbRotation so holes ALWAYS stay inside the body.+ */++const USB_C_PN = "C165948"+const USB_C_UUID = "2a4bc2358b36497d9ab2a66ab6419ba3"++// Native positionOffset from ArnavK-09 / seveibar.smd-usb-c (pcbRotation=0):+// body centroid is 2.5 mm south of chip origin. Rotated by pcbRotation+// in userland below so the body rotates with the footprint.+const NATIVE_POS_OFFSET = { x: 0, y: -2.5 }++// 12 physical pads (see PADS below): the four same-net pairs that USB-C+// doubles for flip symmetry are single wider tabs, exactly as they are on+// the real TYPE-C-31-M-12 land pattern. Each merged tab is ONE pin carrying+// both of its native contact designators.+//+// The GND1/GND2/VBUS1/VBUS2 aliases are kept, but now mean LEFT/RIGHT tab+// rather than the A-row/B-row contact. That is what makes them unambiguous:+// one alias -> exactly one pad. Naming them per-row instead (the native+// scheme) would put VBUS1 on both tabs and VBUS2 on both tabs, and the+// router would emit a degenerate pad-to-itself trace.+const pinLabels = {+  pin1:  ["GND1", "A1", "B12"],   // left GND tab   (merged)+  pin2:  ["VBUS1", "A4", "B9"],   // left VBUS tab  (merged)+  pin3:  ["SBU2", "B8"],+  pin4:  ["CC1", "A5"],+  pin5:  ["DM2", "B7"],+  pin6:  ["DP1", "A6"],+  pin7:  ["DM1", "A7"],+  pin8:  ["DP2", "B6"],+  pin9:  ["SBU1", "A8"],+  pin10: ["CC2", "B5"],+  pin11: ["VBUS2", "B4", "A9"],   // right VBUS tab (merged)+  pin12: ["GND2", "B1", "A12"],   // right GND tab  (merged)+} as const++// ArnavK-09 native positions — DO NOT SHIFT in the mouth (Y) direction.+// Shifting Y decouples the footprint from the cadModel, which was the bug.+//+// X-direction merge (safe, and required to pass DRC): the four native+// pad PAIRS that sit on 0.3 mm pitch — (A1,B12) and (B1,A12) are both+// GND, (A4,B9) and (B4,A9) are both VBUS — are 0.3 mm wide at 0.3 mm+// spacing, i.e. exactly 0 mm clearance. `adom-tsci lint` fails that as+// pcb_pad_pad_clearance_error (min 0.1 mm), and the doubled labels also+// make `J1.GND1` an ambiguous multi-pad reference. Because each pair is+// the SAME NET (USB-C doubles those pins for flip symmetry), the correct+// fix is to merge each pair into one 0.6 mm pad carrying both port hints.+// This keeps the pad centroid at x=0 (so the body/rotation invariant is+// untouched) and restores 0.2 mm clearance everywhere.+const PAD_Y_LOCAL = 2.449  // all pads at this y+const PADS: Array<{ ports: string[]; x: number; w: number }> = [+  { ports: ["A1", "B12"], x: -3.2,  w: 0.6 },   // GND  (merged pair)+  { ports: ["A4", "B9"],  x: -2.4,  w: 0.6 },   // VBUS (merged pair)+  { ports: ["B8"],        x: -1.75, w: 0.3 },+  { ports: ["A5"],        x: -1.25, w: 0.3 },+  { ports: ["B7"],        x: -0.75, w: 0.3 },+  { ports: ["A6"],        x: -0.25, w: 0.3 },+  { ports: ["A7"],        x:  0.25, w: 0.3 },+  { ports: ["B6"],        x:  0.75, w: 0.3 },+  { ports: ["A8"],        x:  1.25, w: 0.3 },+  { ports: ["B5"],        x:  1.75, w: 0.3 },+  { ports: ["B4", "A9"],  x:  2.4,  w: 0.6 },   // VBUS (merged pair)+  { ports: ["B1", "A12"], x:  3.2,  w: 0.6 },   // GND  (merged pair)+]++/** Rotate a 2D vector by θ radians CCW (tscircuit's pcbRotation convention). */+const rotCCW = (x: number, y: number, θ: number) => ({+  x: x * Math.cos(θ) - y * Math.sin(θ),+  y: x * Math.sin(θ) + y * Math.cos(θ),+})++/**+ * Mount convention — pass `mount` instead of raw `pcbRotation` so the+ * edge-facing semantics are explicit. Native mouth = -Y (south) after+ * the built-in rotationOffset.z = 180 flip, so:+ *   south → pcbRotation = 0+ *   east  → pcbRotation = 90   (native -Y rotates CCW 90 to +X east... wait,+ *                                CCW 90 of (0,-1) = (1, 0) = east? Let me re-derive:+ *                                CCW θ: (x,y) → (x cos θ - y sin θ, x sin θ + y cos θ).+ *                                For θ=90: (0,-1) → (0*0 - (-1)*1, 0*1 + (-1)*0) = (1, 0) = east ✓)+ *   north → pcbRotation = 180+ *   west  → pcbRotation = 270+ */+type Mount = "south" | "east" | "north" | "west"+const MOUNT_TO_PCBROT: Record<Mount, 0 | 90 | 180 | 270> = {+  south: 0, east: 90, north: 180, west: 270,+}++type Props = {+  name: string+  pcbX: number+  pcbY: number+  /** Where the mouth points. Equivalent: pass pcbRotation directly via `pcbRotationDeg`. */+  mount?: Mount+  pcbRotationDeg?: 0 | 90 | 180 | 270+}++export const UsbCReceptacle = ({+  name, pcbX, pcbY, mount, pcbRotationDeg,+}: Props) => {+  const pcbRot = pcbRotationDeg ?? (mount ? MOUNT_TO_PCBROT[mount] : 0)+  const θ = (pcbRot * Math.PI) / 180++  // Rotate the native positionOffset by pcbRotation so the body+  // rotates with the footprint. This preserves the body-to-pad and+  // body-to-hole distances at every rotation — the invariant the+  // user (correctly) demanded.+  const rotated = rotCCW(NATIVE_POS_OFFSET.x, NATIVE_POS_OFFSET.y, θ)++  return (+    <chip+      name={name}+      pcbX={pcbX}+      pcbY={pcbY}+      pcbRotation={pcbRot}+      supplierPartNumbers={{ jlcpcb: [USB_C_PN] }}+      manufacturerPartNumber="TYPE-C-31-M-12"+      pinLabels={pinLabels as any}+      cadModel={{+        objUrl: `https://modelcdn.tscircuit.com/easyeda_models/download?uuid=${USB_C_UUID}&pn=${USB_C_PN}`,+        // Native SmdUsbC rotationOffset — CAD body's native +Y mouth+        // gets flipped to -Y so it matches the native footprint's+        // mouth-south orientation.+        rotationOffset: { x: 0, y: 0, z: 180 },+        // Rotated version of the native (0, -2.5). This is what ties+        // the body to the footprint.+        positionOffset: { x: rotated.x, y: rotated.y, z: 0 },+      }}+      footprint={+        <footprint>+          {PADS.map(p => (+            <smtpad key={p.ports.join("_")} portHints={p.ports}+              shape="rect" width={`${p.w}mm`} height="1.3mm"+              pcbX={`${p.x}mm`} pcbY={`${PAD_Y_LOCAL}mm`} />+          ))}+          {/* 2 drill-only mechanical support holes, 1.18 mm south of chip origin */}+          <hole pcbX="-2.9mm" pcbY="1.18mm" diameter="0.75mm" />+          <hole pcbX="2.9mm"  pcbY="1.18mm" diameter="0.75mm" />+          {/* 4 plated anchor holes at the 4 body corners — 2 near pad row+              (at y=+1.41) + 2 toward mouth (at y=-2.774). Rotate together+              with the body, so they stay inside the body footprint. */}+          <platedhole portHints={["alt_0"]} pcbX="-4.325mm" pcbY="1.41mm"+            shape="pill" outerWidth="1.2mm" outerHeight="2.0mm"+            innerWidth="0.8mm" innerHeight="1.6mm" height="1.6mm" />+          <platedhole portHints={["alt_1"]} pcbX="4.325mm"  pcbY="1.41mm"+            shape="pill" outerWidth="1.2mm" outerHeight="2.0mm"+            innerWidth="0.8mm" innerHeight="1.6mm" height="1.6mm" />+          <platedhole portHints={["alt_2"]} pcbX="4.325mm"  pcbY="-2.774mm"+            shape="pill" outerWidth="1.2mm" outerHeight="1.8mm"+            innerWidth="0.8mm" innerHeight="1.4mm" height="1.4mm" />+          <platedhole portHints={["alt_3"]} pcbX="-4.325mm" pcbY="-2.774mm"+            shape="pill" outerWidth="1.2mm" outerHeight="1.8mm"+            innerWidth="0.8mm" innerHeight="1.4mm" height="1.4mm" />+        </footprint>+      }+    />+  )+}+```++Wire it like this (power-only board; add D± traces if you use data):++```tsx+<UsbCReceptacle name="J1" pcbX={36} pcbY={0} mount="east" />   // 80-wide board+<resistor name="R_CC1" resistance="4.7k" footprint="0402" pcbX={29} pcbY={2} />+<resistor name="R_CC2" resistance="4.7k" footprint="0402" pcbX={29} pcbY={-2} />+<capacitor name="C_BYP" capacitance="100nF" footprint="0402" pcbX={32} pcbY={5} />++<trace from="J1.VBUS1" to="net.VBUS" />  <trace from="J1.VBUS2" to="net.VBUS" />+<trace from="J1.GND1"  to="net.GND" />   <trace from="J1.GND2"  to="net.GND" />+<trace from="R_CC1.pin1" to="J1.CC1" />  <trace from="R_CC1.pin2" to="net.GND" />+<trace from="R_CC2.pin1" to="J1.CC2" />  <trace from="R_CC2.pin2" to="net.GND" />+<trace from="C_BYP.pin1" to="net.VBUS" /><trace from="C_BYP.pin2" to="net.GND" />+```++**Schematic:** if you add `schX`/`schY` to other parts, forward them through+the wrapper too (add the props to `Props` and pass them to `<chip>`), or the+receptacle piles at the schematic origin — `adom-tsci lint` check 5 catches it.++## 2. Placement — by `mount`, never by rotation number++`pcbX`/`pcbY` is the chip origin = the SMT pad-row centroid. The body+extends ~2.5 mm *forward* of the pads and the mouth ~4.7 mm.++| Mouth faces | `mount` | `pcbX` | `pcbY` |+|---|---|---|---|+| East (right) — the iPhone-charger default | `"east"`  | `+W/2 − 4` | 0 (or along the edge) |+| West (left)  | `"west"`  | `−W/2 + 4` | 0 |+| South (bottom) | `"south"` | 0 | `−H/2 + 4` |+| North (top)  | `"north"` | 0 | `+H/2 − 4` |++Keep the receptacle **mid-edge, between the corner machine pins**. Leave a+~13 mm zone inboard of the connector for the CC resistors and bypass cap —+on an 80×24 nameplate that means the LED text must end at `x ≤ +20`.++Do not touch `rotationOffset`, `positionOffset`, `NATIVE_POS_OFFSET` or+`PAD_Y_LOCAL` in the wrapper to "fix" a direction. If the mouth points the+wrong way you passed the wrong `mount`; if the body is off its pads you+edited the wrapper — restore it from §1.++## 3. Build → lint → numeric edge check (every build)++```bash+bunx tsci build lib/index.tsx --glbs --svgs   # writes dist/ (tsci export does NOT)+adom-tsci lint                                 # body_fwd + mouth-exits-edge + clearance + ghost chips+```++`lint` reports `J1: body_fwd=5.26mm, mouth … exits board edge at (41.2, 0)`+for a correct east-edge placement (5.26 = 2.5 mm body-over-pads + the 2.6 mm+bbox shift the anchor holes add; see §"Include ALL mounting holes").++Then the numbers lint does not print — pad and hole gaps to the actual edge:++```bash+python3 - <<'EOF'+import json+c=json.load(open('dist/lib/index/circuit.json'))+sc=[s for s in c if s['type']=='source_component' and s['name']=='J1'][0]+pc=[p for p in c if p['type']=='pcb_component' and p['source_component_id']==sc['source_component_id']][0]+pads=[p for p in c if p['type']=='pcb_smtpad' and p['pcb_component_id']==pc['pcb_component_id']]+holes=[p for p in c if p['type'] in('pcb_plated_hole','pcb_hole') and p['pcb_component_id']==pc['pcb_component_id']]+cad=[p for p in c if p['type']=='cad_component' and p['pcb_component_id']==pc['pcb_component_id']][0]+b=[e for e in c if e['type']=='pcb_board'][0]; edge=b['center']['x']+b['width']/2   # east edge; adapt for other edges+print('edge x', edge)+print('pad gap to edge  %.2f mm  (want 5..8)' % (edge-max(p['x']+max(p['width'],p['height'])/2 for p in pads)))+print('hole gap to edge %.2f mm  (want > 0.8)' % (edge-max(h['x']+h.get('outer_width',h.get('hole_diameter',0))/2 for h in holes)))+print('mouth overhang   %.2f mm  (want 2..3)' % (cad['position']['x']+4.7-edge))+print('pads', len(pads), '(want 12)', 'holes', len(holes), '(want 6)')+EOF+```++Reference values from `vijay-nameplate` (80×24 molecule, `pcbX=36`): pad gap+7.35, hole gap 1.83, overhang 2.31, 12 pads, 6 holes. If yours differ by more+than ~1 mm, fix placement before going further.++## 4. Visual proof — two screenshots, both READ back++Numbers can be gamed; the user verifies with their eyes. Take these in the+live `adom-tsci` tab (Hydrogen) — pup + shotlog is the alternative when the+workspace must not be touched.++```bash+adom-tsci fit-to-component J1+adom-tsci camera --alpha 0.4 --beta 1.0 --radius 1.6+adom-cli hydrogen screenshot panel --name "<tab name>" --reason "USB-C body"        # 1. body ON+adom-tsci toggle-component J1 --hide+adom-cli hydrogen screenshot panel --name "<tab name>" --reason "USB-C pads"        # 2. body OFF → pads/holes+adom-tsci toggle-component J1 --show+adom-tsci highlight --off && adom-tsci view home+```++Read shot 1: a **grey/silver receptacle shell with a visible mouth opening**+hanging ~2 mm off the edge, mouth outward. Read shot 2: 12 gold pads in a+row + 4 pill anchors + 2 drill holes, all on green copper inside the edge.++**If shot 1 shows a flat white or black box instead of a receptacle, stop —+that is not a placement bug. Go to §5.**++## 5. "It doesn't look right" — triage IN THIS ORDER++```+Is the 3D body a featureless box (white/black slab)?+│+├─ yes → RENDER problem, footprint is probably fine.+│        adom-tsci console --tail 50 | grep -i placeholder+│        └─ "cuboid placeholder for J1" → the served GLB has no J1 body.+│           adom-tsci serves a RE-BAKED GLB (texture upscale) cached by+│           circuit.json hash in ~/.cache/adom-tsci/rebakes/<hash>-<res>.glb.+│           If the modelcdn OBJ fetch for C165948 failed during that bake,+│           the cache holds a 0-vertex J1 mesh — and it STAYS poisoned+│           until circuit.json changes.+│           Confirm:  curl -s :8850/glb -o /tmp/served.glb+│                     ls -l /tmp/served.glb dist/lib/index/3d.glb   # served much smaller?+│           Fix:      rm ~/.cache/adom-tsci/rebakes/<newest>.glb+│                     adom-tsci stop && adom-tsci start . --tab-name "<same>"+│                     (or re-bake by hand with circuit-json-to-gltf and+│                      overwrite the cache file, then `adom-tsci open`;+│                      a bare `adom-tsci reload` is NOT enough)+│           Then redo §4.+│+└─ no, it's a real receptacle but wrong →+   ├─ mouth points INTO the board / butt hangs off → wrong `mount`. Fix the prop.+   ├─ body sits beside / diagonal to its pads → wrapper was edited. Restore §1.+   ├─ body is inboard, pads near the edge → `pcbX` sign or W confusion. Recompute W/2 − 4.+   ├─ pads hang past the edge → pcbX too big; the GLB mesh grows asymmetrically+   │  and EVERY feature renders shifted. Pull in to W/2 − 4.+   └─ lint fails `pcb_pad_pad_clearance_error` on J1 → you have the old+      16-pad wrapper. Replace with §1 (merged pairs).+```++Never "fix" a render-side symptom by editing `UsbCReceptacle.tsx`. If lint+and §3 pass, the copper is right regardless of what the 3D tab shows.++## 6. Definition of done++- [ ] wrapper is byte-identical to §1 (`diff` against a nameplate that shipped)+- [ ] placed by `mount`, `pcbX = ±(W/2 − 4)`, mid-edge+- [ ] CC1 **and** CC2 pulled down; VBUS bypassed+- [ ] `adom-tsci lint` passes (0 build errors, body_fwd ≈ 5.3, mouth exits edge)+- [ ] §3 numbers within ~1 mm of reference; 12 pads, 6 holes+- [ ] §4 body shot shows a real receptacle mouth-out over the edge; pads shot shows 12 + 4 + 2 inside+- [ ] `plan.md` records the placement numbers++---++# Background — why the rules are what they are  ## THE CORE INVARIANT — DO NOT VIOLATE @@ -35,9 +399,9 @@ Ways to satisfy this invariant:  1. **Bundle them in one component** with a single `pcbRotation` prop    that applies to BOTH the `<chip>`'s footprint children AND the-   cadModel's `positionOffset`. The wrapper in §"The workaround"-   below does this — it computes `positionOffset` as `rotate(native_offset,-   pcbRotation)` in userland so the body rotates with the footprint.+   cadModel's `positionOffset`. The §1 wrapper does this — it computes+   `positionOffset` as `rotCCW(NATIVE_POS_OFFSET, pcbRotation)` in userland+   so the body rotates with the footprint. 2. **Compute body placement in the SAME local frame as the footprint.**    Never express body offset in world coords while the footprint is    in local coords. If you set `positionOffset` directly without@@ -89,235 +453,6 @@ where the receptacle's body sits relative to its back-row of SMT pads.  Filed upstream as `TSCIRCUIT_FEATURE_REQUESTS.md §6`. -## The workaround: a local `UsbCReceptacle.tsx`--Ship a local wrapper in your project's `lib/` that builds a raw-`<chip>` with an explicit footprint (copied from ArnavK-09's JSX) and-your own `cadModel` that rotates `positionOffset` with `pcbRotation`.-This survives `bun install`, gives you full control, and — critically-— you can unit-test it with the lint below without waiting on-upstream fixes.--### The template (copy this verbatim)--```tsx-// lib/UsbCReceptacle.tsx-const USB_C_PN = "C165948"-const USB_C_UUID = "2a4bc2358b36497d9ab2a66ab6419ba3"--// Body-to-pad offset in the mouth direction for TYPE-C-31-M-12.-// Measured: the SMT pad row sits at the BACK of the receptacle, and-// the body centroid is 2.5 mm forward of that. Centering body on pads-// (positionOffset 0) makes the back half of the body extend BEHIND-// the pads into the board — wrong geometry, even if numerics look-// "clean".-const BODY_FORWARD_MAG = 2.5--const pinLabels = {-  pin1:  ["GND1", "A1"],  pin2:  ["GND2", "B12"],-  pin3:  ["VBUS1", "A4"], pin4:  ["VBUS2", "B9"],-  pin5:  ["SBU2", "B8"],  pin6:  ["CC1", "A5"],-  pin7:  ["DM2", "B7"],   pin8:  ["DP1", "A6"],-  pin9:  ["DM1", "A7"],   pin10: ["DP2", "B6"],-  pin11: ["SBU1", "A8"],  pin12: ["CC2", "B5"],-  pin13: ["VBUS1", "A9"], pin14: ["VBUS2", "B4"],-  pin15: ["GND1", "A12"], pin16: ["GND2", "B1"],-} as const--// 16 SMT pads, pitch 0.5 mm, centered at y=0 so bounds.center = pad-// centroid (no holes included — see §Why we drop the holes).-const PADS: Array<{ port: string; x: number }> = [-  { port: "A1",  x: -3.35 }, { port: "B12", x: -3.05 },-  { port: "A4",  x: -2.55 }, { port: "B9",  x: -2.25 },-  { port: "B8",  x: -1.75 }, { port: "A5",  x: -1.25 },-  { port: "B7",  x: -0.75 }, { port: "A6",  x: -0.25 },-  { port: "A7",  x:  0.25 }, { port: "B6",  x:  0.75 },-  { port: "A8",  x:  1.25 }, { port: "B5",  x:  1.75 },-  { port: "B4",  x:  2.25 }, { port: "A9",  x:  2.55 },-  { port: "B1",  x:  3.05 }, { port: "A12", x:  3.35 },-]--export const UsbCReceptacle = ({-  name, pcbX, pcbY, pcbRotation = 0, schX, schY, schRotation,-}: {-  name: string; pcbX: number; pcbY: number;-  pcbRotation?: 0 | 90 | 180 | 270-  // SCHEMATIC placement — ALWAYS forward to the inner <chip>. Without these-  // the receptacle silently defaults to the schematic origin (0,0) and piles-  // on top of other parts (caught by `adom-tsci lint` Check 5). A wrapper-  // component MUST pass schX/schY/schRotation through, or it can't be placed-  // on the schematic.-  schX?: number; schY?: number; schRotation?: number-}) => {-  // Native CAD has mouth at local +Y. cad.rotation.z = pcbRotation-  // + rotationOffset.z (tscircuit adds them, CCW). We set-  // rotationOffset.z=0 so cad_z = pcbRotation. Then the mouth direction-  // in world frame is rotate((0,+1), pcbRotation CCW) =-  // (-sin pcbRotation, cos pcbRotation).-  const rad = (pcbRotation * Math.PI) / 180-  const mouthX = -Math.sin(rad)-  const mouthY =  Math.cos(rad)-  const posX = BODY_FORWARD_MAG * mouthX-  const posY = BODY_FORWARD_MAG * mouthY--  return (-    <chip-      name={name}-      pcbX={pcbX} pcbY={pcbY} pcbRotation={pcbRotation}-      schX={schX} schY={schY} schRotation={schRotation}-      supplierPartNumbers={{ jlcpcb: [USB_C_PN] }}-      manufacturerPartNumber="TYPE-C-31-M-12"-      pinLabels={pinLabels as any}-      cadModel={{-        objUrl: `https://modelcdn.tscircuit.com/easyeda_models/download?uuid=${USB_C_UUID}&pn=${USB_C_PN}`,-        rotationOffset: { x: 0, y: 0, z: 0 },-        positionOffset: { x: posX, y: posY, z: 0 },-      }}-      footprint={-        <footprint>-          {PADS.map(p => (-            <smtpad key={p.port} portHints={[p.port]}-              shape="rect" width="0.3mm" height="1.3mm"-              pcbX={`${p.x}mm`} pcbY="0mm" />-          ))}-        </footprint>-      }-    />-  )-}-```--## Per-edge placement recipe--For a board of width `W` centered at x=0 (so edges at ±W/2):--| Edge you want mouth to face | `pcbRotation` | `pcbX` | `pcbY` |-|---|---|---|---|-| South (bottom) | 0   | 0 or offset along x  | −(H/2) + 4 |-| East (right)   | 270 | +(W/2) − 4           | 0 or offset along y |-| North (top)    | 180 | 0 or offset along x  | +(H/2) − 4 |-| West (left)    | 90  | −(W/2) + 4           | 0 or offset along y |--`pcbX / pcbY` is the **chip origin** = **pad row centroid** position.-For a 64×32 board with USB-C on east edge: `pcbX=28, pcbY=0,-pcbRotation=270`. Pads at x=28 (inside the board by 4 mm), body at-x=28+2.5=30.5, mouth cantilevers to about x=34.5 (2.5 mm past edge).--### **Corollary: no SMT pad may extend past the board edge.**--The **body / mouth / mechanical shell** can and should cantilever-past the edge. SMT **pads** must not. If any copper pad (VBUS1/2,-GND1/2, DP1/2, DM1/2, CC1, CC2, SBU1/2) ends up at `|pcbX| > W/2`,-tscircuit grows the GLB board mesh asymmetrically to include the-overhang — and because the texture art is drawn with `pcbX=0` at-the canvas centre, the whole silkscreen / copper / annular-ring-layer renders shifted by `asymmetry/2` relative to the 3D mesh.-Every via drill appears off-centre in its annular ring, every-passive body appears shifted from its pad. The PCB itself is fine,-but the 3D view looks broken.--Real failure: a 72×32 board with the USB-C receptacle at `pcbX=35`.-That put the east-most SMT pads at `pcbX≈37.2`, growing the mesh-to `[-38.174, +37.2]` (centre `-0.487`) and shifting every copper-feature ~0.5 mm in the render. Fix: moved the receptacle to-`pcbX=33` (= `W/2 − 4 + overhang_0`). Mesh became `[-37.2, +37.2]`,-shift gone. **Use `pcbX = W/2 − 4` for the east edge; do not improvise.**--## Mandatory verification lint--**Run this after every build with a USB-C receptacle.** Not-optional. The only way we stopped getting USB-C wrong was by-gating on these two numeric checks:--```js-// scripts/check-usbc.mjs-import j from "./dist/lib/index/circuit.json" with { type: "json" }--for (const src of j.filter(x => x.type === "source_component")) {-  // Heuristic: USB-C receptacle = any chip with jlcpcb matching a-  // known USB-C family. Extend as new families are supported.-  const pn = src.supplier_part_numbers?.jlcpcb?.[0]-  if (!pn || !/^C(165948|2760486|283540|840342)/.test(pn)) continue--  const pc = j.find(x => x.type === "pcb_component"-                      && x.source_component_id === src.source_component_id)-  const pads = j.filter(x => x.type === "pcb_smtpad"-                          && x.pcb_component_id === pc.pcb_component_id)-  const cad = j.find(x => x.type === "cad_component"-                        && x.pcb_component_id === pc.pcb_component_id)-  const board = j.find(x => x.type === "pcb_board")--  const padCx = pads.reduce((s, p) => s + p.x, 0) / pads.length-  const padCy = pads.reduce((s, p) => s + p.y, 0) / pads.length-  const rot   = cad.rotation.z * Math.PI / 180-  const mX    = -Math.sin(rot), mY = Math.cos(rot)--  // Check 1: body forward of pads by ~BODY_FORWARD_MAG in mouth dir-  const fwd = (cad.position.x - padCx) * mX + (cad.position.y - padCy) * mY-  if (fwd < 1.5 || fwd > 3.5) {-    throw new Error(`${src.name}: body_forward=${fwd.toFixed(2)} mm; expected ~2.5.-      Body is ${fwd < 0 ? "BEHIND" : "too close to"} pads — geometry wrong.`)-  }--  // Check 2: mouth ray exits the board outline-  const ray = { x: cad.position.x + 12 * mX, y: cad.position.y + 12 * mY }-  const bx = board.center?.x ?? 0, by = board.center?.y ?? 0-  const inside = ray.x >= bx - board.width/2 && ray.x <= bx + board.width/2-              && ray.y >= by - board.height/2 && ray.y <= by + board.height/2-  if (inside) {-    throw new Error(`${src.name}: mouth points INTO the board at ${ray.x.toFixed(1)},${ray.y.toFixed(1)}.-      Cable would collide with FR4. Rotate receptacle 180° (toggle rotationOffset.z 0↔180).`)-  }--  console.log(`✓ ${src.name}: body_fwd=${fwd.toFixed(2)}mm, mouth dir (${mX.toFixed(2)},${mY.toFixed(2)}) exits board at (${ray.x.toFixed(1)},${ray.y.toFixed(1)})`)-}-```--**Wire it into your build loop:**--```bash-bunx tsci build lib/index.tsx --glbs --svgs && adom-tsci lint-```--`adom-tsci lint` does exactly the checks above (for every JLC USB-C-part in its known list) plus catches ghost chips and autorouter-silent-failures. Same exit-code gating. Prefer it over the inline-`node -e` above — it's shipped with the tool and maintained-alongside the lint rules.--Or if you only have `node` and not `adom-tsci lint`:--## Visual proof-of-placement (the toggle-and-shotlog ritual)--**Numeric lint can be gamed.** A body can satisfy `forward_dot >= 1.5`-yet still be visually misaligned with the pad geometry if a footprint-dimension is wrong. After every USB-C placement change, do this-three-step visual proof:--```bash-# 1. Top view with receptacle visible-adom-tsci view top --port 8853-adom-desktop browser_screenshot '{"sessionId":"<project>","maxWidth":1800}'-shotlog inject -c usbc-validate -d "USB-C placed on east edge, body visible cantilevered off the board" -s pup_screenshot <path>--# 2. Hide the receptacle to see the pads underneath-adom-tsci toggle-component J1 --port 8853 --hide-adom-desktop browser_screenshot '{"sessionId":"<project>","maxWidth":1800}'-shotlog inject -c usbc-validate -d "USB-C hidden; pads underneath should be flush with board east edge, all 16 pads on copper" -s pup_screenshot <path>--# 3. Un-hide-adom-tsci toggle-component J1 --port 8853 --show-```--The user reads the two screenshots side-by-side and confirms:-- Body cantilevers off the correct edge (mouth away from board)-- The pads sit inside the board (none hanging off)-- Pad row aligns with where the body's back edge is--**If you don't post both screenshots to shotlog, the user can't-verify, and you can't claim the placement works.** "The lint passes"-is necessary but not sufficient.- ## Include ALL mounting holes — do NOT drop them to simplify the math  **The rule:** the TYPE-C-31-M-12 footprint must include all 4 plated@@ -326,14 +461,18 @@ the connector is held by 16 tiny SMT joints only, which tear off the board under cable insert/extract stress in well under 100 cycles. A USB-C port that rips loose after a week is a bug, not a trade-off. -**The compensation:** tscircuit computes `pcb_component.center` as-the bbox of pads + holes. For the TYPE-C-31-M-12 layout (pads at-local y=0, holes at y ∈ {-1.04, -1.27, -5.22}), the bbox center sits-2.61 mm south of the pad centroid. To keep the cad body 2.5 mm-forward of the pad row, bump `BODY_FORWARD_MAG` from 2.5 to 2.5 +-2.61 = **5.11** in the wrapper template.+**The §1 wrapper already includes all six holes at the native ArnavK-09+positions** (pads at local y=+2.449, drill supports at +1.18, anchors at++1.41 and −2.774) with the native body offset `(0, −2.5)`. That is why+`adom-tsci lint` reports `body_fwd ≈ 5.3 mm` rather than 2.5: lint measures+from the pad-row centroid, and the pad row sits ~2.8 mm behind the chip+origin that the body offset is relative to. **5.3 is the correct number for+this wrapper — do not "fix" it toward 2.5.** (`adom-tsci lint` only requires+`body_fwd > 0.5` plus a mouth that exits the edge within 12 mm; the 2.5+figure in older text was for a pads-only footprint.) -Formula for any directional connector with asymmetric holes:+If you ever build a different directional connector with asymmetric holes,+the compensation you need is:  ```text BODY_FORWARD_MAG = body_over_pads + |bbox_center_y − pad_row_y|@@ -341,33 +480,25 @@ BODY_FORWARD_MAG = body_over_pads + |bbox_center_y − pad_row_y|  Measure the bbox shift by building once with holes included, reading `pcb_component.center` from `circuit.json`, and subtracting your-known pad row y.+known pad row y. For TYPE-C-31-M-12 this is already baked into §1.  Filed upstream as TSCIRCUIT_FEATURE_REQUESTS §6.1: `cadModel` should accept `boundsSource: "pads" | "all"` so we don't have to compensate in userland. Until then, the compensation above is load-bearing. -## Failure modes → diagnosis table--| Symptom | Cause | Fix |-|---|---|---|-| Body visible but pads hanging off the board edge | `pcbX/Y` too close to the edge; pads should be INSIDE copper | Move `pcbX` inward by ~4 mm |-| Mouth points into the board (butt cantilevers off edge) | `rotationOffset.z` is wrong by 180° | Flip `rotationOffset.z` (0 ↔ 180) |-| Body drifts diagonally off the pads at non-zero rotation | You used upstream `SmdUsbC` without the rotation-compensation | Switch to the local `UsbCReceptacle.tsx` wrapper above |-| Body centered on pads, but half the body extends into the board | `positionOffset` is (0,0); forgot `BODY_FORWARD_MAG * mouth_dir` | Add the forward-offset math |-| USB-C missing from Components HUD in adom-tsci | refdes doesn't match `/^J[\d_]/i` OR no cad_component emitted | Rename to `J1`/`J_1`; check `circuit.json` for `cad_component` row |-| `pcb_autorouting_error: Failed to solve 1 nodes` after adding USB-C | USB-C pads near high-density chip pin row | Move USB-C further from adjacent chips, or grow board |- ## USB-C on a MOLECULE edge (not a bare `<board>`)  When the board is an Adom `<Molecule>` (machine-pin corners for the probing workcell), edge placement gets one extra wrinkle: **the molecule's wings grow the real board outline beyond the nominal `size`.** A `size="128x48"` molecule renders as a ~130.4×50.4 mm board (≈1.2 mm wing per side); large-pins grow it more. If you place the USB-C with `pcbX = nominal/2 − 4`, it-ends up several mm *inside* the real edge and the plug can't reach.+pins grow it more. -Rules that make it work:+For the common case — **medium pins, nominal `size`, `wing="nominal"`** (every+LED nameplate: 64x24, 80x24) — `pcbX = nominal/2 − 4` is verified to give a+2.3 mm mouth overhang and 1.8 mm hole clearance (§3 reference numbers). The+rules below are for **large pins or absolute sizes**, where the wing growth+is bigger and the nominal formula lands the mouth inside the edge:  1. **Use absolute sizing** — `size="138x58 absolute"` — so the real outer    edge is exactly `W/2`. The size increments are coarse (…96, 128, 160…)@@ -392,6 +523,21 @@ inside, plated anchor holes ~0.8 mm clear of the edge, body/mouth cantilevers ~2.5 mm proud so a cable seats. (W/2 − 4 is the audited, canonical value; earlier W/2 − 3.2 shoved the anchor holes ~0.03 mm from the edge — too close.) +## Failure modes → diagnosis table++| Symptom | Cause | Fix |+|---|---|---|+| **Body renders as a flat white/black box** | Viewer cuboid placeholder: served (re-baked) GLB has a 0-vertex J1 mesh because the modelcdn OBJ fetch failed during the bake; cache is keyed by circuit.json hash so it sticks | §5: check `adom-tsci console` for `cuboid placeholder`, delete the rebake cache entry, restart, `adom-tsci open`. Do NOT touch the footprint |+| Body visible but pads hanging off the board edge | `pcbX/Y` too close to the edge; pads should be INSIDE copper | Move `pcbX` inward to `W/2 − 4` |+| Mouth points into the board (butt cantilevers off edge) | wrong `mount` | Pass the edge the mouth should face |+| Body drifts diagonally off the pads at non-zero rotation | Upstream `SmdUsbC`, or an edited wrapper | Restore the §1 wrapper verbatim |+| Body centered on pads, half of it inside the board | `positionOffset` zeroed; forgot `NATIVE_POS_OFFSET` rotation | Restore the §1 wrapper verbatim |+| `pcb_pad_pad_clearance_error` on J1 | Old 16-pad wrapper (0 mm between same-net pairs) | Restore the §1 wrapper (merged pairs) |+| `trace[.J1 > port.A4, .J1 > port.A4]` self-trace / "disconnected endpoint" | Pin labels duplicated across pads (`VBUS1` on two pins) | §1 wrapper: one alias → one pad |+| USB-C missing from Components HUD in adom-tsci | refdes doesn't match `/^J[\d_]/i` OR no cad_component emitted | Rename to `J1`; check `circuit.json` for the `cad_component` row |+| `pcb_autorouting_error` after adding USB-C | USB-C pads near a dense chip pin row | Move USB-C further from adjacent chips, or grow the board |+| Charger plugged in, nothing lights | No CC pulldowns (or only one) | 4.7 k–5.1 k from CC1 and CC2 to GND |+ ## Upstream feature requests  See `adom-tsci/TSCIRCUIT_FEATURE_REQUESTS.md §6` for: