# Circuit library — the format

Each circuit is one JavaScript object registered with `CircuitLibrary.add()`. It is the single source of
truth: the schematic, the simulation, the breadboard, the stripboard, the PCB, the KiCad files, the Gerbers
and the BOM are all derived from it, and the tests check every derived layout against its netlist.

## Files

- `../circuit-library-core.js` — part types, registry, schematic router, breadboard/stripboard builders,
  connectivity checks, simulation checks, BOM.
- `../circuit-library-pcb.js` — footprints (KiCad geometry), placement, the maze router, copper
  connectivity, `.kicad_pcb` / `.kicad_sch` / `.kicad_pro`, Gerber + Excellon writers, zip.
- `index.js` — the list of group files, **one line per group**.
- `<group>.js` — one file per group: `CL.group({...})` then `CL.add({...})` for each circuit.
- `../../../../tests/tools/circuit-library.test.js` — runs every check on every registered circuit; a new
  group needs no new test code (add a test file only for something group-specific).

## Adding a group

1. Create `<group>.js` (copy `led.js`): it requires `../circuit-library-core.js` in node and uses
   `window.CircuitLibrary` in the browser; call `CL.group({ id, title, order, about })` once.
2. Add one line `'<group>',` to `index.js`.
3. `node --test tests/tools/circuit-library.test.js`.

## A circuit

```js
CL.add({
  id: 'led-resistor',            // unique, kebab-case; the page link is /tools/circuit-library/?c=<id>
  group: 'led', title: '…', summary: 'one line', tags: ['…'], level: 1,   // 1 beginner … 3 advanced
  parts: [
    { ref: 'J1', type: 'CONN', pins: ['+', '-'], value: '9V battery', sim: { dc: 9 } },
    { ref: 'R1', type: 'R', value: '680', mpn: 'CFR-25JB-52-680R', lcsc: '' },
    { ref: 'D1', type: 'LED', value: 'red' },
  ],
  nets: { VCC: ['J1.+', 'R1.1'], LED_A: ['R1.2', 'D1.A'], GND: ['D1.K', 'J1.-'] },
  netRoles: { V12: 'supply' },   // optional: 'supply' | 'gnd' | 'negative' (wire colours, PCB track width)
  ground: 'GND',                 // optional, default 'GND' (circuit-sim's 0 V)
  explain: ['paragraph', '…'],   // the "How it works" text
  probes: [{ name: 'anode', net: 'LED_A' }],          // circuit-sim voltage probes (optionally at: 'R1.2')
  analysis: { tstop: '1m', points: 100, currents: ['R1'], fstart: '10', fstop: '1meg', acin: 'J2', acout: 'out',
              events: [{ t: 1e-3, part: 'SW1', state: { pressed: true } }] },
  expect: [{ what: 'current', probe: 'R1', value: 0.0104, tol: 0.05, why: '…' }],
  schematic: { J1: [0, 0, 1], R1: [4, -2, 0], D1: [8, 0, 1] },     // [x, y, rot] on circuit-sim's grid
  breadboard: { parts: { J1: ['T+3', 'T-3'], R1: ['c10', 'c14'], D1: ['e15', 'e14'] }, rails: { 'T+': 'VCC', 'T-': 'GND' } },
  stripboard: { size: [10, 6], parts: { J1: ['A1', 'F1'], R1: ['A4', 'E4'], D1: ['F7', 'E7'] } },
  pcb: { size: [30.48, 15.24], parts: { J1: [3.81, 3.81, 0], R1: [8.89, 3.81, 0], D1: [25.4, 3.81, 180] } },
});
```

`CL.add` refuses a circuit whose netlist is inconsistent (unknown part or pin, a pin on two nets, a pin on
no net — list deliberately open pins in the part's `nc: ['5']`).

### Parts and pins

Pins are named per type, in footprint pad order (pad n = n-th pin). `REF.PIN` in `nets`.

| type | pins (pad order) | default footprint |
|---|---|---|
| `R` | 1 2 | R_Axial_DIN0207 P10.16 |
| `C` | 1 2 | C_Disc_D5.0mm P5.00 |
| `CP` | + - | CP_Radial_D5.0mm P2.00 (set `fp` for bigger cans) |
| `LED` | K A | LED_D5.0mm (`value`/`color`: red, green, yellow, blue, white) |
| `D`, `ZD` | K A | DO-41 P10.16 / DO-35 P7.62 (`bv` for a zener) |
| `NPN`, `PNP` | E B C | TO-92_Inline_Wide (2N3904 / 2N3906 order) |
| `NMOS`, `PMOS` | G D S | TO-220-3_Vertical (IRLZ44N order; `vth`, `kp`, `lambda`) |
| `POT` | 1 W 3 | Bourns 3296W (`pos` 0…1 from pin 1) |
| `PUSH` | 1 2 | SW_PUSH_6mm (two pads per pin) |
| `NE555` | GND TRIG OUT RESET CTRL THR DIS VCC | DIP-8 (TLC555 in `mpn` → CMOS model) |
| `OPAMP2` | OUTA INA- INA+ V- INB+ INB- OUTB V+ | DIP-8; model from value/mpn: LM358, TL072, NE5532 |
| `REG78` | IN GND OUT | TO-220 (vout from 78xx in value/mpn) |
| `LM317` | ADJ OUT IN | TO-220 |
| `OPTO` | A K E C | DIP-4 (`ctr`) |
| `LM386` | GAIN1 IN- IN+ GND OUT VS BYP GAIN8 | DIP-8 (`gain`) |
| `RELAY` | COM L1 NO NC L2 | Sanyou/Songle SRD 5 V SPDT (`vcoil`, `rcoil`, `lcoil`); not on the 0.1" grid: give breadboard and stripboard one hole per pin |
| `CONN` | 1 2 or your `pins` | PinHeader 1x02 (1x03 for three pins) |

`CONN` is the outside world; its `sim` says what it is in the simulation: `{ dc: 9 }`, `{ dual: 9 }`
(pins `+ 0 -`), `{ sine: { va, freq, vo } }`, `{ pulse: { v1, v2, td, tr, tf, pw, per } }`, `{ r: '8' }`
(a load). Every part must appear in the simulation (an output header is `{ r: '10meg' }`, a meter).
A dual op-amp's unused half must still be wired (a follower with its + input at ground or a reference).

Any part may override `fp` (a key of `CircuitLibraryPcb.FP`) and `pins`. New part types:
`CL.addType(name, { label, pins, sim: part => [{ type, props, pins }], fp, bb, sym })`; new footprints:
`CircuitLibraryPcb.addFootprint(id, { pads: [{ n, at, shape, size, drill }], silk, crt, ref })` — the
circuit-sim `type` in `sim` must be one of `SCH.PARTS` in `circuit-sim.js`.

### Placement keys

- `schematic`: one key per circuit-sim unit — the ref, plus the unit letter for multi-unit parts (`U1A`,
  `U1B` for `OPAMP2`; `J1P`, `J1N` for a `dual` supply). `[x, y, rot]`, rot in quarter turns clockwise.
  Wires are routed automatically; if a net cannot be routed, move parts apart (leave a free grid line
  between bodies) or give `schematic.wires: [[x1, y1, x2, y2], …]` (and `schematic.ground: [[x, y]]`).
- `breadboard.parts`: holes per pin — `a1`…`j63` (a–e and f–j are 5-hole strips per column, the trench
  between e and f), rails `T-`, `T+`, `B+`, `B-` at columns 3–7, 9–13 … 57–61 (`T+12`). A DIP is the hole
  of pin 1 on row `e` (`'e20'`); a push button its top-left leg on row `e`. `rails` names each rail's net
  (`T-`, `B-` default to ground). Extra hand wires: `wires: [['a1', 'a9', '#e6b800']]`; `auto: false` turns
  automatic jumpers off.
- `stripboard`: `size: [cols, rows]`; holes `A1` (row letter, column number); strips run along rows, so a
  part's pins must be on different rows unless the strip is cut between them (cuts are automatic). A DIP is
  pin 1's hole (pins down a column, the other side 3 columns over). Hand `cuts: ['C5']`, `links: [['E6', 'I6']]`
  (any two holes) are kept; automatic links are straight down a column.
- `pcb`: `size: [w, h]` mm; footprint origin (pad 1) `[x, y, rot°]`, best on the 1.27 mm grid; optional
  `mounting: true` (M3 holes 3.5 mm in), `title`, `titleAt`, `labels: { R1: [x, y] }`, `order` (nets to route
  first), `power` / `thin` (track widths). Leave ≥ 2 mm at the bottom edge for the title.

### Expectations

`expect: [{ what, probe, value, tol | abs, from, f, why }]`, checked by the tests and shown on the page:
`dc` (operating point), `current` (DC, element name), `final`, `max`, `min`, `pp`, `avg`, `freq`, `duty`
(transient, from fraction `from` of the run, default 0.5), `imax`/`imin` (transient |current|), `gain`
(AC |out/in| at `f`, needs `analysis.fstart`/`acin`/`acout`). `tol` is a fraction of `value`, `abs` absolute.

## Layout methods (for the record)

- Schematic wires: A* on the integer grid per net, nearest pin first; a wire never runs along, ends on or
  bends on another net's wire, never crosses another net's pin or a part body. Ground pins pointing down or
  sideways get a ground symbol (sideways ones on a one-step stub).
- Breadboard jumpers: per net — strips to the nearer rail of that net, rails of one net bridged, other strips
  chained nearest-first (same row when possible).
- Stripboard: a cut at the first free hole between neighbouring pins of different nets on a strip; links per
  net between strip segments that share a column whose holes in between are all free.
- PCB: Lee/A* maze router, 0.635 mm grid, two layers, 8-way moves (diagonals only where both corners are
  clear), bend and via costs, per-layer top-two-nets distance field for clearance plus an exact
  segment-to-copper check; nets shortest first, rip-up and retry with failed nets first.
