Adom Fields
Public Made by Adomby adom
Adom Fields: where the current and the heat go on your board.
Select physical terminal pads when logical pin numbers repeat #10
physical-pad-terminal-selectors → distributed-plated-wall-couplingPower packages can have multiple physical lands with the same logical pin number. Explicit terminal assignment currently rejects those parts. Add optional board-coordinate at: [x_mm, y_mm] selectors so each physical land receives its explicitly declared current. Missing, ambiguous, wrong-net, nonfinite and duplicate physical assignments still fail; old unique-pin JSON remains compatible.
Stacked on PR #9, exact base e34bcddab94b3f14442bf511a8b6b35bcdb02237, targeting distributed-plated-wall-coupling. Four focused files: terminal resolver/tests, README, root skill and installed nested skill. Main is b53340e01ab53366b846f7062d01a014f0157ae9 (0.7.1); no flow/viewer code or shared runtime is replaced.
Validation: production cargo check --workspace --bins, 35 dependency-stack tests, release build; separately compiled the selector against current-main Board and ran all 19 current-main tests including flow tests. That compatibility check does not claim the entire unmerged solver stack is integrated with 0.7.1. Current-main preflight has no problems; every actual four-file branch diff hunk reviewed. Vendored viewer assets were supplied from the existing local build dependency.
Native board diagnostic: combined electronic-load candidate v82 has zero KiCad errors, warnings or opens. The explicit two-anode 0.2 mm solve completes for all six nets, maximum 59.04 A/mm² and diode rise 34.6°C under the stated model. This is screening evidence, not acceptance: equal anode splitting is an explicit assumption, MOSFET heat is excluded in the diode study, fine-grid/full-board/package cooling remain open. No fabricated package resistance or inferred automatic sharing is introduced. Coordinates must be rechecked after moving parts.
Please review with the dependency stack before merging/publishing. No shared installation or production qualification is claimed.
Diff Skip to comments (1)
README.md+1−1@@ -115,7 +115,7 @@ Plated through-hole pads (including circular and oblong KiCad drills) connect co ## Explicit pin currents (source candidate) -Each `loads.<net>` may supply `terminals`, an array of `{ "reference": "J1", "pad": "1", "amps": 3.0 }` rows. Positive current enters the board copper; negative current leaves it. Positive and negative totals must each equal that net's `amps`. Include sense-branch current separately instead of assigning the entire load to every narrow track. Pin identifiers must resolve uniquely on the specified net; duplicate, missing, wrong-net and unbalanced assignments are rejected. Pads omitted from this explicit boundary condition draw zero modeled current, so this is an engineering assumption to review against the circuit, including fixture return paths and auxiliary loads. Repeated pad numbers currently require a unique physical pad and are rejected when ambiguous.+Each `loads.<net>` may supply `terminals`, an array of `{ "reference": "J1", "pad": "1", "amps": 3.0 }` rows. Positive current enters the board copper; negative current leaves it. Positive and negative totals must each equal that net's `amps`. Include sense-branch current separately instead of assigning the entire load to every narrow track. Pin identifiers must resolve uniquely on the specified net; duplicate, missing, wrong-net and unbalanced assignments are rejected. Pads omitted from this explicit boundary condition draw zero modeled current, so this is an engineering assumption to review against the circuit, including fixture return paths and auxiliary loads. For repeated logical pin numbers, add `"at": [x_mm, y_mm]` using the physical pad center in board coordinates. The selector must match exactly one physical pad within 0.000001 mm, on the stated net. Coordinates must be finite; assigning the same physical pad twice is rejected even if one row omits `at`. Without `at`, ambiguous pin numbers still fail. Declare each physical branch current explicitly and justify its split from the package/circuit; this feature does not infer equal current sharing or an internal package model. Reports retain the declared pins in `terminalAssignments` and label every net as `explicit` or `heuristic`. Legacy loads without the array retain the existing terminal heuristic. A loaded net that cannot be solved now prevents publishing a partial result. No partial current injection is accepted if any nonzero terminal has no resolved copper.
SKILL.md+1−1@@ -107,7 +107,7 @@ Plated through-hole pads (including circular and oblong KiCad drills) connect co ## Explicit pin currents (source candidate) -Each `loads.<net>` may supply `terminals`, an array of `{ "reference": "J1", "pad": "1", "amps": 3.0 }` rows. Positive current enters the board copper; negative current leaves it. Positive and negative totals must each equal that net's `amps`. Include sense-branch current separately instead of assigning the entire load to every narrow track. Pin identifiers must resolve uniquely on the specified net; duplicate, missing, wrong-net and unbalanced assignments are rejected. Pads omitted from this explicit boundary condition draw zero modeled current, so this is an engineering assumption to review against the circuit, including fixture return paths and auxiliary loads. Repeated pad numbers currently require a unique physical pad and are rejected when ambiguous.+Each `loads.<net>` may supply `terminals`, an array of `{ "reference": "J1", "pad": "1", "amps": 3.0 }` rows. Positive current enters the board copper; negative current leaves it. Positive and negative totals must each equal that net's `amps`. Include sense-branch current separately instead of assigning the entire load to every narrow track. Pin identifiers must resolve uniquely on the specified net; duplicate, missing, wrong-net and unbalanced assignments are rejected. Pads omitted from this explicit boundary condition draw zero modeled current, so this is an engineering assumption to review against the circuit, including fixture return paths and auxiliary loads. For repeated logical pin numbers, add `"at": [x_mm, y_mm]` using the physical pad center in board coordinates. The selector must match exactly one physical pad within 0.000001 mm, on the stated net. Coordinates must be finite; assigning the same physical pad twice is rejected even if one row omits `at`. Without `at`, ambiguous pin numbers still fail. Declare each physical branch current explicitly and justify its split from the package/circuit; this feature does not infer equal current sharing or an internal package model. Reports retain the declared pins in `terminalAssignments` and label every net as `explicit` or `heuristic`. Legacy loads without the array retain the existing terminal heuristic. A loaded net that cannot be solved now prevents publishing a partial result. No partial current injection is accepted if any nonzero terminal has no resolved copper.
skills/adom-fields/SKILL.md+4@@ -88,3 +88,7 @@ For signal screening, provide `signals.capacitance` or `signals.differentialPair Before accepting a current screen, inspect `inputIdentity`, explicit signed `loads.<net>.terminals`, convergence for every loaded net, and `peakJDefinition`. The maximum must be the unclipped, unrounded cell maximum; the texture percentile is display-only. Use actual copper layers and an explicit dielectric stack. Missing or stale report metadata requires a fresh solve, not manual relabeling. The plated-wall model removes plated drill voids from copper and distributes coupling around the wall. Inspect `barrelCoupling`, each net's `platedConnections` (signed amps, mean barrel density and resolved perimeter fraction), and `current-barrel` issues as well as surface maxima. Refine the mesh near holes; a cell center within a hole may still represent a partly copper-covered cell, but a wholly copper-free cell must not carry surface current. Nonzero drill offsets are unsupported. These remain screening approximations, not a package/heatsink model or physical qualification. See the package's `docs/plated-wall-coupling.md`.++## Physical pad selectors for repeated pin numbers++Explicit `loads.<net>.terminals` rows accept optional `"at": [x_mm, y_mm]` in board coordinates. Use it when a package has multiple physical pads with the same reference and logical pin number, such as two anode lands. The center must uniquely match within 0.000001 mm on the stated net; missing, ambiguous, nonfinite, wrong-net or duplicate physical assignments fail. Without `at`, the existing unique-pin behavior is unchanged. Positive and negative current totals must each equal the net load. State and justify the branch split; selecting physical pads does not infer equal sharing or model internal package resistance. Recheck coordinates after any component movement.
src/terminals.rs+41−5@@ -9,6 +9,9 @@ pub struct Terminal { pub reference: String, pub pad: String, pub amps: f64,+ /// Optional board-space pad center in mm, for repeated logical pin numbers.+ #[serde(default, skip_serializing_if = "Option::is_none")]+ pub at: Option<[f64; 2]>, } pub fn resolve<'a>(board: &'a Board, net: &str, amps: f64, rows: &[Terminal])@@ -19,16 +22,22 @@ pub fn resolve<'a>(board: &'a Board, net: &str, amps: f64, rows: &[Terminal]) let mut result = Vec::new(); let (mut positive, mut negative) = (0.0, 0.0); for t in rows {- if !t.amps.is_finite() || !seen.insert((&t.reference, &t.pad)) {+ if !t.amps.is_finite() || t.at.is_some_and(|xy| xy.iter().any(|v| !v.is_finite())) { return Err(format!("{net}: terminal currents must be finite and pin assignments unique")); }- let pads: Vec<_> = board.pads.iter().filter(|p| p.reference == t.reference && p.pad == t.pad).collect();- if pads.len() != 1 || pads[0].net != net {+ let pads: Vec<_> = board.pads.iter().enumerate().filter(|(_, p)| {+ p.reference == t.reference && p.pad == t.pad+ && t.at.is_none_or(|[x, y]| (p.x - x).abs() <= 1e-6 && (p.y - y).abs() <= 1e-6)+ }).collect();+ if pads.len() != 1 || pads[0].1.net != net { return Err(format!("{net}: {}.{} must identify exactly one pad on this net", t.reference, t.pad)); }+ if !seen.insert(pads[0].0) {+ return Err(format!("{net}: physical pad assigned more than once"));+ } positive += t.amps.max(0.0); negative += (-t.amps).max(0.0);- result.push((pads[0], t.amps));+ result.push((pads[0].1, t.amps)); } let tolerance = 1e-9_f64.max(amps.abs() * 1e-8); if !amps.is_finite() || amps < 0.0 || (positive - amps).abs() > tolerance || (negative - amps).abs() > tolerance {@@ -43,7 +52,7 @@ mod tests { fn board() -> Board { Board { pads: [("J1", "1", "V"), ("F1", "1", "V"), ("R1", "1", "V"), ("J2", "1", "GND")].into_iter().map(|(r,p,n)| Pad { reference:r.into(), pad:p.into(), net:n.into(), x:0., y:0., layers:vec!["F.Cu".into()], area:1. }).collect(), ..Board::default() } }- fn row(reference: &str, amps: f64) -> Terminal { Terminal { reference:reference.into(), pad:"1".into(), amps } }+ fn row(reference: &str, amps: f64) -> Terminal { Terminal { reference:reference.into(), pad:"1".into(), amps, at: None } } #[test] fn main_and_sense_branches_preserve_assigned_currents() { let b=board(); let r=resolve(&b,"V",3.,&[row("J1",3.),row("F1",-2.9999),row("R1",-0.0001)]).unwrap();@@ -59,4 +68,31 @@ mod tests { let mut b=board(); b.pads.push(b.pads[0].clone()); assert!(resolve(&b,"V",3.,&[row("J1",3.),row("F1",-3.)]).is_err()); }+ #[test] fn repeated_pin_physical_pads_require_explicit_centers() {+ let mut b=board();+ let mut second=b.pads[1].clone(); second.x=2.; b.pads.push(second);+ let mut first=row("F1",-1.5); first.at=Some([0.,0.]);+ let mut second=row("F1",-1.5); second.at=Some([2.,0.]);+ let resolved=resolve(&b,"V",3.,&[row("J1",3.),first.clone(),second.clone()]).unwrap();+ assert_eq!(resolved[1].0.x,0.); assert_eq!(resolved[2].0.x,2.);+ assert!(resolve(&b,"V",3.,&[row("J1",3.),first.clone(),first.clone()]).is_err());+ for at in [Some([1.,0.]),Some([f64::NAN,0.]),None] {+ second.at=at;+ assert!(resolve(&b,"V",3.,&[row("J1",3.),first.clone(),second.clone()]).is_err());+ }+ }+ #[test] fn selector_cannot_bypass_wrong_net_or_duplicate_assignment() {+ let b=board(); let mut wrong=row("J2",-3.);wrong.at=Some([0.,0.]);+ assert!(resolve(&b,"V",3.,&[row("J1",3.),wrong]).is_err());+ let mut selected=row("F1",-1.5);selected.at=Some([0.,0.]);+ assert!(resolve(&b,"V",3.,&[row("J1",3.),row("F1",-1.5),selected]).is_err());+ }+ #[test] fn old_json_remains_valid_and_new_selector_roundtrips() {+ let old:Terminal=serde_json::from_str(r#"{"reference":"F1","pad":"1","amps":-3}"#).unwrap();+ assert!(old.at.is_none());+ let mut new=old;new.at=Some([111.138,90.08]);+ let restored:Terminal=serde_json::from_str(&serde_json::to_string(&new).unwrap()).unwrap();+ assert_eq!(restored.at,new.at);+ }+ }
Comments
Log in to comment.