AI Flow: Reverse Engineering
Public Made by Adomby adom
Photos of a PCB to a tagged KiCad copy: parts placed, copper and nets with their evidence, a schematic, a measured confidence score, bench tests and a video of the run.
1984b40
name: adom-aiflow-reverse description: "Reverse engineer a PCB from photos into a KiCad copy: identify parts and functional blocks, trace every pour, track and via from the pictures, list the hidden connections with their evidence, state the layer count from what is known, review in a measured loop until the photo score reaches 90, and list tests and bench probes for what is unknown. Use when asked to reverse engineer this board, clone a PCB from pictures, copy a board into KiCad, identify the parts on a PCB, work out how many layers a board has, or infer its inner layers." user-invocable: true
Reverse engineer a board from photos
Run the adom-aiflow-reverse binary. It measures and generates; you name what takes judgement. Do not edit copper, part positions or routes by hand, and do not write your own image code: if a measurement is wrong, fix the program or say so.
mkdir my-board && cd my-board
adom-aiflow-reverse init --photos <folder or photo...> --rules kicad-default --ai-thread "<your thread>"
adom-aiflow-reverse serve --port 8891 &
adom-aiflow-reverse next --ai-thread "<your thread>"
Then repeat: do what next says, run its check, read the hints. Or let the program do its own part: auto runs every program-only step and stops with AUTO NEEDS AI: <input> (exit 2) when it needs you, naming the exact file or command, or AUTO COMPLETE (exit 0). Give the input, run auto again, until it is complete.
Photos: give them all, unsorted
Pass every photo of the board to init --photos, as a folder or a list: close-up tiles, whole-board overviews, both faces, in any order. Do not sort, rename, crop, rotate or stitch them yourself. The first prep runs stitch, which sorts them into faces, tiles and overviews, picks the top and builds one mosaic per face at the close-ups' resolution (photos/top.jpg, photos/bottom.jpg). The registration is Python with OpenCV (tools/stitch.py, shipped inside the binary and run by it; the call is in the run ledger), so the machine needs python3 with numpy and OpenCV.
Before going on, read stitch.json and look at the pictures in reverse-flow/stitch/:
<face>-sources.jpg: which photo each part of the mosaic came from. Grey is overview fill, which is low resolution: copper read there is not to the standard of the rest. Black is not seen.<face>-seams.jpgandseams/: the joins at full size. A track that steps sideways at a join is a registration fault: report it, do not work round it.overlapinstitch.json: how far the tiles disagree where they overlap, in pixels. Under one pixel at the median is normal for the test boards.topDecision: how the top was chosen. When it says the two faces are close, look at both mosaics. If the other face is the top, runstitch --top <name of a photo that shows the top>and say why in your report.facesAssumed: present when the photos alone do not prove which group is which face. Say so to the human.unplaced,missingand each face'snotes: photos that fit nowhere, a face nobody photographed, a board that runs out of the frame. Tell the human what is missing and ask for those photos. Never describe an area the mosaic does not show.
init --top <photo...> --bottom <photo...> remains for a run that already has one finished photo per face.
Displaying this app (REQUIRED, read before showing anything)
To show the flow window, open http://localhost:<port> in a Hydrogen webview tab, loading the webview skill first, and keep it open for the whole run. Never open it in VS Code's Simple Browser: it is not a webview in the Adom ecosystem, with no live channel or console read-back.
Surfaces: wv, a Hydrogen webview panel; pup, see the pup skill; the user's real signed-in browser, through the Adom browser extension; hdbw, Hydrogen's standalone browser window, only under Hydrogen.
What you supply
- Scale: one part body with a datasheet size, from a document fetched this session.
- Parts: read markings from the crops, then identity and value, each with its evidence.
- Blocks: names and functions for the groups it proposes.
- Nets: pin names and functions from datasheets. Tag each one
DATASHEETorINFERRED. An inference needs a test. - Datasheet pins: when
autostops withAUTO NEEDS AI: datasheet pins, fetch the public datasheet of each chip it names and writereverse-flow/datasheets/<ref>.pins.json:{"part": "U2", "mpn", "document": {"name", "number", "revision", "page"}, "pins": [{"pad": "4", "name": "VM", "type": "power_in", "expects": [{"kind": "capacitor_to_pin", "pin": "PGND"}, {"kind": "tied_to_pin", "pin": "VDRAIN"}]}, ...]}. Every pad of the footprint takes an entry (the exposed pad too);typeis one ofpower_in,power_out,ground,input,output,bidirectional,open_drain,passive,no_connect;expectsholds what the typical application shows at the pin (capacitor_to_ground,capacitor_to_pin,tied_to_pin,tied_to_ground,resistor_to_pin,resistor_to_ground,pull_up,crystal,inductor,not_connected,other). Example for the DRV8323S: docs/method.md, "Datasheet pins". No public datasheet, or a marking that fits several parts:{"part": "U5", "available": false, "reason": "..."}. Never write pins for a chip whose marking you did not read: the program refuses them. The program then puts the pin names in parts.json (datasheet apply), draws the chip with them, and names the copper at each named pin (U2_GHA, name taggedDATASHEET, the join as seen).datasheet statusshows what waits. After everyconnect attributethe datasheet witness reports how many pins agree, disagree or are not visible against the copper: readreverse-flow/evidence/witness/findings.mdand look at each picture. A disagreement changes nothing by itself: decide from the picture whether the copper, the part's identity or orientation, or the net you typed is wrong, fix that, and keep the test it raised if you cannot tell. "Unresolved copper" pictures show where the reader merged a chip's fine-pitch pads: those pins have no witness. - Part, then orientation, then pinout: nets are built from the parts first (
connect pinout, run byauto): every pin of an identified chip with a pin table, of a USB Type-C receptacle, and of any part whosepinsyou typed gets a net by rule, tagged DATASHEET or INFERRED, and the copper then confirms or contradicts. So: read pin 1 of every chip from the photo (pin1: the mark andtagSEEN; when no mark can be seen say so withtagUNKNOWN, never guess:autoasks for exactly the chips with a pinout whose orientation is not settled); type the silkscreen names of header pads into the header'spins({"1": "GND", "2": "RX"}) after checking which end pad 1 is; readreverse-flow/detect/pinout.jsonfor the chain and for every part claimed by place, and look at each claim. - Schematic symbols: for each IC or transistor you identified, its KiCad library symbol in parts.json
symbolor its datasheet pin names inpins(pad to name, or to{"name", "type"}); pins a datasheet says are unused in nets.jsonnoConnect. Without them an IC is a box in pad order and every unknown pin shows a?.autodraws the sheets and runscheck schematicby itself in the connect step and stops on a failure;reportwrites a picture of every sheet underreverse-flow/schematic/: look at each block there. - Audits: an independent verdict on every crop the review draws. You do not supply confidence: the program computes it.
Tags are the contract
SEEN and MEASURED come from the photos. DATASHEET needs the document. INFERRED needs a test. UNKNOWN is a good answer. Never upgrade a tag to make a check pass.
Read these results before believing a run
evidence/copper-islands-*.jpgagainst the photos. This is what the board was built from.detect/copper-measured.json:pinClashesandviaClashesare where the copper disagrees with the nets. Each is either a wrong net or a misread island, and both need a test.unrouted.json: the hidden connections and what each rests on.nets.jsoncontradiction: where the copper disagrees with a net you named.layers.json: the count and its tag, andminimum, what the photos alone give. After an optionalconnect route, its candidates:open,drcErrors,kicadOpen,seenCrossingMm.
The nets as pictures
nets draw (also run by report) grades every net and draws it under reverse-flow/nets/. It reads what connect attribute wrote and no photo, so run it again after attribution changes.
- A net's level is its weakest link, not the share of its copper that was seen:
copper(every pin seen joined by continuous copper),via(joined, at the weakest point through verified vias) orinferred(at least one pin or join rests on inference).net-confidence.jsongives each net's level, the weakest link and where it is, pins seen and copper by rung. Probe the weakest link first. Report the counts per level as they are: a board where every net isinferredis a correct result. nets/confidence.jpg: both faces, every piece of copper in the colour and pattern of its net's level, with the legend.confidence-top.jpgandconfidence-bottom.jpgare the same at full size,attribution.jpgshows each piece by its own rung,confidence-table-<n>.jpglists every net.nets/net/<name>.jpg: one picture per net, zoomed to the net when it is small. Filled marks are pins seen joined, hollow marks are pins on the net by inference only, the yellow ring is the weakest link, a cross is a place where the net meets another.nets/sections/: the nets in groups (ground, each supply rail, each bus or interface by name, then by block), up to eight to a sheet.nets/index.mdlinks everything.nets/view.html: the same in the flow window (the Nets tab of the recorded view): a net list beside the board, a click on a net or on the board lights it on both faces, with a switch between by confidence and by net.- Copper with no net (
UNK_ISO,UNK_G,UNK_CONFLICT) gets no picture of its own: it is thenone,groupandconflictcategories of the confidence picture, and index.md says how many there are.
Look at the pictures at full size before showing them. nets draw --net <name> redraws one net.
The review loop: 90 percent on the photo score, measured
There are two scores. The photo score is the lowest of what photos can earn, and the target applies to it. The bench score is the lowest of what needs the real board or outside documents (hidden connectivity, the layer count, values of unmarked parts): report it with what would raise it, and never wait for it. Report the line with both word for word.
Repeat review pass and do what it names until it says TARGET MET or STALLED, then check review. Scores come from measurements and audits: confidence prints each one's inputs and docs/method.md each formula. No typed level or edited score counts.
- Audit:
audit draw --category <c>(a pass draws those it needs), thenaudit verdict --category <c> --sample <seed> --item a001 --verdict ok|wrong|cannot_tell --who <auditor>, or--filefor a sheet. Claims are inaudits/<c>/sheet.md, pictures insheet-*.jpgandcrops/. A copper crop has four panels, explained on its sheet: judge copper from the crops. A connectivity claim states one join, pink to blue, and never a net: a pin's pad to copper the copy drew, or a piece of copper to a pad, a via or other copper. Its crop has a close row. Pins and islands that show no join are not put forward and count as not correct;audit statusgives the counts.audit preview --file <folder>writes crops outside the audit to check legibility. - Who audits: a thread that did not produce the run, or a person. Give the auditor the sheet, not your conclusions. Answer
cannot_tellrather than guess: it counts against the score, and so it should. - Planted claims: some claims are false on purpose. An
okon one voids the sample. Do not readsample.jsonfor them. - Outside evidence: layers can only rise with
--evidence <file> --kind xray|edge-micrograph|.... Photos never show an inner layer. Connectivity, values and identity say what would raise them too. - fidelity.json: copper, vias and placement stay capped until it compares what the layers draw with the photos.
Low numbers are the correct output when that is what was earned. STALLED is an honest answer. Never lower --target to pass without telling the human; every line names it. Report the computed line word for word.
Nets, hidden connections and the layer count
connect measure then connect attribute [--vias <file>] give every pour, track and via a net with graded evidence (copper, via, inferred, group, none, conflict) in copper.json, nets.json and net-evidence.json. Geometry stays as it is. The second command reads no photo, so run it again whenever the via list improves or you add pin names. A net you name from a datasheet keeps its DATASHEET tag and source unless the copper contradicts it; a contradiction is a finding in the net's contradiction, with a test. Read those findings.
The connections nobody can see are not routed. build leaves each as a ratsnest line in a net class named for its evidence and lists them in unrouted.json. layers state states the layer count: give --count <n> --source "<document>" or --count <n> --evidence <file> --kind edge-micrograph|cross-section|xray|ct when you have outside evidence, and nothing when you do not: UNKNOWN with its test is a good answer. Never state a count from a guess.
connect route is optional and no step needs it. Its routes are one way to make the connections, not what is inside the board. If you run it, report what it found, including when nothing closed.
The run video
auto ends by building a two minute video of the run: reverse-flow/video/<project>-run.mp4, with .vtt and .srt captions beside it, a poster and video.json. video build builds it again by hand. It is made from the run's files alone, and every figure in it is read at build time, so it cannot claim more than the run earned.
- Before
autoreaches the end, you may writereverse-flow/video-notes.json:{"board": "<one line naming the board>", "chips": {"U1": "<one line about this chip>"}}. Plain words, one short sentence each. They are spoken as written, so say only what the run's evidence supports. - A failed build does not stop the run:
autostill ends withAUTO COMPLETE, and its last line saysthe video was NOT built: <reason>. Tell the human, fix what it names (python3 with numpy, Pillow and OpenCV; ffmpeg), and runvideo build. - With no voice service the video has captions only;
video.jsonvoice.whysays so. Tell the human. - Hand the video to the human with the report.
video.jsonlists each scene, what was said, and which lines were left out for time.
After the run: the bench passback
bench plan writes reverse-flow/bench/plan.md for a person with the real board and a multimeter: continuity probes between named pads, each with a picture, ordered by what each settles, then the unmarked parts to measure and the layer count. When their answers come back, bench ingest --file <results.json> records them as MEASURED and prints both scores before and after; then auto runs the stale checks again. docs/bench.md gives the file format. Offer the plan; do not wait for it.
---
name: adom-aiflow-reverse
description: "Reverse engineer a PCB from photos into a KiCad copy: identify parts and functional blocks, trace every pour, track and via from the pictures, list the hidden connections with their evidence, state the layer count from what is known, review in a measured loop until the photo score reaches 90, and list tests and bench probes for what is unknown. Use when asked to reverse engineer this board, clone a PCB from pictures, copy a board into KiCad, identify the parts on a PCB, work out how many layers a board has, or infer its inner layers."
user-invocable: true
---
# Reverse engineer a board from photos
Run the `adom-aiflow-reverse` binary. It measures and generates; you name what takes judgement. Do not edit copper, part positions or routes by hand, and do not write your own image code: if a measurement is wrong, fix the program or say so.
```bash
mkdir my-board && cd my-board
adom-aiflow-reverse init --photos <folder or photo...> --rules kicad-default --ai-thread "<your thread>"
adom-aiflow-reverse serve --port 8891 &
adom-aiflow-reverse next --ai-thread "<your thread>"
```
Then repeat: do what `next` says, run its check, read the hints. Or let the program do its own part: `auto` runs every program-only step and stops with `AUTO NEEDS AI: <input>` (exit 2) when it needs you, naming the exact file or command, or `AUTO COMPLETE` (exit 0). Give the input, run `auto` again, until it is complete.
## Photos: give them all, unsorted
Pass every photo of the board to `init --photos`, as a folder or a list: close-up tiles, whole-board overviews, both faces, in any order. Do not sort, rename, crop, rotate or stitch them yourself. The first `prep` runs `stitch`, which sorts them into faces, tiles and overviews, picks the top and builds one mosaic per face at the close-ups' resolution (`photos/top.jpg`, `photos/bottom.jpg`). The registration is Python with OpenCV (`tools/stitch.py`, shipped inside the binary and run by it; the call is in the run ledger), so the machine needs `python3` with numpy and OpenCV.
Before going on, read `stitch.json` and look at the pictures in `reverse-flow/stitch/`:
- `<face>-sources.jpg`: which photo each part of the mosaic came from. Grey is overview fill, which is low resolution: copper read there is not to the standard of the rest. Black is not seen.
- `<face>-seams.jpg` and `seams/`: the joins at full size. A track that steps sideways at a join is a registration fault: report it, do not work round it.
- `overlap` in `stitch.json`: how far the tiles disagree where they overlap, in pixels. Under one pixel at the median is normal for the test boards.
- `topDecision`: how the top was chosen. When it says the two faces are close, look at both mosaics. If the other face is the top, run `stitch --top <name of a photo that shows the top>` and say why in your report.
- `facesAssumed`: present when the photos alone do not prove which group is which face. Say so to the human.
- `unplaced`, `missing` and each face's `notes`: photos that fit nowhere, a face nobody photographed, a board that runs out of the frame. Tell the human what is missing and ask for those photos. Never describe an area the mosaic does not show.
`init --top <photo...> --bottom <photo...>` remains for a run that already has one finished photo per face.
## Displaying this app (REQUIRED, read before showing anything)
To show the flow window, open `http://localhost:<port>` in a Hydrogen webview tab, loading the `webview` skill first, and keep it open for the whole run. Never open it in VS Code's Simple Browser: it is not a webview in the Adom ecosystem, with no live channel or console read-back.
Surfaces: **wv**, a Hydrogen webview panel; **pup**, see the `pup` skill; the user's **real signed-in browser**, through the Adom browser extension; **hdbw**, Hydrogen's standalone browser window, only under Hydrogen.
## What you supply
- **Scale:** one part body with a datasheet size, from a document fetched this session.
- **Parts:** read markings from the crops, then identity and value, each with its evidence.
- **Blocks:** names and functions for the groups it proposes.
- **Nets:** pin names and functions from datasheets. Tag each one `DATASHEET` or `INFERRED`. An inference needs a test.
- **Datasheet pins:** when `auto` stops with `AUTO NEEDS AI: datasheet pins`, fetch the public datasheet of each chip it names and write `reverse-flow/datasheets/<ref>.pins.json`: `{"part": "U2", "mpn", "document": {"name", "number", "revision", "page"}, "pins": [{"pad": "4", "name": "VM", "type": "power_in", "expects": [{"kind": "capacitor_to_pin", "pin": "PGND"}, {"kind": "tied_to_pin", "pin": "VDRAIN"}]}, ...]}`. Every pad of the footprint takes an entry (the exposed pad too); `type` is one of `power_in`, `power_out`, `ground`, `input`, `output`, `bidirectional`, `open_drain`, `passive`, `no_connect`; `expects` holds what the typical application shows at the pin (`capacitor_to_ground`, `capacitor_to_pin`, `tied_to_pin`, `tied_to_ground`, `resistor_to_pin`, `resistor_to_ground`, `pull_up`, `crystal`, `inductor`, `not_connected`, `other`). Example for the DRV8323S: docs/method.md, "Datasheet pins". No public datasheet, or a marking that fits several parts: `{"part": "U5", "available": false, "reason": "..."}`. Never write pins for a chip whose marking you did not read: the program refuses them. The program then puts the pin names in parts.json (`datasheet apply`), draws the chip with them, and names the copper at each named pin (`U2_GHA`, name tagged `DATASHEET`, the join as seen). `datasheet status` shows what waits. After every `connect attribute` the datasheet witness reports how many pins agree, disagree or are not visible against the copper: read `reverse-flow/evidence/witness/findings.md` and look at each picture. A disagreement changes nothing by itself: decide from the picture whether the copper, the part's identity or orientation, or the net you typed is wrong, fix that, and keep the test it raised if you cannot tell. "Unresolved copper" pictures show where the reader merged a chip's fine-pitch pads: those pins have no witness.
- **Part, then orientation, then pinout:** nets are built from the parts first (`connect pinout`, run by `auto`): every pin of an identified chip with a pin table, of a USB Type-C receptacle, and of any part whose `pins` you typed gets a net by rule, tagged DATASHEET or INFERRED, and the copper then confirms or contradicts. So: read pin 1 of every chip from the photo (`pin1`: the mark and `tag` SEEN; when no mark can be seen say so with `tag` UNKNOWN, never guess: `auto` asks for exactly the chips with a pinout whose orientation is not settled); type the silkscreen names of header pads into the header's `pins` (`{"1": "GND", "2": "RX"}`) after checking which end pad 1 is; read `reverse-flow/detect/pinout.json` for the chain and for every part claimed by place, and look at each claim.
- **Schematic symbols:** for each IC or transistor you identified, its KiCad library symbol in parts.json `symbol` or its datasheet pin names in `pins` (pad to name, or to `{"name", "type"}`); pins a datasheet says are unused in nets.json `noConnect`. Without them an IC is a box in pad order and every unknown pin shows a `?`. `auto` draws the sheets and runs `check schematic` by itself in the connect step and stops on a failure; `report` writes a picture of every sheet under `reverse-flow/schematic/`: look at each block there.
- **Audits:** an independent verdict on every crop the review draws. You do not supply confidence: the program computes it.
## Tags are the contract
`SEEN` and `MEASURED` come from the photos. `DATASHEET` needs the document. `INFERRED` needs a test. `UNKNOWN` is a good answer. Never upgrade a tag to make a check pass.
## Read these results before believing a run
- `evidence/copper-islands-*.jpg` against the photos. This is what the board was built from.
- `detect/copper-measured.json`: `pinClashes` and `viaClashes` are where the copper disagrees with the nets. Each is either a wrong net or a misread island, and both need a test.
- `unrouted.json`: the hidden connections and what each rests on. `nets.json` `contradiction`: where the copper disagrees with a net you named.
- `layers.json`: the count and its tag, and `minimum`, what the photos alone give. After an optional `connect route`, its candidates: `open`, `drcErrors`, `kicadOpen`, `seenCrossingMm`.
## The nets as pictures
`nets draw` (also run by `report`) grades every net and draws it under `reverse-flow/nets/`. It reads what `connect attribute` wrote and no photo, so run it again after attribution changes.
- **A net's level is its weakest link**, not the share of its copper that was seen: `copper` (every pin seen joined by continuous copper), `via` (joined, at the weakest point through verified vias) or `inferred` (at least one pin or join rests on inference). `net-confidence.json` gives each net's level, the weakest link and where it is, pins seen and copper by rung. Probe the weakest link first. Report the counts per level as they are: a board where every net is `inferred` is a correct result.
- `nets/confidence.jpg`: both faces, every piece of copper in the colour and pattern of its net's level, with the legend. `confidence-top.jpg` and `confidence-bottom.jpg` are the same at full size, `attribution.jpg` shows each piece by its own rung, `confidence-table-<n>.jpg` lists every net.
- `nets/net/<name>.jpg`: one picture per net, zoomed to the net when it is small. Filled marks are pins seen joined, hollow marks are pins on the net by inference only, the yellow ring is the weakest link, a cross is a place where the net meets another.
- `nets/sections/`: the nets in groups (ground, each supply rail, each bus or interface by name, then by block), up to eight to a sheet. `nets/index.md` links everything.
- `nets/view.html`: the same in the flow window (the Nets tab of the recorded view): a net list beside the board, a click on a net or on the board lights it on both faces, with a switch between by confidence and by net.
- Copper with no net (`UNK_ISO`, `UNK_G`, `UNK_CONFLICT`) gets no picture of its own: it is the `none`, `group` and `conflict` categories of the confidence picture, and index.md says how many there are.
Look at the pictures at full size before showing them. `nets draw --net <name>` redraws one net.
## The review loop: 90 percent on the photo score, measured
There are two scores. The **photo score** is the lowest of what photos can earn, and the target applies to it. The **bench score** is the lowest of what needs the real board or outside documents (hidden connectivity, the layer count, values of unmarked parts): report it with what would raise it, and never wait for it. Report the line with both word for word.
Repeat `review pass` and do what it names until it says TARGET MET or STALLED, then `check review`. Scores come from measurements and audits: `confidence` prints each one's inputs and docs/method.md each formula. No typed level or edited score counts.
- **Audit:** `audit draw --category <c>` (a pass draws those it needs), then `audit verdict --category <c> --sample <seed> --item a001 --verdict ok|wrong|cannot_tell --who <auditor>`, or `--file` for a sheet. Claims are in `audits/<c>/sheet.md`, pictures in `sheet-*.jpg` and `crops/`. A copper crop has four panels, explained on its sheet: judge copper from the crops. A connectivity claim states one join, pink to blue, and never a net: a pin's pad to copper the copy drew, or a piece of copper to a pad, a via or other copper. Its crop has a close row. Pins and islands that show no join are not put forward and count as not correct; `audit status` gives the counts. `audit preview --file <folder>` writes crops outside the audit to check legibility.
- **Who audits:** a thread that did not produce the run, or a person. Give the auditor the sheet, not your conclusions. Answer `cannot_tell` rather than guess: it counts against the score, and so it should.
- **Planted claims:** some claims are false on purpose. An `ok` on one voids the sample. Do not read `sample.json` for them.
- **Outside evidence:** layers can only rise with `--evidence <file> --kind xray|edge-micrograph|...`. Photos never show an inner layer. Connectivity, values and identity say what would raise them too.
- **fidelity.json:** copper, vias and placement stay capped until it compares what the layers draw with the photos.
Low numbers are the correct output when that is what was earned. STALLED is an honest answer. Never lower `--target` to pass without telling the human; every line names it. Report the computed line word for word.
## Nets, hidden connections and the layer count
`connect measure` then `connect attribute [--vias <file>]` give every pour, track and via a net with graded evidence (copper, via, inferred, group, none, conflict) in copper.json, nets.json and net-evidence.json. Geometry stays as it is. The second command reads no photo, so run it again whenever the via list improves or you add pin names. A net you name from a datasheet keeps its `DATASHEET` tag and source unless the copper contradicts it; a contradiction is a finding in the net's `contradiction`, with a test. Read those findings.
The connections nobody can see are not routed. `build` leaves each as a ratsnest line in a net class named for its evidence and lists them in `unrouted.json`. `layers state` states the layer count: give `--count <n> --source "<document>"` or `--count <n> --evidence <file> --kind edge-micrograph|cross-section|xray|ct` when you have outside evidence, and nothing when you do not: UNKNOWN with its test is a good answer. Never state a count from a guess.
`connect route` is optional and no step needs it. Its routes are one way to make the connections, not what is inside the board. If you run it, report what it found, including when nothing closed.
## The run video
`auto` ends by building a two minute video of the run: `reverse-flow/video/<project>-run.mp4`, with `.vtt` and `.srt` captions beside it, a poster and `video.json`. `video build` builds it again by hand. It is made from the run's files alone, and every figure in it is read at build time, so it cannot claim more than the run earned.
- Before `auto` reaches the end, you may write `reverse-flow/video-notes.json`: `{"board": "<one line naming the board>", "chips": {"U1": "<one line about this chip>"}}`. Plain words, one short sentence each. They are spoken as written, so say only what the run's evidence supports.
- A failed build does not stop the run: `auto` still ends with `AUTO COMPLETE`, and its last line says `the video was NOT built: <reason>`. Tell the human, fix what it names (python3 with numpy, Pillow and OpenCV; ffmpeg), and run `video build`.
- With no voice service the video has captions only; `video.json` `voice.why` says so. Tell the human.
- Hand the video to the human with the report. `video.json` lists each scene, what was said, and which lines were left out for time.
## After the run: the bench passback
`bench plan` writes `reverse-flow/bench/plan.md` for a person with the real board and a multimeter: continuity probes between named pads, each with a picture, ordered by what each settles, then the unmarked parts to measure and the layer count. When their answers come back, `bench ingest --file <results.json>` records them as `MEASURED` and prints both scores before and after; then `auto` runs the stale checks again. docs/bench.md gives the file format. Offer the plan; do not wait for it.