Rust native PCB reader/writer Phase 3 slice

Status: accepted. This slice closes the native PCB reader/writer and source-preserving round-trip milestone; it does not approve or expand WASM packaging.

Boundary

PcbView owns no source text and builds no generic S-expression tree. It borrows one UTF-8 board buffer, validates exactly one kicad_pcb root, and retains exact structural spans for direct board forms. Nested footprint, routing, and zone facts are decoded from their bounded source spans.

The first promoted families are layers, nets, top-level properties, footprints, typed nested pads and 3D-model references, segments, vias, and zones. Pads expose their identity, geometry, layers, resolved board-level net reference, UUID, parent-footprint index, and exact source range. Model references expose path, offset, scale, rotation, parent-footprint index, and exact source range. Unknown top-level forms remain iterable and all untouched bytes survive focused edits.

The second native slice adds all eight board graphic carriers through one typed PcbGraphic vocabulary, plus routed arcs, dimensions, groups, generated objects, and embedded-file metadata. Graphic point lists, group/generated members, generated properties, and embedded-file collections have independent limits.

The third native slice promotes board header/setup metadata, board variants, images, barcodes, tables, and nested table cells. Every collection is iterable in source order and carries exact source ranges where it represents a source object. Variant, image, barcode, table, nested-cell, table-value, and independent image-data-part limits fail closed before appending beyond their ceilings. The implementation remains source-backed and splits these less-common carriers into a dedicated module rather than growing the primary board index module further.

The fourth native slice expands zones into their complete source-backed Python-parity record: explicit net-name presence, singular/plural layer form, lock/name/hatch/priority settings, connection and fill settings, keepout and placement-rule fields, per-layer hatch offsets, authored outlines, and authored filled polygons. Polygon and property records retain exact spans. Independent polygon, aggregate point, and layer-property ceilings are intentionally generous defaults and remain configurable; lazy decoding fails before typed collections exceed them. The core consumes authored fill data only and performs no refill, clipping, mesh, convex-hull, or other polygon-kernel work.

The fifth native slice exposes close-to-format physical facts required by later plotters and downstream converters. holes() yields positive-dimension circular or oval pad drills and via drills with plating, layer, UUID, and exact-span evidence. Pad centers, drill offsets, and angles deliberately remain footprint-local; footprint_transforms() separately exposes the source placement, layer, lock, schematic path, and exact span, so callers compose coordinates explicitly. profile_primitives() yields top-level board and footprint-local Edge.Cuts lines, arcs, circles, rectangles, and polygons through the shared graphic vocabulary while retaining owner identity. It does not infer a closed contour or run geometric repair.

Nested footprint profile carriers have an independent configurable ceiling and are indexed with pads and models in one child pass, failing before append. Hole reads inherit the already-promoted pad/via and child ceilings. Layer scalar reads now use the bounded collector directly rather than allocating first and checking afterward. Missing routing, via, arc, and zone net forms resolve to Python's ordinal-zero default; pads preserve their distinct empty-reference behavior.

The sixth native slice adds PcbDocument as the owning writeback boundary. It owns source text and limits, produces fresh borrowed PcbView indices on demand, and therefore avoids self-referential Rust state. Native stream input reads no more than the source ceiling plus one sentinel byte and reports UTF-8, I/O, and resource failures through the shared structured diagnostic. Stream output checks its ceiling before writing. Property, identified-layer, and identified-removal mutations run through the source-preserving patch paths, semantically reparse the candidate, and only then replace the owned source; any failure leaves the document unchanged. Unknown forms remain verbatim, and a repeated mutation is a byte-stable no-op.

The seventh native slice adds an allocation-free PcbSelection plan over the source-backed view. Callers request only the typed families they intend to consume; the index expands that request internally for net resolution, footprint nesting, holes, transforms, and board/footprint profile carriers. Dependency-only families remain hidden from public iterators and counts, known-but-unselected forms are not misclassified as unknown, and the exact top-level structural index remains available for preservation and focused mutation. Family ceilings apply only when that family participates in the effective plan, while structural, source, depth, child-scan, and requested-family ceilings continue to fail closed. The default parse/view path remains the complete view.

The eighth native slice promotes embedded-footprint inputs needed by later board plotters and converters without flattening them into a scene model. PcbFootprint now exposes description, tags, attributes, embedded-font and duplicate-jumper settings, solder mask/paste margins, clearance, zone connectivity, and child counts. Separate source-ordered iterators expose footprint properties and the five Python-modeled non-text graphic families with parent-footprint identity and exact source ranges. Properties preserve graphical/non-graphical distinction, local placement, layer, visibility, unlock, and UUID fields; graphics reuse the same PcbGraphic vocabulary as board carriers while retaining footprint-local coordinates. Property, graphic, attribute, point, child, and source ceilings are independent and fail before appending past their configured bounds. Text effects, render-cache interpretation, and shaping remain governed by the later text evidence gates.

The ninth native slice adds a source-backed PcbSetup and physical PcbStackup. It exposes auxiliary/grid origins, soldermask-bridge policy, front/back tenting, covering and plugging, capping/filling, copper finish, dielectric constraints, edge treatment, and ordered stackup layers. Each layer retains name, type, thickness and lock, material, dielectric constant, loss tangent, color, and exact source range. Setup-child and stackup-layer ceilings are deliberately generous and fail before collection growth. The selector hides setup unless requested, while the complete view remains the default. Rack compares Python-modeled auxiliary-origin and stackup records on both mandatory boards without constructing another whole-file selected view; focused vectors cover the additional close-to-format toggles and selection behavior.

The tenth native slice completes drawing-document metadata needed by native semantic reads and later plotters. KiCadPaper preserves named/custom page size, dimensions, orientation, and source evidence. KiCadTitleBlock preserves the shared board/schematic title, date, revision, company, numbered comments, and source range in one producer-neutral core type rather than duplicating the format in Phase 4. Duplicate comment numbers retain Python's last-value-wins behavior. Separate, deliberately generous title-block child and unique-comment ceilings fail closed before unbounded collection growth. Rack reuses the existing owned full-board view to compare this metadata on both mandatory boards and durable custom-paper/title-block input.

The eleventh native slice promotes footprint-local fp_text and fp_text_box as independently selectable, source-ordered typed families. Text preserves kind/content, local placement, layer and knockout, visibility, identity, shared font/effects fields, and the exact span of any authored render cache. Text boxes preserve rectangle or polygon geometry, margins, orientation, layer, locking, effects, border stroke, tri-state border/knockout fields, identity, and cache evidence. KiCadFont and KiCadTextEffects are producer-neutral core records intended for PCB, schematic, symbol, and library reuse. Family, effect-child, font-child, justification-token, and text-box-point limits are independent and fail closed; the defaults remain sized for real KiCad inputs. This slice intentionally does not interpret cached polygons, shape fonts, or emit plotter operations. Rack forces the new iterators through the existing owned corpus view and compares counts plus representative source semantics without adding per-family corpus parses.

The twelfth native slice expands embedded PcbPad records with the shape and fabrication inputs required by the already-proven footprint pad plot operations: pin metadata, die length, trapezoid delta, round-rectangle and chamfer settings, mask/paste/clearance and thermal settings, zone connection, layer-removal flags, custom anchor/clearance options, and source-evidenced custom primitives. Polygon points are typed without fabricating incomplete coordinates; unsupported primitive kinds remain named source records for deterministic deferral. Pad-header, chamfer, primitive, structural point-form, and aggregate valid-point ceilings are separate and deliberately generous. Lazy decoding remains linear in one pad span and builds no generic S-expression tree. Durable Rack vectors compare these fields and custom polygon semantics to Python while the full corpus continues through the same owned pad iterator.

The thirteenth native slice closes the remaining Python-modeled pad manufacturing subrecords and deliberately shares their Rust types and decoders with vias: backdrill and tertiary-drill layer spans, front/back post-machining, explicit zone-layer connections, and complete teardrop parameters. The in-memory and streaming structural scanners now preserve the legacy KiCad/RoyalBlue bare filter_ratio dialect boundary already normalized by the tree parser, so later pad fields retain their correct ownership. Each nested collection is fail-closed behind a configurable, generous ceiling; absent, empty, incomplete, and falsey records follow the Python source-model semantics. Durable Rack evidence compares both pad and via records through the existing full iterators, avoiding another corpus-wide model projection.

The fourteenth native slice completes the Python-modeled via read surface: required numeric defaults, via kind, free-via status, front/back tenting, covering and plugging, and capping/filling policies now accompany the shared machining records. The first recognized kind follows Python's blind/buried/micro precedence, while optional surface tokens retain the Python yes/no/none semantics and exact source evidence. Dedicated, configurable header, child-form, and nested policy-form ceilings are deliberately generous and fail before collection growth. The via decoder now lives in a focused module; Rack compares it through the existing via iterator over durable and mandatory corpus inputs.

KiCad native-parser provenance

KiCad's official PCB S-expression parser consumes table widths, row heights, cells, and image data incrementally. Its image path reserves 512 KiB, appends each base64 token to a wxString, then decodes a second buffer; its table path applies each numeric token or cell as it is read. Those loops do not expose explicit collection or image-part ceilings. KiCad Monkey follows the incremental fail-before-append shape but intentionally adds configurable source, collection, scalar-value, image-data-part, and output ceilings because the Rust core is also intended for unattended process and browser boundaries. These ceilings are hardening policy, not claimed KiCad file-format semantics.

Write behavior

The initial writer replaces one unambiguous top-level property value. It quotes through the shared deterministic builder, patches only the exact value token, applies an output ceiling, reparses semantically in tests, and requires a byte-stable second write.

A second focused operation removes one unambiguous top-level object by canonical uuid or legacy id. Identity uniqueness is checked across every direct top-level form, including images, barcodes, tables, and unknown future forms; an identified future form can therefore be removed without first adding a typed model. The operation patches only the exact form, preserves surrounding and unrelated source bytes, rejects duplicate identities, reparses semantically, and treats a repeated removal as an unchanged byte-stable result. Changed and unchanged results both enforce max_output_bytes.

A third focused operation replaces the existing layer value of one uniquely identified direct board form. It checks identity across all top-level forms, rejects missing or duplicate layer fields, quotes through the shared builder, patches only the scalar token, enforces the output ceiling before both changed and unchanged results, and proves semantic reparse plus a byte-stable no-op repeat.

Board property writeback now matches the Python mutation surface for create, update, and remove. Existing values are patched in place; missing values are inserted immediately before the board root closes using the source newline convention; removal deletes only the exact property span. All three paths reject duplicate names, quote names and values through the shared builder, preserve unrelated and unknown forms, enforce output and property-count ceilings, and commit through PcbDocument only after semantic reparse. Repeated upserts and absent removals are stable no-ops.

Rack now owns an all-corpus native round-trip gate over every .kicad_pcb below an authoritative corpus input/ boundary, explicitly excluding output/ and reference_output/ derivatives. For each file, the gate constructs the bounded owned document, forces every promoted metadata method and iterable family, performs an exact bounded write, reparses that output, repeats the complete semantic decode, and requires a byte-stable second write. Aggregate family counts are retained only as a decode sanity snapshot; exact byte equality proves source preservation. File-local buffers are released before advancing, and every failure identifies its path and processing stage. The gate also owns the focused mutation, transaction, I/O, and resource-limit oracle and runs in CI after archive restoration. This is the source-preserving writer contract; a normalized from-scratch board builder remains an explicit later capability rather than an implicit requirement for lossless existing-file edits.

Polygon-operation disposition

Phase 3 promotes source geometry and bounded local transformations, not a general polygon kernel. This disposition is the durable decision boundary for later PCB plotter and converter work.

Operation familyDispositionOwnerPhase 3 evidence or retained gate
Read authored board/footprint polygons, custom-pad primitives, zone outlines, and stored filled polygonsPromotedkicad-monkey-core source modelExact source spans, bounded point collection, L1_029 semantic parity, and L1_030 complete decode
Exact affine placement and simple primitive translation/rotation used by plottersSimple/local; promoted only where an accepted plot operation already requires itkicad-monkey-coreProducer-neutral point/graphic records and accepted footprint plotter vectors; PCB footprint placement facts are exposed but their later plotter composition remains unpromoted
Simple deterministic pad construction such as chamfered/custom local polygonsPromoted for the existing footprint plotter contractkicad-monkey-coreL1_026 parity vectors and geometry-point/resource ceilings
Zone refill and thermal generationRetained, not implementedFuture explicitly promoted operation; prefer KiCad semantics and evaluate a Geometer process adapter only if its operation contract fitsStored authored/filled zone geometry remains readable; no implicit refill on parse or write
Polygon boolean, offset, clipping, merging, and large planar-set operationsRetained behind a conditional bridge-entry gateGeometer process/WASM boundary when justified, not the parser coreRequires a named operation, TypeSpec process contract, version/resource/failure semantics, and native/browser parity before adoption
Contour closure, topology repair, self-intersection cleanup, and board-outline inferenceRetainedFuture close-to-format operation or Geometer kernel, selected only after corpus evidence defines semanticsprofile_primitives() exposes source carriers without inventing repaired contours
Render-only tessellation, text outlines, cached render polygons, mesh, convex hull, and shader workRetained or out of scopeLater plotter/text slices or downstream visualizersHarfRust/text-outline evidence remains separate; mesh, convex hull, and shader ports are not Phase 3 requirements

Evidence

Native integration tests cover typed values, Python-compatible defaults (including the distinction between an absent table border/separator block and a present sparse block), exact spans, preservation, board-level ordinal/name net resolution, absolute diagnostics, and fail-closed source/family/nested/output limits. Durable synthetic vectors cover newer KiCad carriers that are sparse in the current corpus. Rack case L1_029 fails with an actionable restore command if either required corpus board is unavailable, builds a native executable through the owned reader, verifies unedited owned writeback is exact, forces every promoted lazy iterator to decode, and compares board metadata, all promoted collection counts, sparse table semantics, embedded-footprint metadata and render inputs, and representative carrier fields against the resolved Python object model. Native JSON is decoded explicitly as UTF-8 on Windows, including non-ASCII property values. The same executable compares selected pads, zones, holes, profile primitives, footprint transforms, footprint properties, and footprint graphics item-by-item with the full view on both mandatory boards and rejects leaked internal dependencies. Zone evidence additionally compares aggregate authored/filled polygon and point counts across every zone on both mandatory boards and detailed first-zone semantics, with a focused durable keepout/placement/fill vector. Physical-fact evidence compares every hole, placement, and board/footprint outline count plus representative hole and placement values on the mandatory corpus and a durable oval/offset/NPTH/footprint-outline vector. Owned-document tests cover chained reparsed edits, transactional failure, unknown preservation, exact/stable output, byte ceilings, invalid UTF-8, and stream I/O diagnostics. L1_030 owns the authoritative all-input exact round trip. L1_031 independently runs every promoted Rust property/object mutation, checks its intended and unrelated semantics after reparse, requires a byte-stable repeated edit, and passes each emitted board through kicad-cli pcb upgrade --force when a PCB-capable CLI is available. CI restores and CRC-checks the archive before running the mandatory corpus gates on Windows and Linux.

Retained gates

This is not complete PCB parity. Remaining deeper footprint child fields and setup plot-parameter content, render-cache polygon interpretation, contour closure/repair, a normalized from-scratch owned builder, broader mutation operations, selective streaming I/O, and PCB plotter parity remain open. The promoted owning document deliberately serializes source-preserving edits rather than normalizing every unknown KiCad construct. Zone refill and analytic polygon operations are excluded; downstream consumers can use the now-iterable authored source geometry, and a kernel bridge remains conditional on a separately promoted operation. TypeSpec interchange contracts are required only before a boundary is made public. PCB WASM packaging remains later work, including the four explicitly deferred adapter gates from the native Phase 2 review.