# Decisions — bluebuzz-rm2

Running log. Newest at the bottom. Format: date, decision, reasoning, alternatives.

- 2026-10-17 — **Host and radio: RP2350A + Raspberry Pi RM2 (CYW43439)** instead of the nRF52840 module of the first board. Reason: Bluetooth Classic (A2DP audio sink) and USB audio in one SoC with the Pico SDK; the RM2 is a certified module with the antenna at a board edge. Alternatives: ESP32-S3 (no BT Classic audio in the newer parts), nRF52840 (BLE only).
- 2026-10-17 — **Project layout keeps the generator pipeline** (`scripts/*.py` generate the schematic, board, placement and routing from `scripts/circuit.py` and `design.yaml`), with the skill's `design/` folder added beside it rather than restructuring into `hw/<board>/<variant>/`. Reason: the pipeline is the user's existing workflow for every board here; the skill's gates apply to its outputs. The variant of record is `rm2/` (board `b` in the parent's `pcbview.toml`); the nRF board is `../` (board `a`).
- 2026-10-17 — **Board 28 × 44 mm, six layers** (F / GND / signal / VSYS pour / GND / B). Reason: four layers left 40–55 connections open at this density; the VSYS pour as an inner layer frees the outer layers. Alternative: a larger four-layer board (rejected: the user wants it as compact as possible).
- 2026-10-17 — **USB-C is the only connector; every other lead is an SMT pad row on the back, each row a single line along an edge** (user). Reason: the board is cast into rubber; wires are soldered before casting; a grid of pads is hard to solder without bridging.
- 2026-10-17 — **USB-C moved to the bottom (short) edge** (user). The top edge is the RM2's RF keep-out, so the two short edges are the only candidates; the bottom keeps the charger's VBUS run away from the antenna.
- 2026-10-17 — **Every passive beside the pin it serves; all components on the front except the sensor array** (user). The RP2350 was lowered 1 mm from its first position so four rows of 0402s fit between it and the RM2's pad row.
- 2026-10-17 — **All trace bends at 45°** (user): hand-laid pre-routes go through a mitring helper, the grid router has diagonal moves, the gap closer tries dog-legs before L's. Freerouting already routes at 45°.
- 2026-10-17 — **Sensing: the load cell (HX711) and the pressure port are replaced by a 2 × 2 array of DPS368 barometers on the back**, after Tenzer, Jentoft & Howe 2014 ("The Feel of MEMS Barometers"), to be cast in elastomer (user). SPI with one chip select per sensor because the DPS368 has only two I2C addresses; a 74HC138 or an I2C mux would be needed beyond 16 sensors. Four sensors chosen by the user ("4 is plenty") at $4.21 each (JLC, qty 1).
- 2026-10-17 — **Array pitch 6 × 8 mm, left of centre on the back.** Reason: the sensor pads must avoid every through-via from the front (the RP2350's thermal vias, the QSPI and channel vias, the receptacle's pegs); this is the largest via-free region. The paper used 5–6 mm.
- 2026-10-17 — **IMU: LSM6DS3TR-C on I2C1, always-on rail** (user asked for an IMU). Reason: $1.67 and 14 k in stock at JLC versus $2.76 (BMI270) and $3.90 (LSM6DSO, 519 in stock); I2C rather than the array's SPI so the IMU stays alive while the switched sensor rail is off (unpowered SPI slaves on a shared bus would be back-fed). CS tied high selects I2C; SA0 low gives address 0x6A. INT1/INT2 to GPIO17/18.
- 2026-10-17 — **SWD pogo pads moved to the back under the RM2.** Reason: the IMU took the left-edge spot, and a pad row under a front part collides with that part's ground vias; the back under the module is via-free.
- 2026-10-17 — **gSPI DATA/CLK on GPIO2/3 rather than the Pico W's GPIO24/29.** Reason: the RM2's DIN/DOUT/SCLK pads are on its left column, facing the RP2350's top-left; the run around the chip's top to GPIO24/29 could not be closed. The board header overrides `CYW43_DEFAULT_PIN_WL_DATA_OUT/_IN/_HOST_WAKE` (2) and `_WL_CLOCK` (3).
- 2026-10-17 — **GND is removed from freerouting's netlist** (planes and pours carry it; its fixed copper becomes keepouts). Reason: freerouting otherwise routes GND as wires and leaves debris. A pre-route pass putting a via in every GND pad before freerouting was tried twice (56, then 33 vias as keepouts) and stalled freerouting at ~150 open every attempt; the via-in-pad pass therefore runs after routing, cutting freerouting's unlocked wires from under a pad when they block it and letting the finish stage re-close them.
- 2026-10-17 — **Hand-laid, locked pre-routes for the nets freerouting never closed**: the six QSPI lines (flash rotated 180° so SS/SD1/SD2 face the pins), VBAT to the cell pad, PG_N down the left side on In2, the PWM and BTN channel vias, the SWD hops, the USB climb to R3/R4, LX to the inductor on B.Cu, WL_CS on B.Cu under the chip, pin 47's GND bridge to the EP. This departs from the skill's "don't hand-route by coordinates" rule for these specific short, placement-constrained nets after 30+ routing runs; each pre-route is DRC-checked with `ROUTE_DRY=1` before freerouting runs.
- 2026-10-17 — **Datasheets not obtainable by download** (ST and LCSC block the fetcher): LSM6DS3TR-C, USBLC6-2, B5819W, the TXM12M crystal, the TYPE-C-31-M-12 receptacle. The LSM6DSO datasheet (same pinout family) is in `datasheets/` as a reference only. **Open: the user to attach the five.**
- 2026-10-18 — **DPS368 footprint corrected to the datasheet land pattern** (0.35 × 0.35 mm solder-mask-defined pads, 0.35 mm paste apertures; audit BLOCKER): the EasyEDA land for C3232508 had 0.6 × 0.4 mm pads with 1:1 paste, 2.5× the paste on a 0.65 mm-pitch LGA that cannot be reworked after casting. Silk pulled clear of the body, the fab-layer vent marker moved to the pin 4/5 end.
- 2026-10-18 — **One 100 nF per sensor, placed within 1 mm of pins 6 and 8** (which share one column with GND pin 7 between them), rather than one per supply pin. Datasheet table 11 asks for 100 nF on each of VDD and VDDIO; one cap 0.5–0.8 mm from both pins on a 0.4 mm-wide GND return serves both. Logged as a MINOR deviation; two caps per sensor if the prototype shows supply noise.
- 2026-10-18 — **Casting method changed from vacuum degassing to a pressure pot** (or pre-degassed rubber): the DPS368 datasheet (p.43) says the vent hole must not be exposed to vacuum; its absolute maximum is 10,000 hPa, so a 2–4 bar pressure pot (the usual alternative for bubble-free silicone/PU) is within limits. The Tenzer paper's MPL115A2 tolerated vacuum; the DPS368 has a 0.4 mm port and no statement about filling it. A one-sensor casting test precedes the full board.
- 2026-10-18 — **Firmware rule for the array** (from the audit): all four chip selects high before the switched rail rises, ≥ 12 ms, then one CS-low pulse per sensor (read ID 0x0D = 0x10) to lock SPI mode; park SCK/SDI/CS low before dropping the rail. Without it a sensor stays an I2C slave at 0x77 on the shared lines.
- 2026-10-18 — **Charger safety timer is 6 h** (R12 45.3 k), not the 10 h the first intent draft said; 10 h is not programmable (9.6 h max). To be revisited with the cell capacity. /PGOOD on GPIO9 uses the RP2350's internal pull-up (firmware must enable it); no external resistor added.
- 2026-10-18 — **User answers to phase 0/4**: optional NTC; motors 0.09 A or 0.2 A each; 4–6 h at 10 % vibration; 5 boards; no repo URL yet; test pads yes; art "dripping fluid"; size TBD, minimal for now. Consequences: the BAT row gains a T pad on TS (fit an NTC cell's thermistor there and leave R13 off); a VSYS/3V3/1V1 pogo row J5 on the back; cell ≥ 700 mAh recommended; the motor audit's ripple, flyback-thermal and charger-ceiling findings close at ≤ 0.2 A per motor.
- 2026-10-18 — **Motor gate pull-downs R41/R43 100 k → 4.7 k** (audit MAJOR): RP2350 erratum E9 lets a Bank-0 input pad leak ~120 µA and float near 2.2 V, above the AO3400A's Vgs(th); ≤ 8.2 k holds the gate low while GPIO0/1 are inputs. Cost: 0.7 mA per channel while on.
- 2026-10-18 — **RM2 side pads moved 0.2 mm away from the antenna end** (audit BLOCKER): datasheet fig. 6 puts the lowest side pad 1.8 mm from the body bottom; the footprint had 2.0.
- 2026-10-18 — **IMU aux bus SDx/SCx tied to GND** (audit MAJOR: inputs with no pull). The IMU moves ≥ 1.5 mm inboard from the board edge (package stress shifts offsets) with the placement-review changes.
- 2026-10-18 — **Firmware battery cutoff at 3.5 V** (LDO and RM2 audits): the TLV75533 leaves regulation below ~3.4 V VSYS and the RM2's RF degrades below 3.2 V Vin. Also: drive the six array GPIOs low before dropping SENS_EN (the TPS22918's quick discharge holds the rail at 25 Ω; a high line back-powers the sensors).
- 2026-10-18 — **Schematic re-flowed** around the RP2350 in signal-flow columns (power entry | rails, debug, UI | flash, host | radio, touch | IMU, motor outputs), blocks numbered in reading order, decoupling hung off the supply pins it serves, wire-name labels drawn as flags beside their wires, title block filled (schematic review MAJORs). Disputed: the tactile block keeps one symbol per sensor in the board's 2 × 2 arrangement with the SPI as labels — the generator forbids wire crossings, and a four-tap bus on three shared lines crosses itself; the labels are the cross-block connection to the RP2350 anyway.
- 2026-10-18 — **Extended parts accepted**: the DPS368, IMU, charger chain, flash and crystal are JLC extended parts; for 5 boards the loading fees dominate the part cost.
- 2026-10-18 — **Crystal: ABM8-272-T3 (CL 10 pF, 50 Ω) with 15 pF loads**, the RP2350 reference design's part (host audit MAJOR-4): the only validated circuit; the TXM12M's datasheet could not be obtained. 3225 package, JLC C20625731 (7,242 in stock).
- 2026-10-18 — **Core inductor stays the Sunlord MPL2016S3R3MHT** for now (host audit MAJOR-1). The reference Abracon AOTA-B201610S3R3-101-T has a polarity dot and better ratings (2.1 A / 115 mΩ vs 1.2 A / 120 mΩ), but JLC has 3 in stock — too few for 5 boards. No published report was found either way for the Sunlord part on an RP2350. Options for the user: consign the Abracon part (Digi-Key/Mouser), or keep the Sunlord part and check the 1V1 ripple at bring-up. The 0806 land fits both.
- 2026-10-18 — **C97 (IOVDD pin 11) and C98 (DVDD pin 6) on the back**, directly behind their pins: the only exception to "everything on the front" besides the sensor array. The front lane between the flash and the left pin row is the escape for pins 2–13 and has no room for an 0402.
- 2026-10-18 — **Placement rework from the placement review**: L1 directly over pins 47–49 with its LX pad down (LX is now pin 48's own 1.2 mm F.Cu stub; pins 49 and 46 drop through vias beside it); battery divider moved down the right edge away from the buck; R3/R4/R81/C101/C102 row moved 0.3 mm off the RM2 castellations; C100 to the right edge 3 mm from pad 16's column; crystal under XIN/XOUT with the XOUT resistor beside it; C92 out of the bottom escape lane; RUN network to the right; motor channel 1 mirrored so the flyback's switch pad sits at the drain; MOT pads moved under the two channels; IMU 1.6 mm and status LED 0.7 mm in from the edge; WL_CS moved from B.Cu to In3; the RF keep-out band now runs the full board width.
- 2026-10-18 — **Disputed: crystal ≈ 5 mm from motor channel 2** (placement review MAJOR; brief said 10 mm). On a 28 × 44 mm board the crystal must sit at XIN/XOUT (bottom-centre) and the two motor channels fill the bottom-left; 10 mm is not available without moving the receptacle. Mitigations: solid GND (In1) under both, the motors are ERM at 25 kHz with 0.09–0.2 A and ~200 ns edges (low di/dt), and the gate network is no longer on the crystal's row. Brief corrected.
- 2026-10-18 — **Accepted MINORs**: C13 (VBUS 100 n) stays 4.9 mm from pin 13 (C10 10 µF is at the pin; the left strip has no room); C15 stays beside the cell pad (the VBAT run passes the pin end); QSPI SCLK/SD0/SD3 run 15–17 mm on In2 at ≤ 75 MHz over solid GND (nets.csv limit relaxed to 20 mm); USB pair runs around the chip on an inner layer (full-speed, 12 Mb/s).
- 2026-10-18 — **Core inductor: Abracon AOTA-B201610S3R3-101-T, consigned** (user). JLC had 3; order ≥ 6 from Digi-Key/Mouser. The footprint is Raspberry Pi's own (RP2350_60QFN_minimal), dot at pad 1. Open: confirm at assembly that the dot lands on the pad the reference layout puts it on (the guide gives the orientation only by its layout).
- 2026-10-18 — **Soft power latch** (user: momentary button, CPU latches power, button stays readable, CPU can switch off). SW1 (the existing side button) switches VSYS onto BTN_RAW; a BAT54C (D5) ORs BTN_RAW and PWR_HOLD (GPIO19) onto the TLV75533's EN, with 100 k (R63) holding EN low; a 2N7002 (Q3, gate on BTN_RAW with 100 k R64) pulls GPIO8 low while pressed. Off = LDO disabled: the RP2350, flash, radio I/O, IMU and sensors are unpowered; the charger, the charge LED and the battery divider stay on VSYS/VBAT. Assumption: SW1 doubles as the power button; a separate user button can be added if wanted. Firmware: drive GPIO19 high early in boot; hold-to-power-on is however long the boot takes to reach that line.
- 2026-10-18 — **Charger EN2 moved from 3V3_SYS to VSYS** (consequence of the latch): with the system off, EN2 on 3V3 would drop the charger to USB100 (100 mA). TI's reference shows EN2 on OUT. **Charge LED D1 moved to VSYS** so it lights while the system is off.
- 2026-10-18 — **Off-state current** (estimate): TLV75533 shutdown ~0.1 µA, BQ24074 battery-only ~6.5 µA (supplement mode), 1M/1M divider 2 µA, RM2 Vin with WL_ON floating low ~ µA, R63/R64 nothing (no voltage across them). ≈ 10–15 µA: a 700 mAh cell lasts years off.
- 2026-10-18 — **Tactile sensors moved toward the four board corners** (user), on the back, each with its 100 n on the inboard side next to VDD/VDDIO (the left pair turned 180°). Positions (front-view mm from centre): top-left (−11.8, −15.1) and top-right (11.6, −14.8) just below the RF keep-out; bottom-left (−11.6, 20.0) inside the corner radius, clear of SW1's pegs; bottom-right (11.7, 11.3) — the true corner holds the USB-C's shell pads, pegs and VBUS hop vias, so this one sits 10 mm up the right edge above the receptacle. Spacing ≈ 23 × 35 mm (was 6 × 8). route.py now keeps vias out from under every sensor body (rule areas), replacing the old "array where no front via lands" constraint.
- 2026-10-18 — **USB-C centred on the bottom edge** (user). The motor channels now flank it (channel 1 bottom-left, channel 2 bottom-right, mirrored, each flyback in its corner), the MOT pad row is centred on the back between the receptacle's shell pads, the ESD array sits above-left of the receptacle (clear of the D+/D− pad-pair join vias), and the power button moved up the left edge.
- 2026-10-18 — **Tactile sensors evened out and a fifth added in the centre** (user). Corners at (±11.7, −14.8) and (±11.7, 20.0) — mirror-symmetric now that the receptacle no longer fills a corner; the fifth at (−1.5, 2.6), midway between the corner rows and 1.5 mm left of centre so the PWM escape vias stay out from under it. CS5 on GPIO20. C98 moved 0.7 mm right to make room.
- 2026-10-18 — **Status LED: XL-1010RGBC-2812B-S** (1.0 × 1.0 mm WS2812B type, C51900942) on GPIO5 through 330 R, replacing the green 0603 (user). It needs 3.5–5.5 V, so it runs from VSYS (≥ 3.5 V above the firmware cutoff), and its 0.35 mA idle current would drain the cell when the system is off, so VSYS reaches it through a second TPS22918 (U11, ON = 3V3_SYS) — one part already on the BOM instead of a P-FET, an N-FET and a resistor. At the right edge above motor channel 2.
- 2026-10-18 — **Raspberry Pi Debug Probe connector** (user): JST-SH 3-pin vertical (BM03B-SRSS-TB) footprint on the back, not fitted (DNP, excluded from BOM and CPL), pinout per the Raspberry Pi debug connector specification (1 SC/SWCLK, 2 GND, 3 SD/SWDIO), below the SWD pogo row.
- 2026-10-18 — **USB keeps the board on** (user, option 1 of two): VBUS → D6 (1N4148WS) → R66 100 k → the LDO's EN, a third input to the power latch. With R63 (100 k to GND) it is a 2:1 divider: 5 V USB puts ≈ 2.35 V on EN (VHI 1 V), and the charger's 10.5 V OVP limit would give ≈ 5.1 V, under EN's 6.0 V absolute maximum (TLV755P datasheet, 5.1). Reason: the boot ROM never drives PWR_HOLD, so without this a blank board or the UF2 bootloader would lose power the moment SW1 is released. Consequence: firmware cannot switch the board fully off while USB is plugged in (it can still sleep, and turn the LED and motors off). The alternative, a solder jumper forcing EN on, was declined.
- 2026-10-18 — **Board art: dripping fluid on the back silkscreen** (user). scripts/art.py draws a filled pool along the top of the art zone with five drips of different lengths, each ending in a bead, two shedding a falling droplet; filled silk polygons, every feature ≥ 0.5 mm. Zone: x −13.2…−5.2, y −10…9 mm (front-view; it reads on the right half when the back is viewed), clear of every back pad, sensor, cap, the probe footprint and the USB-C/SW1 holes. place.py redraws it on every placement run.

- 2026-10-18 — **Freerouting stall fixed: keepout units bug in `tame_dsn`.** After the centred-USB layout every attempt ran past the 900 s kill and wrote no SES (224 open); pass 1 took 13 s with 73 unrouted and pass 2 never finished. `tame_dsn` turns fixed GND vias into keepouts but sized them in 0.1 µm units while the DSN is `(unit um)`, so each 0.4 mm via became a 6.5 mm disc on all six layers. The five fixed GND vias are the charger's four thermal-pad vias and the ESD array's centre-pin via; the latter, new with the centred USB-C, put a 6.5 mm wall across the USB and RP2350 escape area. Sizes now come from the via padstack name in µm plus the 0.127 mm clearance, and `tame_dsn` asserts the unit. Freerouting also gets `--router.job_timeout` (13 min) inside the subprocess kill. Alternative considered: dropping the fixed vias from the DSN entirely; rejected because freerouting would then route through them.
- 2026-10-18 — **Freerouting watchdog and replay.** With the keepouts fixed, passes 1–3 ran in 15 s (45 → 22 unrouted) but pass 4 looped for 15+ minutes at full CPU. A thread dump showed freerouting 2.2.4 stuck in its own trace normalisation (PolylineTrace.normalize → split → remove_if_cycle), so its job timeout, checked only between passes, never fires. route.py now streams freerouting's log, kills it when no pass completes for 3 minutes, and replays it capped at the last completed pass. Freerouting is deterministic for a given DSN (same board hashes and scores on replay), so the replay reaches the same state and writes the SES. Alternative considered: freerouting v2.5.0, which may fix the loop; deferred because it was released 2026-10-05, under two weeks ago, and would change routing behaviour mid-design.
- 2026-10-18 — **Art script crash fixed.** Re-running place.py on a board that already carried the art crashed KiCad 9.0.8's Python bindings: a segfault in SetPolyPoints, or a corrupted footprint list afterwards. The old polygons are now deleted with `BOARD.Delete` rather than `Remove`, and new ones are built as a polygon set rather than a list of temporary points. Three consecutive re-runs pass with exactly three art polygons on the board.
- 2026-10-18 — **DSN quoting bug fixed in `reorder_placement`.** With SOT-23 parts at three rotations, KiCad exports quoted variant names ("Package_TO_SOT_SMD:SOT-23::1"). The canonical renaming split the name with its opening quote attached and dropped the closing one, which unbalanced every later quote in the DSN. Attempt 1 parsed by luck of ordering (likely with parts missing); attempt 2 lost the whole netlist (freerouting: "started with 0 unrouted nets", empty SES), and the failed SES import then exited route.py, discarding attempt 1. Names are now split bare and re-quoted, every image and component header is asserted balanced, and a failed SES import ends only that attempt.
- 2026-10-18 — **Tactile sensor GND pre-routed.** Ten of the last 14 open items were the DPS368 GND pads: the no-via rule areas keep vias out of their pads, and the back GND pour is cut into fragments by back-side wiring. route.py now joins pads 1 and 7 with a 0.15 mm diagonal under each sensor (it passes pads 2 and 8 with about 0.2 mm), then runs a 0.25 mm stub from pad 7 out of the rule area on the inboard side. A search picks the first via spot whose via and stub clear every other-net pad, track and via on the layers they touch. Where a front part sits directly above, so no new via fits, the stub runs to existing GND copper within 2.5 mm instead. Result: U22, U23 and U24 get their own vias; U20 runs to the charger's PowerPad thermal via; U21 runs to C121's GND pad.
- 2026-10-18 — **Freerouting 2.4.1 for this board.** The 2.2.4 loop came back on most orderings once the sensor GND wires became keepouts, sometimes in pass 1, where a replay has nothing to keep. The v2.3.0 release notes list "suppress repeated trace normalization after an oscillation/depth cap (#741)", which is the loop in the thread dump. v2.4.1 (released 2026-09-03; sha256 251101c3…6aa9, matching the GitHub release digest) sits in tools/ beside the old jar, selected by `FR_JAR`. On the input that hung 2.2.4 in pass 4, it finished in under two minutes with 4 unrouted at its best pass. Its post-route optimiser made that 7, so it is switched off. v2.5.0 was passed over because it was released under two weeks ago.
- 2026-10-18 — **Post-route GND fixes.**
  - **Stacked vias.** The USB-C's paired GND pins share one pad and each got a via, so two vias were co-located. Separately, the DNP debug connector's back mounting pad lies under C80's front GND pad, and each got a via 0.34 mm apart. The via-in-pad pass now skips a pad that a GND via already lands in, and keeps new GND vias at least 0.5 mm from other GND vias.
  - **GND finishing width.** The finisher routes GND at 0.25 mm instead of the Power class's 0.4 mm. GND current rides the planes and pours, and a 0.4 mm hop between two 0.5 mm-pitch IMU GND pins violated clearance to the 3V3 pin.
  - **Edge margin.** The finisher's wire grid now keeps 0.2 mm copper-to-edge on each wire's own half-width. The fixed 0.35 mm inset only suited 0.15 mm wires.
  - **Freerouting 2.4.1 fanout off.** Its fanout stage lays escape wires 0.112 mm wide, under the 0.127 mm minimum. An A/B on one DSN gave 10 such wires with fanout and none without; the automatic-neckdown setting made no difference.
- 2026-10-18 — **Audit: footprint thermal vias count as vias.** The RP2350 footprint's exposed-pad thermal vias are 0.2 mm plated holes in 0.5 mm pads. Inner-layer wires pass them at 0.279 mm hole-to-track, which failed the audit's 0.28 mm plated-hole rule. That rule is JLC's for component holes. A 0.2 mm plated hole is a via, and JLC's via-hole-to-track minimum is 0.2 mm, so the audit now puts plated holes of 0.3 mm or less under the via rule.
- 2026-10-18 — **RM2 GND pad 11 via off-centre.** The debug connector J6 on the back sits under the RM2's pad row, and its SWCLK pin is directly below GND pad 11's centre. The via-in-pad pass now tries spots along a long pad's axis after its centre, so pad 11 takes its via at its inboard end. Freerouting also gets `copper_to_edge_clearance_um=300`, because it laid a via 0.1 mm from a rounded corner.
- 2026-10-18 — **GND via-in-pad moved back before freerouting.** After routing, 43 of the route stage's 46 open connections were signal nets, not GND. The post-route via-in-pad pass placed about 70 GND vias and cut the signal wires freerouting had laid under those pads; the finish stage re-closed all but about 9. Placing the vias before routing had been dropped as "stalling freerouting at 152 unconnected", but that was with the 10x keepout bug. With correct keepouts, the pre-route pass places 70 vias, the post pass 1, and the route stage ends with 12 open (10 signal) instead of 46–57. After the finish stage: 5 open, 0 DRC errors, audit clean. Attempts are now ranked by non-GND opens (kicad-cli DRC), every attempt's board is kept as out/bluebuzz-rm2.attemptN.kicad_pcb, and route.py stops early only on an attempt with nothing open.
- 2026-10-18 — **Four missing datasheets obtained** while writing the component-choice document (docs/components/): Abracon ABM8-272-T3 (crystal: 12 MHz, CL 10 pF, ESR ≤ 50 Ω, C0 ≤ 3 pF, ±30 ppm; matches the host audit's assumptions), MCC B5817W–B5819W (1 A, 40 V, VF ≤ 0.60 V at 1 A, Tj ≤ 125 °C, 200 °C/W; MCC's sheet, the LCSC C8598 part may come from another maker), Diodes Inc BAT54/A/C/S (30 V, 200 mA; the BOM's BAT54C,215 is Nexperia's, same function and pinout), JST SH (BM03B-SRSS-TB top entry). Saved in datasheets/. Still missing: LSM6DS3TR-C, USBLC6-2P6, TYPE-C-31-M-12, XL-1010RGBC-2812B-S, 2N7002, 1N4148WS, EVQPUC02K, AOTA-B201610S3R3-101-T (ST timed out; Nexperia/onsemi returned HTML).
- 2026-10-18 — **Schematic symbol fix: a box with a top and a bottom pin in the same column now grows until both pin names fit.** The status LED D2 (VDD/GND) and the ESD array U10 (IO1/IO2) printed their names on top of each other. U10 is taller now, so in the USB block the D+ and D− runs step out from the receptacle's pin pairs to pass above and below it. The netlist is identical before and after (91 nets), and ERC is clean. The sheet's top note now describes the current board: five sensors, the power latch, the status LED.
- 2026-10-18 — **Status LED moved from GPIO5 to GPIO22 (U1 pin 34).** After the routing pass the last opens were LED_STAT and a few signals around the RP2350. GPIO5 is pin 8 on the RP2350's left side; D2 and R62 are in the bottom-right corner, so the line had to go round the chip through the most congested part of the board. A least-crossings search found no route from pin 8 that crossed fewer than 5 other nets. GPIO22 is on the right-hand side facing D2. The LED is driven by PIO (WS2812 protocol), so any GPIO will do, and no firmware exists yet. GPIO21 (pin 33) was considered and rejected: PWR_HOLD's escape via sits in the only via slot beside it. Changed in `circuit.py` (ROLE) and the schematic symbol's pin list. README, architecture.md and the components doc were updated.
- 2026-10-18 — **Button plunger 0.5 mm past the left edge** (user; it was flush). SW1 moves 0.5 mm outboard. This is the most it can move: the locating pegs' 0.75 mm NPTH holes keep a 0.5 mm web to the edge.
- 2026-10-18 — **Finishing router used the wrong clearance.** `finish.py` kept wires 0.2 mm from everything, and `close_gaps.py` did the same, for a USB class that has since gone to 0.127. Both now take the largest netclass clearance from `gen_project`, plus 0.01 for the lattice in `finish.py`. With the extra 0.073 mm gone, GND to U6 pad 4 and USB_DM closed with 0 DRC errors. `finish.py` also pulls its A* staircases straight: runs of 0.05 mm zig-zags became single 45° segments.
- 2026-10-18 — **Three pipeline bugs found while closing the last gaps.**
  - **`Obstacles` (route.py) pad radius.** Both obstacle models gave a pad a radius of max(w, h)/2, which misses a rectangle's corners. The GND fragment stitcher put a via 0.04 mm from J4's MOTOR1_SW pad that way. They now use half the diagonal.
  - **`relayer` moved a run off its own SMD pad.** It shifted J6's back-side GND hop onto In2, which left a dangling locked wire across the In2 corridor by R4. A run that lands on a same-net SMD pad is now pinned to its layer.
  - **The fragment stitcher gave up on the IMU's GND island.** It said "no room for a via" (U30 pins 6/7, boxed in by a 3V3_SYS loop) although a legal spot existed under the package. Its coarse circle model now falls back to the grid router's exact via mask.
  - **Route attempts ranked by copper defects first.** One raw freerouting attempt shorted IMU_INT2 to 3V3_SYS, and the route stage ranked attempts by open connections alone, so a shorted attempt with fewer opens could be kept over a clean one. No later stage repairs a short or a clearance breach. Attempts are now ranked by (copper defects, signal opens, all opens): defects are shorts, crossings, clearance and edge-clearance errors from kicad-cli DRC. On the earlier boards, the shorted attempt scored (7 defects, 1 open) and now loses to a clean (0, 3).
- 2026-10-18 — **RP2350 power copper transplanted from Raspberry Pi's layout** (user: "preserve the power planes for the rp2350 that are used in the rp2350 reference design"). The router had spread 1V1 and 3V3 over three layers as thin wires, and the regulator was a hand arrangement: L1 at −90°, the caps elsewhere, a pin-47 bridge into the exposed pad, the In1 plane under LX. The RP2350 datasheet §6.3.8 says the regulator layout "must be strictly followed". `scripts/rp2350_block.py` now reads the RP2350A minimal board's KiCad file (copied into `design/reference/`), and `route.py` lays its power block relative to U1, locked, before freerouting.
  - **What is copied:**
    - the 1V1 ring (±2.2 mm) and 3V3 band (±2.8 mm) under the chip, with the reference's zone settings;
    - four 1V1 ring vias and their 0.6 mm spokes;
    - the regulator: L1, COUT (C80), CIN (C83), CFILT (C84) and RFILT (R84) at the reference positions and rotations, with COUT and CIN on Raspberry Pi's small-pad 0402;
    - its LX, 1V1, 3V3 and PGND zones with the PGND via pair;
    - the FB and AVDD traces;
    - the USB 27 Ω pair and the shared 53/54 100 nF (C93).
  - **Datasheet rules added:**
    - In1 is cut away under the LX node (fig. 27).
    - Nothing on F.Cu between L1's pads.
    - The LX zone has 0.3 mm clearance.
    - PGND reaches GND only through its via pair: no via-in-pad on C80.2, C83.2 or pin 47; the pin-47 bridge is gone; a U-shaped no-pour moat keeps the F.Cu GND pour out. The reference has its 3V3 pour there, which is another net.
  - **Board-specific deviations** (in the module's docstring):
    - Spokes run on In2, not the back: J6 sits over the N spoke on the back.
    - The N spoke's two vias move 0.43 mm right, off J6's mounting pad.
    - The 53/54 cap's GND via is dropped: the RM2's pad row is there, so the via-in-pad pass serves it.
    - W: the flash is where the reference runs pin 6 out, so pin 6 joins the ring through the band (its only cut) and C98 hangs off the ring's W via.
    - S: the crystal group moves 0.5 mm down; the spoke via sits under the pin row with XOUT passing beneath it, and C81 (the second VOUT 4.7 µF) sits on the back under that via.
    - E: the via comes in to 1 mm past pin 39, next to C82's own spot; the reference's spot is taken by the ADC_AVDD filter.
  - **Consequential moves:**
    - The QSPI fan-out became a ribbon of 45° runs up-left like the reference's: SD3/SCLK/SD0 spread to 0.6 mm lanes, each with its own via.
    - C91 (pin 1) moves to the back under its own via.
    - R81 moves 1.5 mm left; WL_CS's via and its In3 lane move to clear the ring's W via.
    - C101/C102 move right of RFILT.
  - **Guards:** freerouting gets temporary keepouts over the ring and the regulator. The finish router and the obstacle model (stitching vias, close_gaps) treat every `rp2350_*` zone as an obstacle for other nets. The audit checks that:
    - the block zones are present and filled;
    - there are no foreign nets in the ring or band;
    - PGND has one return point (two vias), with no pour contact and no via-in-pad;
    - the layer-2 cut-out is there;
    - L1 is at the reference rotation.
  - This closes the "confirm L1's dot" open item: L1 is now in the reference footprint at the reference rotation.
  - Chip notes for future boards: `~/.claude/skills/pcb-design/chips/rp2350.md`.
- 2026-10-18 — **Routing around the RP2350 power block.** With the 1V1 ring and 3V3 band under the chip, the left-column pins lost the inner escape freerouting used to take. They all have to leave through the 0.9 mm lane between the pin column and the QSPI columns. Every ordering then left 4–9 opens around the chip. Changes, each measured over 12 orderings routed in parallel copies of the project (`ROUTE_FIRST` selects the ordering):
  - **QSPI:** SD3/SCLK/SD0 drop to In2 where they leave the 45° ribbon, not 3–4 mm further west on F.Cu.
  - **C97/C98 move under the chip on the back.** C98 hangs off the ring's W via. C97 takes a filled via in its 3V3 pad up into the band. Their GND pads take a B.Cu stub to the exposed pad's back copper. Behind the pins they held the lane's only via spots.
  - **No GND via may land in any `rp2350_*` zone.** C97/C98's GND via-in-pads had split the band. The finish router's via mask also covers the block zones: a TAC_CS2 via did the same.
  - **C91 stays on the front above the ribbon.** Pin 1 stubs into the 3V3 band like pin 11. A via beside pin 1 had taken the left pins' only exit north.
  - **WL_CLK is pre-routed**, RM2 pad 3 to pin 5 via In3 (open in 7 of 12 orderings, no path even with rip-up).
  - **Pin swaps (no firmware exists yet):**
    - IMU_INT1 moves GPIO17 → GPIO5 (pin 8), beside SDA/SCL on the IMU's side.
    - WL_ON moves GPIO23 → GPIO27 (pin 41), the right-column pin nearest RM2 pads 12/13, which the regulator block now sits under. The header overrides `CYW43_DEFAULT_PIN_WL_REG_ON`.
    - The motor PWMs move GPIO0/1 → GPIO13 (pin 17) and GPIO17 (pin 28), on the bottom row toward their gates, PWM slices 6B and 0B. Their hop vias held two of the lane's via spots and WL_DATA was open in all 12 orderings.
    - TAC_CS1 takes GPIO0 (pin 2), toward its top-left sensor.
    - GPIO1 is free.
  - **PGND isolation is a hole in the F.Cu GND pour over the regulator**, not a moat. With the moat, the pour filled the inside as an island kept alive by CFILT's own via, and that island touched PGND.
  - After these, all 12 orderings pass DRC and the audit, with 3–9 opens; WL_DATA, WL_ON and WL_CLK close in every one.
- 2026-10-18 — **This board is the project.** The nRF52840 board is abandoned; it moved to `archive/nrf52840/` and the `rm2/` tree moved up to the top level (scripts find their files relative to themselves, so nothing else changed). `pcbview.toml` lists only this board.
- 2026-10-18 — **Pin escapes locked for SENS_EN, IMU_INT1 (pins 7, 8), SWCLK and SWDIO (24, 25).** Over the last 12 orderings these were the recurring opens (SWD in 9, IMU_INT1 in 7, SENS_EN in 6), every time at the pin: freerouting runs with fanout off, and whichever net reaches the lane first takes the only via spot. Each escape is copied from an ordering that closed the net with 0 DRC errors (pins 7/8 from ordering 0, SWCLK from ordering 8). Only pin to via is locked, at most 2 mm each, and freerouting routes the rest. That took the average open count from 4.6 to 3.3, but a lone SWDIO diagonal walled pin 27 in (TAC_CS4 open in 8 of 12). So pins 25–28 (SWDIO, RUN, TAC_CS4, MOTOR2_PWM) are locked as one staircase instead: each goes down, then 45° south-east parallel to the last, to its own via. This is the fan ordering 5 laid when it closed all four. Freerouting's JVM heap is now capped (`FR_HEAP`, default 3g): 16 uncapped runs side by side used up the machine's memory.
- 2026-10-18 — **Routed: 0 unconnected, 0 DRC errors, audit clean.** With pins 25–29 locked as one staircase, 12 orderings left 0–4 opens (average 2.0). Ordering 12 (`ROUTE_FIRST=11`) closed everything and is now `out/bluebuzz-rm2.kicad_pcb`. DRC with `--severity-all` shows 9 warnings, all silkscreen (4 silk over copper, 4 silk to edge, 1 silk overlap), left for phase 9. Schematic parity reports 91 issues, none of them a wiring difference:
  - 76 are 24 nets that are auto-named in the schematic (`Net-(U5-CT)`) and named by `circuit.py` on the board (`CT_SENS`). The mapping is one to one in both directions.
  - 9 are no-connect pins.
  - 6 are BOM-exclude / do-not-populate flags on the pad-row connectors J2–J6 that differ between schematic and board.
  - Still to do for the routing gate: the USB pair is split over F/In2/In3 (DP 28.7 mm, DM 28.8 mm) and should run on one layer; the gate file and its independent review.
- 2026-10-18 — **The USB pair is one coupled pair on B.Cu, receptacle to R3/R4.** Freerouting had each line on F, In2 and In3 (28 mm, three layer changes), with the ESD array on a stub. `stackup.md` asks for one layer over a ground plane at 0.15 / 0.15 (90 Ω).
  - F.Cu can't reach R3/R4: their connector-side pads take vias-in-pad because the RM2's pad row sits 0.35 mm above them.
  - In2 is crossed by PG_N and the QSPI lanes, and In3 by WL_CLK and WL_CS, all of them locked.
  - B.Cu, over the In4 ground, has a clear path. Both lines run from the receptacle's D+ join via and D− bridge to the vias in R3/R4, 0.3 mm apart on centres with only 45° bends. D+ is west of D− at both ends, so the lines never cross.
  - D+ is 18.5 mm and D− 19.7 mm on B.Cu; a 1.2 mm skew is irrelevant at 12 Mb/s. There are two layer changes: at the receptacle joins (unavoidable with the reversible pad pairs) and into R3/R4. Each has a GND via within 1 mm.
  - The ESD array taps the lines on F.Cu, a stub of about 2 mm. A flow-through layout would need D+ and D− to cross between the receptacle and the array.
- 2026-10-19 — **R11 and C10 ground through one locked via west of the top-left sensor's no-via box.** Both GND pads sit inside that box, just below the RF keep-out line, so neither can take a via in the pad. On F.Cu, R11.2 is walled in by VBUS, ILIM and the charger, and it was the open connection in 4 of 11 orderings. FACING keeps R11's ILIM end toward pin 12, so turning R11 round would break the placement rule. Instead, a 45° link runs from R11.2 into C10.2, and a stub runs from C10.2's corner to a via 0.2 mm outside the box. The via clears the RF keep-out by 0.55 mm and the board edge by 0.4 mm.
- 2026-10-19 — **Routed again with the USB pair and the R11/C10 ground locked: 0 unconnected, 0 DRC errors, audit clean.** The board is ordering 6 (`ROUTE_FIRST=5 ROUTE_TRIES=1`); a plain `build.py` run tries orderings 1–3 instead. Over 12 orderings the open counts were 0–5. VBUS was the most frequent open (5 of 12), likely because the locked R11/C10 link took the path VBUS used to come down by. Look there first if a later change reopens it.
- 2026-10-19 — **The schematic carries the BOM and DNP flags the board already had.** `gen_schematic.py` wrote `in_bom yes, dnp no` on every symbol, while the board leaves the bare-copper pad rows (J2–J5) out of the BOM and marks the debug-probe JST (J6) DNP. KiCad's parity check flagged all six. The schematic is the source of truth, so it now writes `in_bom no` for `mpn: PCB` parts and `dnp yes` for refs in `circuit.DNP` (`{"J6"}`, also what `place.py` uses). The RM2 also has no LCSC number, so "no LCSC number" could not be the DNP rule.
- 2026-10-19 — **Routing gate (phase 7) failed independent review.** See `design/gates/phase7.md`. The board is connected and DRC-clean, but:
  - Every high-current layer change is a single via.
  - The In3 VSYS pour is cut into two pieces by 312 mm of signal wiring.
  - Return vias are missing at fast-net layer changes.
  - Ground stitching is far sparser than stackup.md claims.

  The gate file holds the fix plan. Three new checks are reusable: `scripts/check_parity.py` (connectivity by pad sets), `scripts/check_current.py` (max-flow over tracks and vias, against intent.md's design currents) and `scripts/check_planes.py`.
- 2026-10-19 — **Phase-7 fixes, part 1: the generator (fix plan in `design/gates/phase7.md`).**
  - **In3 is a solid VSYS plane with no wires** (B1, M8, M9). VSYS becomes a plane net like GND: `tame_dsn` drops it from freerouting's netlist and marks In3 `power`, every VSYS pad takes vias into In3 (`plane_pad_vias`), and `relayer` / `finish` never put a wire there. In2 now has a solid reference under it (In3 is 0.11 mm away). WL_CLK's In3 pre-route moved to B.Cu (over In4 GND, the better reference for a clock). WL_CS's moved to In2, with a 5 mm B.Cu jog under the bottom pin row where the RP2350 block's 1V1 strap runs down In2. Alternative: keep a few wires on In3 at a cost. Rejected: any wire there splits the plane, which is what the review failed.
  - **Power transfers are via arrays sized by `check_current.py`'s rating** (0.53 A per 0.2 mm of drill; B2–B5, M11). Charger OUT: an F.Cu VSYS zone over the pin row's outer end with 4 × 0.6/0.3 vias. C120, a tactile sensor's cap on the back, sits under the obvious spot above the pins, so two vias go in C14's pad and two east of pin 9. Charger BAT: an F.Cu VBAT zone under the pins, across to 4 × 0.5/0.25 vias in the cell pad (west column 0.55 mm in, clear of PG_N's In2 run). The MOT pad row gets arrays in the pads: VSYS 4, each switch node 3, joined on F.Cu by a 0.6 mm bar. The flyback diodes' VSYS pads and the RM2's VIN get 2 each.
  - **Netclasses `Motor` (MOTOR?_SW, 0.4 mm, F.Cu only) and `Vbus` (VBUS, 0.4 mm, outer layers only, 0.8/0.4 vias)**, enforced by a `use_layer` in the DSN and by `close_gaps` / `finish`. The motor nets cannot then pick up a single routed via. A VBUS via (0.4 drill, 1.07 A) carries the 1 A limit alone, so freerouting needs no via pairs, which it cannot place. Alternative: pre-route VBUS by coordinates (33 mm). Rejected: the skill's rule against long hand-routed traces, and freerouting routes it fine within the rules.
  - **C100 (RM2 VIN 10 µF) moved to the back, directly under pad 16** (M11; `parts/rm2-radio.md` proposed it). Its VSYS pad shares pad 16's vias. Its GND pad sits under GND pad 15, not pad 17, which is unconnected.
  - **LX cut-out shrunk to the outline of the LX zone and L1's LX pad, grown by 0.25 mm**, instead of their bounding box (1.7 × 5.4 mm). A track keep-out on In2 under it stops another sense line running beneath the switch node (M6).
  - **New stage `stitch.py`** after `finish`: return vias, the edge fence, interior stitching (M7, M10). See stackup.md for the targets.
  - **Checks corrected** (M12). `check_current.py` rates pours by their fill: a 0.1 mm raster, 4-connected, linear at the IPC-2152 rate for a 2 mm conductor, with the same pad-entry rule as tracks around fine-pitch pins. `check_planes.py` checks In3 as a plane, takes In2's reference as In3, and checks every signal via and the stitching density.
  - **Freerouting is started without `DISPLAY`.** With `--gui.enabled=false` it still asks Swing for a look and feel, and a stale X cookie killed every run (2026-10-19). `tame_dsn` also matched only integer coordinates, so plane-net copper at half-micron positions (`-84502.5`) stayed in the DSN on a net that no longer existed. It now matches decimals and asserts that none is left.
- 2026-10-19 — **Phase-7 fixes, part 2: what the first routing batches turned up.**
  - **VBUS enters through one pre-placed 0.7/0.4 via in the 0.5 mm strip between the board edge and the top-left sensor's no-via box, overlapping C10's VBUS pad.** Pin 13 and C10 sit inside that box, and the TMR / ISET traces wall off the strip below them, so no routed VBUS via fits anywhere near the pin. The Vbus class is 0.65 mm (1.06 A on 0.5 oz inner copper) on F.Cu, In2 and B.Cu. The trunk from that via to D6 by the receptacle is routed first by the finish stage's grid router (`finish.preroute`) and locked: freerouting left it open in 10 of 15 orderings, routing it last into a full board. WL_DATA is routed the same way, on F.Cu and B.Cu: it was open in every ordering once WL_CLK and WL_CS left In3. The receptacle's VBUS pads lose their 0.4/0.2 hop vias: they were the 0.53 A neck of every routed VBUS path. The finish stage now places each class's own via (it laid 0.4/0.2 vias on VBUS). Alternative: route VBUS on F.Cu only. Rejected: no F.Cu path reaches pin 13.
  - **C13 (VBUS 100 nF) moved to the back, below the top-left sensor near the VBUS via** (x −12.55, clear of R12 / R10's GND vias on the front). At (−6.5, −16.3) it was 4.5 mm from pin 13, on the far side of the charger, and walled in by the new OUT copper.
  - **The charger's OUT zone reaches east over C13's old spot to C11 (OUT 10 µF).** C11 cannot take a via because the cell pad J2.1 is under it on the back.
  - **The motor corners get an F.Cu VSYS zone each.** D3 / D4's VSYS pads, C40 / C42 (22 µF) and C41 / C43 sit in the bottom sensors' no-via boxes. Two vias per corner sit just outside the box, between the 100 nF and C122 / C123 (the sensors' caps under the diodes on the back).
  - **CHG_N (U2 pin 9) gets a locked escape**: from the pad's inner end east to a via between the OUT vias and pin 8. It was open in 5 of 8 orderings once the OUT copper was in.
  - **GND vias may go in a pad inside another net's `pwr_` zone** (C14's GND pad in the OUT zone). The pad is a hole in that zone already. Beside-the-pad vias still may not cut a `pwr_` zone.
  - **Return vias for the pre-routes' layer changes are placed before freerouting** (`stitch.return_vias`, called from `route.py`), so their spots are not taken. For slow nets the search reaches 1.45 mm (the check allows 1.5).
  - **The motor switch nodes (SWITCH_HI) are exempt from the GND return-via rule**, as POWER nets already were. Their loop closes through VSYS at J4.1's array, 2 mm from J4.2 / J4.3, not through an image current in the GND planes. J4.2's bottom via can never have a GND via within 1.5 mm anyway: the pad row fills that space.
  - **The stitching check counts only points where a GND via could go**: at least 0.6 mm in from the outline, clear of no-via rule areas and of the RP2350 / `pwr_` power zones by a via's reach, and at least 0.33 mm from another net's pad. Without that, the RM2's and J5's pad rows along the right edge read as fence gaps that no via could close.
  - **`finish.py` mitres right-angle corners between unlocked wires** (m13), where the diagonal clears everything.
- 2026-10-19 — **Phase-7 fixes, part 3: closing what the batches left open.**
  - **More nets routed first by the grid router (`finish.preroute`), each logged and skipped if it finds no path:**
    - the receptacle's two VBUS pad stubs to D6;
    - TAC_CS2 (pin 18 to the top-right sensor, 25 mm, open in 6 of 8 orderings once In3 was gone);
    - WL_IRQ (R81 to RM2 pad 10, cut off by WL_CS's locked F.Cu L, open in 3 of 8).
  - **TAC_CS1 (U1 pin 2) gets a locked escape to a via in the lane.** WL_DATA's grid route comes down that lane to pin 4 and closed pin 2 in, in all 8 orderings, when it went first.
  - **WL_CLK's B.Cu run steps to x 0.15 past C91.** C91's GND pad at x −0.28 had no room for its via over the run's old x 0.0. C91 also gets a locked GND via 0.5 mm above the pad: the QSPI lanes run under the pad on In2, and WL_DATA now takes the F.Cu space it used to reach the pour by.
  - **R61's and C64's GND pads (the battery divider) get a locked link down the edge side and a via below C64.** They sit on F.Cu over J5's test pads on the back, so they can take no via, and VBAT_SENSE walled them in.
  - **`route.py` reserves return vias before freerouting for fast nets only.** Reserved for slow nets too, their GND vias took the next pin's escape (SWCLK, RUN, SDA, SCL and TAC_CS3 went open in every ordering). Slow nets get theirs from `stitch.py` after routing, within their 1.5 mm.
  - **`stitch.py` and the finish stage keep 0.2 mm from hole to copper on NPTHs** (KiCad's hole_clearance runs from the hole to the via's copper). An NPTH's pad box is smaller than its hole.
  - **Also grid-routed first:**
    - TAC_CS1, from pin 2's escape via to the top-left sensor. It is kept off B.Cu between that sensor's pad columns, where it shut in 3V3_SENS and TAC_SDO in all 16 orderings.
    - C13's VBUS pad, to the entry via.
    - Both motor switch nodes (FET to diode to the MOT pad's bar, on F.Cu): power first, since an F.Cu-only net left to freerouting could be shut in.
    - IMU_INT1, from pin 8's escape via to the IMU.
