Add CLAUDE.md: invariants, architecture, and hard-won lessons
Captures the three non-negotiable invariants (DRC-clean output, exact integer geometry, determinism), the engine matrix, the reference/ GPL rule, the JAR oracle strategy, and the lessons that cost real debugging: KiCad's /*52 net names, exact clearance being load-bearing, shove not being a densifier, and the falsifiable-fixture bar for any density claim. Force-added past the global ignore: this is a private repo and the file is excluded from the sdist, so it never reaches PyPI.
This commit is contained in:
parent
f653c33e01
commit
fd3bee1085
136
CLAUDE.md
Normal file
136
CLAUDE.md
Normal file
@ -0,0 +1,136 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Guidance for Claude Code working in the `freeroute` repository.
|
||||
|
||||
## What this is
|
||||
|
||||
A native Python PCB autorouter: a Java-free reimplementation of the
|
||||
[FreeRouting](https://github.com/freerouting/freerouting) engine. Specctra DSN
|
||||
in, Specctra SES out, no JVM anywhere. Published on PyPI as `freeroute`
|
||||
(GPL-3.0-or-later, CalVer).
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
uv run pytest -m "not oracle" # the fast suite (this is what you run)
|
||||
uv run pytest -m oracle # differential tests against the real JAR (needs Java)
|
||||
uv run ruff check src/ tests/
|
||||
uv run freeroute board.dsn --engine room --pack -do board.ses
|
||||
```
|
||||
|
||||
## The three non-negotiable invariants
|
||||
|
||||
Break any of these and the change is wrong, regardless of how good the routing
|
||||
looks:
|
||||
|
||||
1. **DRC-clean output.** For every different-net item pair, the exact
|
||||
`TileShape` clearance intersection must be empty
|
||||
(`ShapeSearchTree.has_violation()` returns `None`). The `exact` and `room`
|
||||
engines are clean *by construction*: they drop a net rather than emit a
|
||||
violation. A router that packs traces densely and quietly shorts two nets is
|
||||
the failure mode this codebase exists to prevent. It has already happened
|
||||
once (a multi-pin wire/owner desync in shove recovery reported `drc_clean=True`
|
||||
over a real short) and was caught only by an independent adversarial check.
|
||||
2. **Exact integer arithmetic in every geometric decision.** No floats in
|
||||
clearance or intersection tests. Points, vectors, lines and tiles use Python's
|
||||
unbounded `int` and projective rationals, mirroring FreeRouting's use of
|
||||
`BigInteger`. `FloatPoint` exists but is quarantined to distances, rounding
|
||||
and heuristics. Where an irrational is unavoidable (the `sqrt(2)` in the 45°
|
||||
octagon cover), it is bounded to an exact integer **once** at tile
|
||||
construction (`math.isqrt`, rounded outward so the cover is a provable
|
||||
superset) and never appears in a pairwise test.
|
||||
3. **Determinism.** Same input, byte-identical output, every run. Sorted
|
||||
iteration, fixed candidate ladders, no RNG. Rip-up and shove are bounded
|
||||
(pass limits, rip caps, escalating penalties) so they provably terminate.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
dsn/ Specctra DSN parser (tokenizer -> sexp -> typed DsnBoard)
|
||||
ses/ Specctra SES writer (the routed session file)
|
||||
geometry/ exact planar geometry: point, vector, line, box, simplex,
|
||||
polygon, polyline (trace copper), octagon (45 degree copper)
|
||||
board/ BasicBoard, items, nets, clearance, build_board (DSN -> board),
|
||||
search_tree (ShapeSearchTree: the exact clearance oracle)
|
||||
route/ grid_router | exact_router | room_router, shove, pipeline, cli
|
||||
```
|
||||
|
||||
### The three engines (`--engine`)
|
||||
|
||||
- **`grid`** (default) is the MVP maze router over a uniform occupancy grid.
|
||||
Highest raw connectivity, but grid-quantised, so its clearance is approximate
|
||||
and its traces are staircases.
|
||||
- **`exact`** routes orthogonally and verifies every trace against exact
|
||||
geometry. DRC-clean where it succeeds. Supports `--shove` and `--diagonal`.
|
||||
- **`room`** is the continuous expansion-room router: free space is exactly
|
||||
decomposed into convex rooms joined by doors, so it routes off-grid channels
|
||||
the grid literally cannot see. DRC-clean. Supports `--pack` and `--shove`.
|
||||
|
||||
The engine/option matrix lives in `freeroute.cli.ENGINE_OPTIONS` and is
|
||||
importable, so consumers can assert against it. **An unsupported combination is
|
||||
a hard error (exit 2), never a silent no-op.** Downstream code (`mckicad`) reads
|
||||
this matrix to avoid sending an invalid pair.
|
||||
|
||||
## `reference/` is GPL Java. Never commit it, never ship it.
|
||||
|
||||
`reference/freerouting` is a clone of the upstream Java source, used only as a
|
||||
porting reference. It is gitignored and excluded from the sdist. Study it and
|
||||
reimplement; do not paste it. Before any release, confirm the built sdist
|
||||
contains zero `.java` / `.jar` / `.class` / `.flex` files and no `reference/`
|
||||
directory. Redistributing upstream source inside this package would be a
|
||||
licensing problem.
|
||||
|
||||
## The oracle: validate behaviour, not vibes
|
||||
|
||||
FreeRouting ships **no unit tests for its geometry or router**, so there are no
|
||||
value-level oracles to port against. Instead, `tests/oracle.py` runs the real
|
||||
`freerouting.jar` as a differential reference: route the same DSN with both, and
|
||||
compare connectivity (`routed_net_set`). Oracle tests are marked
|
||||
`@pytest.mark.oracle` and skip cleanly without Java, so the normal suite stays
|
||||
JVM-free.
|
||||
|
||||
When you cannot get an oracle, use **invariants and falsifiable fixtures**:
|
||||
- `split_to_convex`: tiles union to the polygon, interiors disjoint, a point is
|
||||
inside the polygon iff inside exactly one tile.
|
||||
- Routing: endpoints exactly on pads, traces on valid layers, inside the
|
||||
outline, no different-net crossing, vias at real layer transitions.
|
||||
|
||||
## Hard-won lessons (do not relearn these)
|
||||
|
||||
- **KiCad emits net names like `/*52`.** Specctra's `SpecCharASCII` includes `/`
|
||||
and `*`, so an unclosed `/*` is a *name*, not an unterminated block comment.
|
||||
The tokenizer must fall through to a token when no closing `*/` exists.
|
||||
- **Exact clearance is load-bearing, not decoration.** A 45° trace whose
|
||||
*bounding box* hits an obstacle can still be legal, because its true octagon
|
||||
copper misses. Coarse box checks reject legal routes. See
|
||||
`test_exact_clearance_is_load_bearing`.
|
||||
- **Shove is not a densifier.** It relocates a blocker into space that already
|
||||
exists; it cannot compress copper. Proven with an order-independent fixture
|
||||
(`true_density.dsn`) plus a parameter sweep: shove flips from "recovers" to
|
||||
"powerless" exactly when detour headroom runs out. The real densifier is
|
||||
**lane/gate packing** (`--pack`).
|
||||
- **A density claim needs a falsifiable fixture.** "Plain routing drops a net"
|
||||
is worthless if the drop is an *ordering artifact*. The bar: the drop must
|
||||
hold under **every** net ordering (enumerate the permutations), and the
|
||||
pack/fail boundary must sit at the true geometric feasibility width. This bar
|
||||
killed one over-claim (`rooms_shove_channel`) and validated a real win
|
||||
(`channel_pack.dsn`).
|
||||
- **KiCad's `LoadBoard` is headless-safe; `ImportSpecctraSES` is not.** Use
|
||||
`LoadBoard` to verify KiCad accepts routed output without needing a display.
|
||||
|
||||
## Honest scope
|
||||
|
||||
Alpha. It routes real KiCad boards, multi-layer, with vias, rip-up, shove,
|
||||
channel packing, 45° shortening, and DRC-clean output. It is **not** at
|
||||
FreeRouting/JAR density parity on dense commercial boards: the maze search is
|
||||
orthogonal (45° is a recovery pass, not diagonal-native search), there is no
|
||||
fanout pass and no post-route optimiser, and on a crowded board it will complete
|
||||
fewer nets than the JAR because it drops rather than violates. Say this plainly;
|
||||
do not oversell it in code comments, the README, or release notes.
|
||||
|
||||
## Conventions
|
||||
|
||||
`uv` for everything, src-layout, ruff, pytest. Small single-purpose modules.
|
||||
Commit in logical chunks with professional messages and no AI attribution. Cite
|
||||
the upstream Java file in a module docstring when porting one.
|
||||
Loading…
x
Reference in New Issue
Block a user