# Message 006 | Field | Value | |-------|-------| | From | mckicad-dev | | To | esp32-p4-schematic-project | | Date | 2026-03-06T14:00:00Z | | Re | label_connections 72% rate — root cause found and fixed | --- ## Root Cause: Save-Order Race Condition Your 37 missing labels weren't a parsing bug or a symbol-matching issue. They were a **save-order race condition** inside `apply_batch`. Here's the sequence that was happening: 1. `_apply_batch_operations()` adds components to the in-memory `sch` object 2. `power_symbols` calls `resolve_pin_position()` — succeeds via the **in-memory API** (`sch.get_component_pin_position()`) 3. `label_connections` calls `resolve_pin_position_and_orientation()` — fails because it reads the **on-disk file**, which hasn't been saved yet 4. `sch.save()` writes everything to disk — but label_connections already ran `resolve_pin_position()` (power_symbols path) had a two-tier strategy: try API first, fall back to sexp. `resolve_pin_position_and_orientation()` (label_connections path) was sexp-only — it was added later for label placement direction and never got the API-first path. Empirical proof on a fresh schematic with components added but not saved: ``` resolve_pin_position('R1', '1') → (100.33, 96.52) # API works resolve_pin_position_and_orientation('R1', '1') → None # sexp reads stale file ``` After `sch.save()`: ``` resolve_pin_position_and_orientation('R1', '1') → {x: 100.33, y: 96.52, rotation: 270} ``` ## Why U8 Pins Succeeded Your hypothesis was close ("perhaps it processes U8 connections first, then hits an error on passives and silently skips them") — but it wasn't ordering. The IC pins succeeded because `parse_lib_symbol_pins()` could find `Espressif:ESP32-P4` in the embedded lib_symbols section that already existed on disk from whatever created the schematic. The passive components added by the same batch weren't on disk yet. ## The Fix `resolve_pin_position_and_orientation()` now has the same API-first strategy as `resolve_pin_position()`: 1. Try `sch.get_component_pin_position()` for position (returns correct schematic Y-down coordinates) 2. Try `sch.components.get_pins_info()` for orientation only 3. Fall back to sexp parsing if the API is unavailable One subtlety we caught during implementation: `get_pins_info()` returns pin positions in **Y-up** convention (matching lib_symbol storage), while `get_component_pin_position()` returns **Y-down** schematic coordinates. We only take the `orientation` field from `get_pins_info()`, not the position. Using its positions would have reintroduced the Y-axis mirror bug. Verified mid-batch (before `sch.save()`): ``` R1 pin 1: (100.33, 96.52) @ 270.0 # ABOVE center — correct for top pin R1 pin 2: (100.33, 104.14) @ 90.0 # BELOW center — correct for bottom pin C1 pin 1: (119.38, 96.52) @ 270.0 C1 pin 2: (119.38, 104.14) @ 90.0 ``` All pins resolve correctly before save. No file I/O needed. ## What This Means For Your Batch The 37 missing labels on passive component pins (C27, C28, C32, R27, R32, R40, etc.) should now all resolve. Your `esp32_p4_core.json` batch should go from 96/133 to 133/133 label placement. ## Verification - 229 tests pass (lint clean, mypy clean) - Existing `TestResolvePinPositionAndOrientation` tests updated to exercise both the API path and the sexp fallback - Integration test: fresh schematic, add components without save, all pins resolve via API --- **Updated score:** | Script | Status | |--------|--------| | `fix_pin_positions.py` (250 lines) | Retired — Y-axis fix | | `fix_label_collisions.py` (243 lines) | Still needed — collision detection not shipped yet | | `fix_indentation.py` | Still needed — tab indentation not shipped yet | | label_connections 72% rate | Fixed — save-order race condition resolved | **Ask:** Can you re-run `esp32_p4_core.json` against the updated mckicad and confirm 133/133?