← Commit history

Release 0.3.1: bind field results to exact input hashes and verify the gentle walkthrough

John Lauer ·a7325b5469 · ·parent 153ab9b
12 files changed +564−61
.adomignoreadded+3
@@ -0,0 +1,3 @@+target/+vendor/+**/__pycache__/
Cargo.lockadded+457
@@ -0,0 +1,457 @@+# This file is automatically @generated by Cargo.+# It is not intended for manual editing.+version = 4++[[package]]+name = "adler2"+version = "2.0.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"++[[package]]+name = "adom-fields"+version = "0.3.1"+dependencies = [+ "clap",+ "png",+ "serde",+ "serde_json",+ "sha2",+ "tiny_http",+]++[[package]]+name = "anstream"+version = "1.0.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"+dependencies = [+ "anstyle",+ "anstyle-parse",+ "anstyle-query",+ "anstyle-wincon",+ "colorchoice",+ "is_terminal_polyfill",+ "utf8parse",+]++[[package]]+name = "anstyle"+version = "1.0.14"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"++[[package]]+name = "anstyle-parse"+version = "1.0.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"+dependencies = [+ "utf8parse",+]++[[package]]+name = "anstyle-query"+version = "1.1.5"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"+dependencies = [+ "windows-sys",+]++[[package]]+name = "anstyle-wincon"+version = "3.0.11"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"+dependencies = [+ "anstyle",+ "once_cell_polyfill",+ "windows-sys",+]++[[package]]+name = "ascii"+version = "1.1.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"++[[package]]+name = "bitflags"+version = "1.3.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"++[[package]]+name = "block-buffer"+version = "0.10.4"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"+dependencies = [+ "generic-array",+]++[[package]]+name = "cfg-if"+version = "1.0.5"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"++[[package]]+name = "chunked_transfer"+version = "1.5.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"++[[package]]+name = "clap"+version = "4.6.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"+dependencies = [+ "clap_builder",+ "clap_derive",+]++[[package]]+name = "clap_builder"+version = "4.6.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"+dependencies = [+ "anstream",+ "anstyle",+ "clap_lex",+ "strsim",+]++[[package]]+name = "clap_derive"+version = "4.6.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"+dependencies = [+ "heck",+ "proc-macro2",+ "quote",+ "syn",+]++[[package]]+name = "clap_lex"+version = "1.1.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"++[[package]]+name = "colorchoice"+version = "1.0.5"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"++[[package]]+name = "cpufeatures"+version = "0.2.17"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"+dependencies = [+ "libc",+]++[[package]]+name = "crc32fast"+version = "1.5.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"+dependencies = [+ "cfg-if",+]++[[package]]+name = "crypto-common"+version = "0.1.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"+dependencies = [+ "generic-array",+ "typenum",+]++[[package]]+name = "digest"+version = "0.10.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"+dependencies = [+ "block-buffer",+ "crypto-common",+]++[[package]]+name = "fdeflate"+version = "0.3.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"+dependencies = [+ "simd-adler32",+]++[[package]]+name = "flate2"+version = "1.1.10"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"+dependencies = [+ "crc32fast",+ "miniz_oxide 0.9.1",+ "zlib-rs",+]++[[package]]+name = "generic-array"+version = "0.14.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"+dependencies = [+ "typenum",+ "version_check",+]++[[package]]+name = "heck"+version = "0.5.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"++[[package]]+name = "httpdate"+version = "1.0.3"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"++[[package]]+name = "is_terminal_polyfill"+version = "1.70.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"++[[package]]+name = "itoa"+version = "1.0.18"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"++[[package]]+name = "libc"+version = "0.2.189"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"++[[package]]+name = "log"+version = "0.4.34"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"++[[package]]+name = "memchr"+version = "2.8.3"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"++[[package]]+name = "miniz_oxide"+version = "0.8.9"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"+dependencies = [+ "adler2",+ "simd-adler32",+]++[[package]]+name = "miniz_oxide"+version = "0.9.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"+dependencies = [+ "adler2",+ "simd-adler32",+]++[[package]]+name = "once_cell_polyfill"+version = "1.70.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"++[[package]]+name = "png"+version = "0.17.16"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"+dependencies = [+ "bitflags",+ "crc32fast",+ "fdeflate",+ "flate2",+ "miniz_oxide 0.8.9",+]++[[package]]+name = "proc-macro2"+version = "1.0.107"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"+dependencies = [+ "unicode-ident",+]++[[package]]+name = "quote"+version = "1.0.47"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"+dependencies = [+ "proc-macro2",+]++[[package]]+name = "serde"+version = "1.0.229"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"+dependencies = [+ "serde_core",+ "serde_derive",+]++[[package]]+name = "serde_core"+version = "1.0.229"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"+dependencies = [+ "serde_derive",+]++[[package]]+name = "serde_derive"+version = "1.0.229"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"+dependencies = [+ "proc-macro2",+ "quote",+ "syn",+]++[[package]]+name = "serde_json"+version = "1.0.151"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"+dependencies = [+ "itoa",+ "memchr",+ "serde",+ "serde_core",+ "zmij",+]++[[package]]+name = "sha2"+version = "0.10.9"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"+dependencies = [+ "cfg-if",+ "cpufeatures",+ "digest",+]++[[package]]+name = "simd-adler32"+version = "0.3.10"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"++[[package]]+name = "strsim"+version = "0.11.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"++[[package]]+name = "syn"+version = "3.0.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"+dependencies = [+ "proc-macro2",+ "quote",+ "unicode-ident",+]++[[package]]+name = "tiny_http"+version = "0.12.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"+dependencies = [+ "ascii",+ "chunked_transfer",+ "httpdate",+ "log",+]++[[package]]+name = "typenum"+version = "1.20.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"++[[package]]+name = "unicode-ident"+version = "1.0.26"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"++[[package]]+name = "utf8parse"+version = "0.2.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"++[[package]]+name = "version_check"+version = "0.9.5"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"++[[package]]+name = "windows-link"+version = "0.2.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"++[[package]]+name = "windows-sys"+version = "0.61.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"+dependencies = [+ "windows-link",+]++[[package]]+name = "zlib-rs"+version = "0.6.8"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"++[[package]]+name = "zmij"+version = "1.0.23"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
Cargo.toml+2−1
@@ -1,6 +1,6 @@ [package] name = "adom-fields"-version = "0.3.0"+version = "0.3.1" edition = "2021" license = "MIT" description = "Adom Fields: where the current and the heat go on your board. Current density, copper heat flow and board temperature from the board file itself, in 3D, for the AI to drive and the human to explore."@@ -12,6 +12,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" tiny_http = "0.12" png = "0.17"+sha2 = "0.10"  [profile.release] opt-level = 3
README.md+10
@@ -51,3 +51,13 @@ DC only; one copper thickness for all layers; a flat convection coefficient, no ## Give it back  Anything wrong, rigid or missing: an issue or a PR on this page. Adom processes PRs within minutes during business hours.++## Input identity in 0.3.1++Every solve records SHA-256 identities of its exact board/spec and refuses if either changes during the solve. AI Flow uses this to reject stale current/thermal evidence and skipped required loads. This proves input identity, not the validity of terminal, material, loss or air assumptions. Existing summaries need a fresh solve.++## Gentle walkthrough on the ESC++<video controls preload="metadata" src="https://wiki.adom.inc/api/pages/adom/adom-fields/files/docs/gentle-walkthrough.mp4" width="100%"></video>++Recorded through AI Flow on AdomLapper, 129.8 seconds raw and 13.1 seconds at 10x. The contact sheet was inspected and the camera remained stable when drift was disabled. This is a viewer demonstration: the board still has current and thermal findings, not an independent qualification pass.
SKILL.md+38−46
@@ -1,67 +1,59 @@ ----name: adom-aiflow-description: The board loop as one command for KiCad boards on the Adom KiCad Bridge: start a measured run from the human's prompt, plan the stages and say which ones you take yourself (placement, routing) and which the binary runs (router, pours, gate, landing, measurement, current and thermal analysis), then finish only when the board is 100 percent routed, DRC-clean, poured, analysed and measured. One clock from the prompt to the finish line and one manifest per run, so Claude, Codex and any other engine compare on the same flow. Every command answers OK or ERROR with Hint lines that depend on this board and this run. Trigger words: aiflow, adom-aiflow, place and route the board, route the board to 100%, copper pours, current density, thermal analysis, finish the board, compare engines on a board, board loop, from placement to fab, kelvin keepouts, ablation copper, measured run, prompt-to-board time.+name: adom-fields+description: >-+  Solve where the current and the heat go on a KiCad board and show it on the board in 3D (Adom Fields): current density per loaded net, heat flow in each copper layer with the vias, temperature over the board in still and moving air, the nets by what they carry, the hot parts by their rise, the issues with the fix; a server the AI drives over a live channel (view, select, issue, toast, tour) for the human to explore and for the AI Flow video. Trigger words: adom-fields, fields, current density, heat map, thermal analysis, copper pour analysis, thermal vias, where does the current go, hot tab, temperature rise, IR drop, current neck, heat flow. --- -# adom-aiflow: the loop, measured+# Adom Fields -This tool is the place-route-pour-analyze skill made executable: the step order is a file, the gates refuse, the hints arrive as the answer to the command you just ran, and the ledger records what you did. Follow the commands and the skill follows itself.+One binary, three commands, one app. The AI does the analysis; the human explores the board; the AI drives the same view on camera. -Install: `adom-wiki pkg install adom/adom-aiflow` (one Rust binary; `service-kicad` for the offline DRC gate, and the KiCad Bridge on a desktop for the live stages). `adom-aiflow --version`.+## Solve -Every state-changing command takes `--ai-thread "<your thread name>"`. Every run lives in one directory (`--run DIR`, default `./aiflow-run`) with a `run.json` manifest: the clock, the plan, the decisions, every stage's start and end, the outcomes, the capture markers and the summary. The manifest is the comparison; nothing counts that is not in it.+```+adom-fields analyze --board <board.kicad_pcb> --spec <spec.json> --out fields+``` -## The flow+The board must be saved with its zones filled (the filled polygons are the copper). The spec is the AI Flow spec: `loads` (net: amps, maxRiseC), `hot` (ref: watts, tabNet), `copperUm`, `hotMaxRiseC`, `thermalVias` (pad, count), optional `stackupMm` (three dielectric thicknesses). About two minutes for a 65 x 75 mm four-layer board at the default 0.2 mm cell; `--pitch 0.1` is four times longer and sharper. -The flow is a file, `flows/board.json`: the steps in order (intake, models, placement, routing, pours, current, thermal, nets, 3d, capture, finish), who does each, what the binary offers at each, and the steps that come later (components, libraries, schematic, simulation before the board; moleculize, paste, probe after it). `models` (kicad_model_check) refuses to go on while any footprint lacks its 3D model, so the board renders as it will be built. `nets` (`tour nets`) selects each key net as a whole through the IPC API, pours, tracks, vias and pads together, and frames it with Zoom to Selected Objects, one net at a time on camera. `3d` (`tour 3d`) opens the 3D Viewer and runs the walkthrough by View-menu commands (kicad_3d_view: fit, top, tilt, orbit, zoom, swing, front, bottom, flip), no mouse, with the viewer window as the step's camera. `plan` prints the flow.+What comes out, in `fields/fields.json` and `fields/tex/`:+- `nets`, ordered by amps then by peak density: peak A/mm² and where, the layers, the copper mm², the Joule loss, the vias.+- `chips`, hottest first: the rise at the tab in still and moving air, and where its watts go within 3 mm (along its layer, down its vias, through the bare dielectric, to the air), with how many vias the spec asked for.+- `issues`: `current-neck` (a loaded net over 60 A/mm², about what IPC-2221 allows at 35 µm for 10 °C), `tab-rise` (over the budget), `thermal-vias` (fewer than the spec asks), each with `at`, `layer`, `net`, `reference` and a `fix`.+- textures per layer: `J-<layer>.png` (all nets), `J-<net>-<layer>.png`, `q-<layer>.png` (heat flow, W/mm), `T-<layer>.png` and `Tm-<layer>.png` (rise, still and moving air), `copper-<layer>.png`, plus `.raw.png` 16-bit values for the readout. -1. **`start --board B.kicad_pcb --spec spec.json --engine <you> --prompt-time <UTC of the paste> [--target <box> --remote-board <path on the box>]`**: copies the board into the run and starts the clock at the prompt, not at your first command. Record the paste time honestly.-2. **`plan`**: the stages this board needs and who can take each. Placement is yours (the binary helps: `place pack --wish`, `place check --moves`, `place land --moves`). Routing is the binary's grid router by default; say `take route=ai` if you route yourself. Pours, gate, landing, measurement, both analyses and the finish are the binary's.-3. **`stage start <name>` / `stage end <name>`** around any stage you do yourself, so the clock and the manifest see it.-4. **`route [--passes N]`**: the grid router on the current board with the spec's rules (wide nets, planes, per-net vias, Kelvin taps). If it closes short, the ERROR names the pins and the blockers and the three levers: more passes, back to placement (`place pack` the parts it names or the test points and indicators in the escape band, `place check`, route again), or take the stage yourself.-5. **`gate`**: KiCad's DRC offline on the plan, inherited errors separated. Nothing lands until it passes.-6. **`land route`**, then **`pour`**, **`land vias`**, **`land pours`**: live through the bridge as native undo steps, each DRC-checked before it lands. A refused pour's ERROR says why (starved thermal reliefs, intersecting zones, clearance) and what to change in the spec. Both landings converge the live board on the plan, so you change the spec, run `pour` and land again: a via already at its planned place is not landed twice, a zone with its planned name and outline is kept, and every zone the flow landed that the plan no longer describes (renamed, reshaped, dropped) is removed first. Zones the board started with are never touched.-7. **`measure`**: KiCad's filled copper per layer, the ablation metric. **`analyze current`** and **`analyze thermal`**: IPC-2221 on the narrowest conductor of every loaded net, pour and via capacity, copper and vias at every hot tab. A loaded net whose pads all sit on one poured outer layer is carried by that layer's pour; its vias only spread heat. Hot parts take a theta ceiling (`maxThetaCPerW`) or a rise budget (`hotMaxRiseC` spec-wide, `maxRiseC` per part) against the stated watts, so write the dissipation physically (I2R at the hot Rds(on) over the conduction duty, plus switching) and say so in the spec. A FAIL names the net or part and the fix; fix it (pours, vias, widths, or placement), land, measure and analyze again.-8. **`finish`**, then **`deliver --video <mp4> --message "..."`**: `finish` refuses until the gate passed, routing and pours landed, copper measured, both analyses passed, and the live board validates at 0 unconnected with no new errors. Then it stamps the end and writes the summary: minutes from the prompt, minutes per stage, decisions, copper, live DRC. Add your token and dollar accounting to the manifest under `tokens` and `usd`.+Terminals per net: the wire pads and connectors (MC*, J*, P*) on one side; on the other the spec's hot parts on the net (a FET's tab and its source pins both count), else the Q and U parts, else the parts with real pads, each part's share by its pad area. A net with neither is skipped and says so. -## Capture, so the engines' videos line up+## Serve and show -One clip per step. `capture open` puts the board on the test box; from then on every `step <name>` stops the previous step's clip and starts this step's own window recording, tagged with the step, and its hint says what that clip should show (the flow file's `record` line: the parts landing for placement, the nets landing for routing, the pours filling for pours, the return to an earlier step when an analysis fails). `deliver` lists the clips. The final video is cut from them, one segment per step, so two engines' videos line up step for step, and the page can show a little clip beside every step's numbers.+```+adom-fields serve --fields fields --port 8874 &+``` -Per-step clips stay RAW: the recording, its 10x cut and its motion-only action cut carry no captions, because the final video (and a side-by-side of two runs) draws its own overlays from the ledger. `compose` builds the final 1920x1080 video: each step visit's 10x cut, decimated to motion, with the step chip, the engine and board, and the run and step timers as small hr / min / sec tables in the lower right (Adom faces), narrated by adom-tts from the ledger. No recording may outlive an hour (a hard cap on every recording), `finish` and `deliver` stop the running clip, and every command stops a clip that has run past twice its step's budget: a recorder left running is a mistake, not evidence.+Open `https://<slug>.adom.cloud/proxy/8874/` in pup, the browser, or a Hydrogen webview (never localhost from outside the container). The app: a 44 px header, the board in 3D on the canonical Adom 3D viewer (adom/adom-3d-viewer, embedded; ViewCube, studio lighting, zoom to the cursor), the field painted on the picked layer with the other layers faded, the vias as dots, the hot parts labelled with their rise, the legend, the readout under the cursor, the nets and hot parts on the left, the issues and the method on the right. Phone: the two side panels open from the header buttons. -`capture open` puts the board on screen the same way for every engine (foreground OpenGL on a test box we own, maximised, zoom to fit). `capture start` records the PCB editor window itself through Adom Bridge's window recorder (Windows Graphics Capture, background-capturable: whatever another thread or an updater puts in front of KiCad on a shared box does not reach the take, and nobody at the box is disturbed; the monitor is recorded only when no editor is open), every stage command writes a marker with its timestamp into the manifest, `capture mark <label>` adds yours, `capture stop` pulls the MP4 into the run. The split-screen comparison (gang-takes split mode) aligns the two engines on those markers.+## Drive it (the AI's wheel) -## The spec+```+adom-fields ui view --quantity current|heat|temperature [--layer F.Cu|In1.Cu|In2.Cu|B.Cu] [--camera top|iso|bottom|fit] [--air still|moving]+adom-fields ui select --net +VBAT        # one net lit alone (current)+adom-fields ui select --chip Q3          # fly to a hot part, heat view+adom-fields ui issue --index 0           # fly to an issue+adom-fields ui toast "20 A through a 1.5 mm neck"+adom-fields ui state | console+adom-fields tour --pause-ms 2500         # the scripted walkthrough: nets by relevance, each field, the hot parts, the issues+``` -`docs/spec-example.json` on the page is the ESC G431's: copper thickness, clearances, the inherited error count, fixed refs (the molecule interface), planes, wide and mid nets, Kelvin pairs, loads per net (amps, max rise), hot parts (watts, tab net), the pours (outline, around parts with a margin and exclusions, or explicit polygons; priorities and connection styles), solid patches, thermal and stitching vias, and the nets that are deliberately not poured. Write the spec from the schematic before you start; it is the electrical judgement, and it is what makes two engines' runs comparable.+The server: `GET /state`, `GET /console` (the UI's log), `GET /events` (SSE), `GET /fields.json`, `POST /ui/view|select|issue|toast`. State lives on the server; the UI, the CLI and the AI see the same view. -## The rules behind the stages+## In the AI Flow video -The judgement is in these skills, which this tool executes: kicad-place-route-loop (place for routability, the levers when routing cannot close, go back to placement), kicad-copper-pours (which nets get a pour and which never do, priorities, the Kelvin pair, the preflight lessons), and the tool-neutral eda-engineering skillpack (eda-end-to-end-layout, eda-kelvin-current-sense, eda-copper-ablation, eda-pour-planning-measurement, eda-thermal-bottlenecks, eda-multilayer-current-review).+Record the pup window (Adom Bridge `desktop_record_window_start` on it) while `adom-fields tour` runs: the clip is the fields step, the toasts are the captions, the AI's own analysis on the board in 3D. -## What is measured (read aiflow-measurement)+## Honest limits (0.1) -The number is the human's prompt to your "done, here is your video" (`deliver`), and the cost per step is your thinking, not the binary's seconds. Every command you run is a turn in the append-only run.jsonl with the gap before it as thinking time (`adom-aiflow ledger` reads it back); declare `step <name>` as you move, and `step <name> --back --why "..."` when a later step sends you back. `finish` is the qualified board; `deliver --video --message` ends the clock.+DC only (no skin effect, no switching loss on the copper); one copper thickness for all layers; the 1.6 mm stack is 0.2 / 1.065 / 0.2 mm unless `stackupMm` says otherwise; convection is a flat coefficient on both faces (10 or 25 W/m²K), no heatsink, no enclosure, no radiation; the tab watts come from the spec, not from a switching model. It is a screen that finds the necks and the starved tabs, and shows them; it is not a datasheet number. +## Evidence identity -## The run's own page, as it runs--`report --page <owner/slug> --user-machine <box> --push --refresh` once, early (`--surface webview` instead of a machine when you run inside Adom Hydrogen: the page then lives in a workspace tab named "AI Flow run" and reloads there). From then on every `step` and `deliver` rebuilds the run's sub-README (per-step times, returns and why, decisions, every step visit's 10x clip and its start and end screenshots, where the board stands, what was delivered, what was given back), pushes it to `docs/runs/<id>/` on that page's repo, and reloads it in the human's browser on their machine, so they watch the run progress for hours without asking. The rendered view is `https://wiki.adom.inc/<owner>/<slug>/files/docs/runs/<id>/README.md`; the same path without `files/` is raw markdown.--## Give it back--adom-aiflow is the token cache of every Adom user's flow. When the binary was wrong, rigid or missing a step (an RF board that needs length-matched differential pairs and impedance checks, say), or your own code did a step better, file it: `adom-wiki issue create adom/adom-aiflow ...` or a PR against the repo, then `adom-aiflow giveback <url>` so the run counts it. Adom processes PRs within minutes during business hours (a non-stop PR intake), and `deliver` reports how many issues and PRs the run's AI gave back.---## The code checks what you filmed--Every clip stop runs a blank check (nine sampled frames; a clip whose frames are one flat colour filmed an unpainted or covered window) and writes a contact sheet, `<clip>-sheet.png`, nine frames on one image. The stop prints the sheet's path: LOOK AT IT before the next step, the way a camera operator checks the take. A flagged clip is marked `suspect` in the manifest, shown on the run page with the reason, left out of the composed video, and `deliver` refuses while one is unresolved (`--accept-suspect` overrides, and the message must say why). `tour 3d` shows the viewer once and waits for a painted canvas before it records, because a viewer opened in the background never draws its GL canvas.--## Honesty rules--- The clock starts at the prompt. A late `start` takes `--prompt-time`.-- A stage you take yourself is stamped by you and named in the manifest as `ai`; the binary's stages are `binary`. Both go on the chart.-- `finish` is the only thing that ends a run. A run without `finish` is not a result, and its minutes are still counting.-- KiCad drops pour islands that touch nothing: a heat spreader drawn across dense routing on the other layer fills as fragments and reads small in `measure`. Put the copper where the layer is actually free (the analysis numbers say when it is not).-- 0.1's analyses are conservative heuristics (IPC-2221 for tracks, presence, connection style and vias for pours and tabs); they say so in their output. 0.2 computes cross-sections through the filled copper.+Each solve records SHA-256 identities of the board and spec in `fields.json.inputIdentity`, plus the solver version. If an input changes while solving, analysis refuses to publish the summary. AI Flow uses these identities to reject stale field screens, and requires every positive load and hot part to be represented. Existing summaries without these identities must be re-solved, not manually stamped. The identities establish which inputs were used; they do not establish the accuracy of assumed terminals, losses, materials or convection.
bin/adom-fields
⋯ 1 unchanged line ⋯
docs/gentle-walkthrough.mp4added
⋯ 1 unchanged line ⋯
install.sh+5−1
@@ -2,6 +2,10 @@ set -e HERE="$(cd "$(dirname "$0")" && pwd)" mkdir -p "$HOME/.local/bin" "$HOME/.claude/skills/adom-fields"-cp "$HERE/bin/adom-fields" "$HOME/.local/bin/adom-fields" && chmod +x "$HOME/.local/bin/adom-fields"+install -m 755 "$HERE/bin/adom-fields" "$HOME/.local/bin/.adom-fields-new"+mv -f "$HOME/.local/bin/.adom-fields-new" "$HOME/.local/bin/adom-fields" cp "$HERE/skills/adom-fields/SKILL.md" "$HOME/.claude/skills/adom-fields/SKILL.md" echo "OK: adom-fields installed at ~/.local/bin/adom-fields. Run 'adom-fields --version'."++mkdir -p "$HOME/.codex/skills/adom-fields"+cp "$HERE/skills/adom-fields/SKILL.md" "$HOME/.codex/skills/adom-fields/SKILL.md"
package.json+18−6
@@ -1,7 +1,7 @@ {   "slug": "adom-fields",   "type": "app",-  "version": "0.3.0",+  "version": "0.3.1",   "title": "Adom Fields",   "brief": "Adom Fields: where the current and the heat go on your board.",   "summary": "Current density on the copper, heat flow through the pours and the vias, and the temperature over the board, solved from the board file itself and shown on the board in 3D. The AI does the analysis and drives the view; the human orbits, zooms, hovers for the number under the cursor, and reads the nets by what they carry and the issues by what to fix. A step in Adom's AI Flow: the walkthrough goes into the video.",@@ -37,10 +37,22 @@     "fields"   ],   "sample_prompts": [-    "solve the fields of this board and show me where the 20 A goes",-    "which mosfet tab runs hottest and why",-    "walk me through the copper pours with the heat on them",-    "is any track carrying too much current"+    {+      "label": "Current paths",+      "prompt": "solve the fields of this board and show me where the 20 A goes"+    },+    {+      "label": "Hottest tab",+      "prompt": "which mosfet tab runs hottest and why"+    },+    {+      "label": "Copper heat walkthrough",+      "prompt": "walk me through the copper pours with the heat on them"+    },+    {+      "label": "Track capacity",+      "prompt": "is any track carrying too much current"+    }   ],   "hero": {     "path": "docs/hero.png"@@ -54,4 +66,4 @@     "binary": "bin/adom-fields"   },   "dependencies": {}-}\ No newline at end of file+}
page.json+18−6
@@ -1,7 +1,7 @@ {   "slug": "adom-fields",   "type": "app",-  "version": "0.3.0",+  "version": "0.3.1",   "title": "Adom Fields",   "brief": "Adom Fields: where the current and the heat go on your board.",   "summary": "Current density on the copper, heat flow through the pours and the vias, and the temperature over the board, solved from the board file itself and shown on the board in 3D. The AI does the analysis and drives the view; the human orbits, zooms, hovers for the number under the cursor, and reads the nets by what they carry and the issues by what to fix. A step in Adom's AI Flow: the walkthrough goes into the video.",@@ -37,10 +37,22 @@     "fields"   ],   "sample_prompts": [-    "solve the fields of this board and show me where the 20 A goes",-    "which mosfet tab runs hottest and why",-    "walk me through the copper pours with the heat on them",-    "is any track carrying too much current"+    {+      "label": "Current paths",+      "prompt": "solve the fields of this board and show me where the 20 A goes"+    },+    {+      "label": "Hottest tab",+      "prompt": "which mosfet tab runs hottest and why"+    },+    {+      "label": "Copper heat walkthrough",+      "prompt": "walk me through the copper pours with the heat on them"+    },+    {+      "label": "Track capacity",+      "prompt": "is any track carrying too much current"+    }   ],   "hero": {     "path": "docs/hero.png"@@ -54,4 +66,4 @@     "binary": "bin/adom-fields"   },   "dependencies": {}-}\ No newline at end of file+}
skills/adom-fields/SKILL.md+6−1
@@ -1,6 +1,7 @@ --- name: adom-fields-description: Solve where the current and the heat go on a KiCad board and show it on the board in 3D (Adom Fields): current density per loaded net, heat flow in each copper layer with the vias, temperature over the board in still and moving air, the nets by what they carry, the hot parts by their rise, the issues with the fix; a server the AI drives over a live channel (view, select, issue, toast, tour) for the human to explore and for the AI Flow video. Trigger words: adom-fields, fields, current density, heat map, thermal analysis, copper pour analysis, thermal vias, where does the current go, hot tab, temperature rise, IR drop, current neck, heat flow.+description: >-+  Solve where the current and the heat go on a KiCad board and show it on the board in 3D (Adom Fields): current density per loaded net, heat flow in each copper layer with the vias, temperature over the board in still and moving air, the nets by what they carry, the hot parts by their rise, the issues with the fix; a server the AI drives over a live channel (view, select, issue, toast, tour) for the human to explore and for the AI Flow video. Trigger words: adom-fields, fields, current density, heat map, thermal analysis, copper pour analysis, thermal vias, where does the current go, hot tab, temperature rise, IR drop, current neck, heat flow. ---  # Adom Fields@@ -52,3 +53,7 @@ Record the pup window (Adom Bridge `desktop_record_window_start` on it) while `a ## Honest limits (0.1)  DC only (no skin effect, no switching loss on the copper); one copper thickness for all layers; the 1.6 mm stack is 0.2 / 1.065 / 0.2 mm unless `stackupMm` says otherwise; convection is a flat coefficient on both faces (10 or 25 W/m²K), no heatsink, no enclosure, no radiation; the tab watts come from the spec, not from a switching model. It is a screen that finds the necks and the starved tabs, and shows them; it is not a datasheet number.++## Evidence identity++Each solve records SHA-256 identities of the board and spec in `fields.json.inputIdentity`, plus the solver version. If an input changes while solving, analysis refuses to publish the summary. AI Flow uses these identities to reject stale field screens, and requires every positive load and hot part to be represented. Existing summaries without these identities must be re-solved, not manually stamped. The identities establish which inputs were used; they do not establish the accuracy of assumed terminals, losses, materials or convection.
src/main.rs+7
@@ -1,4 +1,5 @@ //! adom-fields: where the current and the heat go on your board.+use sha2::{Digest, Sha256}; mod board; mod fields; mod raster; mod sexp; mod solve; mod tex; mod serve; mod vendor; use clap::{Parser, Subcommand}; use std::path::PathBuf;@@ -42,6 +43,8 @@ pub fn err(msg: &str, hints: &[String]) -> ! { println!("ERROR: {msg}"); for h i fn slug(s: &str) -> String { s.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect() }  pub fn run_analyze(board: &str, spec_path: &str, out: &PathBuf, pitch: f64, neck: f64) -> Result<serde_json::Value, String> {+    let board_bytes = std::fs::read(board).map_err(|e|e.to_string())?;+    let spec_bytes = std::fs::read(spec_path).map_err(|e|e.to_string())?;     let b = board::Board::load(board)?;     let spec: fields::Spec = serde_json::from_str(&std::fs::read_to_string(spec_path).map_err(|e| format!("{spec_path}: {e}"))?).map_err(|e| format!("{spec_path}: {e}"))?;     if spec.loads.is_empty() && spec.hot.is_empty() { return Err("the spec has no loads and no hot parts: nothing to solve".into()); }@@ -83,6 +86,10 @@ pub fn run_analyze(board: &str, spec_path: &str, out: &PathBuf, pitch: f64, neck     summary["log"] = serde_json::json!(lines);     summary["seconds"] = serde_json::json!((t0.elapsed().as_secs_f64() * 10.0).round() / 10.0);     summary["scales"]["jHi"] = serde_json::json!(jhi); summary["scales"]["qHi"] = serde_json::json!(qhi); summary["scales"]["tHi"] = serde_json::json!(thi);+    if std::fs::read(board).map_err(|e|e.to_string())? != board_bytes || std::fs::read(spec_path).map_err(|e|e.to_string())? != spec_bytes {+        return Err("board or spec changed during the solve; run analyze again on stable inputs".into());+    }+    summary["inputIdentity"] = serde_json::json!({"algorithm":"sha256", "board":format!("{:x}",Sha256::digest(&board_bytes)), "spec":format!("{:x}",Sha256::digest(&spec_bytes)), "solverVersion":env!("CARGO_PKG_VERSION")});     summary["source"] = serde_json::json!({"board": std::fs::canonicalize(board).map(|p| p.display().to_string()).unwrap_or(board.to_string()), "spec": spec_path});     std::fs::write(out.join("fields.json"), serde_json::to_string_pretty(&summary).unwrap()).map_err(|e| e.to_string())?;     Ok(summary)