<!-- Sema documentation — magna-ev-digital-twin
     Sema is a new AI-native, neurosymbolic programming language most models
     have not been trained on. Full corpus: https://sema.cortex.a2olabs.com/llms-full.txt
     Install language support: https://sema.cortex.a2olabs.com/install-skill/ -->

# magna-ev-digital-twin

> The magna-ev-digital-twin worked example.

> The magna-ev-digital-twin worked example.

Run it from `sema/`:

```bash
sema check examples/magna-ev-digital-twin
SEMA_STRICT=1 sema run examples/magna-ev-digital-twin
sema assure examples/magna-ev-digital-twin --grade silver
```

## Source

### `src/main.sema`

```sema
"""CircuitFrame Lab automotive digital-twin entrypoint."""

from magna_ev_digital_twin.aero import aero_solve
from magna_ev_digital_twin.live import serve_live
from magna_ev_digital_twin.physics import physics_context
from magna_ev_digital_twin.vehicle import completeness_report, vehicle_identity

assure silver


args CircuitFrameCli:
    live: bool = flag("--live")
    port: int = option("--port", default=8794)


def main() !{net.listen}:
    identity = vehicle_identity()
    report = completeness_report("circuitframe")
    ensure not report.major_assembly_complete and not report.production_complete
    if CircuitFrameCli.live:
        serve_live(CircuitFrameCli.port)
        return
    context = physics_context()
    head_on = aero_solve(context.basis, 27.8, 0.0)
    yawed = aero_solve(context.basis, 27.8, 0.34906585039886593)
    print(
        "configuration=" + identity.id
        + " revision=" + str(identity.revision)
        + " profile=" + report.profile
        + " parts=" + str(report.declared_parts)
        + " instances=" + str(report.declared_instances)
        + " nets=" + str(report.declared_nets)
        + " operations=" + str(report.declared_operations)
        + " major_assembly_complete=" + str(report.major_assembly_complete)
        + " production_complete=" + str(report.production_complete)
    )
    print(
        "aero panels=" + str(context.basis.panel_count)
        + " cd0=" + str(head_on.cd)
        + " cd20=" + str(yawed.cd)
        + " cy20=" + str(yawed.cy)
        + " drag_n=" + str(head_on.drag_n)
        + " solver_residual=" + str(head_on.solver_residual)
        + " closure_residual=" + str(head_on.force_closure_residual)
    )
    print(
        "surrogate calm_rms=" + str(context.calm.fit_rms_cd)
        + " crosswind_rms=" + str(context.crosswind.fit_rms_cd)
        + " active_validation=" + str(context.holdout.active_validation_error)
        + " candidate_validation=" + str(context.holdout.candidate_validation_error)
        + " mass_kg=" + str(context.parameters.mass_kg)
        + " izz=" + str(context.parameters.yaw_inertia_kg_m2)
        + " cg_height_m=" + str(context.parameters.cg_height_m)
    )
```

### `src/adaptation.sema`

```sema
"""Automotive adaptive-aerodynamics lifecycle over Sema standard-library gates."""

from std.adaptive_dynamics import ActivationDecision, ModelDescriptor, ModelLifecycle, RegimeStatus, ResidualDetector, SelectionPolicy, activate_validated, make_detector, update_detector, validate_candidate
from std.epistemic import Assumption, Evidence, ValidityRegion

assure silver


pub struct AeroAdaptationSummary:
    active_model_id: str
    candidate_model_id: str
    selected_model_id: str
    lifecycle: str
    regime: str
    mode: str
    validation_status: str
    detector_score: f64
    training_error: f64
    validation_error: f64
    invariant_violations: int
    dwell_steps: int
    hysteresis_margin: f64
    activated: bool
    reason: str
    evidence_ids: list[str]
    invariant len(active_model_id) > 0
    invariant len(candidate_model_id) > 0
    invariant len(selected_model_id) > 0
    invariant detector_score >= 0.0
    invariant training_error >= 0.0
    invariant validation_error >= 0.0
    invariant invariant_violations >= 0
    invariant dwell_steps >= 0
    invariant hysteresis_margin >= 0.0
    invariant len(reason) > 0
    invariant len(evidence_ids) <= 32


pub def initial_aero_detector() -> ResidualDetector !{}:
    return make_detector(32, 4, 1.5, 3, 0.01, 0.01)


pub def update_aero_detector(detector: ResidualDetector, residual: f64, observed_at: f64) -> ResidualDetector !{}:
    return update_detector(detector, residual, observed_at)


pub def regime_label(detector: ResidualDetector) -> str !{}:
    if detector.status == RegimeStatus.stable:
        return "stable"
    if detector.status == RegimeStatus.suspected:
        return "suspected"
    if detector.status == RegimeStatus.shifted:
        return "shifted"
    return "unknown"


pub def retained_adaptation(detector: ResidualDetector, active_model_id: str) -> AeroAdaptationSummary !{}:
    mode = "safe_unknown" if detector.status == RegimeStatus.unknown or detector.status == RegimeStatus.suspected else "retain"
    reason = "residual history is not yet decisive" if mode == "safe_unknown" else "active aerodynamic model remains inside the observed regime"
    return AeroAdaptationSummary(
        active_model_id=active_model_id,
        candidate_model_id="aero-rom-crosswind-v2",
        selected_model_id=active_model_id,
        lifecycle="active",
        regime=regime_label(detector),
        mode=mode,
        validation_status="pending" if mode == "safe_unknown" else "not_required",
        detector_score=detector.score,
        training_error=0.0,
        validation_error=0.0,
        invariant_violations=0,
        dwell_steps=12,
        hysteresis_margin=0.01,
        activated=false,
        reason=reason,
        evidence_ids=[],
    )


pub def evaluate_yaw_candidate(detector: ResidualDetector, step: int, observed_at: f64, speed_mps: f64, sideslip_deg: f64, holdout: dict[str, f64]) -> AeroAdaptationSummary !{}:
    require detector.status == RegimeStatus.shifted
    require step >= 1 and observed_at >= 0.0 and speed_mps >= 0.0
    evidence_id = "panel-solve-yaw-holdout-v1"
    policy = SelectionPolicy(min_validation_improvement=0.25, max_validation_error=0.04, max_invariant_violations=0, min_dwell_steps=12, hysteresis_margin=0.01, required_horizon_steps=12)
    if speed_mps > 55.0 or abs(sideslip_deg) > 35.0:
        return AeroAdaptationSummary(active_model_id="aero-rom-calm-v1", candidate_model_id="aero-rom-crosswind-v2", selected_model_id="aero-rom-calm-v1", lifecycle="proposed", regime="shifted", mode="safe_unknown", validation_status="blocked_out_of_scope", detector_score=detector.score, training_error=holdout["candidate_training_error"], validation_error=holdout["candidate_validation_error"], invariant_violations=int(holdout["invariant_violations"]), dwell_steps=policy.min_dwell_steps, hysteresis_margin=policy.hysteresis_margin, activated=false, reason="operating point is outside the declared speed and sideslip validity region", evidence_ids=[])
    distance_to_boundary = min(55.0 - speed_mps, 35.0 - abs(sideslip_deg))
    validity = ValidityRegion(label="speed 0-55 m/s; absolute apparent-wind sideslip <= 35 deg", in_scope=true, distance_to_boundary=distance_to_boundary, checked_at=observed_at)
    assumption = Assumption(id="bounded-yaw-envelope", statement="Both reduced-order surrogates are fitted to and scored against the live source-panel solve inside the declared sideslip window; the panel solve itself carries an empirical viscous and base-pressure closure, not a governing CFD field", active=true, checked_at=observed_at, evidence_ids=[evidence_id])
    active = ModelDescriptor(id="aero-rom-calm-v1", family="vehicle-aero-reduced", version=1, lifecycle=ModelLifecycle.active, parameter_names=["C_d0", "c_2"], parameters=[0.23, 0.48], structure_signature="quadratic-in-sideslip drag fitted over 0-8 deg", assumptions=[assumption], validity=validity, fit_error=holdout["active_training_error"], validation_error=holdout["active_validation_error"], created_at=0.0, activated_step=0)
    candidate = ModelDescriptor(id="aero-rom-crosswind-v2", family="vehicle-aero-reduced", version=2, lifecycle=ModelLifecycle.proposed, parameter_names=["C_d0", "c_2", "c_4"], parameters=[0.23, 0.90, -2.19], structure_signature="quartic-in-sideslip drag and cubic side force fitted over 0-35 deg", assumptions=[assumption], validity=validity, fit_error=holdout["candidate_training_error"], validation_error=holdout["candidate_validation_error"], created_at=observed_at, activated_step=0)
    evidence = Evidence(id=evidence_id, source="CircuitFrame source-panel solve swept over sideslip", observed_at=observed_at, reliability=0.72, summary="Training stations at 4 and 8 deg and held-out stations at 22 and 32 deg of apparent-wind sideslip, scored against the live panel solve; this is reduced-model evidence, not governing CFD", provenance=["aero.sema", "physics.sema", evidence_id])
    validation = validate_candidate(active, candidate, holdout["candidate_training_error"], holdout["candidate_validation_error"], int(holdout["invariant_violations"]), [evidence], observed_at, policy)
    decision = activate_validated(active, validation.model, step, step, [evidence], policy)
    match decision:
        case ActivationDecision.activated(previous, current, transition):
            return AeroAdaptationSummary(active_model_id=previous.id, candidate_model_id=candidate.id, selected_model_id=current.id, lifecycle="active", regime="shifted", mode="structural_switch", validation_status="passed_reduced_analytic", detector_score=detector.score, training_error=validation.training_error, validation_error=validation.validation_error, invariant_violations=validation.invariant_violations, dwell_steps=policy.min_dwell_steps, hysteresis_margin=policy.hysteresis_margin, activated=true, reason=transition.reason, evidence_ids=[evidence_id])
        case ActivationDecision.blocked(reason, transition):
            return AeroAdaptationSummary(active_model_id=active.id, candidate_model_id=candidate.id, selected_model_id=active.id, lifecycle="validated_not_active", regime="shifted", mode="safe_unknown", validation_status="passed_reduced_analytic", detector_score=detector.score, training_error=validation.training_error, validation_error=validation.validation_error, invariant_violations=validation.invariant_violations, dwell_steps=policy.min_dwell_steps, hysteresis_margin=policy.hysteresis_margin, activated=false, reason=reason, evidence_ids=[evidence_id])
```

### `src/aero.sema`

```sema
"""Vehicle aerodynamics by a 3D constant-strength source-panel method with an image ground plane.

Method: Hess-Smith constant-strength source panels over a closed lofted body, with every panel
mirrored through the road surface (z = 0) so the ground boundary condition is satisfied exactly.
The Neumann condition (zero normal velocity on every panel) is closed by two dense linear solves,
one per unit freestream direction; the boundary-value problem is linear in the freestream, so
sigma(V, beta) = V (cos beta sigma_x + sin beta sigma_y) is exact for every wind speed and yaw and
no matrix work is needed per frame.

Honesty: this is NOT a CFD run and no Navier-Stokes equation is integrated. Potential flow alone
yields exactly zero drag and zero side force on a closed body (d'Alembert's paradox), so every
drag and side-force number here comes from the empirical viscous and base-pressure closure layered
on top of the solved pressure field. Each published quantity is tagged `solved` (out of the linear
system), `correlated` (empirical closure with a cited form), or `numerical` (a discretisation
diagnostic). See `aero_equations()` for the per-quantity provenance.
"""

import math

assure silver


NOSE_X_M = 2.31
TAIL_X_M = -2.44
LENGTH_M = 4.751
HALF_WIDTH_M = 0.9605
HEIGHT_M = 1.624
AXLE_X_M = 1.445
RIDE_HEIGHT_M = 0.167
FRONTAL_AREA_M2 = 2.48
SIDE_AREA_M2 = 4.32
PLANFORM_AREA_M2 = 7.95
WETTED_AREA_M2 = 19.6
AIR_DENSITY_KG_M3 = 1.225
AIR_VISCOSITY_PA_S = 0.0000181

RING_COUNT = 21
AROUND = 12
PANEL_COUNT = 240
HALF_PANEL_COUNT = 120
HALF_AROUND = 6
SECTION_EXPONENT = 3.2
REGULARISATION_FACTOR = 0.5
FOUR_PI = 12.566370614359172

NOSE_APEX_Z_M = 0.46
HOOD_Z_M = 0.97
HOOD_END_U = 0.28
ROOF_START_U = 0.46
FASTBACK_START_U = 0.54
TAIL_TOP_Z_M = 0.98
DIFFUSER_Z_M = 0.50
WIDTH_FULL_U = 0.38
TAPER_START_U = 0.48
TAPER_DEPTH = 0.26
NOSE_FULLNESS = 2.2
TUMBLEHOME = 0.30
SILL_TUCK = 0.20

STRATFORD_LONGITUDINAL = 0.24
STRATFORD_CROSSFLOW = 0.60
SIDE_LINE_NORMAL_Z = -0.6
BASE_PRESSURE_CP = 0.20
BASE_PRESSURE_YAW_GAIN = 0.80
CROSSFLOW_BASE_CP = -1.00
WHEEL_CD = 0.036947
WHEEL_CD_YAW_GAIN = 0.60
CALIBRATION_SPEED_MPS = 27.8
CALIBRATION_TARGET_CD = 0.23
STROUHAL = 0.20
CP_FLOOR = -4.0


pub struct AeroPanel:
    index: int
    station: int
    ring_index: int
    center_x: f64
    center_y: f64
    center_z: f64
    normal_x: f64
    normal_y: f64
    normal_z: f64
    area_m2: f64
    invariant index >= 0 and index < 4096
    invariant station >= 0 and station < 64
    invariant ring_index >= 0 and ring_index < 64
    invariant area_m2 > 0.0
    invariant center_z > 0.0 and center_z < 3.0
    invariant center_x >= -3.0 and center_x <= 3.0
    invariant abs(normal_x * normal_x + normal_y * normal_y + normal_z * normal_z - 1.0) < 0.000001


pub struct AeroBasis:
    panels: list[AeroPanel]
    source_x: list[f64]
    source_y: list[f64]
    mode_x: list[f64]
    mode_y: list[f64]
    wake_forward: list[f64]
    wake_forward_cp: list[f64]
    wake_reverse: list[f64]
    wake_reverse_cp: list[f64]
    wake_crossflow: list[f64]
    wake_crossflow_cp: list[f64]
    base_face: list[f64]
    panel_count: int
    wetted_area_m2: f64
    frontal_area_m2: f64
    side_area_m2: f64
    planform_area_m2: f64
    closure_residual_m2: f64
    residual_x: f64
    residual_y: f64
    separation_x_m: f64
    calibrated_wheel_cd: f64
    calibration_speed_mps: f64
    calibration_target_cd: f64
    invariant panel_count == PANEL_COUNT
    invariant len(panels) == PANEL_COUNT
    invariant len(source_x) == PANEL_COUNT and len(source_y) == PANEL_COUNT
    invariant len(mode_x) == 3 * PANEL_COUNT and len(mode_y) == 3 * PANEL_COUNT
    invariant len(wake_forward) == PANEL_COUNT and len(wake_crossflow) == PANEL_COUNT
    invariant len(base_face) == PANEL_COUNT
    invariant wetted_area_m2 > 0.0 and frontal_area_m2 > 0.0
    invariant closure_residual_m2 >= 0.0 and closure_residual_m2 < 0.000001
    invariant residual_x >= 0.0 and residual_x < 0.00000001
    invariant residual_y >= 0.0 and residual_y < 0.00000001
    invariant separation_x_m >= TAIL_X_M and separation_x_m <= NOSE_X_M
    invariant calibrated_wheel_cd > 0.0
    invariant calibration_speed_mps > 0.0
    invariant calibration_target_cd > 0.0


pub struct AeroForces:
    air_speed_mps: f64
    yaw_rad: f64
    dynamic_pressure_pa: f64
    reynolds_number: f64
    cd: f64
    cd_pressure: f64
    cd_base: f64
    cd_friction: f64
    cd_wheels: f64
    cy: f64
    cl: f64
    cmz: f64
    drag_n: f64
    side_n: f64
    lift_n: f64
    yaw_moment_nm: f64
    front_lift_n: f64
    rear_lift_n: f64
    pressure_drag_n: f64
    cp_min: f64
    cp_max: f64
    base_pressure_coefficient: f64
    separation_x_m: f64
    wake_width_m: f64
    wake_deficit_fraction: f64
    wake_shedding_hz: f64
    turbulence_intensity: f64
    wake_recirculation_length_m: f64
    dalembert_residual_cd: f64
    solver_residual: f64
    force_closure_residual: f64
    surface_cp: list[f64]
    invariant air_speed_mps >= 0.0
    invariant dynamic_pressure_pa >= 0.0
    invariant reynolds_number >= 0.0
    invariant cd_friction > 0.0
    invariant cd_wheels > 0.0
    invariant abs(cd - (cd_pressure + cd_base + cd_friction + cd_wheels)) < 0.000000001
    invariant cp_min >= CP_FLOOR and cp_min <= 1.0
    invariant cp_max >= CP_FLOOR and cp_max <= 1.0
    invariant cp_min <= cp_max
    invariant base_pressure_coefficient < 0.0
    invariant separation_x_m >= TAIL_X_M and separation_x_m <= NOSE_X_M
    invariant wake_width_m >= 0.0
    invariant wake_deficit_fraction >= 0.0 and wake_deficit_fraction <= 1.0
    invariant wake_shedding_hz >= 0.0
    invariant turbulence_intensity >= 0.0 and turbulence_intensity <= 1.0
    invariant wake_recirculation_length_m > 0.0
    invariant solver_residual >= 0.0
    invariant force_closure_residual >= 0.0
    invariant len(surface_cp) == PANEL_COUNT


def smooth_step(low: f64, high: f64, value: f64):
    """Cubic Hermite ramp, clamped to [0, 1] outside the interval."""
    span = max(0.000001, high - low)
    u = min(1.0, max(0.0, (value - low) / span))
    return u * u * (3.0 - 2.0 * u)


def roof_height(u: f64):
    """Upper silhouette: low pointed nose, hood, raked windscreen, roof peak, fastback taper."""
    return (NOSE_APEX_Z_M
            + (HOOD_Z_M - NOSE_APEX_Z_M) * smooth_step(0.0, HOOD_END_U, u)
            + (HEIGHT_M - HOOD_Z_M) * smooth_step(HOOD_END_U, ROOF_START_U, u)
            - (HEIGHT_M - TAIL_TOP_Z_M) * smooth_step(FASTBACK_START_U, 1.0, u))


def floor_height(u: f64):
    """Lower silhouette: approach ramp, flat underbody at ride height, rear diffuser."""
    return (RIDE_HEIGHT_M
            + (NOSE_APEX_Z_M - RIDE_HEIGHT_M) * (1.0 - smooth_step(0.0, 0.09, u))
            + (DIFFUSER_Z_M - RIDE_HEIGHT_M) * smooth_step(0.86, 1.0, u))


def half_width(u: f64):
    """Plan-view half width: blunt nose growth to full width, mild fastback taper aft."""
    grow = 1.0 - min(1.0, u / WIDTH_FULL_U)
    front = math.pow(max(0.0, 1.0 - grow * grow), 1.0 / NOSE_FULLNESS)
    aft = max(0.0, u - TAPER_START_U) / (1.0 - TAPER_START_U)
    return HALF_WIDTH_M * front * (1.0 - TAPER_DEPTH * math.pow(aft, 2.4))


def station_ring(u: f64, x_m: f64):
    """One superelliptic cross-section as AROUND flattened x,y,z points, mirror symmetric in y."""
    width = half_width(u)
    low = floor_height(u)
    high = roof_height(u)
    middle = 0.5 * (low + high)
    reach = 0.5 * (high - low)
    mut points = []
    for k in range(0, AROUND):
        angle = 0.5 * math.pi - 2.0 * math.pi * float(k) / float(AROUND)
        across = math.cos(angle)
        upward = math.sin(angle)
        height_shape = math.pow(abs(upward), 2.0 / SECTION_EXPONENT)
        width_shape = math.pow(abs(across), 2.0 / SECTION_EXPONENT)
        if upward >= 0.0:
            edge = max(0.0, (height_shape - 0.35) / 0.65)
            narrow = 1.0 - TUMBLEHOME * edge * edge
        else:
            edge = max(0.0, (height_shape - 0.30) / 0.70)
            narrow = 1.0 - SILL_TUCK * edge * edge
        y = width * narrow * width_shape
        if across < 0.0:
            y = 0.0 - y
        z = middle + reach * height_shape
        if upward < 0.0:
            z = middle - reach * height_shape
        points = points + [x_m, y, z]
    return points


def panel_mesh():
    """Closed lofted body: two degenerate end caps plus RING_COUNT - 3 superelliptic stations.

    Panels are ordered index = station * AROUND + ring_index, and ring_index k mirrors to
    AROUND - 1 - k through the y = 0 plane, which the symmetry-reduced solve relies on.
    """
    span = NOSE_X_M - TAIL_X_M
    middle_x = 0.5 * (NOSE_X_M + TAIL_X_M)
    mut rings = []
    for r in range(0, RING_COUNT):
        x = middle_x + 0.5 * span * math.cos(math.pi * float(r) / float(RING_COUNT - 1))
        u = (NOSE_X_M - x) / span
        if r == 0:
            mut apex = []
            for k in range(0, AROUND):
                apex = apex + [NOSE_X_M, 0.0, NOSE_APEX_Z_M]
            rings = rings + [apex]
        elif r == RING_COUNT - 1:
            centre = 0.5 * (floor_height(1.0) + roof_height(1.0))
            mut tail = []
            for k in range(0, AROUND):
                tail = tail + [TAIL_X_M, 0.0, centre]
            rings = rings + [tail]
        else:
            rings = rings + [station_ring(u, x)]
    mut panels = []
    for r in range(0, RING_COUNT - 1):
        near = rings[r]
        far = rings[r + 1]
        for k in range(0, AROUND):
            j = 3 * k
            m = 3 * ((k + 1) % AROUND)
            ax = far[m] - near[j]
            ay = far[m + 1] - near[j + 1]
            az = far[m + 2] - near[j + 2]
            bx = far[j] - near[m]
            by = far[j + 1] - near[m + 1]
            bz = far[j + 2] - near[m + 2]
            vx = 0.5 * (ay * bz - az * by)
            vy = 0.5 * (az * bx - ax * bz)
            vz = 0.5 * (ax * by - ay * bx)
            area = math.sqrt(vx * vx + vy * vy + vz * vz)
            panels = panels + [AeroPanel(
                index=r * AROUND + k,
                station=r,
                ring_index=k,
                center_x=0.25 * (near[j] + near[m] + far[m] + far[j]),
                center_y=0.25 * (near[j + 1] + near[m + 1] + far[m + 1] + far[j + 1]),
                center_z=0.25 * (near[j + 2] + near[m + 2] + far[m + 2] + far[j + 2]),
                normal_x=vx / area,
                normal_y=vy / area,
                normal_z=vz / area,
                area_m2=area,
            )]
    return panels


def influence_blocks(tx: list[f64], ty: list[f64], tz: list[f64], sx: list[f64], sy: list[f64],
                     sz: list[f64], area: list[f64], epsilon: list[f64]) -> any:
    """Velocity induced at every target by unit constant sources plus their z = 0 ground images.

    Returns the three dense component matrices [Kx, Ky, Kz]; the near field is regularised with
    r_eps = REGULARISATION_FACTOR * sqrt(area_j) exactly as the browser-side field evaluator does.
    """
    target_ones = [1.0] * len(tx)
    source_ones = [1.0] * len(sx)
    equation:
        tgx := outer(tx, source_ones)
        tgy := outer(ty, source_ones)
        tgz := outer(tz, source_ones)
        srx := outer(target_ones, sx)
        sry := outer(target_ones, sy)
        srz := outer(target_ones, sz)
        strength := outer(target_ones, area)
        guard := outer(target_ones, epsilon)
    dx = tgx - srx
    dy = tgy - sry
    dz = tgz - srz
    dzi = tgz + srz
    flat = dx * dx + dy * dy
    direct = math.sqrt(flat + dz * dz)
    image = math.sqrt(flat + dzi * dzi)
    rd = where(direct > guard, direct, guard)
    ri = where(image > guard, image, guard)
    sd = strength / (FOUR_PI * rd * rd * rd)
    si = strength / (FOUR_PI * ri * ri * ri)
    return [(sd + si) * dx, (sd + si) * dy, sd * dz + si * dzi]


def normal_projection(blocks: any, nx: list[f64], ny: list[f64], nz: list[f64], columns: int):
    """Contract the three influence blocks against the target normals into one dense matrix."""
    ones = [1.0] * columns
    equation:
        gx := outer(nx, ones)
        gy := outer(ny, ones)
        gz := outer(nz, ones)
    return gx * blocks[0] + gy * blocks[1] + gz * blocks[2]


def identity_matrix(n: int):
    mut rows = []
    for i in range(0, n):
        mut row = [0.0] * n
        row[i] = 1.0
        rows = rows + [row]
    return tensor(rows)


def dense_solve(matrix: any, rhs: list[f64]):
    equation:
        answer := solve(matrix, rhs)
    mut out = []
    for i in range(0, len(rhs)):
        out = out + [float(answer[i])]
    return out


def dense_apply(matrix: any, vector: list[f64], rows: int):
    equation:
        product := matvec(matrix, vector)
    mut out = []
    for i in range(0, rows):
        out = out + [float(product[i])]
    return out


def stratford_limit(recovery: f64, run_m: f64, gradient: f64, constant: f64):
    """Stratford (1959) turbulent separation: Cbar (xi dCbar/dxi)^1/2 >= k (1e-6 Re_xi)^1/10.

    Re_xi is taken at the calibration speed so the separation topology stays geometry locked, which
    is what road vehicles show above Re_L ~ 4e6 where separation sits on fixed edges. `correlated`.
    """
    run = max(run_m, 0.0001)
    reynolds = AIR_DENSITY_KG_M3 * CALIBRATION_SPEED_MPS * run / AIR_VISCOSITY_PA_S
    return recovery * math.sqrt(max(0.0, run * gradient)) >= constant * math.pow(0.000001 * reynolds, 0.1)


def line_walk(panels: list[AeroPanel], cp: list[f64], side_line: list[f64], forward: bool):
    """March each longitudinal surface line and freeze the pressure downstream of separation.

    Returns 2 * PANEL_COUNT values: a 0/1 separation flag per panel followed by the frozen
    separation pressure coefficient carried into the wake.
    """
    stations = RING_COUNT - 1
    mut flag = [0.0] * PANEL_COUNT
    mut frozen = [0.0] * PANEL_COUNT
    for k in range(0, AROUND):
        if side_line[k] < 0.5:
            continue
        mut order = []
        for s in range(0, stations):
            if forward:
                order = order + [s * AROUND + k]
            else:
                order = order + [(stations - 1 - s) * AROUND + k]
        mut lowest = cp[order[0]]
        mut run = 0.0
        mut previous = 0.0
        for j in range(1, stations):
            here = panels[order[j]]
            back = panels[order[j - 1]]
            value = cp[order[j]]
            if value <= lowest:
                lowest = value
                run = 0.0
                previous = 0.0
                continue
            dx = here.center_x - back.center_x
            dy = here.center_y - back.center_y
            dz = here.center_z - back.center_z
            step = max(0.000001, math.sqrt(dx * dx + dy * dy + dz * dz))
            run = run + step
            recovery = (value - lowest) / (1.0 - lowest)
            gradient = max(0.0, (recovery - previous) / step)
            previous = recovery
            if stratford_limit(recovery, run, gradient, STRATFORD_LONGITUDINAL):
                wake_cp = cp[order[j - 1]]
                for m in range(j, stations):
                    flag[order[m]] = 1.0
                    frozen[order[m]] = wake_cp
                break
    return flag + frozen


def ring_walk(panels: list[AeroPanel], cp: list[f64]):
    """March each cross-section from its crossflow stagnation point and mark the leeward wake.

    Crossflow-analogy separation (Allen & Perkins): the pressure field driving it is the unit
    lateral mode alone, so the topology is yaw independent and can be precomputed once.
    """
    mut flag = [0.0] * PANEL_COUNT
    mut frozen = [0.0] * PANEL_COUNT
    for s in range(1, RING_COUNT - 2):
        mut start = 0
        for k in range(1, AROUND):
            if cp[s * AROUND + k] > cp[s * AROUND + start]:
                start = k
        for direction in range(0, 2):
            step_sign = 1
            if direction == 1:
                step_sign = -1
            mut lowest = cp[s * AROUND + start]
            mut run = 0.0
            mut previous = 0.0
            mut wake_cp = 0.0
            mut separated = false
            for d in range(1, AROUND):
                k = (start + step_sign * d + 2 * AROUND) % AROUND
                index = s * AROUND + k
                if separated:
                    flag[index] = 1.0
                    frozen[index] = wake_cp
                    continue
                previous_k = (k - step_sign + 2 * AROUND) % AROUND
                here = panels[index]
                back = panels[s * AROUND + previous_k]
                value = cp[index]
                if value <= lowest:
                    lowest = value
                    run = 0.0
                    previous = 0.0
                    continue
                dx = here.center_x - back.center_x
                dy = here.center_y - back.center_y
                dz = here.center_z - back.center_z
                step = max(0.000001, math.sqrt(dx * dx + dy * dy + dz * dz))
                run = run + step
                recovery = (value - lowest) / (1.0 - lowest)
                gradient = max(0.0, (recovery - previous) / step)
                previous = recovery
                if stratford_limit(recovery, run, gradient, STRATFORD_CROSSFLOW):
                    separated = true
                    wake_cp = min(cp[s * AROUND + previous_k], CROSSFLOW_BASE_CP)
                    flag[index] = 1.0
                    frozen[index] = wake_cp
    for s in range(0, RING_COUNT - 1):
        for k in range(0, HALF_AROUND):
            a = s * AROUND + k
            b = s * AROUND + (AROUND - 1 - k)
            if flag[a] > 0.5 or flag[b] > 0.5:
                mut wake = frozen[a]
                if flag[a] < 0.5:
                    wake = frozen[b]
                elif flag[b] > 0.5:
                    wake = min(frozen[a], frozen[b])
                flag[a] = 1.0
                flag[b] = 1.0
                frozen[a] = wake
                frozen[b] = wake
    return flag + frozen


pub def aero_basis() -> AeroBasis !{}:
    """Build the panel mesh, run both dense source solves once, and freeze the wake topology."""
    panels = panel_mesh()
    half = HALF_PANEL_COUNT
    mut hx = []
    mut hy = []
    mut hz = []
    mut hnx = []
    mut hny = []
    mut hnz = []
    mut harea = []
    mut heps = []
    mut mx = []
    mut my = []
    mut mz = []
    mut marea = []
    mut meps = []
    mut ax = []
    mut ay = []
    mut az = []
    mut anx = []
    mut any_y = []
    mut anz = []
    mut aarea = []
    mut aeps = []
    for i in range(0, PANEL_COUNT):
        p = panels[i]
        radius = REGULARISATION_FACTOR * math.sqrt(p.area_m2)
        ax = ax + [p.center_x]
        ay = ay + [p.center_y]
        az = az + [p.center_z]
        anx = anx + [p.normal_x]
        any_y = any_y + [p.normal_y]
        anz = anz + [p.normal_z]
        aarea = aarea + [p.area_m2]
        aeps = aeps + [radius]
        if p.ring_index < HALF_AROUND:
            hx = hx + [p.center_x]
            hy = hy + [p.center_y]
            hz = hz + [p.center_z]
            hnx = hnx + [p.normal_x]
            hny = hny + [p.normal_y]
            hnz = hnz + [p.normal_z]
            harea = harea + [p.area_m2]
            heps = heps + [radius]
            mx = mx + [p.center_x]
            my = my + [0.0 - p.center_y]
            mz = mz + [p.center_z]
            marea = marea + [p.area_m2]
            meps = meps + [radius]

    near = influence_blocks(hx, hy, hz, hx, hy, hz, harea, heps)
    across = influence_blocks(hx, hy, hz, mx, my, mz, marea, meps)
    plus = [near[0] + across[0], near[1] + across[1], near[2] + across[2]]
    minus = [near[0] - across[0], near[1] - across[1], near[2] - across[2]]
    unit = identity_matrix(half) * 0.5
    symmetric = normal_projection(plus, hnx, hny, hnz, half) + unit
    antisymmetric = normal_projection(minus, hnx, hny, hnz, half) + unit
    mut rhs_x = []
    mut rhs_y = []
    for h in range(0, half):
        rhs_x = rhs_x + [0.0 - hnx[h]]
        rhs_y = rhs_y + [0.0 - hny[h]]
    half_sx = dense_solve(symmetric, rhs_x)
    half_sy = dense_solve(antisymmetric, rhs_y)

    plus_x = dense_apply(plus[0], half_sx, half)
    plus_y = dense_apply(plus[1], half_sx, half)
    plus_z = dense_apply(plus[2], half_sx, half)
    minus_x = dense_apply(minus[0], half_sy, half)
    minus_y = dense_apply(minus[1], half_sy, half)
    minus_z = dense_apply(minus[2], half_sy, half)

    mut source_x = [0.0] * PANEL_COUNT
    mut source_y = [0.0] * PANEL_COUNT
    mut mode_x = [0.0] * (3 * PANEL_COUNT)
    mut mode_y = [0.0] * (3 * PANEL_COUNT)
    for h in range(0, half):
        station = h // HALF_AROUND
        k = h % HALF_AROUND
        i = station * AROUND + k
        mirror = station * AROUND + (AROUND - 1 - k)
        source_x[i] = half_sx[h]
        source_x[mirror] = half_sx[h]
        source_y[i] = half_sy[h]
        source_y[mirror] = 0.0 - half_sy[h]
        vxx = 1.0 + 0.5 * half_sx[h] * hnx[h] + plus_x[h]
        vxy = 0.5 * half_sx[h] * hny[h] + plus_y[h]
        vxz = 0.5 * half_sx[h] * hnz[h] + plus_z[h]
        vyx = 0.5 * half_sy[h] * hnx[h] + minus_x[h]
        vyy = 1.0 + 0.5 * half_sy[h] * hny[h] + minus_y[h]
        vyz = 0.5 * half_sy[h] * hnz[h] + minus_z[h]
        mode_x[3 * i] = vxx
        mode_x[3 * i + 1] = vxy
        mode_x[3 * i + 2] = vxz
        mode_x[3 * mirror] = vxx
        mode_x[3 * mirror + 1] = 0.0 - vxy
        mode_x[3 * mirror + 2] = vxz
        mode_y[3 * i] = vyx
        mode_y[3 * i + 1] = vyy
        mode_y[3 * i + 2] = vyz
        mode_y[3 * mirror] = 0.0 - vyx
        mode_y[3 * mirror + 1] = vyy
        mode_y[3 * mirror + 2] = 0.0 - vyz

    full = influence_blocks(ax, ay, az, ax, ay, az, aarea, aeps)
    operator = normal_projection(full, anx, any_y, anz, PANEL_COUNT) + identity_matrix(PANEL_COUNT) * 0.5
    check_x = dense_apply(operator, source_x, PANEL_COUNT)
    check_y = dense_apply(operator, source_y, PANEL_COUNT)
    mut residual_x = 0.0
    mut residual_y = 0.0
    mut wetted = 0.0
    mut frontal = 0.0
    mut lateral = 0.0
    mut planform = 0.0
    mut sum_ax = 0.0
    mut sum_ay = 0.0
    mut sum_az = 0.0
    mut cp_axial = [0.0] * PANEL_COUNT
    mut cp_crossflow = [0.0] * PANEL_COUNT
    mut base_face = [0.0] * PANEL_COUNT
    for i in range(0, PANEL_COUNT):
        p = panels[i]
        residual_x = max(residual_x, abs(check_x[i] + p.normal_x))
        residual_y = max(residual_y, abs(check_y[i] + p.normal_y))
        wetted = wetted + p.area_m2
        frontal = frontal + p.area_m2 * max(0.0, p.normal_x)
        lateral = lateral + p.area_m2 * max(0.0, p.normal_y)
        planform = planform + p.area_m2 * max(0.0, p.normal_z)
        sum_ax = sum_ax + p.area_m2 * p.normal_x
        sum_ay = sum_ay + p.area_m2 * p.normal_y
        sum_az = sum_az + p.area_m2 * p.normal_z
        vx = mode_x[3 * i]
        vy = mode_x[3 * i + 1]
        vz = mode_x[3 * i + 2]
        vn = vx * p.normal_x + vy * p.normal_y + vz * p.normal_z
        tx = vx - vn * p.normal_x
        ty = vy - vn * p.normal_y
        tz = vz - vn * p.normal_z
        cp_axial[i] = max(CP_FLOOR, min(1.0, 1.0 - (tx * tx + ty * ty + tz * tz)))
        wy = mode_y[3 * i + 1]
        wz = mode_y[3 * i + 2]
        cp_crossflow[i] = max(CP_FLOOR, min(1.0, 1.0 - (wy * wy + wz * wz)))
        if p.station == 0 or p.station == RING_COUNT - 2:
            base_face[i] = 1.0

    mut side_line = [0.0] * AROUND
    for k in range(0, AROUND):
        mut mean_nz = 0.0
        for s in range(0, RING_COUNT - 1):
            mean_nz = mean_nz + panels[s * AROUND + k].normal_z
        if mean_nz / float(RING_COUNT - 1) > SIDE_LINE_NORMAL_Z:
            side_line[k] = 1.0

    forward = line_walk(panels, cp_axial, side_line, true)
    reverse = line_walk(panels, cp_axial, side_line, false)
    crossflow = ring_walk(panels, cp_crossflow)
    mut wake_forward = [0.0] * PANEL_COUNT
    mut wake_forward_cp = [0.0] * PANEL_COUNT
    mut wake_reverse = [0.0] * PANEL_COUNT
    mut wake_reverse_cp = [0.0] * PANEL_COUNT
    mut wake_crossflow = [0.0] * PANEL_COUNT
    mut wake_crossflow_cp = [0.0] * PANEL_COUNT
    mut separated_area = 0.0
    mut separated_moment = 0.0
    for i in range(0, PANEL_COUNT):
        p = panels[i]
        wake_forward[i] = forward[i]
        wake_forward_cp[i] = forward[PANEL_COUNT + i]
        wake_reverse[i] = reverse[i]
        wake_reverse_cp[i] = reverse[PANEL_COUNT + i]
        wake_crossflow[i] = crossflow[i]
        wake_crossflow_cp[i] = crossflow[PANEL_COUNT + i]
        if p.station == RING_COUNT - 2:
            wake_forward[i] = 1.0
            wake_forward_cp[i] = 1.0
        if p.station == 0:
            wake_reverse[i] = 1.0
            wake_reverse_cp[i] = 1.0
        if wake_forward[i] > 0.5:
            separated_area = separated_area + p.area_m2
            separated_moment = separated_moment + p.area_m2 * p.center_x

    return AeroBasis(
        panels=panels,
        source_x=source_x,
        source_y=source_y,
        mode_x=mode_x,
        mode_y=mode_y,
        wake_forward=wake_forward,
        wake_forward_cp=wake_forward_cp,
        wake_reverse=wake_reverse,
        wake_reverse_cp=wake_reverse_cp,
        wake_crossflow=wake_crossflow,
        wake_crossflow_cp=wake_crossflow_cp,
        base_face=base_face,
        panel_count=PANEL_COUNT,
        wetted_area_m2=wetted,
        frontal_area_m2=frontal,
        side_area_m2=lateral,
        planform_area_m2=planform,
        closure_residual_m2=math.sqrt(sum_ax * sum_ax + sum_ay * sum_ay + sum_az * sum_az),
        residual_x=residual_x,
        residual_y=residual_y,
        separation_x_m=separated_moment / separated_area,
        calibrated_wheel_cd=WHEEL_CD,
        calibration_speed_mps=CALIBRATION_SPEED_MPS,
        calibration_target_cd=CALIBRATION_TARGET_CD,
    )


pub def aero_solve(basis: AeroBasis, air_speed_mps: f64, yaw_rad: f64) -> AeroForces !{}:
    """Integrate the surface pressure for one wind speed and yaw. O(panels), no matrix work.

    The apparent wind in the vehicle frame is U = -V (cos beta, sin beta, 0): air arrives at the
    nose and leaves past the tail at zero yaw, and beta > 0 puts the wind on the vehicle's left.
    The solved Cp field is invariant under freestream reversal, so it is evaluated straight from
    the exported +x and +y modes; only the viscous closure needs the physical flow direction.
    """
    speed = max(0.0, air_speed_mps)
    cb = math.cos(yaw_rad)
    sb = math.sin(yaw_rad)
    q = 0.5 * AIR_DENSITY_KG_M3 * speed * speed
    base_cp = 0.0 - (BASE_PRESSURE_CP + BASE_PRESSURE_YAW_GAIN * abs(sb))
    weight_forward = max(0.0, cb)
    weight_reverse = max(0.0, 0.0 - cb)
    weight_cross = abs(sb)
    lee = 0.0
    if sb > 0.0:
        lee = 1.0
    if sb < 0.0:
        lee = -1.0
    across_x = 0.0 - sb
    across_y = cb

    panels = basis.panels
    mode_x = basis.mode_x
    mode_y = basis.mode_y
    forward_flag = basis.wake_forward
    forward_cp = basis.wake_forward_cp
    reverse_flag = basis.wake_reverse
    reverse_cp = basis.wake_reverse_cp
    cross_flag = basis.wake_crossflow
    cross_cp = basis.wake_crossflow_cp
    base_flag = basis.base_face
    mut surface_cp = [0.0] * basis.panel_count
    mut potential_x = 0.0
    mut potential_y = 0.0
    mut potential_z = 0.0
    mut face_x = 0.0
    mut face_y = 0.0
    mut face_z = 0.0
    mut shear_x = 0.0
    mut shear_y = 0.0
    mut shear_z = 0.0
    mut closure_x = 0.0
    mut lift_moment = 0.0
    mut yaw_moment = 0.0
    mut cp_min = 1.0
    mut cp_max = CP_FLOOR
    mut wake_area = 0.0
    mut wake_moment = 0.0
    mut wake_low = 1000.0
    mut wake_high = -1000.0
    mut wake_floor = 1000.0
    mut wake_ceiling = -1000.0
    for i in range(0, basis.panel_count):
        p = panels[i]
        nx = p.normal_x
        ny = p.normal_y
        nz = p.normal_z
        px = p.center_x
        py = p.center_y
        b = 3 * i
        ux = cb * mode_x[b] + sb * mode_y[b]
        uy = cb * mode_x[b + 1] + sb * mode_y[b + 1]
        uz = cb * mode_x[b + 2] + sb * mode_y[b + 2]
        un = ux * nx + uy * ny + uz * nz
        tx = ux - un * nx
        ty = uy - un * ny
        tz = uz - un * nz
        potential = max(CP_FLOOR, min(1.0, 1.0 - (tx * tx + ty * ty + tz * tz)))
        mut cp = potential
        mut active = 0.0
        if forward_flag[i] > 0.5 and weight_forward > 0.0:
            cp = cp + weight_forward * (min(forward_cp[i], base_cp) - cp)
            active = weight_forward
        if reverse_flag[i] > 0.5 and weight_reverse > 0.0:
            cp = cp + weight_reverse * (min(reverse_cp[i], base_cp) - cp)
            active = max(active, weight_reverse)
        if cross_flag[i] > 0.5 and lee * py < 0.0:
            cp = cp + weight_cross * (cross_cp[i] - cp)
            active = max(active, weight_cross)
        cp = max(CP_FLOOR, min(1.0, cp))
        surface_cp[i] = cp
        cp_min = min(cp_min, cp)
        cp_max = max(cp_max, cp)
        wx = 0.0 - p.area_m2 * nx
        wy = 0.0 - p.area_m2 * ny
        wz = 0.0 - p.area_m2 * nz
        potential_x = potential_x + potential * wx
        potential_y = potential_y + potential * wy
        potential_z = potential_z + potential * wz
        delta = cp - potential
        dx = delta * wx
        dy = delta * wy
        dz = delta * wz
        closure_x = closure_x + dx
        if base_flag[i] > 0.5:
            face_x = face_x + dx
            face_y = face_y + dy
            face_z = face_z + dz
        else:
            shear_x = shear_x + dx
            shear_y = shear_y + dy
            shear_z = shear_z + dz
        lift_moment = lift_moment + cp * wz * px
        yaw_moment = yaw_moment + cp * (px * wy - py * wx)
        if active > 0.0:
            reach = across_x * px + across_y * py
            weighted = p.area_m2 * active
            wake_area = wake_area + weighted
            wake_moment = wake_moment + weighted * px
            wake_low = min(wake_low, reach)
            wake_high = max(wake_high, reach)
            wake_floor = min(wake_floor, p.center_z)
            wake_ceiling = max(wake_ceiling, p.center_z)

    cd_pressure = 0.0 - shear_x / FRONTAL_AREA_M2
    cd_base = 0.0 - face_x / FRONTAL_AREA_M2
    reynolds = AIR_DENSITY_KG_M3 * speed * LENGTH_M / AIR_VISCOSITY_PA_S
    effective = max(reynolds, AIR_DENSITY_KG_M3 * 1.0 * LENGTH_M / AIR_VISCOSITY_PA_S)
    skin_friction = 0.455 / math.pow(math.log(effective) / math.log(10.0), 2.58)
    cd_friction = skin_friction * WETTED_AREA_M2 / FRONTAL_AREA_M2
    cd_wheels = WHEEL_CD * (1.0 + WHEEL_CD_YAW_GAIN * abs(sb))
    cd = cd_pressure + cd_base + cd_friction + cd_wheels
    cy = 0.0 - (face_y + shear_y) / SIDE_AREA_M2
    cl = (potential_z + face_z + shear_z) / PLANFORM_AREA_M2
    cmz = 0.0 - yaw_moment / (SIDE_AREA_M2 * LENGTH_M)

    lift_unit = potential_z + face_z + shear_z
    front_unit = (lift_moment + lift_unit * AXLE_X_M) / (2.0 * AXLE_X_M)
    drag_n = cd * q * FRONTAL_AREA_M2
    pressure_drag_n = 0.0 - closure_x * q
    recomposed = pressure_drag_n + (cd_friction + cd_wheels) * q * FRONTAL_AREA_M2
    closure_residual = abs(drag_n - recomposed) / max(1.0, abs(drag_n))

    separation_x = basis.separation_x_m
    if wake_area > 0.0:
        separation_x = wake_moment / wake_area
    width = max(0.0, wake_high - wake_low)
    frontal_wake = max(0.05, width * max(0.05, wake_ceiling - wake_floor))
    deficit = min(1.0, max(0.0, (cd_pressure + cd_base) * FRONTAL_AREA_M2 / (2.0 * frontal_wake)))
    recirculation = HEIGHT_M * max(1.2, min(2.5, 2.5 - 1.3 * min(1.0, abs(base_cp) / 0.5)))

    return AeroForces(
        air_speed_mps=speed,
        yaw_rad=yaw_rad,
        dynamic_pressure_pa=q,
        reynolds_number=reynolds,
        cd=cd,
        cd_pressure=cd_pressure,
        cd_base=cd_base,
        cd_friction=cd_friction,
        cd_wheels=cd_wheels,
        cy=cy,
        cl=cl,
        cmz=cmz,
        drag_n=drag_n,
        side_n=cy * q * SIDE_AREA_M2,
        lift_n=lift_unit * q,
        yaw_moment_nm=cmz * q * SIDE_AREA_M2 * LENGTH_M,
        front_lift_n=front_unit * q,
        rear_lift_n=(lift_unit - front_unit) * q,
        pressure_drag_n=pressure_drag_n,
        cp_min=cp_min,
        cp_max=cp_max,
        base_pressure_coefficient=base_cp,
        separation_x_m=separation_x,
        wake_width_m=width,
        wake_deficit_fraction=deficit,
        wake_shedding_hz=STROUHAL * speed / HEIGHT_M,
        turbulence_intensity=min(1.0, max(0.0, 0.14 + 0.45 * deficit)),
        wake_recirculation_length_m=recirculation,
        dalembert_residual_cd=0.0 - potential_x / FRONTAL_AREA_M2,
        solver_residual=max(basis.residual_x, basis.residual_y),
        force_closure_residual=closure_residual,
        surface_cp=surface_cp,
    )


def rounded(value: f64):
    """Half-up rounding to five decimals so the exported panel payload stays compact."""
    if value < 0.0:
        return 0.0 - float(int(0.0 - value * 100000.0 + 0.5)) / 100000.0
    return float(int(value * 100000.0 + 0.5)) / 100000.0


pub def aero_basis_export(basis: AeroBasis) -> dict[str, any] !{}:
    """`sema.circuitframe-aero-basis/v1`: geometry, source strengths and the solver provenance."""
    mut rows = []
    for i in range(0, basis.panel_count):
        p = basis.panels[i]
        rows = rows + [{
            "i": p.index,
            "c": [rounded(p.center_x), rounded(p.center_y), rounded(p.center_z)],
            "n": [rounded(p.normal_x), rounded(p.normal_y), rounded(p.normal_z)],
            "a": rounded(p.area_m2),
            "sx": rounded(basis.source_x[i]),
            "sy": rounded(basis.source_y[i]),
        }]
    calibration = aero_solve(basis, basis.calibration_speed_mps, 0.0)
    return {
        "schema": "sema.circuitframe-aero-basis/v1",
        "method": "constant-source-panel + image ground plane",
        "panel_count": basis.panel_count,
        "reference": {
            "frontal_area_m2": FRONTAL_AREA_M2,
            "planform_area_m2": PLANFORM_AREA_M2,
            "side_area_m2": SIDE_AREA_M2,
            "wetted_area_m2": WETTED_AREA_M2,
            "length_m": LENGTH_M,
            "width_m": 2.0 * HALF_WIDTH_M,
            "height_m": HEIGHT_M,
            "wheelbase_m": 2.0 * AXLE_X_M,
            "ride_height_m": RIDE_HEIGHT_M,
        },
        "mesh": {
            "wetted_area_m2": rounded(basis.wetted_area_m2),
            "frontal_area_m2": rounded(basis.frontal_area_m2),
            "side_area_m2": rounded(basis.side_area_m2),
            "planform_area_m2": rounded(basis.planform_area_m2),
            "closure_residual_m2": basis.closure_residual_m2,
            "stations": RING_COUNT - 1,
            "around": AROUND,
        },
        "panels": rows,
        "solver": {
            "residual_max": max(basis.residual_x, basis.residual_y),
            "modes": 2,
            "ground_image": true,
            "regularisation": "r_eps = 0.5 sqrt(a_i)",
            "reduction": "mirror-symmetry split: two 120x120 dense solves span the 240-panel system",
            "freestream_convention": "U_inf = V (cos yaw, sin yaw, 0); render -u for nose-to-tail smoke",
            "render_sign": -1.0,
        },
        "calibration": {
            "cd_pressure": calibration.cd_pressure,
            "cd_friction": calibration.cd_friction,
            "cd_base": calibration.cd_base,
            "cd_wheels": calibration.cd_wheels,
            "cd_total": calibration.cd,
            "speed_mps": basis.calibration_speed_mps,
            "target_cd": basis.calibration_target_cd,
            "free_parameter": "wheel and underbody increment",
            "wheel_increment_zero_yaw": basis.calibrated_wheel_cd,
        },
    }


pub def aero_equations() -> list[dict[str, str]] !{}:
    """Equation manifest rows; every owner states solved, correlated or numerical provenance."""
    return [
        {"id": "panel-neumann", "source": "aero_basis", "expression": "sum_j A_ij sigma_j = -n_i . e_inf, A_ii = 1/2", "unit": "m/s", "owner": "Sema source-panel solve (solved)"},
        {"id": "panel-influence", "source": "influence_blocks", "expression": "A_ij = a_j n_i . (c_i - c_j) / (4 pi max(r, r_eps)^3) + ground image", "unit": "1/s", "owner": "Sema source-panel solve (solved)"},
        {"id": "panel-ground-image", "source": "influence_blocks", "expression": "every source mirrored through z = 0 so u_z(z=0) = 0 exactly", "unit": "1", "owner": "Sema source-panel solve (solved)"},
        {"id": "panel-superposition", "source": "aero_solve", "expression": "sigma(V, beta) = V (cos beta sigma_x + sin beta sigma_y)", "unit": "m/s", "owner": "Sema source-panel solve (solved, exact in the freestream)"},
        {"id": "surface-pressure", "source": "aero_solve", "expression": "Cp_i = 1 - |v_i|^2 / V^2, v_i tangential", "unit": "1", "owner": "Sema source-panel solve (solved)"},
        {"id": "pressure-integral", "source": "aero_solve", "expression": "F = -q sum_i Cp_i a_i n_i, q = 1/2 rho V^2", "unit": "N", "owner": "Sema surface integration (solved)"},
        {"id": "dalembert", "source": "aero_solve", "expression": "closed-body potential flow gives F_x = F_y = 0; the residual is discretisation error", "unit": "1", "owner": "Sema solver diagnostic (numerical)"},
        {"id": "stratford-separation", "source": "line_walk", "expression": "Cbar (xi dCbar/dxi)^1/2 >= k (1e-6 Re_xi)^1/10", "unit": "1", "owner": "Stratford 1959 turbulent recovery (correlated)"},
        {"id": "crossflow-separation", "source": "ring_walk", "expression": "same criterion walked around each section on the unit crossflow mode", "unit": "1", "owner": "Allen-Perkins crossflow analogy (correlated)"},
        {"id": "base-pressure", "source": "aero_solve", "expression": "Cp_b = -(0.20 + 0.80 |sin beta|) held over the separated wake", "unit": "1", "owner": "Sema bluff-body base closure (correlated)"},
        {"id": "free-streamline", "source": "aero_solve", "expression": "separated panels hold min(Cp at separation, Cp_b)", "unit": "1", "owner": "Kirchhoff constant-pressure wake (correlated)"},
        {"id": "skin-friction", "source": "aero_solve", "expression": "C_f = 0.455 / (log10 Re_L)^2.58, cd_f = C_f A_wet / A_front", "unit": "1", "owner": "Schlichting turbulent flat plate (correlated)"},
        {"id": "wheel-increment", "source": "aero_solve", "expression": "cd_w = 0.036947 (1 + 0.60 |sin beta|)", "unit": "1", "owner": "Sema wheel and underbody increment (correlated, calibrated)"},
        {"id": "drag-buildup", "source": "aero_solve", "expression": "C_D = cd_pressure + cd_base + cd_friction + cd_wheels", "unit": "1", "owner": "Sema drag build-up (solved + correlated)"},
        {"id": "axle-lift-split", "source": "aero_solve", "expression": "F_zf = (M_y + F_z a) / 2a about the mid-wheelbase origin", "unit": "N", "owner": "Sema surface integration (solved + correlated)"},
        {"id": "yaw-moment", "source": "aero_solve", "expression": "M_z = sum_i (x_i dF_y,i - y_i dF_x,i), C_Mz = -M_z / (q A_side L)", "unit": "N.m", "owner": "Sema surface integration (solved + correlated)"},
        {"id": "wake-shedding", "source": "aero_solve", "expression": "f_s = St V / H, St = 0.20", "unit": "Hz", "owner": "Sema bluff-body wake scalar (correlated)"},
        {"id": "wake-deficit", "source": "aero_solve", "expression": "delta_u = C_D,pressure A_front / (2 A_wake), bounded to [0, 1]", "unit": "fraction", "owner": "Sema momentum-deficit estimate (correlated)"},
        {"id": "wake-recirculation", "source": "aero_solve", "expression": "L_r = H (2.5 - 1.3 min(1, |Cp_b|/0.5)) bounded to 1.2..2.5 H", "unit": "m", "owner": "Sema bluff-body wake scalar (correlated)"},
        {"id": "separation-front", "source": "aero_solve", "expression": "x_sep = area-weighted centroid of the panels whose wake closure is active", "unit": "m", "owner": "Sema wake geometry (solved topology, correlated weighting)"},
        {"id": "force-closure", "source": "aero_solve", "expression": "|C_D q A - (F_pressure + (cd_f + cd_w) q A)| / max(1, |C_D q A|)", "unit": "1", "owner": "Sema bookkeeping check (numerical)"},
    ]


def yaw_forces(basis: AeroBasis, degrees: f64):
    """Evaluate one yaw station at the calibration wind speed. Used by the tests below."""
    return aero_solve(basis, CALIBRATION_SPEED_MPS, degrees * math.pi / 180.0)


test "panel mesh closes and matches the declared reference areas":
    basis = aero_basis()
    ensure basis.panel_count == 240
    ensure len(basis.panels) == 240
    ensure basis.closure_residual_m2 < 0.000000001
    ensure abs(basis.wetted_area_m2 / WETTED_AREA_M2 - 1.0) < 0.15
    ensure abs(basis.frontal_area_m2 - FRONTAL_AREA_M2) < 0.15
    ensure basis.separation_x_m < 0.0


test "both dense source solves satisfy the Neumann condition on every panel":
    basis = aero_basis()
    ensure basis.residual_x < 0.00000001
    ensure basis.residual_y < 0.00000001
    mut worst = 0.0
    for i in range(0, basis.panel_count):
        p = basis.panels[i]
        vn = (basis.mode_x[3 * i] * p.normal_x + basis.mode_x[3 * i + 1] * p.normal_y
              + basis.mode_x[3 * i + 2] * p.normal_z)
        worst = max(worst, abs(vn))
    ensure worst < 0.00000001


test "zero yaw is symmetric and lands on the calibrated drag coefficient":
    basis = aero_basis()
    forces = yaw_forces(basis, 0.0)
    ensure abs(forces.cd - 0.23) < 0.005
    ensure abs(forces.cy) < 0.000001 * forces.cd
    ensure abs(forces.cmz) < 0.000001 * forces.cd
    ensure abs(forces.side_n) < 0.000001 * forces.drag_n
    ensure abs(forces.yaw_moment_nm) < 0.000001 * forces.drag_n


test "yaw sweep raises drag and grows side force nearly linearly":
    basis = aero_basis()
    calm = yaw_forces(basis, 0.0)
    five = yaw_forces(basis, 5.0)
    ten = yaw_forces(basis, 10.0)
    fifteen = yaw_forces(basis, 15.0)
    twenty = yaw_forces(basis, 20.0)
    ensure fifteen.cd > calm.cd
    ensure twenty.cd > fifteen.cd
    ensure ten.cy > 0.0
    ensure abs(ten.cy / five.cy - 2.0) < 0.5
    ensure yaw_forces(basis, -10.0).cy < 0.0
    ensure abs(yaw_forces(basis, -10.0).cd - ten.cd) < 0.000000001


test "pressure forces scale with the square of the wind speed":
    basis = aero_basis()
    low = aero_solve(basis, 12.0, 0.2)
    high = aero_solve(basis, 24.0, 0.2)
    ensure low.pressure_drag_n > 0.0
    ensure abs(high.pressure_drag_n / low.pressure_drag_n - 4.0) < 0.000000001
    ensure abs(high.dynamic_pressure_pa / low.dynamic_pressure_pa - 4.0) < 0.000000001


test "a stationary car in moving air still reports drag":
    basis = aero_basis()
    forces = aero_solve(basis, 27.8, 0.0)
    ensure forces.drag_n > 100.0
    ensure forces.dynamic_pressure_pa > 0.0
    ensure aero_solve(basis, 0.0, 0.0).drag_n == 0.0


test "coefficient build-up reproduces the integrated pressure force":
    basis = aero_basis()
    for step in range(0, 7):
        forces = yaw_forces(basis, float(step) * 6.0)
        ensure forces.force_closure_residual < 0.000000001
        ensure len(forces.surface_cp) == 240
        ensure forces.cp_max <= 1.0 and forces.cp_min >= -4.0


test "exported basis carries the panel field the browser recombines":
    basis = aero_basis()
    payload = aero_basis_export(basis)
    ensure payload["schema"] == "sema.circuitframe-aero-basis/v1"
    ensure payload["panel_count"] == 240
    ensure len(payload["panels"]) == 240
    ensure len(aero_equations()) == 21
```

### `src/assembly.sema`

```sema
"""Physical assembly model: ports, mates, joints, mass properties, load paths, and the coupled thermal network."""

import math

from magna_ev_digital_twin.domain import PartGroup, PortDefinition, PortKind
from magna_ev_digital_twin.vehicle import vehicle_parts

assure silver


enum JointKind:
    fixed | revolute | prismatic | spherical | cylindrical | gear | rigid_bond


enum PlacementPattern:
    single | distributed | lateral_pair | longitudinal_pair | corner_quad


enum LoadPathKind:
    vertical | longitudinal | lateral | structural | crash | restraint


struct MateDefinition:
    id: str
    port_a_id: str
    port_b_id: str
    kind: PortKind
    preload_n: f64
    sealed: bool
    serviceable: bool
    robot_accessible: bool
    invariant len(id) > 0
    invariant len(port_a_id) > 0
    invariant len(port_b_id) > 0
    invariant port_a_id != port_b_id
    invariant preload_n >= 0.0


struct JointDefinition:
    id: str
    name: str
    kind: JointKind
    parent_part_id: str
    child_part_id: str
    parent_link: str
    child_link: str
    axis_x: f64
    axis_y: f64
    axis_z: f64
    origin_x: f64
    origin_y: f64
    origin_z: f64
    limit_lower: f64
    limit_upper: f64
    ratio: f64
    dof: int
    idle_dof: int
    corner: str
    loop_closing: bool
    invariant len(id) > 0
    invariant len(name) > 0
    invariant len(parent_part_id) > 0
    invariant len(child_part_id) > 0
    invariant len(parent_link) > 0
    invariant len(child_link) > 0
    invariant parent_link != child_link
    invariant limit_upper >= limit_lower
    invariant ratio != 0.0
    invariant dof >= 0 and dof <= 6
    invariant idle_dof >= 0 and idle_dof <= dof


struct PartPlacement:
    part_id: str
    pattern: PlacementPattern
    pattern_instances: int
    mass_kg: f64
    centre_x: f64
    centre_y: f64
    centre_z: f64
    extent_x: f64
    extent_y: f64
    extent_z: f64
    span_x: f64
    span_y: f64
    unsprung_fraction: f64
    invariant len(part_id) > 0
    invariant pattern_instances >= 1 and pattern_instances <= 4
    invariant mass_kg >= 0.0
    invariant extent_x > 0.0 and extent_y > 0.0 and extent_z > 0.0
    invariant span_x >= 0.0 and span_y >= 0.0
    invariant unsprung_fraction >= 0.0 and unsprung_fraction <= 1.0


pub struct MassProperties:
    total_mass_kg: f64
    sprung_mass_kg: f64
    unsprung_mass_kg: f64
    cg_x_m: f64
    cg_y_m: f64
    cg_z_m: f64
    ixx_kg_m2: f64
    iyy_kg_m2: f64
    izz_kg_m2: f64
    front_axle_load_kg: f64
    rear_axle_load_kg: f64
    front_mass_fraction: f64
    wheelbase_m: f64
    cg_height_m: f64
    part_count: int
    instance_count: int
    closure_residual_kg: f64
    invariant total_mass_kg > 0.0
    invariant sprung_mass_kg >= 0.0
    invariant unsprung_mass_kg >= 0.0
    invariant ixx_kg_m2 > 0.0 and iyy_kg_m2 > 0.0 and izz_kg_m2 > 0.0
    invariant front_axle_load_kg >= 0.0 and rear_axle_load_kg >= 0.0
    invariant front_mass_fraction > 0.0 and front_mass_fraction < 1.0
    invariant wheelbase_m > 0.0
    invariant cg_height_m > 0.0
    invariant part_count > 0
    invariant instance_count >= part_count


struct LoadPath:
    id: str
    name: str
    kind: LoadPathKind
    part_ids: list[str]
    rated_load_n: f64
    utilisation: f64
    invariant len(id) > 0
    invariant len(name) > 0
    invariant len(part_ids) >= 2 and len(part_ids) <= 32
    invariant rated_load_n > 0.0
    invariant utilisation >= 0.0


struct LoadPathReaction:
    path_id: str
    element_index: int
    part_id: str
    role: str
    force_n: f64
    moment_nm: f64
    invariant len(path_id) > 0
    invariant element_index >= 0
    invariant len(part_id) > 0
    invariant len(role) > 0


struct LoadPathResult:
    path_id: str
    name: str
    kind: LoadPathKind
    applied_n: f64
    reaction_n: f64
    residual_n: f64
    peak_force_n: f64
    rated_load_n: f64
    utilisation: f64
    reactions: list[LoadPathReaction]
    invariant len(path_id) > 0
    invariant rated_load_n > 0.0
    invariant peak_force_n >= 0.0
    invariant utilisation >= 0.0
    invariant len(reactions) >= 2


struct LoadCase:
    vertical_n: f64
    longitudinal_n: f64
    lateral_n: f64
    results: list[LoadPathResult]
    equilibrium_residual_n: f64
    moment_residual_nm: f64
    worst_path_id: str
    worst_utilisation: f64
    invariant len(results) > 0
    invariant equilibrium_residual_n >= 0.0
    invariant moment_residual_nm >= 0.0
    invariant worst_utilisation >= 0.0


pub struct SuspensionState:
    corner_id: str
    vertical_load_n: f64
    arm_rotation_rad: f64
    wheel_travel_m: f64
    spring_stroke_m: f64
    damper_length_m: f64
    spring_force_n: f64
    wheel_rate_n_m: f64
    motion_ratio: f64
    camber_deg: f64
    camber_change_deg: f64
    toe_deg: f64
    toe_change_deg: f64
    kingpin_rotation_rad: f64
    jounce_limit_m: f64
    rebound_limit_m: f64
    travel_utilisation: f64
    at_limit: bool
    invariant len(corner_id) > 0
    invariant vertical_load_n >= 0.0
    invariant damper_length_m > 0.0
    invariant motion_ratio > 0.0
    invariant jounce_limit_m > 0.0
    invariant rebound_limit_m < 0.0
    invariant travel_utilisation >= 0.0 and travel_utilisation <= 1.0


struct ThermalNode:
    id: str
    part_id: str
    capacity_kj_k: f64
    ambient_coupling_w_k: f64
    initial_temp_c: f64
    invariant len(id) > 0
    invariant len(part_id) > 0
    invariant capacity_kj_k > 0.0
    invariant ambient_coupling_w_k >= 0.0
    invariant initial_temp_c >= -60.0 and initial_temp_c <= 200.0


struct ThermalLink:
    id: str
    from_node_id: str
    to_node_id: str
    conductance_w_k: f64
    medium: str
    invariant len(id) > 0
    invariant len(from_node_id) > 0
    invariant len(to_node_id) > 0
    invariant from_node_id != to_node_id
    invariant conductance_w_k > 0.0
    invariant len(medium) > 0


struct ThermalNetwork:
    id: str
    nodes: list[ThermalNode]
    links: list[ThermalLink]
    invariant len(id) > 0
    invariant len(nodes) >= 2
    invariant len(links) >= 1


struct MobilityReport:
    corner_id: str
    link_count: int
    joint_count: int
    joint_dof_sum: int
    gross_mobility: int
    idle_dof: int
    effective_mobility: int
    expected_mobility: int
    independent_loops: int
    loop_closing_joint_ids: list[str]
    ok: bool
    invariant len(corner_id) > 0
    invariant link_count >= 2
    invariant joint_count >= 1
    invariant joint_dof_sum >= joint_count
    invariant independent_loops >= 0


CONFIGURATION_VARIANT_DEFAULT = "circuitframe"
ASSEMBLY_SCHEMA = "sema.circuitframe-assembly/v1"
ASSEMBLY_AUTHORITY = "bounded engineered reconstruction: hard points, mates, joints, and mass properties are declared assumptions derived from the published exterior envelope, not OEM CAD or measured inertia data"
GRAVITY_MPS2 = 9.80665
WHEELBASE_M = 2.890
HALF_TRACK_M = 0.8175
FRONT_AXLE_X_M = 1.445
REAR_AXLE_X_M = -1.445
WHEEL_RADIUS_M = 0.3695
RIDE_HEIGHT_M = 0.167
ASSEMBLY_MASS_KG = 2108.2

HP_LCA_IN_Y = 0.320
HP_LCA_IN_Z = 0.215
HP_LCA_BUSH_DX = 0.140
HP_UCA_IN_Y = 0.400
HP_UCA_IN_Z = 0.470
HP_LBJ_Y = 0.735
HP_LBJ_Z = 0.185
HP_UBJ_Y = 0.700
HP_UBJ_Z = 0.545
HP_WC_Y = 0.8175
HP_WC_Z = 0.3695
HP_TIE_DX = -0.125
HP_TIE_IN_Y = 0.360
HP_TIE_IN_Z = 0.2885
HP_TIE_OUT_Y = 0.720
HP_TIE_OUT_Z = 0.300
HP_KINGPIN_DX = -0.047
HP_DAMPER_LO_Y = 0.620
HP_DAMPER_LO_Z = 0.19331
HP_DAMPER_UP_Y = 0.560
HP_DAMPER_UP_Z = 0.760
HP_ARB_BAR_DX = -0.230
HP_ARB_BAR_Y = 0.480
HP_ARB_BAR_Z = 0.250
HP_ARB_LINK_Y = 0.660
HP_ARB_LINK_Z = 0.205

AIR_SPRING_AREA_M2 = 0.01539
AIR_SPRING_VOLUME_M3 = 0.00232
AIR_SPRING_POLYTROPIC_N = 1.38
ATMOSPHERIC_PRESSURE_PA = 101325.0
JOUNCE_LIMIT_M = 0.090
REBOUND_LIMIT_M = -0.080
STATIC_CAMBER_DEG = -0.5
STATIC_TOE_DEG = 0.08
TRAVEL_SOLVE_ITERATIONS = 22
STEER_SOLVE_ITERATIONS = 6

REFERENCE_BRAKING_N = 9000.0
REFERENCE_CORNERING_N = 6000.0
GEAR_STAGE_ONE_RATIO = 3.25
GEAR_STAGE_TWO_RATIO = 2.8


equation linear_solve_3(matrix, rhs) -> any:
    """Dense 3x3 static-equilibrium solve; returns the reaction vector."""
    return solve(matrix, rhs)


equation grubler_mobility(links, joints, joint_dof_sum) -> any:
    """Grubler-Kutzbach spatial mobility of a closed kinematic loop."""
    return 6 * (links - 1 - joints) + joint_dof_sum


equation polytropic_pressure(reference_pressure, reference_volume, volume, exponent) -> any:
    """Polytropic gas state of an air spring bellows."""
    return reference_pressure * (reference_volume / volume)^exponent


equation box_inertia_term(mass, edge_a, edge_b) -> any:
    """Second moment of a uniform rectangular prism about a centroidal axis."""
    return mass * (edge_a^2 + edge_b^2) / 12.0


equation parallel_axis_term(mass, offset_a, offset_b) -> any:
    """Parallel-axis transfer of a body inertia to the vehicle centre of mass."""
    return mass * (offset_a^2 + offset_b^2)


def interface_specs() -> list[dict[str, any]]:
    """Declared part-to-part interfaces. Every row expands into two ports and one terminating mate."""
    return [
        {"mate": "mate-front-subframe-body-rail", "part_a": "front-subframe", "port_a": "front-subframe.mech.body-rail", "part_b": "body-shell", "port_b": "body-shell.mech.front-subframe-rail", "kind": PortKind.mechanical, "preload_n": 42000.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-rear-subframe-body-rail", "part_a": "rear-subframe", "port_a": "rear-subframe.mech.body-rail", "part_b": "body-shell", "port_b": "body-shell.mech.rear-subframe-rail", "kind": PortKind.mechanical, "preload_n": 38000.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-floor-pan-body-bond", "part_a": "floor-pan", "port_a": "floor-pan.mech.body-bond", "part_b": "body-shell", "port_b": "body-shell.mech.floor-flange", "kind": PortKind.mechanical, "preload_n": 0.0, "sealed": true, "serviceable": false, "robot_accessible": true},
        {"mate": "mate-sill-panels-floor-flange", "part_a": "sill-panels", "port_a": "sill-panels.mech.floor-flange", "part_b": "floor-pan", "port_b": "floor-pan.mech.sill-flange", "kind": PortKind.mechanical, "preload_n": 26000.0, "sealed": true, "serviceable": false, "robot_accessible": true},
        {"mate": "mate-sill-panels-body-flange", "part_a": "sill-panels", "port_a": "sill-panels.mech.body-flange", "part_b": "body-shell", "port_b": "body-shell.mech.sill-flange", "kind": PortKind.mechanical, "preload_n": 24000.0, "sealed": true, "serviceable": false, "robot_accessible": true},
        {"mate": "mate-battery-enclosure-floor-mount", "part_a": "battery-enclosure", "port_a": "battery-enclosure.mech.floor-mount", "part_b": "floor-pan", "port_b": "floor-pan.mech.pack-mount", "kind": PortKind.mechanical, "preload_n": 96000.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-battery-enclosure-sill-mount", "part_a": "battery-enclosure", "port_a": "battery-enclosure.mech.sill-mount", "part_b": "sill-panels", "port_b": "sill-panels.mech.pack-mount", "kind": PortKind.mechanical, "preload_n": 54000.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-battery-modules-enclosure-rail", "part_a": "battery-modules", "port_a": "battery-modules.mech.enclosure-rail", "part_b": "battery-enclosure", "port_b": "battery-enclosure.mech.module-rail", "kind": PortKind.mechanical, "preload_n": 18000.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-battery-management-enclosure-bracket", "part_a": "battery-management", "port_a": "battery-management.mech.enclosure-bracket", "part_b": "battery-enclosure", "port_b": "battery-enclosure.mech.bms-bracket", "kind": PortKind.mechanical, "preload_n": 900.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-front-motor-subframe-mount", "part_a": "front-motor", "port_a": "front-motor.mech.subframe-mount", "part_b": "front-subframe", "port_b": "front-subframe.mech.motor-mount", "kind": PortKind.mechanical, "preload_n": 34000.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-rear-motor-subframe-mount", "part_a": "rear-motor", "port_a": "rear-motor.mech.subframe-mount", "part_b": "rear-subframe", "port_b": "rear-subframe.mech.motor-mount", "kind": PortKind.mechanical, "preload_n": 37000.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-reduction-gears-front-motor-flange", "part_a": "reduction-gears", "port_a": "reduction-gears.mech.front-motor-flange", "part_b": "front-motor", "port_b": "front-motor.mech.gearbox-flange", "kind": PortKind.mechanical, "preload_n": 21000.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-reduction-gears-rear-motor-flange", "part_a": "reduction-gears", "port_a": "reduction-gears.mech.rear-motor-flange", "part_b": "rear-motor", "port_b": "rear-motor.mech.gearbox-flange", "kind": PortKind.mechanical, "preload_n": 23000.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-front-inverter-motor-mount", "part_a": "front-inverter", "port_a": "front-inverter.mech.motor-mount", "part_b": "front-motor", "port_b": "front-motor.mech.inverter-pad", "kind": PortKind.mechanical, "preload_n": 5200.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-rear-inverter-motor-mount", "part_a": "rear-inverter", "port_a": "rear-inverter.mech.motor-mount", "part_b": "rear-motor", "port_b": "rear-motor.mech.inverter-pad", "kind": PortKind.mechanical, "preload_n": 5600.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-suspension-front-subframe-pivot", "part_a": "suspension", "port_a": "suspension.mech.front-subframe-pivot", "part_b": "front-subframe", "port_b": "front-subframe.mech.control-arm-pivot", "kind": PortKind.mechanical, "preload_n": 28000.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-suspension-rear-subframe-pivot", "part_a": "suspension", "port_a": "suspension.mech.rear-subframe-pivot", "part_b": "rear-subframe", "port_b": "rear-subframe.mech.control-arm-pivot", "kind": PortKind.mechanical, "preload_n": 29000.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-suspension-damper-top", "part_a": "suspension", "port_a": "suspension.mech.damper-top", "part_b": "body-shell", "port_b": "body-shell.mech.damper-tower", "kind": PortKind.mechanical, "preload_n": 16000.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-brakes-knuckle-mount", "part_a": "brakes", "port_a": "brakes.mech.knuckle-mount", "part_b": "suspension", "port_b": "suspension.mech.caliper-pad", "kind": PortKind.mechanical, "preload_n": 12500.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-wheels-hub-face", "part_a": "wheels", "port_a": "wheels.mech.hub-face", "part_b": "brakes", "port_b": "brakes.mech.hub-face", "kind": PortKind.mechanical, "preload_n": 11800.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-tires-bead-seat", "part_a": "tires", "port_a": "tires.mech.bead-seat", "part_b": "wheels", "port_b": "wheels.mech.rim-flange", "kind": PortKind.mechanical, "preload_n": 0.0, "sealed": true, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-steering-subframe-mount", "part_a": "steering", "port_a": "steering.mech.subframe-mount", "part_b": "front-subframe", "port_b": "front-subframe.mech.rack-mount", "kind": PortKind.mechanical, "preload_n": 14000.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-steering-tie-rod-end", "part_a": "steering", "port_a": "steering.mech.tie-rod-end", "part_b": "suspension", "port_b": "suspension.mech.steering-arm", "kind": PortKind.mechanical, "preload_n": 3800.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-closures-hinge-pin", "part_a": "closures", "port_a": "closures.mech.hinge-pin", "part_b": "body-shell", "port_b": "body-shell.mech.hinge-boss", "kind": PortKind.mechanical, "preload_n": 7400.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-glazing-urethane-bead", "part_a": "glazing", "port_a": "glazing.mech.urethane-bead", "part_b": "body-shell", "port_b": "body-shell.mech.glazing-flange", "kind": PortKind.mechanical, "preload_n": 0.0, "sealed": true, "serviceable": false, "robot_accessible": true},
        {"mate": "mate-glazing-door-channel", "part_a": "glazing", "port_a": "glazing.mech.door-channel", "part_b": "closures", "port_b": "closures.mech.glazing-channel", "kind": PortKind.mechanical, "preload_n": 0.0, "sealed": true, "serviceable": false, "robot_accessible": false},
        {"mate": "mate-seats-floor-rail", "part_a": "seats", "port_a": "seats.mech.floor-rail", "part_b": "floor-pan", "port_b": "floor-pan.mech.seat-rail", "kind": PortKind.mechanical, "preload_n": 9600.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-interior-body-clip", "part_a": "interior", "port_a": "interior.mech.body-clip", "part_b": "body-shell", "port_b": "body-shell.mech.trim-boss", "kind": PortKind.mechanical, "preload_n": 1400.0, "sealed": false, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-restraints-pillar-anchor", "part_a": "restraints", "port_a": "restraints.mech.pillar-anchor", "part_b": "body-shell", "port_b": "body-shell.mech.belt-anchor", "kind": PortKind.mechanical, "preload_n": 13500.0, "sealed": false, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-restraints-seat-buckle", "part_a": "restraints", "port_a": "restraints.mech.seat-buckle", "part_b": "seats", "port_b": "seats.mech.buckle-anchor", "kind": PortKind.mechanical, "preload_n": 13500.0, "sealed": false, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-lamps-body-socket", "part_a": "lamps", "port_a": "lamps.mech.body-socket", "part_b": "body-shell", "port_b": "body-shell.mech.lamp-aperture", "kind": PortKind.mechanical, "preload_n": 900.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-heat-pump-subframe-bracket", "part_a": "heat-pump", "port_a": "heat-pump.mech.subframe-bracket", "part_b": "front-subframe", "port_b": "front-subframe.mech.hvac-bracket", "kind": PortKind.mechanical, "preload_n": 3200.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-thermal-loop-body-clip", "part_a": "thermal-loop", "port_a": "thermal-loop.mech.body-clip", "part_b": "body-shell", "port_b": "body-shell.mech.coolant-clip", "kind": PortKind.mechanical, "preload_n": 700.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-charge-port-body-aperture", "part_a": "charge-port", "port_a": "charge-port.mech.body-aperture", "part_b": "body-shell", "port_b": "body-shell.mech.charge-aperture", "kind": PortKind.mechanical, "preload_n": 800.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-onboard-charger-floor-bracket", "part_a": "onboard-charger", "port_a": "onboard-charger.mech.floor-bracket", "part_b": "floor-pan", "port_b": "floor-pan.mech.charger-bracket", "kind": PortKind.mechanical, "preload_n": 2600.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-hv-junction-enclosure-mount", "part_a": "hv-junction", "port_a": "hv-junction.mech.enclosure-mount", "part_b": "battery-enclosure", "port_b": "battery-enclosure.mech.junction-pad", "kind": PortKind.mechanical, "preload_n": 4200.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-dc-dc-body-bracket", "part_a": "dc-dc", "port_a": "dc-dc.mech.body-bracket", "part_b": "body-shell", "port_b": "body-shell.mech.converter-bracket", "kind": PortKind.mechanical, "preload_n": 2100.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-central-compute-body-bracket", "part_a": "central-compute", "port_a": "central-compute.mech.body-bracket", "part_b": "body-shell", "port_b": "body-shell.mech.compute-bracket", "kind": PortKind.mechanical, "preload_n": 1500.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-lv-battery-body-tray", "part_a": "lv-battery", "port_a": "lv-battery.mech.body-tray", "part_b": "body-shell", "port_b": "body-shell.mech.lv-tray", "kind": PortKind.mechanical, "preload_n": 2300.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-front-zone-controller-body-bracket", "part_a": "front-zone-controller", "port_a": "front-zone-controller.mech.body-bracket", "part_b": "body-shell", "port_b": "body-shell.mech.front-zone-bracket", "kind": PortKind.mechanical, "preload_n": 800.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-rear-zone-controller-body-bracket", "part_a": "rear-zone-controller", "port_a": "rear-zone-controller.mech.body-bracket", "part_b": "body-shell", "port_b": "body-shell.mech.rear-zone-bracket", "kind": PortKind.mechanical, "preload_n": 800.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-cabin-zone-controller-interior-bracket", "part_a": "cabin-zone-controller", "port_a": "cabin-zone-controller.mech.interior-bracket", "part_b": "interior", "port_b": "interior.mech.zone-bracket", "kind": PortKind.mechanical, "preload_n": 600.0, "sealed": false, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-sensors-body-boss", "part_a": "sensors", "port_a": "sensors.mech.body-boss", "part_b": "body-shell", "port_b": "body-shell.mech.sensor-boss", "kind": PortKind.mechanical, "preload_n": 400.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-fasteners-seals-closure-seal", "part_a": "fasteners-seals", "port_a": "fasteners-seals.mech.closure-seal", "part_b": "closures", "port_b": "closures.mech.seal-groove", "kind": PortKind.mechanical, "preload_n": 0.0, "sealed": true, "serviceable": false, "robot_accessible": false},
        {"mate": "mate-battery-modules-hv-positive", "part_a": "battery-modules", "port_a": "battery-modules.hv.hv-positive", "part_b": "hv-junction", "port_b": "hv-junction.hv.pack-positive", "kind": PortKind.high_voltage, "preload_n": 320.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-battery-modules-hv-negative", "part_a": "battery-modules", "port_a": "battery-modules.hv.hv-negative", "part_b": "hv-junction", "port_b": "hv-junction.hv.pack-negative", "kind": PortKind.high_voltage, "preload_n": 320.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-hv-junction-front-drive", "part_a": "hv-junction", "port_a": "hv-junction.hv.front-drive", "part_b": "front-inverter", "port_b": "front-inverter.hv.hv-input", "kind": PortKind.high_voltage, "preload_n": 260.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-hv-junction-rear-drive", "part_a": "hv-junction", "port_a": "hv-junction.hv.rear-drive", "part_b": "rear-inverter", "port_b": "rear-inverter.hv.hv-input", "kind": PortKind.high_voltage, "preload_n": 260.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-hv-junction-dcdc-feed", "part_a": "hv-junction", "port_a": "hv-junction.hv.dcdc-feed", "part_b": "dc-dc", "port_b": "dc-dc.hv.hv-input", "kind": PortKind.high_voltage, "preload_n": 180.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-hv-junction-charger-feed", "part_a": "hv-junction", "port_a": "hv-junction.hv.charger-feed", "part_b": "onboard-charger", "port_b": "onboard-charger.hv.hv-output", "kind": PortKind.high_voltage, "preload_n": 180.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-charge-port-dc-inlet", "part_a": "charge-port", "port_a": "charge-port.hv.dc-inlet", "part_b": "hv-junction", "port_b": "hv-junction.hv.dc-fast-input", "kind": PortKind.high_voltage, "preload_n": 240.0, "sealed": true, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-hv-junction-thermal-feed", "part_a": "hv-junction", "port_a": "hv-junction.hv.thermal-feed", "part_b": "heat-pump", "port_b": "heat-pump.hv.hv-input", "kind": PortKind.high_voltage, "preload_n": 160.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-front-inverter-phase-output", "part_a": "front-inverter", "port_a": "front-inverter.hv.phase-output", "part_b": "front-motor", "port_b": "front-motor.hv.phase-terminal", "kind": PortKind.high_voltage, "preload_n": 210.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-rear-inverter-phase-output", "part_a": "rear-inverter", "port_a": "rear-inverter.hv.phase-output", "part_b": "rear-motor", "port_b": "rear-motor.hv.phase-terminal", "kind": PortKind.high_voltage, "preload_n": 210.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-sill-panels-hv-busbar", "part_a": "sill-panels", "port_a": "sill-panels.hv.hv-busbar", "part_b": "hv-junction", "port_b": "hv-junction.hv.sill-busbar", "kind": PortKind.high_voltage, "preload_n": 140.0, "sealed": true, "serviceable": false, "robot_accessible": true},
        {"mate": "mate-dc-dc-lv-output", "part_a": "dc-dc", "port_a": "dc-dc.lv.lv-output", "part_b": "lv-battery", "port_b": "lv-battery.lv.lv-terminal", "kind": PortKind.low_voltage, "preload_n": 60.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-dc-dc-front-zone-feed", "part_a": "dc-dc", "port_a": "dc-dc.lv.front-zone-feed", "part_b": "front-zone-controller", "port_b": "front-zone-controller.lv.lv-input", "kind": PortKind.low_voltage, "preload_n": 40.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-dc-dc-rear-zone-feed", "part_a": "dc-dc", "port_a": "dc-dc.lv.rear-zone-feed", "part_b": "rear-zone-controller", "port_b": "rear-zone-controller.lv.lv-input", "kind": PortKind.low_voltage, "preload_n": 40.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-dc-dc-cabin-zone-feed", "part_a": "dc-dc", "port_a": "dc-dc.lv.cabin-zone-feed", "part_b": "cabin-zone-controller", "port_b": "cabin-zone-controller.lv.lv-input", "kind": PortKind.low_voltage, "preload_n": 40.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-dc-dc-compute-feed", "part_a": "dc-dc", "port_a": "dc-dc.lv.compute-feed", "part_b": "central-compute", "port_b": "central-compute.lv.lv-input", "kind": PortKind.low_voltage, "preload_n": 40.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-lv-battery-contactor-supply", "part_a": "lv-battery", "port_a": "lv-battery.lv.contactor-supply", "part_b": "hv-junction", "port_b": "hv-junction.lv.contactor-coil", "kind": PortKind.low_voltage, "preload_n": 25.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-front-zone-controller-lamp-feed", "part_a": "front-zone-controller", "port_a": "front-zone-controller.lv.lamp-feed", "part_b": "lamps", "port_b": "lamps.lv.lv-input", "kind": PortKind.low_voltage, "preload_n": 20.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-front-zone-controller-steering-feed", "part_a": "front-zone-controller", "port_a": "front-zone-controller.lv.steering-feed", "part_b": "steering", "port_b": "steering.lv.lv-input", "kind": PortKind.low_voltage, "preload_n": 55.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-rear-zone-controller-brake-feed", "part_a": "rear-zone-controller", "port_a": "rear-zone-controller.lv.brake-feed", "part_b": "brakes", "port_b": "brakes.lv.lv-input", "kind": PortKind.low_voltage, "preload_n": 45.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-cabin-zone-controller-seat-feed", "part_a": "cabin-zone-controller", "port_a": "cabin-zone-controller.lv.seat-feed", "part_b": "seats", "port_b": "seats.lv.lv-input", "kind": PortKind.low_voltage, "preload_n": 20.0, "sealed": false, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-cabin-zone-controller-squib-feed", "part_a": "cabin-zone-controller", "port_a": "cabin-zone-controller.lv.squib-feed", "part_b": "restraints", "port_b": "restraints.lv.lv-input", "kind": PortKind.low_voltage, "preload_n": 15.0, "sealed": false, "serviceable": false, "robot_accessible": false},
        {"mate": "mate-central-compute-front-zone-link", "part_a": "central-compute", "port_a": "central-compute.data.front-zone-link", "part_b": "front-zone-controller", "port_b": "front-zone-controller.data.backbone-a", "kind": PortKind.data, "preload_n": 12.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-central-compute-rear-zone-link", "part_a": "central-compute", "port_a": "central-compute.data.rear-zone-link", "part_b": "rear-zone-controller", "port_b": "rear-zone-controller.data.backbone-a", "kind": PortKind.data, "preload_n": 12.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-central-compute-cabin-zone-link", "part_a": "central-compute", "port_a": "central-compute.data.cabin-zone-link", "part_b": "cabin-zone-controller", "port_b": "cabin-zone-controller.data.backbone-a", "kind": PortKind.data, "preload_n": 12.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-front-zone-controller-inverter-link", "part_a": "front-zone-controller", "port_a": "front-zone-controller.data.inverter-link", "part_b": "front-inverter", "port_b": "front-inverter.data.control-port", "kind": PortKind.data, "preload_n": 10.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-rear-zone-controller-inverter-link", "part_a": "rear-zone-controller", "port_a": "rear-zone-controller.data.inverter-link", "part_b": "rear-inverter", "port_b": "rear-inverter.data.control-port", "kind": PortKind.data, "preload_n": 10.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-front-zone-controller-brake-link", "part_a": "front-zone-controller", "port_a": "front-zone-controller.data.brake-link", "part_b": "brakes", "port_b": "brakes.data.control-port", "kind": PortKind.data, "preload_n": 10.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-front-zone-controller-steer-link", "part_a": "front-zone-controller", "port_a": "front-zone-controller.data.steer-link", "part_b": "steering", "port_b": "steering.data.control-port", "kind": PortKind.data, "preload_n": 10.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-battery-management-cell-sense", "part_a": "battery-management", "port_a": "battery-management.data.cell-sense", "part_b": "battery-modules", "port_b": "battery-modules.data.sense-header", "kind": PortKind.data, "preload_n": 8.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-battery-management-thermal-link", "part_a": "battery-management", "port_a": "battery-management.data.thermal-link", "part_b": "cabin-zone-controller", "port_b": "cabin-zone-controller.data.battery-port", "kind": PortKind.data, "preload_n": 8.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-sensors-compute-link", "part_a": "sensors", "port_a": "sensors.data.compute-link", "part_b": "central-compute", "port_b": "central-compute.data.sensor-port", "kind": PortKind.data, "preload_n": 8.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-cabin-zone-controller-loop-link", "part_a": "cabin-zone-controller", "port_a": "cabin-zone-controller.data.loop-link", "part_b": "thermal-loop", "port_b": "thermal-loop.data.sensor-port", "kind": PortKind.data, "preload_n": 8.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-cabin-zone-controller-heatpump-link", "part_a": "cabin-zone-controller", "port_a": "cabin-zone-controller.data.heatpump-link", "part_b": "heat-pump", "port_b": "heat-pump.data.control-port", "kind": PortKind.data, "preload_n": 8.0, "sealed": false, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-thermal-loop-pack-supply", "part_a": "thermal-loop", "port_a": "thermal-loop.fluid.pack-supply", "part_b": "battery-enclosure", "port_b": "battery-enclosure.fluid.cold-plate-inlet", "kind": PortKind.fluid, "preload_n": 1200.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-thermal-loop-front-drive-supply", "part_a": "thermal-loop", "port_a": "thermal-loop.fluid.front-drive-supply", "part_b": "front-motor", "port_b": "front-motor.fluid.jacket-inlet", "kind": PortKind.fluid, "preload_n": 900.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-thermal-loop-rear-drive-supply", "part_a": "thermal-loop", "port_a": "thermal-loop.fluid.rear-drive-supply", "part_b": "rear-motor", "port_b": "rear-motor.fluid.jacket-inlet", "kind": PortKind.fluid, "preload_n": 900.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-thermal-loop-front-inverter-supply", "part_a": "thermal-loop", "port_a": "thermal-loop.fluid.front-inverter-supply", "part_b": "front-inverter", "port_b": "front-inverter.fluid.cold-plate-inlet", "kind": PortKind.fluid, "preload_n": 700.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-thermal-loop-rear-inverter-supply", "part_a": "thermal-loop", "port_a": "thermal-loop.fluid.rear-inverter-supply", "part_b": "rear-inverter", "port_b": "rear-inverter.fluid.cold-plate-inlet", "kind": PortKind.fluid, "preload_n": 700.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-thermal-loop-chiller-port", "part_a": "thermal-loop", "port_a": "thermal-loop.fluid.chiller-port", "part_b": "heat-pump", "port_b": "heat-pump.fluid.chiller-inlet", "kind": PortKind.fluid, "preload_n": 1000.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-thermal-loop-charger-supply", "part_a": "thermal-loop", "port_a": "thermal-loop.fluid.charger-supply", "part_b": "onboard-charger", "port_b": "onboard-charger.fluid.cold-plate-inlet", "kind": PortKind.fluid, "preload_n": 600.0, "sealed": true, "serviceable": true, "robot_accessible": true},
        {"mate": "mate-heat-pump-cabin-duct", "part_a": "heat-pump", "port_a": "heat-pump.therm.cabin-duct", "part_b": "interior", "port_b": "interior.therm.hvac-duct", "kind": PortKind.thermal, "preload_n": 0.0, "sealed": false, "serviceable": false, "robot_accessible": false},
        {"mate": "mate-battery-modules-pad-interface", "part_a": "battery-modules", "port_a": "battery-modules.therm.pad-interface", "part_b": "battery-enclosure", "port_b": "battery-enclosure.therm.thermal-pad", "kind": PortKind.thermal, "preload_n": 0.0, "sealed": false, "serviceable": false, "robot_accessible": true},
        {"mate": "mate-front-inverter-housing-interface", "part_a": "front-inverter", "port_a": "front-inverter.therm.housing-interface", "part_b": "front-motor", "port_b": "front-motor.therm.housing-pad", "kind": PortKind.thermal, "preload_n": 0.0, "sealed": false, "serviceable": false, "robot_accessible": true},
        {"mate": "mate-rear-inverter-housing-interface", "part_a": "rear-inverter", "port_a": "rear-inverter.therm.housing-interface", "part_b": "rear-motor", "port_b": "rear-motor.therm.housing-pad", "kind": PortKind.thermal, "preload_n": 0.0, "sealed": false, "serviceable": false, "robot_accessible": true},
        {"mate": "mate-service-links-hv-disconnect", "part_a": "service-links", "port_a": "service-links.svc.hv-disconnect", "part_b": "hv-junction", "port_b": "hv-junction.svc.service-disconnect", "kind": PortKind.service, "preload_n": 300.0, "sealed": true, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-service-links-pack-service", "part_a": "service-links", "port_a": "service-links.svc.pack-service", "part_b": "battery-enclosure", "port_b": "battery-enclosure.svc.service-port", "kind": PortKind.service, "preload_n": 250.0, "sealed": true, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-service-links-front-zone-service", "part_a": "service-links", "port_a": "service-links.svc.front-zone-service", "part_b": "front-zone-controller", "port_b": "front-zone-controller.svc.service-port", "kind": PortKind.service, "preload_n": 60.0, "sealed": false, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-service-links-rear-zone-service", "part_a": "service-links", "port_a": "service-links.svc.rear-zone-service", "part_b": "rear-zone-controller", "port_b": "rear-zone-controller.svc.service-port", "kind": PortKind.service, "preload_n": 60.0, "sealed": false, "serviceable": true, "robot_accessible": false},
        {"mate": "mate-service-links-charge-service", "part_a": "service-links", "port_a": "service-links.svc.charge-service", "part_b": "charge-port", "port_b": "charge-port.svc.service-port", "kind": PortKind.service, "preload_n": 80.0, "sealed": false, "serviceable": true, "robot_accessible": false},
    ]


def open_port_specs() -> list[dict[str, any]]:
    """Declared but intentionally unmated interfaces. None of them is a required port."""
    return [
        {"part": "body-shell", "port": "body-shell.mech.tow-eye-front", "kind": PortKind.mechanical},
        {"part": "body-shell", "port": "body-shell.mech.tow-eye-rear", "kind": PortKind.mechanical},
        {"part": "body-shell", "port": "body-shell.mech.roof-rail-left", "kind": PortKind.mechanical},
        {"part": "body-shell", "port": "body-shell.mech.roof-rail-right", "kind": PortKind.mechanical},
        {"part": "central-compute", "port": "central-compute.svc.obd-diagnostic", "kind": PortKind.service},
        {"part": "central-compute", "port": "central-compute.data.telemetry-expansion", "kind": PortKind.data},
        {"part": "hv-junction", "port": "hv-junction.hv.v2l-outlet", "kind": PortKind.high_voltage},
        {"part": "thermal-loop", "port": "thermal-loop.fluid.auxiliary-heater-port", "kind": PortKind.fluid},
    ]


def vehicle_ports():
    """Full port inventory: two terminated ports per declared interface plus the open optional ports."""
    mut ports = []
    for spec in interface_specs():
        ports.append(PortDefinition(id=spec["port_a"], owner_part_id=spec["part_a"], kind=spec["kind"], mate_port_id=spec["port_b"], required=true, terminated=true))
        ports.append(PortDefinition(id=spec["port_b"], owner_part_id=spec["part_b"], kind=spec["kind"], mate_port_id=spec["port_a"], required=true, terminated=true))
    for spec in open_port_specs():
        ports.append(PortDefinition(id=spec["port"], owner_part_id=spec["part"], kind=spec["kind"], mate_port_id="", required=false, terminated=false))
    return ports


def vehicle_mates():
    """One mate per declared interface, carrying its preload, sealing, and service classification."""
    mut mates = []
    for spec in interface_specs():
        mates.append(MateDefinition(id=spec["mate"], port_a_id=spec["port_a"], port_b_id=spec["port_b"], kind=spec["kind"], preload_n=spec["preload_n"], sealed=spec["sealed"], serviceable=spec["serviceable"], robot_accessible=spec["robot_accessible"]))
    return mates


def port_termination_report():
    """Counts required, terminated, open, and multiply-mated ports by scanning the inventory."""
    ports = vehicle_ports()
    mates = vehicle_mates()
    mut required_open = 0
    mut open_optional = 0
    mut mismated = 0
    for port in ports:
        if port.required and not port.terminated:
            required_open = required_open + 1
        if not port.terminated:
            open_optional = open_optional + 1
        if port.terminated:
            mut hits = 0
            for mate in mates:
                if mate.port_a_id == port.id or mate.port_b_id == port.id:
                    hits = hits + 1
            if hits != 1:
                mismated = mismated + 1
    return {
        "ports": len(ports),
        "mates": len(mates),
        "unterminated_required_ports": required_open,
        "open_optional_ports": open_optional,
        "ports_mated_exactly_once": len(ports) - open_optional - mismated,
        "mismated_ports": mismated,
    }


def part_placements():
    """Placement of every declared part in vehicle coordinates, in the same order as vehicle_parts()."""
    return [
        PartPlacement(part_id="body-shell", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=236.0, centre_x=0.0, centre_y=0.0, centre_z=0.98, extent_x=4.4, extent_y=1.86, extent_z=1.18, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="floor-pan", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=82.0, centre_x=-0.05, centre_y=0.0, centre_z=0.235, extent_x=3.15, extent_y=1.52, extent_z=0.13, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="front-subframe", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=31.0, centre_x=1.445, centre_y=0.0, centre_z=0.3, extent_x=0.64, extent_y=1.4, extent_z=0.23, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="rear-subframe", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=34.0, centre_x=-1.48, centre_y=0.0, centre_z=0.345, extent_x=0.7, extent_y=1.42, extent_z=0.26, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="sill-panels", pattern=PlacementPattern.lateral_pair, pattern_instances=2, mass_kg=26.0, centre_x=0.0, centre_y=0.0, centre_z=0.29, extent_x=2.9, extent_y=0.135, extent_z=0.24, span_x=0.0, span_y=1.545, unsprung_fraction=0.0),
        PartPlacement(part_id="battery-enclosure", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=118.0, centre_x=-0.02, centre_y=0.0, centre_z=0.23, extent_x=2.96, extent_y=1.56, extent_z=0.17, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="battery-modules", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=438.0, centre_x=-0.02, centre_y=0.0, centre_z=0.25, extent_x=2.82, extent_y=1.44, extent_z=0.115, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="battery-management", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=6.0, centre_x=-0.62, centre_y=0.3, centre_z=0.31, extent_x=0.24, extent_y=0.18, extent_z=0.055, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="hv-junction", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=13.0, centre_x=-0.98, centre_y=0.42, centre_z=0.36, extent_x=0.32, extent_y=0.24, extent_z=0.15, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="charge-port", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=4.0, centre_x=-2.18, centre_y=0.76, centre_z=0.76, extent_x=0.19, extent_y=0.13, extent_z=0.17, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="onboard-charger", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=14.0, centre_x=-1.7, centre_y=-0.43, centre_z=0.42, extent_x=0.34, extent_y=0.26, extent_z=0.12, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="dc-dc", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=8.0, centre_x=1.32, centre_y=-0.4, centre_z=0.52, extent_x=0.26, extent_y=0.21, extent_z=0.11, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="front-inverter", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=11.0, centre_x=1.64, centre_y=0.18, centre_z=0.52, extent_x=0.28, extent_y=0.26, extent_z=0.18, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="rear-inverter", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=12.0, centre_x=-1.64, centre_y=-0.18, centre_z=0.54, extent_x=0.29, extent_y=0.27, extent_z=0.19, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="front-motor", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=58.0, centre_x=1.56, centre_y=0.0, centre_z=0.33, extent_x=0.4, extent_y=0.56, extent_z=0.34, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="rear-motor", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=71.0, centre_x=-1.56, centre_y=0.0, centre_z=0.345, extent_x=0.43, extent_y=0.6, extent_z=0.36, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="reduction-gears", pattern=PlacementPattern.longitudinal_pair, pattern_instances=2, mass_kg=42.0, centre_x=0.0, centre_y=0.0, centre_z=0.335, extent_x=0.24, extent_y=0.42, extent_z=0.33, span_x=3.06, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="wheels", pattern=PlacementPattern.corner_quad, pattern_instances=4, mass_kg=52.0, centre_x=0.0, centre_y=0.0, centre_z=0.3695, extent_x=0.235, extent_y=0.235, extent_z=0.739, span_x=2.89, span_y=1.635, unsprung_fraction=1.0),
        PartPlacement(part_id="tires", pattern=PlacementPattern.corner_quad, pattern_instances=4, mass_kg=48.0, centre_x=0.0, centre_y=0.0, centre_z=0.3695, extent_x=0.285, extent_y=0.285, extent_z=0.739, span_x=2.89, span_y=1.635, unsprung_fraction=1.0),
        PartPlacement(part_id="suspension", pattern=PlacementPattern.corner_quad, pattern_instances=4, mass_kg=104.0, centre_x=0.0, centre_y=0.0, centre_z=0.38, extent_x=0.48, extent_y=0.52, extent_z=0.56, span_x=2.89, span_y=1.3, unsprung_fraction=0.45),
        PartPlacement(part_id="steering", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=23.0, centre_x=1.3, centre_y=0.0, centre_z=0.33, extent_x=0.16, extent_y=1.18, extent_z=0.17, span_x=0.0, span_y=0.0, unsprung_fraction=0.1),
        PartPlacement(part_id="brakes", pattern=PlacementPattern.corner_quad, pattern_instances=4, mass_kg=76.0, centre_x=0.0, centre_y=0.0, centre_z=0.3695, extent_x=0.19, extent_y=0.23, extent_z=0.4, span_x=2.89, span_y=1.5, unsprung_fraction=0.85),
        PartPlacement(part_id="thermal-loop", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=31.0, centre_x=0.15, centre_y=0.0, centre_z=0.43, extent_x=3.9, extent_y=1.32, extent_z=0.42, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="heat-pump", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=24.0, centre_x=1.82, centre_y=0.24, centre_z=0.56, extent_x=0.42, extent_y=0.38, extent_z=0.33, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="front-zone-controller", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=2.2, centre_x=1.4, centre_y=0.56, centre_z=0.64, extent_x=0.2, extent_y=0.16, extent_z=0.06, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="rear-zone-controller", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=2.2, centre_x=-1.52, centre_y=-0.52, centre_z=0.62, extent_x=0.2, extent_y=0.16, extent_z=0.06, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="cabin-zone-controller", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=1.8, centre_x=0.56, centre_y=0.38, centre_z=0.62, extent_x=0.18, extent_y=0.15, extent_z=0.055, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="central-compute", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=5.0, centre_x=0.88, centre_y=0.0, centre_z=0.64, extent_x=0.3, extent_y=0.24, extent_z=0.09, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="lv-battery", pattern=PlacementPattern.single, pattern_instances=1, mass_kg=12.0, centre_x=1.68, centre_y=-0.56, centre_z=0.44, extent_x=0.26, extent_y=0.18, extent_z=0.19, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="sensors", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=18.0, centre_x=0.2, centre_y=0.0, centre_z=0.98, extent_x=4.5, extent_y=1.86, extent_z=1.24, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="lamps", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=16.0, centre_x=0.05, centre_y=0.0, centre_z=0.83, extent_x=4.56, extent_y=1.84, extent_z=0.56, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="glazing", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=47.0, centre_x=-0.15, centre_y=0.0, centre_z=1.19, extent_x=3.5, extent_y=1.7, extent_z=0.98, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="closures", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=112.0, centre_x=-0.06, centre_y=0.0, centre_z=0.93, extent_x=3.3, extent_y=1.88, extent_z=1.06, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="seats", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=96.0, centre_x=-0.23, centre_y=0.0, centre_z=0.76, extent_x=1.9, extent_y=1.44, extent_z=1.02, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="interior", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=118.0, centre_x=-0.12, centre_y=0.0, centre_z=0.79, extent_x=2.7, extent_y=1.68, extent_z=1.06, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="restraints", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=34.0, centre_x=0.15, centre_y=0.0, centre_z=1.02, extent_x=2.9, extent_y=1.76, extent_z=0.9, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="fasteners-seals", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=63.0, centre_x=0.0, centre_y=0.0, centre_z=0.82, extent_x=4.5, extent_y=1.88, extent_z=1.4, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
        PartPlacement(part_id="service-links", pattern=PlacementPattern.distributed, pattern_instances=1, mass_kg=19.0, centre_x=0.05, centre_y=0.0, centre_z=0.56, extent_x=4.2, extent_y=1.74, extent_z=0.9, span_x=0.0, span_y=0.0, unsprung_fraction=0.0),
    ]


def placement_instances(placement: PartPlacement):
    """Expands a placement pattern into its instance centres. Each row is [mass, x, y, z]."""
    share = placement.mass_kg / float(placement.pattern_instances)
    half_x = placement.span_x * 0.5
    half_y = placement.span_y * 0.5
    if placement.pattern == PlacementPattern.corner_quad:
        return [
            [share, placement.centre_x + half_x, placement.centre_y + half_y, placement.centre_z],
            [share, placement.centre_x + half_x, placement.centre_y - half_y, placement.centre_z],
            [share, placement.centre_x - half_x, placement.centre_y + half_y, placement.centre_z],
            [share, placement.centre_x - half_x, placement.centre_y - half_y, placement.centre_z],
        ]
    if placement.pattern == PlacementPattern.lateral_pair:
        return [
            [share, placement.centre_x, placement.centre_y + half_y, placement.centre_z],
            [share, placement.centre_x, placement.centre_y - half_y, placement.centre_z],
        ]
    if placement.pattern == PlacementPattern.longitudinal_pair:
        return [
            [share, placement.centre_x + half_x, placement.centre_y, placement.centre_z],
            [share, placement.centre_x - half_x, placement.centre_y, placement.centre_z],
        ]
    return [[share, placement.centre_x, placement.centre_y, placement.centre_z]]


pub def mass_properties() -> MassProperties !{}:
    """Total mass, centre of gravity, and inertia tensor summed from the part placements.

    Every part is treated as a uniform rectangular prism of its declared extent, transferred to the
    vehicle centre of mass by the parallel-axis theorem. The closure residual compares the declared
    part masses against the placement masses and must be exactly zero.
    """
    parts = vehicle_parts()
    placements = part_placements()
    mut aligned = len(parts) == len(placements)
    mut declared_mass = 0.0
    mut placed_mass = 0.0
    mut instances = 0
    mut unsprung = 0.0
    mut moment_x = 0.0
    mut moment_y = 0.0
    mut moment_z = 0.0
    for index in range(0, len(parts)):
        part = parts[index]
        placement = placements[index]
        if part.id != placement.part_id:
            aligned = false
        declared_mass = declared_mass + part.mass_kg
        placed_mass = placed_mass + placement.mass_kg
        instances = instances + part.count
        unsprung = unsprung + placement.mass_kg * placement.unsprung_fraction
        for row in placement_instances(placement):
            moment_x = moment_x + row[0] * row[1]
            moment_y = moment_y + row[0] * row[2]
            moment_z = moment_z + row[0] * row[3]
    ensure aligned
    cg_x = moment_x / placed_mass
    cg_y = moment_y / placed_mass
    cg_z = moment_z / placed_mass
    mut ixx = 0.0
    mut iyy = 0.0
    mut izz = 0.0
    for placement in placements:
        for row in placement_instances(placement):
            ixx = ixx + box_inertia_term(row[0], placement.extent_y, placement.extent_z) + parallel_axis_term(row[0], row[2] - cg_y, row[3] - cg_z)
            iyy = iyy + box_inertia_term(row[0], placement.extent_x, placement.extent_z) + parallel_axis_term(row[0], row[1] - cg_x, row[3] - cg_z)
            izz = izz + box_inertia_term(row[0], placement.extent_x, placement.extent_y) + parallel_axis_term(row[0], row[1] - cg_x, row[2] - cg_y)
    front_fraction = (cg_x - REAR_AXLE_X_M) / WHEELBASE_M
    return MassProperties(
        total_mass_kg=placed_mass,
        sprung_mass_kg=placed_mass - unsprung,
        unsprung_mass_kg=unsprung,
        cg_x_m=cg_x,
        cg_y_m=cg_y,
        cg_z_m=cg_z,
        ixx_kg_m2=ixx,
        iyy_kg_m2=iyy,
        izz_kg_m2=izz,
        front_axle_load_kg=placed_mass * front_fraction,
        rear_axle_load_kg=placed_mass * (1.0 - front_fraction),
        front_mass_fraction=front_fraction,
        wheelbase_m=WHEELBASE_M,
        cg_height_m=cg_z,
        part_count=len(parts),
        instance_count=instances,
        closure_residual_kg=declared_mass - placed_mass,
    )


def corner_table():
    """The four suspension corners: mirror signs, steering capability, and the subframe they hang from."""
    return [
        {"corner": "front-left", "prefix": "fl", "axle": "front", "sign_x": 1.0, "sign_y": 1.0, "steerable": true, "subframe": "front-subframe"},
        {"corner": "front-right", "prefix": "fr", "axle": "front", "sign_x": 1.0, "sign_y": -1.0, "steerable": true, "subframe": "front-subframe"},
        {"corner": "rear-left", "prefix": "rl", "axle": "rear", "sign_x": -1.0, "sign_y": 1.0, "steerable": false, "subframe": "rear-subframe"},
        {"corner": "rear-right", "prefix": "rr", "axle": "rear", "sign_x": -1.0, "sign_y": -1.0, "steerable": false, "subframe": "rear-subframe"},
    ]


def joint(id: str, name: str, kind: JointKind, parent_part_id: str, child_part_id: str, parent_link: str, child_link: str, axis: list[f64], origin: list[f64], limits: list[f64], ratio: f64, dof: int, idle_dof: int, corner: str, loop_closing: bool):
    """Constructs a joint from packed axis, origin, and limit triples."""
    return JointDefinition(
        id=id,
        name=name,
        kind=kind,
        parent_part_id=parent_part_id,
        child_part_id=child_part_id,
        parent_link=parent_link,
        child_link=child_link,
        axis_x=axis[0],
        axis_y=axis[1],
        axis_z=axis[2],
        origin_x=origin[0],
        origin_y=origin[1],
        origin_z=origin[2],
        limit_lower=limits[0],
        limit_upper=limits[1],
        ratio=ratio,
        dof=dof,
        idle_dof=idle_dof,
        corner=corner,
        loop_closing=loop_closing,
    )


def corner_joints(entry: dict[str, any]):
    """Every joint of one suspension corner, mirrored from the reference hard points."""
    prefix = entry["prefix"]
    corner = entry["corner"]
    axle = entry["axle"]
    subframe = entry["subframe"]
    sign_x = entry["sign_x"]
    sign_y = entry["sign_y"]
    axle_x = sign_x * FRONT_AXLE_X_M
    knuckle = prefix + "-knuckle"
    lower_arm = prefix + "-lower-arm"
    upper_arm = prefix + "-upper-arm"
    hub = prefix + "-hub"
    damper_dy = sign_y * (HP_DAMPER_UP_Y - HP_DAMPER_LO_Y)
    damper_dz = HP_DAMPER_UP_Z - HP_DAMPER_LO_Z
    damper_axis_length = math.sqrt(damper_dy * damper_dy + damper_dz * damper_dz)
    steer_x = axle_x + sign_x * HP_TIE_DX
    steer_link = prefix + "-tie-rod" if entry["steerable"] else prefix + "-toe-link"
    steer_root = "steering-rack" if entry["steerable"] else "chassis"
    steer_root_part = "steering" if entry["steerable"] else subframe
    steer_name = "tie rod" if entry["steerable"] else "toe link"
    steer_slug = "tie-rod" if entry["steerable"] else "toe-link"
    return [
        joint(prefix + "-lca-inboard", corner + " lower control arm inboard pivot", JointKind.revolute, subframe, "suspension", "chassis", lower_arm, [1.0, 0.0, 0.0], [axle_x, sign_y * HP_LCA_IN_Y, HP_LCA_IN_Z], [-0.35, 0.35], 1.0, 1, 0, corner, false),
        joint(prefix + "-uca-inboard", corner + " upper control arm inboard pivot", JointKind.revolute, subframe, "suspension", "chassis", upper_arm, [1.0, 0.0, 0.0], [axle_x, sign_y * HP_UCA_IN_Y, HP_UCA_IN_Z], [-0.35, 0.35], 1.0, 1, 0, corner, false),
        joint(prefix + "-lca-outboard", corner + " lower ball joint", JointKind.spherical, "suspension", "suspension", lower_arm, knuckle, [0.0, 0.0, 1.0], [axle_x, sign_y * HP_LBJ_Y, HP_LBJ_Z], [-0.45, 0.45], 1.0, 3, 0, corner, false),
        joint(prefix + "-uca-outboard", corner + " upper ball joint", JointKind.spherical, "suspension", "suspension", upper_arm, knuckle, [0.0, 0.0, 1.0], [axle_x, sign_y * HP_UBJ_Y, HP_UBJ_Z], [-0.45, 0.45], 1.0, 3, 0, corner, true),
        joint(prefix + "-damper-top", corner + " damper upper mount", JointKind.spherical, "body-shell", "suspension", "chassis", prefix + "-damper-body", [0.0, damper_dy / damper_axis_length, damper_dz / damper_axis_length], [axle_x, sign_y * HP_DAMPER_UP_Y, HP_DAMPER_UP_Z], [-0.20, 0.20], 1.0, 3, 0, corner, false),
        joint(prefix + "-damper-slide", corner + " air spring and damper stroke", JointKind.prismatic, "suspension", "suspension", prefix + "-damper-body", prefix + "-damper-rod", [0.0, damper_dy / damper_axis_length, damper_dz / damper_axis_length], [axle_x, sign_y * (HP_DAMPER_LO_Y + HP_DAMPER_UP_Y) * 0.5, (HP_DAMPER_LO_Z + HP_DAMPER_UP_Z) * 0.5], [-0.05296641106708366, 0.0603908906802505], 1.0, 1, 1, corner, false),
        joint(prefix + "-damper-lower", corner + " damper lower mount", JointKind.spherical, "suspension", "suspension", prefix + "-damper-rod", lower_arm, [0.0, 0.0, 1.0], [axle_x, sign_y * HP_DAMPER_LO_Y, HP_DAMPER_LO_Z], [-0.20, 0.20], 1.0, 3, 0, corner, true),
        joint(prefix + "-" + steer_slug + "-inner", corner + " " + steer_name + " inner joint", JointKind.spherical, steer_root_part, "suspension", steer_root, steer_link, [0.0, 0.0, 1.0], [steer_x, sign_y * HP_TIE_IN_Y, HP_TIE_IN_Z], [-0.60, 0.60], 1.0, 3, 1, corner, false),
        joint(prefix + "-" + steer_slug + "-outer", corner + " " + steer_name + " outer joint", JointKind.spherical, "suspension", "suspension", steer_link, knuckle, [0.0, 0.0, 1.0], [steer_x, sign_y * HP_TIE_OUT_Y, HP_TIE_OUT_Z], [-0.60, 0.60], 1.0, 3, 0, corner, true),
        joint(prefix + "-arb-bearing", corner + " anti-roll bar bearing", JointKind.cylindrical, subframe, "suspension", "chassis", axle + "-anti-roll-bar", [0.0, 1.0, 0.0], [axle_x + sign_x * HP_ARB_BAR_DX, sign_y * HP_ARB_BAR_Y, HP_ARB_BAR_Z], [-0.30, 0.30], 1.0, 2, 0, corner, false),
        joint(prefix + "-arb-link", corner + " anti-roll bar drop link", JointKind.revolute, "suspension", "suspension", axle + "-anti-roll-bar", lower_arm, [1.0, 0.0, 0.0], [axle_x + sign_x * HP_ARB_BAR_DX, sign_y * HP_ARB_LINK_Y, HP_ARB_LINK_Z], [-0.25, 0.25], 1.0, 1, 0, corner, true),
        joint(prefix + "-halfshaft-inboard", corner + " inboard constant-velocity joint", JointKind.spherical, "reduction-gears", "suspension", axle + "-final-drive", prefix + "-halfshaft", [0.0, sign_y, 0.0], [axle_x, sign_y * 0.190, HP_WC_Z], [-0.40, 0.40], 1.0, 3, 1, corner, false),
        joint(prefix + "-halfshaft-outboard", corner + " outboard constant-velocity joint", JointKind.spherical, "suspension", "suspension", prefix + "-halfshaft", hub, [0.0, sign_y, 0.0], [axle_x, sign_y * 0.700, HP_WC_Z], [-0.85, 0.85], 1.0, 3, 0, corner, false),
        joint(prefix + "-hub", corner + " wheel bearing", JointKind.revolute, "suspension", "suspension", knuckle, hub, [0.0, sign_y, 0.0], [axle_x, sign_y * HP_WC_Y, HP_WC_Z], [-6.283185307179586, 6.283185307179586], 1.0, 1, 0, corner, false),
        joint(prefix + "-brake-disc", corner + " brake disc mount", JointKind.fixed, "suspension", "brakes", hub, prefix + "-brake-disc", [0.0, sign_y, 0.0], [axle_x, sign_y * 0.760, HP_WC_Z], [0.0, 0.0], 1.0, 0, 0, corner, false),
        joint(prefix + "-brake-caliper", corner + " brake caliper mount", JointKind.fixed, "suspension", "brakes", knuckle, prefix + "-brake-caliper", [0.0, sign_y, 0.0], [axle_x + sign_x * 0.075, sign_y * 0.735, HP_WC_Z + 0.055], [0.0, 0.0], 1.0, 0, 0, corner, false),
        joint(prefix + "-wheel-mount", corner + " wheel bolt circle", JointKind.fixed, "suspension", "wheels", hub, prefix + "-wheel", [0.0, sign_y, 0.0], [axle_x, sign_y * 0.790, HP_WC_Z], [0.0, 0.0], 1.0, 0, 0, corner, false),
        joint(prefix + "-tire-bead", corner + " tire bead seat", JointKind.fixed, "wheels", "tires", prefix + "-wheel", prefix + "-tire", [0.0, sign_y, 0.0], [axle_x, sign_y * HP_WC_Y, HP_WC_Z], [0.0, 0.0], 1.0, 0, 0, corner, false),
    ]


def variant_joint_kind(configuration_variant: str):
    """Subframe and pack mounts are elastomeric bolted joints conventionally and bonded structural joints in the CircuitFrame variant."""
    require configuration_variant == "conventional" or configuration_variant == "circuitframe"
    return JointKind.fixed if configuration_variant == "conventional" else JointKind.rigid_bond


def structural_joints(configuration_variant: str):
    """Driveline, body, pack, and equipment joints outside the four suspension corners."""
    mount_kind = variant_joint_kind(configuration_variant)
    return [
        joint("steering-rack-slide", "Steering rack translation", JointKind.prismatic, "steering", "steering", "chassis", "steering-rack", [0.0, 1.0, 0.0], [1.32, 0.0, 0.29], [-0.078, 0.078], 1.0, 1, 0, "vehicle", false),
        joint("front-rotor", "Front motor rotor bearing", JointKind.revolute, "front-motor", "front-motor", "front-motor-housing", "front-motor-rotor", [0.0, 1.0, 0.0], [1.56, 0.0, 0.33], [-6.283185307179586, 6.283185307179586], 1.0, 1, 0, "vehicle", false),
        joint("rear-rotor", "Rear motor rotor bearing", JointKind.revolute, "rear-motor", "rear-motor", "rear-motor-housing", "rear-motor-rotor", [0.0, 1.0, 0.0], [-1.56, 0.0, 0.345], [-6.283185307179586, 6.283185307179586], 1.0, 1, 0, "vehicle", false),
        joint("front-gear-stage-1", "Front reduction first stage", JointKind.gear, "front-motor", "reduction-gears", "front-motor-rotor", "front-intermediate-shaft", [0.0, 1.0, 0.0], [1.5, 0.14, 0.33], [-6.283185307179586, 6.283185307179586], 3.25, 1, 0, "vehicle", false),
        joint("front-gear-stage-2", "Front reduction second stage", JointKind.gear, "reduction-gears", "reduction-gears", "front-intermediate-shaft", "front-final-drive", [0.0, 1.0, 0.0], [1.47, 0.07, 0.335], [-6.283185307179586, 6.283185307179586], 2.8, 1, 0, "vehicle", false),
        joint("rear-gear-stage-1", "Rear reduction first stage", JointKind.gear, "rear-motor", "reduction-gears", "rear-motor-rotor", "rear-intermediate-shaft", [0.0, 1.0, 0.0], [-1.5, 0.14, 0.345], [-6.283185307179586, 6.283185307179586], 3.25, 1, 0, "vehicle", false),
        joint("rear-gear-stage-2", "Rear reduction second stage", JointKind.gear, "reduction-gears", "reduction-gears", "rear-intermediate-shaft", "rear-final-drive", [0.0, 1.0, 0.0], [-1.47, 0.07, 0.34], [-6.283185307179586, 6.283185307179586], 2.8, 1, 0, "vehicle", false),
        joint("front-subframe-mount", "Front subframe to body rails", mount_kind, "body-shell", "front-subframe", "body-structure", "front-subframe", [0.0, 0.0, 1.0], [1.445, 0.0, 0.3], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("rear-subframe-mount", "Rear subframe to body rails", mount_kind, "body-shell", "rear-subframe", "body-structure", "rear-subframe", [0.0, 0.0, 1.0], [-1.48, 0.0, 0.345], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("floor-pan-bond", "Structural floor pan to body", JointKind.rigid_bond, "body-shell", "floor-pan", "body-structure", "floor-pan", [0.0, 0.0, 1.0], [-0.05, 0.0, 0.235], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("sill-left-bond", "Left structural sill to floor and body", JointKind.rigid_bond, "floor-pan", "sill-panels", "floor-pan", "sill-left", [0.0, 0.0, 1.0], [0.0, 0.7725, 0.29], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("sill-right-bond", "Right structural sill to floor and body", JointKind.rigid_bond, "floor-pan", "sill-panels", "floor-pan", "sill-right", [0.0, 0.0, 1.0], [0.0, -0.7725, 0.29], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("pack-enclosure-mount", "Battery enclosure to floor and sills", mount_kind, "floor-pan", "battery-enclosure", "floor-pan", "battery-enclosure", [0.0, 0.0, 1.0], [-0.02, 0.0, 0.23], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("pack-module-mount", "Battery modules to enclosure rails", JointKind.fixed, "battery-enclosure", "battery-modules", "battery-enclosure", "battery-modules", [0.0, 0.0, 1.0], [-0.02, 0.0, 0.25], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("bms-mount", "Battery management to enclosure", JointKind.fixed, "battery-enclosure", "battery-management", "battery-enclosure", "battery-management", [0.0, 0.0, 1.0], [-0.62, 0.3, 0.31], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("front-motor-mount", "Front drive unit to subframe", JointKind.fixed, "front-subframe", "front-motor", "front-subframe", "front-motor-housing", [0.0, 0.0, 1.0], [1.56, 0.0, 0.33], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("rear-motor-mount", "Rear drive unit to subframe", JointKind.fixed, "rear-subframe", "rear-motor", "rear-subframe", "rear-motor-housing", [0.0, 0.0, 1.0], [-1.56, 0.0, 0.345], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("front-gearbox-mount", "Front gearbox housing to motor", JointKind.fixed, "front-motor", "reduction-gears", "front-motor-housing", "front-gearbox-housing", [0.0, 0.0, 1.0], [1.48, 0.13, 0.33], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("rear-gearbox-mount", "Rear gearbox housing to motor", JointKind.fixed, "rear-motor", "reduction-gears", "rear-motor-housing", "rear-gearbox-housing", [0.0, 0.0, 1.0], [-1.48, 0.13, 0.345], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("steering-rack-mount", "Steering rack housing to subframe", JointKind.fixed, "front-subframe", "steering", "front-subframe", "steering-rack-housing", [0.0, 0.0, 1.0], [1.3, 0.0, 0.33], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("front-inverter-mount", "Front inverter to drive unit", JointKind.fixed, "front-motor", "front-inverter", "front-motor-housing", "front-inverter", [0.0, 0.0, 1.0], [1.64, 0.18, 0.52], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("rear-inverter-mount", "Rear inverter to drive unit", JointKind.fixed, "rear-motor", "rear-inverter", "rear-motor-housing", "rear-inverter", [0.0, 0.0, 1.0], [-1.64, -0.18, 0.54], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("hood-hinge", "Hood hinge", JointKind.revolute, "body-shell", "closures", "body-structure", "hood", [0.0, 1.0, 0.0], [1.02, 0.0, 1.06], [0.0, 1.2], 1.0, 1, 0, "vehicle", false),
        joint("liftgate-hinge", "Liftgate hinge", JointKind.revolute, "body-shell", "closures", "body-structure", "liftgate", [0.0, 1.0, 0.0], [-1.93, 0.0, 1.56], [0.0, 1.45], 1.0, 1, 0, "vehicle", false),
        joint("door-hinge-front-left", "Front left door hinge", JointKind.revolute, "body-shell", "closures", "body-structure", "door-front-left", [0.0, 0.0, 1.0], [0.76, 0.86, 0.9], [0.0, 1.28], 1.0, 1, 0, "vehicle", false),
        joint("door-hinge-front-right", "Front right door hinge", JointKind.revolute, "body-shell", "closures", "body-structure", "door-front-right", [0.0, 0.0, 1.0], [0.76, -0.86, 0.9], [0.0, 1.28], 1.0, 1, 0, "vehicle", false),
        joint("door-hinge-rear-left", "Rear left door hinge", JointKind.revolute, "body-shell", "closures", "body-structure", "door-rear-left", [0.0, 0.0, 1.0], [-0.42, 0.86, 0.9], [0.0, 1.22], 1.0, 1, 0, "vehicle", false),
        joint("door-hinge-rear-right", "Rear right door hinge", JointKind.revolute, "body-shell", "closures", "body-structure", "door-rear-right", [0.0, 0.0, 1.0], [-0.42, -0.86, 0.9], [0.0, 1.22], 1.0, 1, 0, "vehicle", false),
        joint("seat-rail-slide", "Front seat longitudinal rail", JointKind.prismatic, "floor-pan", "seats", "floor-pan", "front-seat-frame", [1.0, 0.0, 0.0], [0.18, 0.38, 0.42], [-0.12, 0.12], 1.0, 1, 0, "vehicle", false),
        joint("glazing-bond", "Laminated glazing urethane bond", JointKind.rigid_bond, "body-shell", "glazing", "body-structure", "glazing", [0.0, 0.0, 1.0], [-0.15, 0.0, 1.19], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("interior-mount", "Interior and trim to body", JointKind.fixed, "body-shell", "interior", "body-structure", "interior", [0.0, 0.0, 1.0], [-0.12, 0.0, 0.79], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("restraints-mount", "Restraint anchorages to body", JointKind.fixed, "body-shell", "restraints", "body-structure", "restraints", [0.0, 0.0, 1.0], [0.15, 0.0, 1.02], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("lamps-mount", "Exterior lighting to body apertures", JointKind.fixed, "body-shell", "lamps", "body-structure", "lamps", [0.0, 0.0, 1.0], [0.05, 0.0, 0.83], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("sensors-mount", "Sensing suite to body bosses", JointKind.fixed, "body-shell", "sensors", "body-structure", "sensors", [0.0, 0.0, 1.0], [0.2, 0.0, 0.98], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("hv-junction-mount", "HV junction unit to pack", JointKind.fixed, "battery-enclosure", "hv-junction", "battery-enclosure", "hv-junction", [0.0, 0.0, 1.0], [-0.98, 0.42, 0.36], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("charge-port-mount", "Charge port to body aperture", JointKind.fixed, "body-shell", "charge-port", "body-structure", "charge-port", [0.0, 0.0, 1.0], [-2.18, 0.76, 0.76], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("onboard-charger-mount", "On-board charger to floor", JointKind.fixed, "floor-pan", "onboard-charger", "floor-pan", "onboard-charger", [0.0, 0.0, 1.0], [-1.7, -0.43, 0.42], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("dc-dc-mount", "DC/DC converter to body bracket", JointKind.fixed, "body-shell", "dc-dc", "body-structure", "dc-dc", [0.0, 0.0, 1.0], [1.32, -0.4, 0.52], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("lv-battery-mount", "Low-voltage battery tray", JointKind.fixed, "body-shell", "lv-battery", "body-structure", "lv-battery", [0.0, 0.0, 1.0], [1.68, -0.56, 0.44], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("central-compute-mount", "Central compute to body bracket", JointKind.fixed, "body-shell", "central-compute", "body-structure", "central-compute", [0.0, 0.0, 1.0], [0.88, 0.0, 0.64], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("front-zone-controller-mount", "Front zone controller bracket", JointKind.fixed, "body-shell", "front-zone-controller", "body-structure", "front-zone-controller", [0.0, 0.0, 1.0], [1.4, 0.56, 0.64], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("rear-zone-controller-mount", "Rear zone controller bracket", JointKind.fixed, "body-shell", "rear-zone-controller", "body-structure", "rear-zone-controller", [0.0, 0.0, 1.0], [-1.52, -0.52, 0.62], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("cabin-zone-controller-mount", "Cabin zone controller bracket", JointKind.fixed, "interior", "cabin-zone-controller", "interior", "cabin-zone-controller", [0.0, 0.0, 1.0], [0.56, 0.38, 0.62], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("heat-pump-mount", "Heat pump to front subframe", JointKind.fixed, "front-subframe", "heat-pump", "front-subframe", "heat-pump", [0.0, 0.0, 1.0], [1.82, 0.24, 0.56], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("thermal-loop-mount", "Coolant loop routing to body", JointKind.fixed, "body-shell", "thermal-loop", "body-structure", "thermal-loop", [0.0, 0.0, 1.0], [0.15, 0.0, 0.43], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("service-links-mount", "Serviceable harness set to body", JointKind.fixed, "body-shell", "service-links", "body-structure", "service-links", [0.0, 0.0, 1.0], [0.05, 0.0, 0.56], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
        joint("fasteners-seals-bond", "Closure seal and joining family", JointKind.rigid_bond, "closures", "fasteners-seals", "door-front-left", "closure-seal-set", [0.0, 0.0, 1.0], [0.76, 0.86, 0.9], [0.0, 0.0], 1.0, 0, 0, "vehicle", false),
    ]


def vehicle_joints_for(configuration_variant: str):
    """Complete kinematic joint set for one configuration variant."""
    require configuration_variant == "conventional" or configuration_variant == "circuitframe"
    mut joints = []
    for entry in corner_table():
        for corner_joint in corner_joints(entry):
            joints.append(corner_joint)
    for structural in structural_joints(configuration_variant):
        joints.append(structural)
    return joints


def vehicle_joints():
    """Complete kinematic joint set for the default CircuitFrame configuration."""
    return vehicle_joints_for(CONFIGURATION_VARIANT_DEFAULT)


def corner_loop_joint_ids(entry: dict[str, any]):
    """The joints that form the closed kinematic loop of one corner.

    Wheel bearing, brake, wheel, tire, halfshaft, and anti-roll-bar joints are excluded: they extend
    the corner as an open chain or couple the two corners of an axle, and are analysed separately.
    """
    prefix = entry["prefix"]
    steer_slug = "tie-rod" if entry["steerable"] else "toe-link"
    mut ids = [
        prefix + "-lca-inboard",
        prefix + "-uca-inboard",
        prefix + "-lca-outboard",
        prefix + "-uca-outboard",
        prefix + "-damper-top",
        prefix + "-damper-slide",
        prefix + "-damper-lower",
        prefix + "-" + steer_slug + "-inner",
        prefix + "-" + steer_slug + "-outer",
    ]
    if entry["steerable"]:
        ids = ids + ["steering-rack-slide"]
    return ids


def corner_mobility(entry: dict[str, any]) !{}:
    """Grubler-Kutzbach mobility of one suspension corner loop.

    Gross mobility counts every joint freedom; the idle freedoms are the spins of the
    spherical-spherical steering link and of the spherical-prismatic-spherical damper unit, which
    move no wheel state. A steered corner must retain one wheel-travel and one steer freedom; a
    rear corner must retain wheel travel only.
    """
    loop_ids = corner_loop_joint_ids(entry)
    joints = vehicle_joints()
    mut links = []
    mut dof_sum = 0
    mut idle_sum = 0
    mut loop_closing = []
    mut found = 0
    for candidate in joints:
        mut selected = false
        for loop_id in loop_ids:
            if candidate.id == loop_id:
                selected = true
        if selected:
            found = found + 1
            dof_sum = dof_sum + candidate.dof
            idle_sum = idle_sum + candidate.idle_dof
            if candidate.loop_closing:
                loop_closing = loop_closing + [candidate.id]
            for link in [candidate.parent_link, candidate.child_link]:
                mut seen = false
                for known in links:
                    if known == link:
                        seen = true
                if not seen:
                    links = links + [link]
    gross = grubler_mobility(len(links), found, dof_sum)
    expected = 2 if entry["steerable"] else 1
    effective = gross - idle_sum
    return MobilityReport(
        corner_id=entry["corner"],
        link_count=len(links),
        joint_count=found,
        joint_dof_sum=dof_sum,
        gross_mobility=gross,
        idle_dof=idle_sum,
        effective_mobility=effective,
        expected_mobility=expected,
        independent_loops=found - len(links) + 1,
        loop_closing_joint_ids=loop_closing,
        ok=effective == expected and found == len(loop_ids),
    )


def mobility_reports() !{}:
    """Mobility of all four suspension corner loops."""
    mut reports = []
    for entry in corner_table():
        reports.append(corner_mobility(entry))
    return reports


pub def arm_rotation_limits() -> list[f64] !{}:
    """Lower control arm rotation at the rebound and jounce stops, from the declared wheel travel."""
    return [-0.18232180232086856, 0.2017847836373893]


pub def corner_pose(arm_rotation_rad: f64) -> list[f64] !{}:
    """Closes the planar double-wishbone four-bar for a given lower-arm rotation.

    Returns [lbj_y, lbj_z, ubj_y, ubj_z, knuckle_rotation_rad, wheel_y, wheel_z, wheel_travel_m].
    The upper ball joint is the circle-circle intersection of the upper arm sweep and the rigid
    knuckle, so wheel travel and camber are consequences of the joint set, not of a fitted curve.
    """
    lower_dy = HP_LBJ_Y - HP_LCA_IN_Y
    lower_dz = HP_LBJ_Z - HP_LCA_IN_Z
    upper_dy = HP_UBJ_Y - HP_UCA_IN_Y
    upper_dz = HP_UBJ_Z - HP_UCA_IN_Z
    knuckle_dy = HP_UBJ_Y - HP_LBJ_Y
    knuckle_dz = HP_UBJ_Z - HP_LBJ_Z
    lower_length = math.sqrt(lower_dy * lower_dy + lower_dz * lower_dz)
    upper_length = math.sqrt(upper_dy * upper_dy + upper_dz * upper_dz)
    knuckle_span = math.sqrt(knuckle_dy * knuckle_dy + knuckle_dz * knuckle_dz)
    angle = math.atan2(lower_dz, lower_dy) + arm_rotation_rad
    lbj_y = HP_LCA_IN_Y + lower_length * math.cos(angle)
    lbj_z = HP_LCA_IN_Z + lower_length * math.sin(angle)
    span_y = lbj_y - HP_UCA_IN_Y
    span_z = lbj_z - HP_UCA_IN_Z
    span = math.sqrt(span_y * span_y + span_z * span_z)
    projection = (upper_length * upper_length - knuckle_span * knuckle_span + span * span) / (2.0 * span)
    offset = math.sqrt(max(0.0, upper_length * upper_length - projection * projection))
    mid_y = HP_UCA_IN_Y + projection * span_y / span
    mid_z = HP_UCA_IN_Z + projection * span_z / span
    first_y = mid_y - offset * span_z / span
    first_z = mid_z + offset * span_y / span
    second_y = mid_y + offset * span_z / span
    second_z = mid_z - offset * span_y / span
    first_error = (first_y - HP_UBJ_Y) * (first_y - HP_UBJ_Y) + (first_z - HP_UBJ_Z) * (first_z - HP_UBJ_Z)
    second_error = (second_y - HP_UBJ_Y) * (second_y - HP_UBJ_Y) + (second_z - HP_UBJ_Z) * (second_z - HP_UBJ_Z)
    ubj_y = first_y if first_error <= second_error else second_y
    ubj_z = first_z if first_error <= second_error else second_z
    knuckle_rotation = math.atan2(ubj_z - lbj_z, ubj_y - lbj_y) - math.atan2(knuckle_dz, knuckle_dy)
    arm_y = HP_WC_Y - HP_LBJ_Y
    arm_z = HP_WC_Z - HP_LBJ_Z
    cosine = math.cos(knuckle_rotation)
    sine = math.sin(knuckle_rotation)
    wheel_y = lbj_y + cosine * arm_y - sine * arm_z
    wheel_z = lbj_z + sine * arm_y + cosine * arm_z
    return [lbj_y, lbj_z, ubj_y, ubj_z, knuckle_rotation, wheel_y, wheel_z, wheel_z - HP_WC_Z]


def damper_length(arm_rotation_rad: f64):
    """Installed length of the air spring and damper unit for a given lower-arm rotation."""
    mount_dy = HP_DAMPER_LO_Y - HP_LCA_IN_Y
    mount_dz = HP_DAMPER_LO_Z - HP_LCA_IN_Z
    radius = math.sqrt(mount_dy * mount_dy + mount_dz * mount_dz)
    angle = math.atan2(mount_dz, mount_dy) + arm_rotation_rad
    mount_y = HP_LCA_IN_Y + radius * math.cos(angle)
    mount_z = HP_LCA_IN_Z + radius * math.sin(angle)
    return math.sqrt((HP_DAMPER_UP_Y - mount_y) * (HP_DAMPER_UP_Y - mount_y) + (HP_DAMPER_UP_Z - mount_z) * (HP_DAMPER_UP_Z - mount_z))


def motion_ratio(arm_rotation_rad: f64):
    """Spring shortening per unit wheel rise, differentiated through the linkage."""
    step = 0.00001
    length_change = damper_length(arm_rotation_rad + step) - damper_length(arm_rotation_rad - step)
    travel_change = corner_pose(arm_rotation_rad + step)[7] - corner_pose(arm_rotation_rad - step)[7]
    return -length_change / travel_change


def static_corner_load_n():
    """Static vertical load at one wheel with the vehicle at its declared kerb mass."""
    return ASSEMBLY_MASS_KG * GRAVITY_MPS2 / 4.0


pub def air_spring_reference_pressure() -> f64 !{}:
    """Absolute bellows pressure that balances the static corner load at design height."""
    return static_corner_load_n() / (motion_ratio(0.0) * AIR_SPRING_AREA_M2) + ATMOSPHERIC_PRESSURE_PA


def air_spring_force(arm_rotation_rad: f64, reference_pressure: f64) !{}:
    """Axial air spring force from the polytropic gas state of the bellows."""
    stroke = damper_length(0.0) - damper_length(arm_rotation_rad)
    volume = AIR_SPRING_VOLUME_M3 - AIR_SPRING_AREA_M2 * stroke
    pressure = polytropic_pressure(reference_pressure, AIR_SPRING_VOLUME_M3, volume, AIR_SPRING_POLYTROPIC_N)
    return AIR_SPRING_AREA_M2 * (pressure - ATMOSPHERIC_PRESSURE_PA)


pub def wheel_force(arm_rotation_rad: f64, reference_pressure: f64) -> f64 !{}:
    """Vertical wheel force supported by the corner, by virtual work through the motion ratio."""
    return air_spring_force(arm_rotation_rad, reference_pressure) * motion_ratio(arm_rotation_rad)


def vector_rotate(vector: list[f64], axis: list[f64], angle: f64):
    """Rodrigues rotation of a vector about a unit axis."""
    cosine = math.cos(angle)
    sine = math.sin(angle)
    projection = axis[0] * vector[0] + axis[1] * vector[1] + axis[2] * vector[2]
    return [
        vector[0] * cosine + (axis[1] * vector[2] - axis[2] * vector[1]) * sine + axis[0] * projection * (1.0 - cosine),
        vector[1] * cosine + (axis[2] * vector[0] - axis[0] * vector[2]) * sine + axis[1] * projection * (1.0 - cosine),
        vector[2] * cosine + (axis[0] * vector[1] - axis[1] * vector[0]) * sine + axis[2] * projection * (1.0 - cosine),
    ]


def tie_rod_residual(pose: list[f64], steer_rotation_rad: f64):
    """Length error of the steering link when the knuckle is rotated about its kingpin axis."""
    kingpin_y = pose[2] - pose[0]
    kingpin_z = pose[3] - pose[1]
    kingpin_length = math.sqrt(HP_KINGPIN_DX * HP_KINGPIN_DX + kingpin_y * kingpin_y + kingpin_z * kingpin_z)
    axis = [HP_KINGPIN_DX / kingpin_length, kingpin_y / kingpin_length, kingpin_z / kingpin_length]
    cosine = math.cos(pose[4])
    sine = math.sin(pose[4])
    offset_y = HP_TIE_OUT_Y - HP_LBJ_Y
    offset_z = HP_TIE_OUT_Z - HP_LBJ_Z
    carried = [HP_TIE_DX, cosine * offset_y - sine * offset_z, sine * offset_y + cosine * offset_z]
    turned = vector_rotate(carried, axis, steer_rotation_rad)
    delta_x = turned[0] - HP_TIE_DX
    delta_y = pose[0] + turned[1] - HP_TIE_IN_Y
    delta_z = pose[1] + turned[2] - HP_TIE_IN_Z
    reference_y = HP_TIE_OUT_Y - HP_TIE_IN_Y
    reference_z = HP_TIE_OUT_Z - HP_TIE_IN_Z
    return math.sqrt(delta_x * delta_x + delta_y * delta_y + delta_z * delta_z) - math.sqrt(reference_y * reference_y + reference_z * reference_z)


def bump_steer(pose: list[f64]):
    """Kingpin rotation forced by the fixed-length steering link, and the toe change it produces.

    Returns [kingpin_rotation_rad, toe_change_deg]. Solved with a damped Newton iteration seeded at
    the design position, so it tracks the physically near root of the length constraint.
    """
    kingpin_y = pose[2] - pose[0]
    kingpin_z = pose[3] - pose[1]
    kingpin_length = math.sqrt(HP_KINGPIN_DX * HP_KINGPIN_DX + kingpin_y * kingpin_y + kingpin_z * kingpin_z)
    vertical_component = kingpin_z / kingpin_length
    mut rotation = 0.0
    mut index = 0
    while index < STEER_SOLVE_ITERATIONS:
        step = 0.000001
        slope = (tie_rod_residual(pose, rotation + step) - tie_rod_residual(pose, rotation - step)) / (2.0 * step)
        if abs(slope) < 0.000000000001:
            index = STEER_SOLVE_ITERATIONS
        else:
            correction = tie_rod_residual(pose, rotation) / slope
            rotation = rotation - max(-0.05, min(0.05, correction))
            index = index + 1
    return [rotation, rotation * vertical_component * 180.0 / math.pi]


pub def suspension_travel(vertical_load_n: f64) -> SuspensionState !{}:
    """Solves the corner for a vertical wheel load and reports the resulting wheel motion.

    The load is balanced against the polytropic air spring through the linkage motion ratio; the
    lower-arm rotation that satisfies it is bisected between the rebound and jounce stops. Camber and
    toe are then read out of the four-bar closure and the steering-link length constraint, so the
    wheel pose is a consequence of the declared joints.
    """
    require vertical_load_n >= 0.0 and vertical_load_n <= 40000.0
    limits = arm_rotation_limits()
    reference_pressure = air_spring_reference_pressure()
    mut low = limits[0]
    mut high = limits[1]
    if vertical_load_n <= wheel_force(low, reference_pressure):
        high = low
    elif vertical_load_n >= wheel_force(high, reference_pressure):
        low = high
    else:
        mut index = 0
        while index < TRAVEL_SOLVE_ITERATIONS:
            middle = (low + high) * 0.5
            if wheel_force(middle, reference_pressure) < vertical_load_n:
                low = middle
            else:
                high = middle
            index = index + 1
    rotation = (low + high) * 0.5
    pose = corner_pose(rotation)
    steer = bump_steer(pose)
    travel = pose[7]
    step = 0.00001
    rate = (wheel_force(rotation + step, reference_pressure) - wheel_force(rotation - step, reference_pressure)) / (corner_pose(rotation + step)[7] - corner_pose(rotation - step)[7])
    camber_change = -pose[4] * 180.0 / math.pi
    utilisation = travel / JOUNCE_LIMIT_M if travel >= 0.0 else travel / REBOUND_LIMIT_M
    return SuspensionState(
        corner_id="front-left",
        vertical_load_n=vertical_load_n,
        arm_rotation_rad=rotation,
        wheel_travel_m=travel,
        spring_stroke_m=damper_length(0.0) - damper_length(rotation),
        damper_length_m=damper_length(rotation),
        spring_force_n=air_spring_force(rotation, reference_pressure),
        wheel_rate_n_m=rate,
        motion_ratio=motion_ratio(rotation),
        camber_deg=STATIC_CAMBER_DEG + camber_change,
        camber_change_deg=camber_change,
        toe_deg=STATIC_TOE_DEG + steer[1],
        toe_change_deg=steer[1],
        kingpin_rotation_rad=steer[0],
        jounce_limit_m=JOUNCE_LIMIT_M,
        rebound_limit_m=REBOUND_LIMIT_M,
        travel_utilisation=min(1.0, max(0.0, utilisation)),
        at_limit=travel >= JOUNCE_LIMIT_M or travel <= REBOUND_LIMIT_M,
    )


def load_path_declarations():
    """Declared load paths from load introduction to structural reaction, in transmission order.

    Utilisation is left at zero here; load_paths() fills it from the reference static load case.
    """
    return [
        LoadPath(id="lp-vertical-corner", name="Vertical wheel load to body and pack rails", kind=LoadPathKind.vertical, part_ids=["tires", "wheels", "brakes", "suspension", "front-subframe", "body-shell", "battery-enclosure"], rated_load_n=16000.0, utilisation=0.0),
        LoadPath(id="lp-braking-longitudinal", name="Braking reaction to subframe and floor", kind=LoadPathKind.longitudinal, part_ids=["tires", "wheels", "brakes", "suspension", "front-subframe", "floor-pan", "body-shell"], rated_load_n=26000.0, utilisation=0.0),
        LoadPath(id="lp-cornering-lateral", name="Cornering side force to sills and pack", kind=LoadPathKind.lateral, part_ids=["tires", "wheels", "suspension", "front-subframe", "sill-panels", "battery-enclosure", "body-shell"], rated_load_n=16000.0, utilisation=0.0),
        LoadPath(id="lp-pack-stressed-member", name="Battery pack as a stressed structural member", kind=LoadPathKind.structural, part_ids=["sill-panels", "battery-enclosure", "battery-modules", "floor-pan", "body-shell"], rated_load_n=90000.0, utilisation=0.0),
        LoadPath(id="lp-front-crash-rail", name="Front crash rail crush path", kind=LoadPathKind.crash, part_ids=["body-shell", "front-subframe", "sill-panels", "floor-pan", "battery-enclosure"], rated_load_n=420000.0, utilisation=0.0),
        LoadPath(id="lp-rear-crash-rail", name="Rear crash rail crush path", kind=LoadPathKind.crash, part_ids=["body-shell", "rear-subframe", "sill-panels", "floor-pan"], rated_load_n=300000.0, utilisation=0.0),
        LoadPath(id="lp-belt-anchorage", name="Seat belt anchorage reaction", kind=LoadPathKind.restraint, part_ids=["restraints", "seats", "floor-pan", "sill-panels", "body-shell"], rated_load_n=13500.0, utilisation=0.0),
    ]


def corner_statics(lateral_n: f64, vertical_n: f64) !{}:
    """Static equilibrium of one corner at the design position.

    Two 3x3 solves: the knuckle against the lower ball joint reaction and the axial upper-arm force,
    then the lower arm against the damper force and its inboard pivot reaction. Returns
    [lower_ball_y, lower_ball_z, upper_arm_axial, damper_axial, pivot_y, pivot_z,
     chassis_y, chassis_z, force_residual, moment_residual].
    """
    upper_dy = HP_UBJ_Y - HP_UCA_IN_Y
    upper_dz = HP_UBJ_Z - HP_UCA_IN_Z
    upper_length = math.sqrt(upper_dy * upper_dy + upper_dz * upper_dz)
    upper_unit_y = upper_dy / upper_length
    upper_unit_z = upper_dz / upper_length
    damper_dy = HP_DAMPER_UP_Y - HP_DAMPER_LO_Y
    damper_dz = HP_DAMPER_UP_Z - HP_DAMPER_LO_Z
    damper_length_static = math.sqrt(damper_dy * damper_dy + damper_dz * damper_dz)
    damper_unit_y = damper_dy / damper_length_static
    damper_unit_z = damper_dz / damper_length_static
    knuckle_matrix = [
        [1.0, 0.0, upper_unit_y],
        [0.0, 1.0, upper_unit_z],
        [0.0, 0.0, (HP_UBJ_Y - HP_LBJ_Y) * upper_unit_z - (HP_UBJ_Z - HP_LBJ_Z) * upper_unit_y],
    ]
    knuckle_rhs = [
        -lateral_n,
        -vertical_n,
        -((HP_WC_Y - HP_LBJ_Y) * vertical_n - (0.0 - HP_LBJ_Z) * lateral_n),
    ]
    knuckle = linear_solve_3(knuckle_matrix, knuckle_rhs)
    lower_ball_y = knuckle[0]
    lower_ball_z = knuckle[1]
    upper_axial = knuckle[2]
    arm_matrix = [
        [damper_unit_y, 1.0, 0.0],
        [damper_unit_z, 0.0, 1.0],
        [(HP_DAMPER_LO_Y - HP_LCA_IN_Y) * damper_unit_z - (HP_DAMPER_LO_Z - HP_LCA_IN_Z) * damper_unit_y, 0.0, 0.0],
    ]
    arm_rhs = [
        lower_ball_y,
        lower_ball_z,
        (HP_LBJ_Y - HP_LCA_IN_Y) * lower_ball_z - (HP_LBJ_Z - HP_LCA_IN_Z) * lower_ball_y,
    ]
    arm = linear_solve_3(arm_matrix, arm_rhs)
    damper_axial = arm[0]
    pivot_y = arm[1]
    pivot_z = arm[2]
    chassis_y = -pivot_y - upper_axial * upper_unit_y - damper_axial * damper_unit_y
    chassis_z = -pivot_z - upper_axial * upper_unit_z - damper_axial * damper_unit_z
    applied_moment = (HP_WC_Y - HP_LCA_IN_Y) * vertical_n - (0.0 - HP_LCA_IN_Z) * lateral_n
    reaction_moment = (HP_UCA_IN_Y - HP_LCA_IN_Y) * (-upper_axial * upper_unit_z) - (HP_UCA_IN_Z - HP_LCA_IN_Z) * (-upper_axial * upper_unit_y) + (HP_DAMPER_UP_Y - HP_LCA_IN_Y) * (-damper_axial * damper_unit_z) - (HP_DAMPER_UP_Z - HP_LCA_IN_Z) * (-damper_axial * damper_unit_y)
    return [
        lower_ball_y,
        lower_ball_z,
        upper_axial,
        damper_axial,
        pivot_y,
        pivot_z,
        chassis_y,
        chassis_z,
        math.sqrt((chassis_y - lateral_n) * (chassis_y - lateral_n) + (chassis_z - vertical_n) * (chassis_z - vertical_n)),
        abs(applied_moment - reaction_moment),
    ]


def load_paths() !{}:
    """Declared load paths with the utilisation each reaches under the reference static load case."""
    evaluated = load_path_reactions(static_corner_load_n(), REFERENCE_BRAKING_N, REFERENCE_CORNERING_N)
    declarations = load_path_declarations()
    mut paths = []
    for index in range(0, len(declarations)):
        declaration = declarations[index]
        paths.append(LoadPath(id=declaration.id, name=declaration.name, kind=declaration.kind, part_ids=declaration.part_ids, rated_load_n=declaration.rated_load_n, utilisation=evaluated.results[index].utilisation))
    return paths


def reaction(path_id: str, index: int, part_id: str, role: str, force_n: f64, moment_nm: f64):
    """One element of a load path chain."""
    return LoadPathReaction(path_id=path_id, element_index=index, part_id=part_id, role=role, force_n=force_n, moment_nm=moment_nm)


def shared_path_result(path: LoadPath, applied_n: f64, shares: list[f64], roles: list[str]):
    """Distributes an applied load over a branching path whose shares sum to one."""
    require len(shares) == len(path.part_ids)
    require len(roles) == len(path.part_ids)
    mut reactions = []
    mut total = 0.0
    mut peak = 0.0
    for index in range(0, len(shares)):
        element = applied_n * shares[index]
        total = total + element
        peak = max(peak, abs(element))
        reactions.append(reaction(path.id, index, path.part_ids[index], roles[index], element, 0.0))
    return LoadPathResult(
        path_id=path.id,
        name=path.name,
        kind=path.kind,
        applied_n=applied_n,
        reaction_n=total,
        residual_n=abs(applied_n - total),
        peak_force_n=peak,
        rated_load_n=path.rated_load_n,
        utilisation=peak / path.rated_load_n,
        reactions=reactions,
    )


def load_path_reactions(vertical_n: f64, longitudinal_n: f64, lateral_n: f64) !{}:
    """Reactions along every declared load path for one static load case.

    The vertical and lateral corner paths are closed by the two 3x3 equilibrium solves in
    corner_statics; the remaining paths distribute their input over branch shares that sum to one.
    Every path reports the residual between the applied load and the summed reaction.
    """
    require vertical_n >= 0.0 and vertical_n <= 40000.0
    require longitudinal_n >= -60000.0 and longitudinal_n <= 60000.0
    require lateral_n >= -60000.0 and lateral_n <= 60000.0
    paths = load_path_declarations()
    braking = abs(longitudinal_n)
    cornering = abs(lateral_n)
    vertical_solve = corner_statics(0.0, vertical_n)
    lateral_solve = corner_statics(lateral_n, vertical_n)
    lower_ball = math.sqrt(vertical_solve[0] * vertical_solve[0] + vertical_solve[1] * vertical_solve[1])
    pivot = math.sqrt(vertical_solve[4] * vertical_solve[4] + vertical_solve[5] * vertical_solve[5])
    chassis = math.sqrt(vertical_solve[6] * vertical_solve[6] + vertical_solve[7] * vertical_solve[7])
    lateral_ball = math.sqrt(lateral_solve[0] * lateral_solve[0] + lateral_solve[1] * lateral_solve[1])
    lateral_pivot = math.sqrt(lateral_solve[4] * lateral_solve[4] + lateral_solve[5] * lateral_solve[5])
    vertical_path = paths[0]
    vertical_reactions = [
        reaction(vertical_path.id, 0, "tires", "contact patch", vertical_n, 0.0),
        reaction(vertical_path.id, 1, "wheels", "rim to hub face", vertical_n, 0.0),
        reaction(vertical_path.id, 2, "brakes", "hub bearing", vertical_n, 0.0),
        reaction(vertical_path.id, 3, "suspension", "lower ball joint and upper arm", max(lower_ball, abs(vertical_solve[2])), 0.0),
        reaction(vertical_path.id, 4, "front-subframe", "control arm pivot", pivot, 0.0),
        reaction(vertical_path.id, 5, "body-shell", "damper tower", abs(vertical_solve[3]), 0.0),
        reaction(vertical_path.id, 6, "battery-enclosure", "pack rail reaction", chassis, 0.0),
    ]
    mut vertical_peak = 0.0
    for entry in vertical_reactions:
        vertical_peak = max(vertical_peak, abs(entry.force_n))
    vertical_result = LoadPathResult(path_id=vertical_path.id, name=vertical_path.name, kind=vertical_path.kind, applied_n=vertical_n, reaction_n=chassis, residual_n=vertical_solve[8], peak_force_n=vertical_peak, rated_load_n=vertical_path.rated_load_n, utilisation=vertical_peak / vertical_path.rated_load_n, reactions=vertical_reactions)
    braking_path = paths[1]
    anti_dive_couple = braking * WHEEL_RADIUS_M / (HP_UBJ_Z - HP_LBJ_Z)
    braking_reactions = [
        reaction(braking_path.id, 0, "tires", "contact patch", braking, 0.0),
        reaction(braking_path.id, 1, "wheels", "rim to hub face", braking, 0.0),
        reaction(braking_path.id, 2, "brakes", "caliper torque reaction", braking * WHEEL_RADIUS_M / 0.150, braking * WHEEL_RADIUS_M),
        reaction(braking_path.id, 3, "suspension", "arm couple against wheel torque", anti_dive_couple, braking * WHEEL_RADIUS_M),
        reaction(braking_path.id, 4, "front-subframe", "fore-aft bush reaction", braking, 0.0),
        reaction(braking_path.id, 5, "floor-pan", "front rail shear", braking * 0.5, 0.0),
        reaction(braking_path.id, 6, "body-shell", "front rail shear", braking * 0.5, 0.0),
    ]
    mut braking_peak = 0.0
    for entry in braking_reactions:
        braking_peak = max(braking_peak, abs(entry.force_n))
    braking_result = LoadPathResult(path_id=braking_path.id, name=braking_path.name, kind=braking_path.kind, applied_n=braking, reaction_n=braking * 0.5 + braking * 0.5, residual_n=abs(braking - (braking * 0.5 + braking * 0.5)), peak_force_n=braking_peak, rated_load_n=braking_path.rated_load_n, utilisation=braking_peak / braking_path.rated_load_n, reactions=braking_reactions)
    lateral_path = paths[2]
    lateral_reactions = [
        reaction(lateral_path.id, 0, "tires", "contact patch side force", cornering, 0.0),
        reaction(lateral_path.id, 1, "wheels", "bead seat shear", cornering, 0.0),
        reaction(lateral_path.id, 2, "suspension", "ball joint resultant", lateral_ball, 0.0),
        reaction(lateral_path.id, 3, "front-subframe", "control arm pivot", lateral_pivot, 0.0),
        reaction(lateral_path.id, 4, "sill-panels", "sill shear panel", cornering * 0.5, 0.0),
        reaction(lateral_path.id, 5, "battery-enclosure", "pack cross member", cornering * 0.25, 0.0),
        reaction(lateral_path.id, 6, "body-shell", "floor cross member", cornering * 0.25, 0.0),
    ]
    mut lateral_peak = 0.0
    for entry in lateral_reactions:
        lateral_peak = max(lateral_peak, abs(entry.force_n))
    lateral_total = cornering * 0.5 + cornering * 0.25 + cornering * 0.25
    lateral_result = LoadPathResult(path_id=lateral_path.id, name=lateral_path.name, kind=lateral_path.kind, applied_n=cornering, reaction_n=lateral_total, residual_n=abs(cornering - lateral_total), peak_force_n=lateral_peak, rated_load_n=lateral_path.rated_load_n, utilisation=lateral_peak / lateral_path.rated_load_n, reactions=lateral_reactions)
    pack_result = shared_path_result(paths[3], vertical_n * 4.0 * 0.5, [0.5, 0.25, 0.125, 0.0625, 0.0625], ["sill shear panel", "enclosure top and bottom skin", "module stack shear", "floor tunnel", "body ring"])
    front_crash_result = shared_path_result(paths[4], braking, [0.375, 0.1875, 0.25, 0.125, 0.0625], ["upper rail", "subframe crush can", "sill front horn", "floor front rail", "pack front bumper beam"])
    rear_crash_result = shared_path_result(paths[5], braking, [0.4375, 0.1875, 0.25, 0.125], ["upper rail", "subframe crush can", "sill rear horn", "floor rear rail"])
    belt_result = shared_path_result(paths[6], paths[6].rated_load_n, [0.5, 0.25, 0.125, 0.0625, 0.0625], ["upper anchorage", "buckle anchorage", "floor anchor plate", "sill reinforcement", "pillar ring"])
    results = [vertical_result, braking_result, lateral_result, pack_result, front_crash_result, rear_crash_result, belt_result]
    mut residual = 0.0
    mut worst_id = results[0].path_id
    mut worst = 0.0
    for path_result in results:
        residual = max(residual, path_result.residual_n)
        if path_result.utilisation > worst:
            worst = path_result.utilisation
            worst_id = path_result.path_id
    return LoadCase(
        vertical_n=vertical_n,
        longitudinal_n=longitudinal_n,
        lateral_n=lateral_n,
        results=results,
        equilibrium_residual_n=residual,
        moment_residual_nm=max(vertical_solve[9], lateral_solve[9]),
        worst_path_id=worst_id,
        worst_utilisation=worst,
    )


def thermal_network():
    """Coupled lumped thermal network: the coolant loop is a shared node, not a fixed sink.

    Heat leaves a component into the glycol loop, travels to the chiller and the cabin through the
    heat pump, and only then reaches ambient through the radiator and the body skin.
    """
    return ThermalNetwork(
        id="circuitframe-thermal-v1",
        nodes=[
            ThermalNode(id="tn-battery", part_id="battery-modules", capacity_kj_k=540.0, ambient_coupling_w_k=6.0, initial_temp_c=24.0),
            ThermalNode(id="tn-front-motor", part_id="front-motor", capacity_kj_k=27.0, ambient_coupling_w_k=3.5, initial_temp_c=26.0),
            ThermalNode(id="tn-rear-motor", part_id="rear-motor", capacity_kj_k=33.0, ambient_coupling_w_k=3.8, initial_temp_c=26.0),
            ThermalNode(id="tn-front-inverter", part_id="front-inverter", capacity_kj_k=7.7, ambient_coupling_w_k=1.6, initial_temp_c=25.0),
            ThermalNode(id="tn-rear-inverter", part_id="rear-inverter", capacity_kj_k=8.4, ambient_coupling_w_k=1.7, initial_temp_c=25.0),
            ThermalNode(id="tn-cabin", part_id="interior", capacity_kj_k=160.0, ambient_coupling_w_k=42.0, initial_temp_c=22.0),
            ThermalNode(id="tn-coolant", part_id="thermal-loop", capacity_kj_k=60.0, ambient_coupling_w_k=260.0, initial_temp_c=24.0),
            ThermalNode(id="tn-heat-pump", part_id="heat-pump", capacity_kj_k=22.0, ambient_coupling_w_k=4.0, initial_temp_c=24.0),
        ],
        links=[
            ThermalLink(id="tl-battery-coolant", from_node_id="tn-battery", to_node_id="tn-coolant", conductance_w_k=900.0, medium="glycol cold plate"),
            ThermalLink(id="tl-front-motor-coolant", from_node_id="tn-front-motor", to_node_id="tn-coolant", conductance_w_k=420.0, medium="stator jacket"),
            ThermalLink(id="tl-rear-motor-coolant", from_node_id="tn-rear-motor", to_node_id="tn-coolant", conductance_w_k=480.0, medium="stator jacket"),
            ThermalLink(id="tl-front-inverter-coolant", from_node_id="tn-front-inverter", to_node_id="tn-coolant", conductance_w_k=260.0, medium="power module cold plate"),
            ThermalLink(id="tl-rear-inverter-coolant", from_node_id="tn-rear-inverter", to_node_id="tn-coolant", conductance_w_k=280.0, medium="power module cold plate"),
            ThermalLink(id="tl-coolant-heat-pump", from_node_id="tn-coolant", to_node_id="tn-heat-pump", conductance_w_k=700.0, medium="chiller plate heat exchanger"),
            ThermalLink(id="tl-heat-pump-cabin", from_node_id="tn-heat-pump", to_node_id="tn-cabin", conductance_w_k=520.0, medium="cabin condenser and evaporator"),
            ThermalLink(id="tl-cabin-battery", from_node_id="tn-cabin", to_node_id="tn-battery", conductance_w_k=55.0, medium="floor conduction"),
            ThermalLink(id="tl-front-inverter-motor", from_node_id="tn-front-inverter", to_node_id="tn-front-motor", conductance_w_k=34.0, medium="shared drive unit housing"),
            ThermalLink(id="tl-rear-inverter-motor", from_node_id="tn-rear-inverter", to_node_id="tn-rear-motor", conductance_w_k=36.0, medium="shared drive unit housing"),
        ],
    )


def isolated_thermal_network():
    """The same graph with every ambient coupling removed, for closed-system energy checks."""
    source = thermal_network()
    mut nodes = []
    for node in source.nodes:
        nodes.append(ThermalNode(id=node.id, part_id=node.part_id, capacity_kj_k=node.capacity_kj_k, ambient_coupling_w_k=0.0, initial_temp_c=node.initial_temp_c))
    return ThermalNetwork(id=source.id + "-isolated", nodes=nodes, links=source.links)


def node_index(network: ThermalNetwork, node_id: str):
    """Position of a node in the network ordering, or -1 when it is absent."""
    for index in range(0, len(network.nodes)):
        if network.nodes[index].id == node_id:
            return index
    return -1


def thermal_step_network(network: ThermalNetwork, temps: list[f64], heat_w: list[f64], ambient_c: f64, dt_s: f64):
    """Advances an explicit lumped-capacitance network one step.

    C_i dT_i/dt = Q_i + sum_j k_ij (T_j - T_i) - h_i (T_i - T_amb). The link term is antisymmetric, so
    with the ambient couplings removed the total stored energy changes by exactly the injected heat.
    """
    require len(temps) == len(network.nodes)
    require len(heat_w) == len(network.nodes)
    require dt_s > 0.0 and dt_s <= 5.0
    mut flux = []
    for index in range(0, len(temps)):
        flux = flux + [heat_w[index] - network.nodes[index].ambient_coupling_w_k * (temps[index] - ambient_c)]
    for link in network.links:
        source = node_index(network, link.from_node_id)
        target = node_index(network, link.to_node_id)
        transfer = link.conductance_w_k * (temps[source] - temps[target])
        flux[source] = flux[source] - transfer
        flux[target] = flux[target] + transfer
    mut next_temps = []
    for index in range(0, len(temps)):
        next_temps = next_temps + [temps[index] + flux[index] * dt_s / (network.nodes[index].capacity_kj_k * 1000.0)]
    return next_temps


pub def thermal_step(temps: list[f64], heat_w: list[f64], ambient_c: f64, dt_s: f64) -> list[f64] !{}:
    """Advances the vehicle thermal network one step."""
    return thermal_step_network(thermal_network(), temps, heat_w, ambient_c, dt_s)


pub def thermal_initial_temps() -> list[f64] !{}:
    """Declared soak temperatures of the network, in node order."""
    mut temps = []
    for node in thermal_network().nodes:
        temps = temps + [node.initial_temp_c]
    return temps


def thermal_stability_limit_s():
    """Largest explicit step the network tolerates: min over nodes of 2 C / (sum k + h)."""
    network = thermal_network()
    mut limit = 1000000.0
    for index in range(0, len(network.nodes)):
        mut conductance = network.nodes[index].ambient_coupling_w_k
        for link in network.links:
            if link.from_node_id == network.nodes[index].id or link.to_node_id == network.nodes[index].id:
                conductance = conductance + link.conductance_w_k
        limit = min(limit, 2.0 * network.nodes[index].capacity_kj_k * 1000.0 / conductance)
    return limit


pub def assembly_export() -> dict[str, any] !{}:
    """Full assembly payload: placements, interfaces, kinematics, load paths, and the thermal graph."""
    properties = mass_properties()
    parts = vehicle_parts()
    placements = part_placements()
    termination = port_termination_report()
    reports = mobility_reports()
    load_case = load_path_reactions(static_corner_load_n(), REFERENCE_BRAKING_N, REFERENCE_CORNERING_N)
    mut placement_rows = []
    for index in range(0, len(placements)):
        placement = placements[index]
        mut centres = []
        for row in placement_instances(placement):
            centres = centres + [[row[1], row[2], row[3]]]
        placement_rows = placement_rows + [{
            "part_id": placement.part_id,
            "name": parts[index].name,
            "group": parts[index].group,
            "pattern": placement.pattern,
            "pattern_instances": placement.pattern_instances,
            "declared_count": parts[index].count,
            "mass_kg": placement.mass_kg,
            "centre": [placement.centre_x, placement.centre_y, placement.centre_z],
            "extent": [placement.extent_x, placement.extent_y, placement.extent_z],
            "span": [placement.span_x, placement.span_y, 0.0],
            "instance_centres": centres,
            "unsprung_fraction": placement.unsprung_fraction,
        }]
    mut port_rows = []
    for port in vehicle_ports():
        port_rows = port_rows + [{"id": port.id, "part": port.owner_part_id, "kind": port.kind, "mate_port": port.mate_port_id, "required": port.required, "terminated": port.terminated}]
    mut mate_rows = []
    for mate in vehicle_mates():
        mate_rows = mate_rows + [{"id": mate.id, "port_a": mate.port_a_id, "port_b": mate.port_b_id, "kind": mate.kind, "preload_n": mate.preload_n, "sealed": mate.sealed, "serviceable": mate.serviceable, "robot_accessible": mate.robot_accessible}]
    mut joint_rows = []
    for item in vehicle_joints():
        joint_rows = joint_rows + [{
            "id": item.id,
            "name": item.name,
            "kind": item.kind,
            "parent_part": item.parent_part_id,
            "child_part": item.child_part_id,
            "parent_link": item.parent_link,
            "child_link": item.child_link,
            "axis": [item.axis_x, item.axis_y, item.axis_z],
            "origin": [item.origin_x, item.origin_y, item.origin_z],
            "limit_lower": item.limit_lower,
            "limit_upper": item.limit_upper,
            "ratio": item.ratio,
            "dof": item.dof,
            "idle_dof": item.idle_dof,
            "corner": item.corner,
            "loop_closing": item.loop_closing,
        }]
    mut load_path_rows = []
    for path_result in load_case.results:
        mut entries = []
        for entry in path_result.reactions:
            entries = entries + [{"index": entry.element_index, "part_id": entry.part_id, "role": entry.role, "force_n": entry.force_n, "moment_nm": entry.moment_nm}]
        load_path_rows = load_path_rows + [{
            "id": path_result.path_id,
            "name": path_result.name,
            "kind": path_result.kind,
            "part_ids": [entry.part_id for entry in path_result.reactions],
            "applied_n": path_result.applied_n,
            "reaction_n": path_result.reaction_n,
            "residual_n": path_result.residual_n,
            "peak_force_n": path_result.peak_force_n,
            "rated_load_n": path_result.rated_load_n,
            "utilisation": path_result.utilisation,
            "reactions": entries,
        }]
    network = thermal_network()
    mut node_rows = []
    for node in network.nodes:
        node_rows = node_rows + [{"id": node.id, "part_id": node.part_id, "capacity_kj_k": node.capacity_kj_k, "ambient_coupling_w_k": node.ambient_coupling_w_k, "initial_temp_c": node.initial_temp_c}]
    mut link_rows = []
    for link in network.links:
        link_rows = link_rows + [{"id": link.id, "from": link.from_node_id, "to": link.to_node_id, "conductance_w_k": link.conductance_w_k, "medium": link.medium}]
    mut mobility_rows = []
    mut mobility_ok = true
    for report in reports:
        mobility_rows = mobility_rows + [{
            "corner": report.corner_id,
            "links": report.link_count,
            "joints": report.joint_count,
            "joint_dof_sum": report.joint_dof_sum,
            "gross_mobility": report.gross_mobility,
            "idle_dof": report.idle_dof,
            "effective_mobility": report.effective_mobility,
            "expected_mobility": report.expected_mobility,
            "independent_loops": report.independent_loops,
            "loop_closing_joints": report.loop_closing_joint_ids,
            "ok": report.ok,
        }]
        if not report.ok:
            mobility_ok = false
    mut suspension_rows = []
    for load in [3800.0, static_corner_load_n(), 6500.0, 8500.0]:
        state = suspension_travel(load)
        suspension_rows = suspension_rows + [{
            "load_n": state.vertical_load_n,
            "travel_m": state.wheel_travel_m,
            "spring_stroke_m": state.spring_stroke_m,
            "wheel_rate_n_m": state.wheel_rate_n_m,
            "motion_ratio": state.motion_ratio,
            "camber_deg": state.camber_deg,
            "toe_deg": state.toe_deg,
            "travel_utilisation": state.travel_utilisation,
        }]
    return {
        "schema": ASSEMBLY_SCHEMA,
        "configuration_variant": CONFIGURATION_VARIANT_DEFAULT,
        "authority": ASSEMBLY_AUTHORITY,
        "frame": {
            "origin": "ground plane at mid-wheelbase",
            "x": "forward",
            "y": "left",
            "z": "up",
            "front_axle_x_m": FRONT_AXLE_X_M,
            "rear_axle_x_m": REAR_AXLE_X_M,
            "half_track_m": HALF_TRACK_M,
            "wheel_radius_m": WHEEL_RADIUS_M,
            "ride_height_m": RIDE_HEIGHT_M,
            "wheelbase_m": WHEELBASE_M,
        },
        "mass_properties": {
            "total_mass_kg": properties.total_mass_kg,
            "sprung_mass_kg": properties.sprung_mass_kg,
            "unsprung_mass_kg": properties.unsprung_mass_kg,
            "cg_x_m": properties.cg_x_m,
            "cg_y_m": properties.cg_y_m,
            "cg_z_m": properties.cg_z_m,
            "ixx_kg_m2": properties.ixx_kg_m2,
            "iyy_kg_m2": properties.iyy_kg_m2,
            "izz_kg_m2": properties.izz_kg_m2,
            "front_axle_load_kg": properties.front_axle_load_kg,
            "rear_axle_load_kg": properties.rear_axle_load_kg,
            "front_mass_fraction": properties.front_mass_fraction,
            "wheelbase_m": properties.wheelbase_m,
            "cg_height_m": properties.cg_height_m,
            "part_count": properties.part_count,
            "instance_count": properties.instance_count,
            "closure_residual_kg": properties.closure_residual_kg,
        },
        "ports": port_rows,
        "mates": mate_rows,
        "joints": joint_rows,
        "placements": placement_rows,
        "load_paths": load_path_rows,
        "thermal": {"nodes": node_rows, "links": link_rows, "stability_limit_s": thermal_stability_limit_s()},
        "mobility": mobility_rows,
        "suspension": {
            "static_corner_load_n": static_corner_load_n(),
            "travel_jounce_m": JOUNCE_LIMIT_M,
            "travel_rebound_m": REBOUND_LIMIT_M,
            "motion_ratio": motion_ratio(0.0),
            "static_camber_deg": STATIC_CAMBER_DEG,
            "static_toe_deg": STATIC_TOE_DEG,
            "samples": suspension_rows,
        },
        "checks": {
            "mobility_ok": mobility_ok,
            "ports_terminated": termination["unterminated_required_ports"] == 0 and termination["mismated_ports"] == 0,
            "equilibrium_residual_n": load_case.equilibrium_residual_n,
            "moment_residual_nm": load_case.moment_residual_nm,
            "mass_closure_residual_kg": properties.closure_residual_kg,
            "unterminated_required_ports": termination["unterminated_required_ports"],
            "open_optional_ports": termination["open_optional_ports"],
            "mismated_ports": termination["mismated_ports"],
            "port_count": termination["ports"],
            "mate_count": termination["mates"],
            "joint_count": len(joint_rows),
            "placement_count": len(placement_rows),
            "load_path_count": len(load_path_rows),
            "thermal_node_count": len(node_rows),
            "thermal_link_count": len(link_rows),
        },
    }


pub def assembly_equations() -> list[dict[str, str]] !{}:
    """Manifest rows for the assembly model, in the same shape as physics.sema:dynamics_equations()."""
    return [
        {"id": "centre-of-mass", "source": "mass_properties", "expression": "r_cg = sum(m_i r_i) / sum(m_i)", "unit": "m", "owner": "Sema assembly mass model"},
        {"id": "parallel-axis", "source": "parallel_axis_term", "expression": "I_zz = sum(m_i (a_i^2 + b_i^2)/12 + m_i ((x_i - x_cg)^2 + (y_i - y_cg)^2))", "unit": "kg m^2", "owner": "Sema assembly mass model"},
        {"id": "axle-load-split", "source": "mass_properties", "expression": "f_front = (x_cg - x_rear) / L", "unit": "fraction", "owner": "Sema assembly mass model"},
        {"id": "grubler-mobility", "source": "grubler_mobility", "expression": "M = 6 (n - 1 - j) + sum f_i - f_idle", "unit": "1", "owner": "Sema kinematic assembly"},
        {"id": "four-bar-closure", "source": "corner_pose", "expression": "|p_ubj - p_uca| = L_upper and |p_ubj - p_lbj| = L_knuckle", "unit": "m", "owner": "Sema suspension kinematics"},
        {"id": "motion-ratio", "source": "motion_ratio", "expression": "MR = -d(l_damper)/d(z_wheel)", "unit": "1", "owner": "Sema suspension kinematics"},
        {"id": "air-spring", "source": "polytropic_pressure", "expression": "p = p_0 (V_0 / (V_0 - A s))^n; F_wheel = MR A (p - p_atm)", "unit": "N", "owner": "Sema suspension compliance"},
        {"id": "bump-steer", "source": "bump_steer", "expression": "|R(u, delta) (p_tie - p_lbj) + p_lbj - p_rack| = L_tie", "unit": "rad", "owner": "Sema suspension kinematics"},
        {"id": "static-equilibrium", "source": "corner_statics", "expression": "sum F = 0 and sum M = 0 solved per free body", "unit": "N", "owner": "Sema load-path statics"},
        {"id": "load-path-closure", "source": "load_path_reactions", "expression": "R_total - F_applied = 0", "unit": "N", "owner": "Sema load-path statics"},
        {"id": "thermal-network", "source": "thermal_step_network", "expression": "C_i dT_i/dt = Q_i + sum_j k_ij (T_j - T_i) - h_i (T_i - T_amb)", "unit": "degC", "owner": "Sema coupled thermal network"},
        {"id": "thermal-stability", "source": "thermal_stability_limit_s", "expression": "dt_max = min_i 2 C_i / (sum_j k_ij + h_i)", "unit": "s", "owner": "Sema coupled thermal network"},
    ]


test "part placements close the declared mass exactly":
    properties = mass_properties()
    ensure properties.closure_residual_kg == 0.0
    ensure properties.total_mass_kg == ASSEMBLY_MASS_KG
    ensure properties.part_count == 38
    ensure properties.instance_count > properties.part_count
    ensure abs(properties.sprung_mass_kg + properties.unsprung_mass_kg - properties.total_mass_kg) < 0.000000001
    ensure properties.unsprung_mass_kg > 150.0 and properties.unsprung_mass_kg < 280.0


test "centre of gravity and inertia stay inside the crossover envelope":
    properties = mass_properties()
    ensure properties.cg_height_m >= 0.45 and properties.cg_height_m <= 0.60
    ensure properties.front_mass_fraction >= 0.47 and properties.front_mass_fraction <= 0.53
    ensure properties.izz_kg_m2 >= 2800.0 and properties.izz_kg_m2 <= 4500.0
    ensure properties.ixx_kg_m2 >= 500.0 and properties.ixx_kg_m2 <= 1400.0
    ensure properties.iyy_kg_m2 >= 2400.0 and properties.iyy_kg_m2 <= 4200.0
    ensure abs(properties.cg_y_m) < 0.02
    ensure abs(properties.front_axle_load_kg + properties.rear_axle_load_kg - properties.total_mass_kg) < 0.000000001


test "every required port is terminated by exactly one mate":
    report = port_termination_report()
    ensure report["unterminated_required_ports"] == 0
    ensure report["mismated_ports"] == 0
    ensure report["ports"] >= 90
    ensure report["mates"] >= 45
    ensure report["open_optional_ports"] > 0
    ensure report["ports_mated_exactly_once"] == report["mates"] * 2


test "suspension corner loops have the mobility their joints imply":
    reports = mobility_reports()
    ensure len(reports) == 4
    mut steered = 0
    for report in reports:
        ensure report.ok
        ensure report.effective_mobility == report.expected_mobility
        ensure report.independent_loops == 3
        ensure len(report.loop_closing_joint_ids) == 3
        if report.expected_mobility == 2:
            steered = steered + 1
    ensure steered == 2


test "the declared joint set covers the real chains":
    joints = vehicle_joints()
    ensure len(joints) >= 100
    mut gears = 0
    mut spherical = 0
    mut prismatic = 0
    mut ratio_product = 1.0
    for item in joints:
        if item.kind == JointKind.gear:
            gears = gears + 1
            if item.parent_link == "front-motor-rotor" or item.parent_link == "front-intermediate-shaft":
                ratio_product = ratio_product * item.ratio
        if item.kind == JointKind.spherical:
            spherical = spherical + 1
        if item.kind == JointKind.prismatic:
            prismatic = prismatic + 1
    ensure gears == 4
    ensure abs(ratio_product - 9.1) < 0.000000001
    ensure spherical >= 24
    ensure prismatic >= 5


test "load path reactions close against the applied load":
    load_case = load_path_reactions(5200.0, 9000.0, 6000.0)
    ensure len(load_case.results) == 7
    ensure load_case.equilibrium_residual_n < 0.000000001
    ensure load_case.moment_residual_nm < 0.000000001
    for path_result in load_case.results:
        ensure path_result.residual_n < 0.000000001
        ensure path_result.peak_force_n > 0.0
    ensure load_case.worst_utilisation > 0.0


test "an isolated thermal network conserves energy exactly":
    network = isolated_thermal_network()
    temps = [31.0, 58.0, 64.0, 47.0, 52.0, 21.0, 35.0, 27.0]
    heat = [1800.0, 2400.0, 3100.0, 900.0, 1100.0, 0.0, 0.0, 600.0]
    dt = 0.05
    next_temps = thermal_step_network(network, temps, heat, 20.0, dt)
    mut stored = 0.0
    mut injected = 0.0
    for index in range(0, len(temps)):
        stored = stored + network.nodes[index].capacity_kj_k * 1000.0 * (next_temps[index] - temps[index])
        injected = injected + heat[index] * dt
    ensure abs(stored - injected) < 0.000000001 * abs(injected)


test "the coupled network moves heat between components":
    temps = thermal_initial_temps()
    heat = [0.0, 9000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    next_temps = thermal_step(temps, heat, 24.0, 0.05)
    ensure next_temps[1] > temps[1]
    ensure next_temps[6] > temps[6]
    ensure thermal_stability_limit_s() > 1.0
    ensure len(thermal_network().nodes) == 8
    ensure len(thermal_network().links) == 10


test "suspension travel rises with load and keeps camber bounded":
    light = suspension_travel(3800.0)
    static = suspension_travel(static_corner_load_n())
    heavy = suspension_travel(7200.0)
    limit = suspension_travel(20000.0)
    ensure light.wheel_travel_m < static.wheel_travel_m
    ensure static.wheel_travel_m < heavy.wheel_travel_m
    ensure heavy.wheel_travel_m <= limit.wheel_travel_m
    ensure abs(static.wheel_travel_m) < 0.001
    ensure abs(static.camber_change_deg) < 0.05
    ensure abs(light.camber_change_deg) < 6.0 and abs(limit.camber_change_deg) < 6.0
    ensure abs(limit.toe_change_deg) < 1.0
    ensure limit.at_limit and not static.at_limit
    ensure static.wheel_rate_n_m > 20000.0 and static.wheel_rate_n_m < 60000.0
    ensure light.spring_stroke_m < 0.0 and heavy.spring_stroke_m > 0.0


test "declared damper stroke limits match the solved linkage":
    limits = arm_rotation_limits()
    joints = vehicle_joints()
    mut checked = 0
    for item in joints:
        if item.id == "fl-damper-slide":
            ensure abs(item.limit_lower - (damper_length(0.0) - damper_length(limits[0]))) < 0.001
            ensure abs(item.limit_upper - (damper_length(0.0) - damper_length(limits[1]))) < 0.001
            checked = checked + 1
    ensure checked == 1
    ensure abs(corner_pose(limits[0])[7] - REBOUND_LIMIT_M) < 0.0001
    ensure abs(corner_pose(limits[1])[7] - JOUNCE_LIMIT_M) < 0.0001


test "the assembly export publishes a complete and self-consistent payload":
    payload = assembly_export()
    checks = payload["checks"]
    ensure payload["schema"] == "sema.circuitframe-assembly/v1"
    ensure checks["mass_closure_residual_kg"] == 0.0
    ensure checks["unterminated_required_ports"] == 0
    ensure checks["mobility_ok"]
    ensure checks["ports_terminated"]
    ensure checks["equilibrium_residual_n"] < 0.000000001
    ensure checks["port_count"] >= 90
    ensure checks["mate_count"] >= 45
    ensure checks["joint_count"] >= 100
    ensure checks["placement_count"] == 38
    ensure checks["load_path_count"] == 7
    ensure len(assembly_equations()) >= 10


test "declared load paths report a real utilisation under the reference load case":
    paths = load_paths()
    ensure len(paths) == 7
    mut covered = 0
    for declared in paths:
        ensure declared.utilisation > 0.0 and declared.utilisation < 1.0
        ensure len(declared.part_ids) >= 4
        covered = covered + 1
    ensure covered == len(load_path_declarations())
    ensure paths[0].id == "lp-vertical-corner"
    ensure paths[6].id == "lp-belt-anchorage"
```

### `src/authoring.sema`

```sema
"""Bounded natural-language circuit authoring compiled into typed Sema net definitions."""

from magna_ev_digital_twin.domain import Fidelity, NetDefinition

assure silver


pub enum AuthoringIntent:
    connect | constrain | unknown


pub struct CircuitAuthoringResult:
    intent: AuthoringIntent
    accepted: bool
    natural_description: str
    formal_sema: str
    net: NetDefinition
    requirements: list[str]
    assumptions: list[str]
    diagnostics: list[str]
    invariant len(natural_description) > 0 and len(natural_description) <= 2048
    invariant len(requirements) <= 16 and len(assumptions) <= 16 and len(diagnostics) <= 16


def formal_hv_drive_net():
    sem "A typed formal Sema definition of the protected 800 V source-to-inverter relation"
    return NetDefinition(
        id="authoring-hv-drive",
        name="Authored pack-to-inverter protected power relation",
        domain="HV",
        source_part_id="hv-junction",
        target_part_ids=["front-inverter", "rear-inverter"],
        nominal_voltage_v=800.0,
        maximum_current_a=520.0,
        route="protected CircuitFrame sill conductors",
        fidelity=Fidelity.derived,
        evidence_ids=["circuitframe-electrical-v1"],
    )


def formal_zonal_supply_net():
    sem "A typed formal Sema definition of the protected 48 V zonal supply relation"
    return NetDefinition(
        id="authoring-lv-zonal",
        name="Authored 48 V zonal supply relation",
        domain="LV",
        source_part_id="dc-dc",
        target_part_ids=["front-zone-controller", "rear-zone-controller", "cabin-zone-controller"],
        nominal_voltage_v=48.0,
        maximum_current_a=180.0,
        route="dual protected structural conductors",
        fidelity=Fidelity.derived,
        evidence_ids=["circuitframe-electrical-v1"],
    )


def formal_data_backbone_net():
    sem "A typed formal Sema definition of the redundant vehicle data relation"
    return NetDefinition(
        id="authoring-data-backbone",
        name="Authored redundant Ethernet backbone",
        domain="DATA",
        source_part_id="central-compute",
        target_part_ids=["front-zone-controller", "rear-zone-controller", "cabin-zone-controller"],
        nominal_voltage_v=1.0,
        maximum_current_a=0.2,
        route="shielded differential structural links",
        fidelity=Fidelity.derived,
        evidence_ids=["circuitframe-electrical-v1"],
    )


def unsupported_net():
    return NetDefinition(
        id="unsupported",
        name="Unsupported authoring request",
        domain="UNKNOWN",
        source_part_id="unknown",
        target_part_ids=["unknown"],
        nominal_voltage_v=0.0,
        maximum_current_a=0.0,
        route="none",
        fidelity=Fidelity.unknown,
        evidence_ids=[],
    )


pub def compile_circuit_description(description: str) -> CircuitAuthoringResult !{}:
    sem "Interpret bounded plain automotive circuit language and emit a typed formal Sema relation or fail visibly"
    require len(description) > 0 and len(description) <= 2048
    text = description.lower()
    if text.contains("800") and (text.contains("battery") or text.contains("pack")) and text.contains("inverter"):
        net = formal_hv_drive_net()
        return CircuitAuthoringResult(
            intent=AuthoringIntent.connect,
            accepted=true,
            natural_description=description,
            formal_sema="NetDefinition(id=\"authoring-hv-drive\", domain=\"HV\", source_part_id=\"hv-junction\", target_part_ids=[\"front-inverter\", \"rear-inverter\"], nominal_voltage_v=800.0, maximum_current_a=520.0, route=\"protected CircuitFrame sill conductors\", fidelity=Fidelity.derived, evidence_ids=[\"circuitframe-electrical-v1\"])",
            net=net,
            requirements=["800 V nominal class", "protected source-to-load path", "front and rear inverter targets", "current limit not above 520 A"],
            assumptions=["battery source terminates at the canonical HV junction", "protected panel conductors use the active section profile"],
            diagnostics=[],
        )
    if text.contains("48") and (text.contains("zone") or text.contains("controller")):
        net = formal_zonal_supply_net()
        return CircuitAuthoringResult(
            intent=AuthoringIntent.connect,
            accepted=true,
            natural_description=description,
            formal_sema="NetDefinition(id=\"authoring-lv-zonal\", domain=\"LV\", source_part_id=\"dc-dc\", target_part_ids=[\"front-zone-controller\", \"rear-zone-controller\", \"cabin-zone-controller\"], nominal_voltage_v=48.0, maximum_current_a=180.0, route=\"dual protected structural conductors\", fidelity=Fidelity.derived, evidence_ids=[\"circuitframe-electrical-v1\"])",
            net=net,
            requirements=["48 V nominal class", "front, rear, and cabin zone targets", "dual protected paths"],
            assumptions=["the canonical DC/DC is the low-voltage source"],
            diagnostics=[],
        )
    if text.contains("ethernet") or (text.contains("data") and text.contains("zone")):
        net = formal_data_backbone_net()
        return CircuitAuthoringResult(
            intent=AuthoringIntent.connect,
            accepted=true,
            natural_description=description,
            formal_sema="NetDefinition(id=\"authoring-data-backbone\", domain=\"DATA\", source_part_id=\"central-compute\", target_part_ids=[\"front-zone-controller\", \"rear-zone-controller\", \"cabin-zone-controller\"], nominal_voltage_v=1.0, maximum_current_a=0.2, route=\"shielded differential structural links\", fidelity=Fidelity.derived, evidence_ids=[\"circuitframe-electrical-v1\"])",
            net=net,
            requirements=["redundant Ethernet relation", "shielded differential route", "all zone controllers addressed"],
            assumptions=["central compute is the logical source"],
            diagnostics=[],
        )
    return CircuitAuthoringResult(
        intent=AuthoringIntent.unknown,
        accepted=false,
        natural_description=description,
        formal_sema="",
        net=unsupported_net(),
        requirements=[],
        assumptions=[],
        diagnostics=["Unsupported bounded intent. Name voltage/domain plus known source and target roles.", "Supported profiles: 800 V battery-to-inverters, 48 V zonal supply, redundant Ethernet data backbone."],
    )


test "natural 800 V request compiles to a typed protected drive relation":
    result = compile_circuit_description("Connect the 800 V battery to both inverters through protected CircuitFrame conductors")
    ensure result.accepted and result.intent == AuthoringIntent.connect
    ensure result.net.domain == "HV" and result.net.nominal_voltage_v == 800.0
    ensure result.net.source_part_id == "hv-junction" and len(result.net.target_part_ids) == 2
    ensure result.formal_sema.contains("NetDefinition")


test "natural 48 V request compiles to the three zonal controllers":
    result = compile_circuit_description("Supply every zone controller over dual protected 48 V conductors")
    ensure result.accepted and result.net.domain == "LV"
    ensure len(result.net.target_part_ids) == 3 and result.net.maximum_current_a == 180.0


test "unknown natural request fails visibly rather than inventing topology":
    result = compile_circuit_description("Make the circuit magical and perfect")
    ensure not result.accepted and result.intent == AuthoringIntent.unknown
    ensure len(result.formal_sema) == 0 and len(result.diagnostics) == 2
```

### `src/candidate.sema`

```sema
"""Bounded structural-conductor candidate search with immutable gate results."""

assure silver


struct PanelCandidate:
    id: str
    base_revision: int
    width_mm: f64
    thickness_mm: f64
    length_m: f64
    mass_kg: f64
    current_density_a_mm2: f64
    voltage_drop_v: f64
    loss_kw: f64
    estimated_temp_c: f64
    mass_reduction_fraction: f64
    passed: bool
    reason: str
    invariant len(id) > 0
    invariant base_revision >= 1
    invariant width_mm > 0.0
    invariant thickness_mm > 0.0
    invariant length_m > 0.0
    invariant mass_kg > 0.0
    invariant current_density_a_mm2 >= 0.0
    invariant voltage_drop_v >= 0.0
    invariant loss_kw >= 0.0
    invariant estimated_temp_c >= -40.0
    invariant mass_reduction_fraction >= 0.0 and mass_reduction_fraction <= 1.0
    invariant len(reason) > 0


pub equation conductor_resistance(resistivity_ohm_m, length_m, width_mm, thickness_mm) -> any:
    return resistivity_ohm_m * length_m / (width_mm * thickness_mm * 0.000001)


pub equation conductor_mass(density_kg_m3, length_m, width_mm, thickness_mm) -> any:
    return density_kg_m3 * length_m * width_mm * thickness_mm * 0.000001


def evaluate_panel_candidate(width_mm: f64, base_revision: int) !{}:
    thickness_mm = 1.2
    length_m = 2.6
    current_a = 420.0
    area_mm2 = width_mm * thickness_mm
    resistance = conductor_resistance(0.0000000168, length_m, width_mm, thickness_mm)
    voltage_drop = current_a * resistance
    loss_kw = current_a * current_a * resistance / 1000.0
    mass = conductor_mass(8960.0, length_m, width_mm, thickness_mm)
    baseline_mass = conductor_mass(8960.0, length_m, 90.0, thickness_mm)
    current_density = current_a / area_mm2
    estimated_temp = 24.0 + loss_kw * 18.0
    passed = current_density <= 6.5 and voltage_drop <= 1.8 and estimated_temp <= 90.0 and width_mm >= 40.0
    reason = "all current-density, voltage-drop, thermal, manufacturability and service-width gates passed" if passed else "one or more electrical, thermal or manufacturability gates failed"
    return PanelCandidate(
        id="panel-width-" + str(int(width_mm)) + "-r" + str(base_revision),
        base_revision=base_revision,
        width_mm=width_mm,
        thickness_mm=thickness_mm,
        length_m=length_m,
        mass_kg=mass,
        current_density_a_mm2=current_density,
        voltage_drop_v=voltage_drop,
        loss_kw=loss_kw,
        estimated_temp_c=estimated_temp,
        mass_reduction_fraction=max(0.0, (baseline_mass - mass) / baseline_mass),
        passed=passed,
        reason=reason,
    )


pub def optimize_panel(base_revision: int) -> dict[str, any] !{}:
    require base_revision >= 1
    widths = [80.0, 70.0, 60.0, 50.0, 40.0]
    mut evaluated = []
    mut selected = evaluate_panel_candidate(90.0, base_revision)
    for width in widths:
        candidate = evaluate_panel_candidate(width, base_revision)
        evaluated.append(candidate)
        if candidate.passed and candidate.mass_kg < selected.mass_kg:
            selected = candidate
    ensure selected.passed
    return {
        "schema": "sema.circuitframe-candidate-preview/v1",
        "objective": "reduce structural-conductor mass",
        "protected_constraints": ["current density <= 6.5 A/mm2", "voltage drop <= 1.8 V", "estimated temperature <= 90 C", "manufacturable width >= 40 mm"],
        "evaluated": evaluated,
        "selected": selected,
        "commit_authorized": selected.passed,
        "decision_authority": false,
        "evidence": ["Sema deterministic bounded enumeration", "electrical and thermal equations", "immutable base revision"],
    }


test "bounded panel search selects the lightest candidate that passes every gate":
    preview = optimize_panel(1)
    selected = preview["selected"]
    ensure preview["commit_authorized"]
    ensure selected.passed
    ensure selected.width_mm == 60.0
    ensure selected.mass_reduction_fraction > 0.3
    ensure selected.current_density_a_mm2 <= 6.5
    ensure selected.voltage_drop_v <= 1.8
    ensure selected.estimated_temp_c <= 90.0
```

### `src/domain.sema`

```sema
"""Canonical automotive identities, completeness, evidence, and interface contracts."""

assure silver


pub enum Fidelity:
    canonical | derived | reduced | illustrative | unknown | excluded


pub enum PartGroup:
    body | chassis | energy | drive | motion | thermal | electrical | control | cabin | safety | manufacturing


pub enum PortKind:
    mechanical | high_voltage | low_voltage | data | thermal | fluid | service


pub enum AutomationClass:
    robot_validated | assisted | manual | failed | unknown


pub enum QualificationStatus:
    executable_reduced | planned_external | reference_only | blocked_missing_evidence


pub enum EvidenceClass:
    computational | structural | direct_parity | reduced_model | manufacturing | negative | provenance


pub enum SourceAuthority:
    customer_oem | licensed_benchmark | open_research | engineered_assumption | unqualified_fixture


pub struct SourceRecord:
    id: str
    authority: SourceAuthority
    provider: str
    vehicle_model: str
    source_revision: str
    artifact_kind: str
    rights_scope: str
    digest: str
    fidelity: Fidelity
    render_allowed: bool
    simulation_allowed: bool
    export_allowed: bool
    invariant len(id) > 0
    invariant len(provider) > 0
    invariant len(vehicle_model) > 0
    invariant len(source_revision) > 0
    invariant len(artifact_kind) > 0
    invariant len(rights_scope) > 0
    invariant len(digest) > 0


pub struct SourceBinding:
    entity_id: str
    source_record_id: str
    source_entity_id: str
    fidelity: Fidelity
    invariant len(entity_id) > 0
    invariant len(source_record_id) > 0
    invariant len(source_entity_id) > 0


struct ConfigurationDelta:
    id: str
    base_configuration: str
    target_configuration: str
    unchanged_part_ids: list[str]
    removed_route_ids: list[str]
    added_feature_ids: list[str]
    changed_operation_ids: list[str]
    invariant len(id) > 0
    invariant len(base_configuration) > 0
    invariant len(target_configuration) > 0
    invariant base_configuration != target_configuration
    invariant len(unchanged_part_ids) <= 100000
    invariant len(removed_route_ids) <= 100000
    invariant len(added_feature_ids) <= 100000
    invariant len(changed_operation_ids) <= 100000


pub struct VehicleIdentity:
    id: str
    revision: int
    name: str
    category: str
    seats: int
    fidelity: Fidelity
    invariant len(id) > 0
    invariant revision >= 1
    invariant len(name) > 0
    invariant len(category) > 0
    invariant seats > 0 and seats <= 9


pub struct PartDefinition:
    id: str
    name: str
    group: PartGroup
    count: int
    material: str
    mass_kg: f64
    fidelity: Fidelity
    opaque: bool
    evidence_ids: list[str]
    invariant len(id) > 0
    invariant len(name) > 0
    invariant count > 0 and count <= 10000
    invariant len(material) > 0
    invariant mass_kg >= 0.0
    invariant len(evidence_ids) <= 64


pub struct PortDefinition:
    id: str
    owner_part_id: str
    kind: PortKind
    mate_port_id: str
    required: bool
    terminated: bool
    invariant len(id) > 0
    invariant len(owner_part_id) > 0
    invariant len(mate_port_id) > 0 or not required


pub struct NetDefinition:
    id: str
    name: str
    domain: str
    source_part_id: str
    target_part_ids: list[str]
    nominal_voltage_v: f64
    maximum_current_a: f64
    route: str
    fidelity: Fidelity
    evidence_ids: list[str]
    invariant len(id) > 0
    invariant len(name) > 0
    invariant len(domain) > 0
    invariant len(source_part_id) > 0
    invariant len(target_part_ids) > 0 and len(target_part_ids) <= 128
    invariant nominal_voltage_v >= 0.0
    invariant maximum_current_a >= 0.0
    invariant len(route) > 0
    invariant len(evidence_ids) <= 64


pub struct OperationDefinition:
    id: str
    name: str
    station: str
    resource: str
    consumed_part_ids: list[str]
    created_features: list[str]
    automation: AutomationClass
    cycle_time_s: f64
    evidence_ids: list[str]
    invariant len(id) > 0
    invariant len(name) > 0
    invariant len(station) > 0
    invariant len(resource) > 0
    invariant len(consumed_part_ids) <= 128
    invariant len(created_features) <= 128
    invariant cycle_time_s >= 0.0
    invariant len(evidence_ids) <= 64


pub struct ValidationCaseDefinition:
    id: str
    name: str
    domain: str
    authority: str
    method: str
    status: QualificationStatus
    fidelity: Fidelity
    evidence_ids: list[str]
    invariant len(id) > 0
    invariant len(name) > 0
    invariant len(domain) > 0
    invariant len(authority) > 0
    invariant len(method) > 0
    invariant len(evidence_ids) <= 64


pub struct EvidenceRecord:
    id: str
    kind: EvidenceClass
    source: str
    summary: str
    fidelity: Fidelity
    accepted: bool
    invariant len(id) > 0
    invariant len(source) > 0
    invariant len(summary) > 0


pub struct CompletenessReport:
    profile: str
    declared_parts: int
    declared_instances: int
    declared_nets: int
    declared_operations: int
    unterminated_required_ports: int
    required_port_inventory_complete: bool
    unknown_operations: int
    major_assembly_complete: bool
    production_complete: bool
    exclusions: list[str]
    invariant len(profile) > 0
    invariant declared_parts > 0
    invariant declared_instances >= declared_parts
    invariant declared_nets > 0
    invariant declared_operations > 0
    invariant unterminated_required_ports >= 0
    invariant unknown_operations >= 0
    invariant len(exclusions) <= 128
```

### `src/hardware.sema`

```sema
"""Engineering specification of the declared drive, brake, tyre, pack and gear hardware.

The vehicle's mass is summed from the declared part placements in `assembly.sema`, but until now
nothing that accelerated or stopped that mass was declared: peak torque, drive power, brake force,
regenerative force, pack energy, pack resistance, tyre grip, cornering stiffness and rolling
resistance were all bare literals in `physics.sema`. This module declares the engineering
parameters of the components `vehicle.sema` already lists and derives those limits from them, so
the performance follows from the hardware rather than sitting beside it.

Every specification is keyed to the part id it represents, and `part_reconciliation()` checks that
each id resolves in `vehicle_parts()`, that the declared instance counts agree, and that the mass
the specification implies matches the mass the part already declares. `hardware_limits()` fails
loudly on any mismatch, so a specification cannot quietly drift away from the part it describes.

Honesty: these are engineered assumptions for a reconstruction, not OEM data. No motor was
dynamometer tested, no caliper was pressure tested and no cell was cycled. Each declared parameter
carries a `solved`, `correlated` or `assumed` tag in `hardware_parameters()`, using the same
vocabulary as the aerodynamic and assembly modules: `assumed` for a bounded engineering choice,
`correlated` for an empirical closure with a cited form, `solved` for a value that falls out of a
closed derivation over the declared inputs.
"""

import math

from magna_ev_digital_twin.domain import PartDefinition
from magna_ev_digital_twin.vehicle import vehicle_nets, vehicle_parts

assure silver


SOURCE_SOLVED = "solved"
SOURCE_CORRELATED = "correlated"
SOURCE_ASSUMED = "assumed"

GRAVITY_MPS2 = 9.80665
ROOT_THREE = 1.7320508075688772
ROOT_TWO = 1.4142135623730951
INCH_M = 0.0254

# Shared inverter design choices: one silicon generation and one DC-link ripple budget for both
# units, so only the current rating, the on-state resistance and the housing differ per axle.
INVERTER_SWITCHING_FREQUENCY_HZ = 12000.0
INVERTER_DC_LINK_RIPPLE_V = 8.0
INVERTER_SWITCHING_ENERGY_MJ_PER_A = 0.060
INVERTER_CAPACITOR_KG_PER_MF = 3.0
INVERTER_MODULE_KG_PER_100A = 0.16
INVERTER_SWITCH_COUNT = 6.0

# Shared machine build: mean density of the active stack (laminations, copper, magnets, slot air)
# and the assembled-to-active mass ratio that covers housing, shaft, bearings and end windings.
MOTOR_ACTIVE_DENSITY_KG_M3 = 4400.0
MOTOR_ASSEMBLED_MASS_FACTOR = 1.34
MOTOR_EFFICIENCY = 0.95

# Shared friction-brake material and hydraulic limits, common to both axles.
PAD_FRICTION_COEFFICIENT = 0.42
MAX_LINE_PRESSURE_PA = 16000000.0
DISC_DENSITY_KG_M3 = 7200.0
DISC_SPECIFIC_HEAT_J_KG_K = 460.0
FADE_ONSET_TEMP_C = 340.0
FADE_RATE_PER_K = 0.0012
FADE_FLOOR_FRACTION = 0.45
DISC_COOLING_BASE_W_PER_K = 1.6
DISC_COOLING_SPEED_W_PER_K_MPS = 0.75

STEEL_DENSITY_KG_M3 = 7850.0
GEAR_MESH_EFFICIENCY = 0.985
GEAR_BLANK_WEB_FACTOR = 0.62
GEARBOX_HOUSING_MASS_FACTOR = 1.16

MASS_TOLERANCE_FRACTION = 0.03


struct TractionMotorSpec:
    """One interior-permanent-magnet traction machine, keyed to its declared drive part."""
    part_id: str
    pole_pairs: int
    rotor_radius_m: f64
    stack_length_m: f64
    stator_outer_radius_m: f64
    magnet_flux_linkage_wb: f64
    d_axis_inductance_h: f64
    q_axis_inductance_h: f64
    peak_phase_current_a: f64
    continuous_phase_current_a: f64
    current_angle_deg: f64
    max_speed_rpm: f64
    invariant len(part_id) > 0
    invariant pole_pairs >= 1
    invariant rotor_radius_m > 0.0 and rotor_radius_m < stator_outer_radius_m
    invariant stack_length_m > 0.0
    invariant magnet_flux_linkage_wb > 0.0
    invariant q_axis_inductance_h >= d_axis_inductance_h and d_axis_inductance_h > 0.0
    invariant peak_phase_current_a >= continuous_phase_current_a and continuous_phase_current_a > 0.0
    invariant current_angle_deg >= 0.0 and current_angle_deg < 90.0
    invariant max_speed_rpm > 0.0


struct InverterSpec:
    """One traction inverter, keyed to its declared power-electronics part."""
    part_id: str
    dc_link_voltage_v: f64
    peak_phase_current_a: f64
    on_state_resistance_ohm: f64
    housing_mass_kg: f64
    net_id: str
    invariant len(part_id) > 0
    invariant dc_link_voltage_v > 0.0
    invariant peak_phase_current_a > 0.0
    invariant on_state_resistance_ohm > 0.0
    invariant housing_mass_kg > 0.0
    invariant len(net_id) > 0


struct BrakeCornerSpec:
    """One brake-by-wire corner module. Both axles are instances of the declared `brakes` part."""
    part_id: str
    axle: str
    disc_outer_radius_m: f64
    disc_inner_radius_m: f64
    hat_radius_m: f64
    disc_thickness_m: f64
    vent_solid_fraction: f64
    piston_count: int
    piston_bore_m: f64
    caliper_mass_kg: f64
    pad_mass_kg: f64
    actuator_mass_kg: f64
    invariant len(part_id) > 0
    invariant disc_outer_radius_m > disc_inner_radius_m and disc_inner_radius_m > hat_radius_m
    invariant disc_thickness_m > 0.0
    invariant vent_solid_fraction > 0.0 and vent_solid_fraction <= 1.0
    invariant piston_count >= 1
    invariant piston_bore_m > 0.0
    invariant caliper_mass_kg > 0.0 and pad_mass_kg > 0.0 and actuator_mass_kg > 0.0


struct TireSpec:
    """The declared road tyre, in the size notation moulded on its sidewall."""
    part_id: str
    section_width_m: f64
    aspect_ratio: f64
    rim_diameter_in: f64
    rolling_radius_factor: f64
    load_index_kg: f64
    peak_friction: f64
    slide_friction: f64
    load_sensitivity: f64
    friction_reference_load_fraction: f64
    cornering_stiffness_per_n: f64
    rolling_coefficient: f64
    rolling_speed_coefficient: f64
    carcass_thickness_m: f64
    carcass_density_kg_m3: f64
    invariant len(part_id) > 0
    invariant section_width_m > 0.0 and aspect_ratio > 0.0 and rim_diameter_in > 0.0
    invariant rolling_radius_factor > 0.0 and rolling_radius_factor <= 1.0
    invariant load_index_kg > 0.0
    invariant peak_friction > slide_friction and slide_friction > 0.0
    invariant load_sensitivity >= 0.0
    invariant friction_reference_load_fraction > 0.0 and friction_reference_load_fraction <= 1.0
    invariant cornering_stiffness_per_n > 0.0
    invariant rolling_coefficient > 0.0 and rolling_speed_coefficient >= 0.0
    invariant carcass_thickness_m > 0.0 and carcass_density_kg_m3 > 0.0


struct PackSpec:
    """The declared battery modules, described at the cell that fills them."""
    part_id: str
    module_count: int
    series_per_module: int
    parallel_per_module: int
    cell_nominal_voltage_v: f64
    cell_capacity_ah: f64
    cell_resistance_ohm: f64
    cell_specific_energy_wh_kg: f64
    module_packaging_factor: f64
    module_interconnect_resistance_ohm: f64
    pack_bus_resistance_ohm: f64
    cell_pulse_charge_c_rate: f64
    cell_pulse_discharge_c_rate: f64
    invariant len(part_id) > 0
    invariant module_count >= 1 and series_per_module >= 1 and parallel_per_module >= 1
    invariant cell_nominal_voltage_v > 0.0 and cell_capacity_ah > 0.0
    invariant cell_resistance_ohm > 0.0 and cell_specific_energy_wh_kg > 0.0
    invariant module_packaging_factor >= 1.0
    invariant module_interconnect_resistance_ohm >= 0.0 and pack_bus_resistance_ohm >= 0.0
    invariant cell_pulse_charge_c_rate > 0.0 and cell_pulse_discharge_c_rate > 0.0


struct GearStageSpec:
    """One helical reduction stage, described by the two pitch diameters that set its ratio."""
    pinion_pitch_diameter_m: f64
    wheel_pitch_diameter_m: f64
    face_width_m: f64
    invariant wheel_pitch_diameter_m > pinion_pitch_diameter_m and pinion_pitch_diameter_m > 0.0
    invariant face_width_m > 0.0


struct GearboxSpec:
    """The declared single-speed reduction gearset, one unit per drive axle."""
    part_id: str
    unit_count: int
    first: GearStageSpec
    second: GearStageSpec
    invariant len(part_id) > 0
    invariant unit_count >= 1


pub struct MotorLimits:
    """What one traction machine can actually deliver through its own inverter."""
    part_id: str
    inverter_part_id: str
    effective_peak_current_a: f64
    current_limited_by: str
    peak_torque_nm: f64
    continuous_torque_nm: f64
    base_speed_rpm: f64
    max_speed_rpm: f64
    constant_power_ratio: f64
    corner_power_kw: f64
    stator_flux_linkage_wb: f64
    power_factor: f64
    inverter_apparent_power_kva: f64
    inverter_peak_power_kw: f64
    inverter_loss_kw: f64
    dc_link_peak_current_a: f64
    airgap_shear_stress_kpa: f64
    invariant len(part_id) > 0
    invariant effective_peak_current_a > 0.0
    invariant peak_torque_nm > continuous_torque_nm and continuous_torque_nm > 0.0
    invariant base_speed_rpm > 0.0 and max_speed_rpm > base_speed_rpm
    invariant corner_power_kw > 0.0
    invariant power_factor > 0.0 and power_factor <= 1.0
    invariant inverter_peak_power_kw > 0.0
    invariant airgap_shear_stress_kpa > 0.0


pub struct BrakeAxleLimits:
    """Both corner modules of one axle, reduced to the numbers the dynamics needs."""
    part_id: str
    axle: str
    effective_radius_m: f64
    piston_area_m2: f64
    clamp_force_n: f64
    corner_torque_nm: f64
    axle_force_n: f64
    disc_mass_kg: f64
    disc_thermal_capacity_j_k: f64
    invariant len(part_id) > 0
    invariant effective_radius_m > 0.0
    invariant piston_area_m2 > 0.0
    invariant clamp_force_n > 0.0
    invariant corner_torque_nm > 0.0
    invariant axle_force_n > 0.0
    invariant disc_thermal_capacity_j_k > 0.0


pub struct TireLimits:
    """The contact-patch model the dynamics reads, evaluated against live corner loads."""
    part_id: str
    unloaded_radius_m: f64
    rolling_radius_m: f64
    rated_load_n: f64
    friction_reference_load_n: f64
    peak_friction: f64
    slide_friction: f64
    load_sensitivity: f64
    cornering_stiffness_per_n: f64
    rolling_coefficient: f64
    rolling_speed_coefficient: f64
    invariant len(part_id) > 0
    invariant rolling_radius_m > 0.0 and rolling_radius_m < unloaded_radius_m
    invariant rated_load_n > 0.0 and friction_reference_load_n > 0.0
    invariant peak_friction > slide_friction and slide_friction > 0.0


pub struct PackLimits:
    """The traction pack, summed from the declared cell out to the terminals."""
    part_id: str
    cell_count: int
    series_count: int
    parallel_count: int
    energy_kwh: f64
    nominal_voltage_v: f64
    resistance_ohm: f64
    charge_power_kw: f64
    discharge_power_kw: f64
    mass_kg: f64
    invariant len(part_id) > 0
    invariant cell_count >= 1
    invariant energy_kwh > 0.0
    invariant nominal_voltage_v > 0.0
    invariant resistance_ohm > 0.0
    invariant charge_power_kw > 0.0 and discharge_power_kw > 0.0
    invariant mass_kg > 0.0


struct PartReconciliation:
    """One declared part, checked against the specification that claims to describe it."""
    part_id: str
    resolved: bool
    declared_count: int
    expected_count: int
    declared_mass_kg: f64
    derived_mass_kg: f64
    mass_error_fraction: f64
    tolerance_fraction: f64
    derivation: str
    passed: bool
    invariant len(part_id) > 0
    invariant expected_count >= 1
    invariant mass_error_fraction >= 0.0
    invariant tolerance_fraction >= 0.0
    invariant len(derivation) > 0


pub struct AxleHardware:
    """Everything one axle's 20 Hz longitudinal solve needs, pre-solved into flat scalars.

    Nothing on this struct depends on the live vehicle state, so it is built once with the rest of
    the hardware. The loop is then left with only the arithmetic that genuinely varies: the friction
    circle on this axle's live vertical load, the fade factor at its live disc temperature, and the
    comparison that decides which of the two is binding. Keeping it flat also keeps it cheap to pass
    around: the interpreter copies a struct argument by value, so the per-step helpers must never be
    handed anything that carries the panel basis or a pre-sampled table.
    """
    axle: str
    brake_axle_force_n: f64
    drive_force_per_throttle_n: f64
    power_force_numerator_w: f64
    power_constraint: str
    peak_friction: f64
    slide_friction: f64
    load_sensitivity: f64
    friction_reference_load_n: f64
    cornering_stiffness_per_n: f64
    fade_onset_temp_c: f64
    fade_rate_per_k: f64
    fade_floor_fraction: f64
    disc_thermal_capacity_j_k: f64
    invariant len(axle) > 0 and len(power_constraint) > 0
    invariant brake_axle_force_n > 0.0
    invariant drive_force_per_throttle_n > 0.0
    invariant power_force_numerator_w > 0.0
    invariant peak_friction > slide_friction and slide_friction > 0.0
    invariant load_sensitivity >= 0.0
    invariant friction_reference_load_n > 0.0
    invariant cornering_stiffness_per_n > 0.0
    invariant fade_onset_temp_c > 0.0
    invariant fade_rate_per_k >= 0.0
    invariant fade_floor_fraction > 0.0 and fade_floor_fraction <= 1.0
    invariant disc_thermal_capacity_j_k > 0.0


pub struct HardwareLimits:
    """Everything `physics.sema` used to hardcode, derived from the declared components."""
    front_motor: MotorLimits
    rear_motor: MotorLimits
    front_brake: BrakeAxleLimits
    rear_brake: BrakeAxleLimits
    tire: TireLimits
    pack: PackLimits
    front_axle: AxleHardware
    rear_axle: AxleHardware
    final_drive_ratio: f64
    gearbox_efficiency: f64
    motor_efficiency: f64
    inverter_efficiency: f64
    max_motor_torque_nm: f64
    max_drive_power_kw: f64
    max_brake_force_n: f64
    max_regenerative_force_n: f64
    max_regenerative_power_kw: f64
    regenerative_front_share: f64
    max_line_pressure_pa: f64
    pad_friction: f64
    fade_onset_temp_c: f64
    fade_rate_per_k: f64
    fade_floor_fraction: f64
    disc_cooling_base_w_per_k: f64
    disc_cooling_speed_w_per_k_mps: f64
    driveline_top_speed_mps: f64
    invariant final_drive_ratio > 1.0
    invariant gearbox_efficiency > 0.0 and gearbox_efficiency <= 1.0
    invariant max_motor_torque_nm > 0.0
    invariant max_drive_power_kw > 0.0
    invariant motor_efficiency > 0.0 and motor_efficiency <= 1.0
    invariant inverter_efficiency > 0.0 and inverter_efficiency <= 1.0
    invariant max_brake_force_n > 0.0
    invariant max_regenerative_force_n > 0.0
    invariant max_regenerative_power_kw > 0.0
    invariant regenerative_front_share > 0.0 and regenerative_front_share < 1.0
    invariant fade_onset_temp_c > 0.0
    invariant fade_rate_per_k >= 0.0
    invariant fade_floor_fraction > 0.0 and fade_floor_fraction <= 1.0
    invariant driveline_top_speed_mps > 0.0
    invariant max_line_pressure_pa > 0.0


equation ipm_shaft_torque(pole_pairs, flux_linkage, d_inductance, q_inductance, current, angle_rad) -> any:
    return 1.5 * pole_pairs * current * cos(angle_rad) * (flux_linkage + (q_inductance - d_inductance) * current * sin(angle_rad))


equation stator_flux_linkage(flux_linkage, d_inductance, q_inductance, current, angle_rad) -> any:
    return sqrt((flux_linkage - d_inductance * current * sin(angle_rad))^2 + (q_inductance * current * cos(angle_rad))^2)


equation voltage_limited_speed(dc_link_voltage, stator_flux, pole_pairs) -> any:
    return dc_link_voltage / (sqrt(3.0) * stator_flux * pole_pairs)


equation airgap_shear_stress(torque, rotor_radius, stack_length) -> any:
    return torque / (2.0 * 3.141592653589793 * rotor_radius^2 * stack_length)


equation uniform_pressure_radius(outer_radius, inner_radius) -> any:
    return (2.0 / 3.0) * (outer_radius^3 - inner_radius^3) / (outer_radius^2 - inner_radius^2)


equation caliper_clamp_force(line_pressure, piston_count, piston_bore) -> any:
    return line_pressure * piston_count * 3.141592653589793 * (piston_bore / 2.0)^2


equation corner_brake_torque(pad_friction, clamp_force, effective_radius) -> any:
    return 2.0 * pad_friction * clamp_force * effective_radius


pub equation brake_fade_factor(disc_temp_c, onset_c, rate_per_k, floor_fraction) -> any:
    return max(floor_fraction, 1.0 - rate_per_k * max(0.0, disc_temp_c - onset_c))


pub equation tire_friction(peak, slide, sensitivity, reference_load, corner_load) -> any:
    return max(slide, min(peak, peak * (1.0 - sensitivity * (corner_load / reference_load - 1.0))))


pub equation rolling_coefficient(base, speed_coefficient, speed) -> any:
    return base + speed_coefficient * speed^2


def front_motor_spec():
    """Front machine: smaller rotor, more turns, sized for the lighter front axle duty."""
    return TractionMotorSpec(
        part_id="front-motor",
        pole_pairs=4,
        rotor_radius_m=0.078,
        stack_length_m=0.160,
        stator_outer_radius_m=0.140,
        magnet_flux_linkage_wb=0.260,
        d_axis_inductance_h=0.00070,
        q_axis_inductance_h=0.00095,
        peak_phase_current_a=145.0,
        continuous_phase_current_a=65.0,
        current_angle_deg=15.0,
        max_speed_rpm=17000.0,
    )


def rear_motor_spec():
    """Rear machine: longer, wider rotor and roughly twice the current for the primary drive axle."""
    return TractionMotorSpec(
        part_id="rear-motor",
        pole_pairs=4,
        rotor_radius_m=0.093,
        stack_length_m=0.170,
        stator_outer_radius_m=0.150,
        magnet_flux_linkage_wb=0.212,
        d_axis_inductance_h=0.00030,
        q_axis_inductance_h=0.00044,
        peak_phase_current_a=355.0,
        continuous_phase_current_a=145.0,
        current_angle_deg=15.0,
        max_speed_rpm=16000.0,
    )


def front_inverter_spec():
    """Front inverter: standalone subframe housing, rated above the machine it feeds."""
    return InverterSpec(part_id="front-inverter", dc_link_voltage_v=800.0, peak_phase_current_a=160.0, on_state_resistance_ohm=0.0045, housing_mass_kg=8.20, net_id="hv-pack-front")


def rear_inverter_spec():
    """Rear inverter: shares the drive-unit casting, and is the binding current limit of that axle."""
    return InverterSpec(part_id="rear-inverter", dc_link_voltage_v=800.0, peak_phase_current_a=335.0, on_state_resistance_ohm=0.0028, housing_mass_kg=6.20, net_id="hv-pack-rear")


def front_brake_spec():
    """Front corner: 355 mm vented disc, four-piston fixed caliper."""
    return BrakeCornerSpec(part_id="brakes", axle="front", disc_outer_radius_m=0.1775, disc_inner_radius_m=0.1175, hat_radius_m=0.090, disc_thickness_m=0.032, vent_solid_fraction=0.60, piston_count=4, piston_bore_m=0.024, caliper_mass_kg=6.60, pad_mass_kg=1.55, actuator_mass_kg=2.90)


def rear_brake_spec():
    """Rear corner: 330 mm vented disc, two-piston caliper carrying the park-brake actuator."""
    return BrakeCornerSpec(part_id="brakes", axle="rear", disc_outer_radius_m=0.1650, disc_inner_radius_m=0.1120, hat_radius_m=0.085, disc_thickness_m=0.022, vent_solid_fraction=0.65, piston_count=2, piston_bore_m=0.022, caliper_mass_kg=5.20, pad_mass_kg=1.15, actuator_mass_kg=4.15)


def tire_spec():
    """255/45 R21 summer tyre on all four corners."""
    return TireSpec(
        part_id="tires",
        section_width_m=0.255,
        aspect_ratio=0.45,
        rim_diameter_in=21.0,
        rolling_radius_factor=0.97,
        load_index_kg=950.0,
        peak_friction=1.04,
        slide_friction=0.86,
        load_sensitivity=0.09,
        friction_reference_load_fraction=0.55,
        cornering_stiffness_per_n=8.2,
        rolling_coefficient=0.0088,
        rolling_speed_coefficient=0.0000038,
        carcass_thickness_m=0.0098,
        carcass_density_kg_m3=1140.0,
    )


def pack_spec():
    """Twenty-four 9s2p prismatic NMC modules wired in series into one 216s2p traction pack."""
    return PackSpec(
        part_id="battery-modules",
        module_count=24,
        series_per_module=9,
        parallel_per_module=2,
        cell_nominal_voltage_v=3.68,
        cell_capacity_ah=57.9,
        cell_resistance_ohm=0.00072,
        cell_specific_energy_wh_kg=240.0,
        module_packaging_factor=1.142,
        module_interconnect_resistance_ohm=0.00022,
        pack_bus_resistance_ohm=0.0040,
        cell_pulse_charge_c_rate=2.0,
        cell_pulse_discharge_c_rate=5.0,
    )


def gearbox_spec():
    """Two helical stages per drive unit; their pitch diameters are what make the 9.1:1 final drive."""
    return GearboxSpec(
        part_id="reduction-gears",
        unit_count=2,
        first=GearStageSpec(pinion_pitch_diameter_m=0.062, wheel_pitch_diameter_m=0.217, face_width_m=0.045),
        second=GearStageSpec(pinion_pitch_diameter_m=0.078, wheel_pitch_diameter_m=0.2028, face_width_m=0.052),
    )


def motor_limits(motor: TractionMotorSpec, inverter: InverterSpec) !{}:
    """Solve one machine at its own current limit, then at whichever limit its inverter imposes.

    Torque is the interior-permanent-magnet expression: magnet torque plus the reluctance term the
    declared saliency provides at the declared current angle. The base speed is where the stator
    flux linkage the same operating point produces can no longer be driven at the space-vector
    voltage ceiling, and the constant-power region runs from there to the declared maximum speed.
    Power factor comes out of the corner-point voltage and current phasors rather than being
    declared, so the inverter's apparent-power rating and the machine's shaft power stay consistent.
    """
    current = min(motor.peak_phase_current_a, inverter.peak_phase_current_a)
    angle = motor.current_angle_deg * math.pi / 180.0
    torque = ipm_shaft_torque(f64(motor.pole_pairs), motor.magnet_flux_linkage_wb, motor.d_axis_inductance_h, motor.q_axis_inductance_h, current, angle)
    flux = stator_flux_linkage(motor.magnet_flux_linkage_wb, motor.d_axis_inductance_h, motor.q_axis_inductance_h, current, angle)
    mechanical_rps = voltage_limited_speed(inverter.dc_link_voltage_v, flux, f64(motor.pole_pairs))
    electrical_rps = mechanical_rps * f64(motor.pole_pairs)
    direct_current = 0.0 - current * math.sin(angle)
    quadrature_current = current * math.cos(angle)
    direct_voltage = 0.0 - electrical_rps * motor.q_axis_inductance_h * quadrature_current
    quadrature_voltage = electrical_rps * (motor.magnet_flux_linkage_wb + motor.d_axis_inductance_h * direct_current)
    real_power = 1.5 * (direct_voltage * direct_current + quadrature_voltage * quadrature_current)
    apparent_power = 1.5 * inverter.dc_link_voltage_v / ROOT_THREE * current
    switching_loss = INVERTER_SWITCH_COUNT * INVERTER_SWITCHING_FREQUENCY_HZ * INVERTER_SWITCHING_ENERGY_MJ_PER_A * current / 1000.0
    conduction_loss = 3.0 * (current / ROOT_TWO) * (current / ROOT_TWO) * inverter.on_state_resistance_ohm
    inverter_loss = (switching_loss + conduction_loss) / 1000.0
    corner_power = torque * mechanical_rps / 1000.0
    return MotorLimits(
        part_id=motor.part_id,
        inverter_part_id=inverter.part_id,
        effective_peak_current_a=current,
        current_limited_by="motor-winding" if motor.peak_phase_current_a <= inverter.peak_phase_current_a else "inverter-rating",
        peak_torque_nm=torque,
        continuous_torque_nm=ipm_shaft_torque(f64(motor.pole_pairs), motor.magnet_flux_linkage_wb, motor.d_axis_inductance_h, motor.q_axis_inductance_h, motor.continuous_phase_current_a, angle),
        base_speed_rpm=mechanical_rps * 60.0 / (2.0 * math.pi),
        max_speed_rpm=motor.max_speed_rpm,
        constant_power_ratio=motor.max_speed_rpm / (mechanical_rps * 60.0 / (2.0 * math.pi)),
        corner_power_kw=corner_power,
        stator_flux_linkage_wb=flux,
        power_factor=real_power / apparent_power,
        inverter_apparent_power_kva=apparent_power / 1000.0,
        inverter_peak_power_kw=real_power / 1000.0,
        inverter_loss_kw=inverter_loss,
        dc_link_peak_current_a=(real_power / 1000.0 + inverter_loss) * 1000.0 / inverter.dc_link_voltage_v,
        airgap_shear_stress_kpa=airgap_shear_stress(torque, motor.rotor_radius_m, motor.stack_length_m) / 1000.0,
    )


def brake_axle_limits(corner: BrakeCornerSpec, rolling_radius_m: f64) !{}:
    """Pressure, clamp force, corner torque and the wheel force both corners of one axle can make.

    The effective radius is the uniform-pressure integral over the swept annulus, and the disc
    thermal mass is the vented swept volume in cast iron. That thermal mass is what turns half m v
    squared into a disc temperature, so a heavier car heats the same disc further on the same stop.
    """
    effective_radius = uniform_pressure_radius(corner.disc_outer_radius_m, corner.disc_inner_radius_m)
    clamp_force = caliper_clamp_force(MAX_LINE_PRESSURE_PA, f64(corner.piston_count), corner.piston_bore_m)
    torque = corner_brake_torque(PAD_FRICTION_COEFFICIENT, clamp_force, effective_radius)
    swept_volume = math.pi * (corner.disc_outer_radius_m * corner.disc_outer_radius_m - corner.hat_radius_m * corner.hat_radius_m) * corner.disc_thickness_m * corner.vent_solid_fraction
    disc_mass = DISC_DENSITY_KG_M3 * swept_volume
    return BrakeAxleLimits(
        part_id=corner.part_id,
        axle=corner.axle,
        effective_radius_m=effective_radius,
        piston_area_m2=clamp_force / MAX_LINE_PRESSURE_PA,
        clamp_force_n=clamp_force,
        corner_torque_nm=torque,
        axle_force_n=2.0 * torque / rolling_radius_m,
        disc_mass_kg=disc_mass,
        disc_thermal_capacity_j_k=disc_mass * DISC_SPECIFIC_HEAT_J_KG_K,
    )


def tire_limits():
    """Turn the moulded tyre size into the radius, rated load and grip model the dynamics reads."""
    spec = tire_spec()
    rim_radius = spec.rim_diameter_in * INCH_M / 2.0
    unloaded_radius = rim_radius + spec.section_width_m * spec.aspect_ratio
    rated_load = spec.load_index_kg * GRAVITY_MPS2
    return TireLimits(
        part_id=spec.part_id,
        unloaded_radius_m=unloaded_radius,
        rolling_radius_m=unloaded_radius * spec.rolling_radius_factor,
        rated_load_n=rated_load,
        friction_reference_load_n=rated_load * spec.friction_reference_load_fraction,
        peak_friction=spec.peak_friction,
        slide_friction=spec.slide_friction,
        load_sensitivity=spec.load_sensitivity,
        cornering_stiffness_per_n=spec.cornering_stiffness_per_n,
        rolling_coefficient=spec.rolling_coefficient,
        rolling_speed_coefficient=spec.rolling_speed_coefficient,
    )


def pack_limits():
    """Sum 24 declared modules of cells into pack energy, voltage, resistance, power and mass."""
    spec = pack_spec()
    series = spec.module_count * spec.series_per_module
    strings = spec.parallel_per_module
    cells = series * strings
    capacity_ah = f64(strings) * spec.cell_capacity_ah
    nominal_voltage = f64(series) * spec.cell_nominal_voltage_v
    cell_mass = spec.cell_nominal_voltage_v * spec.cell_capacity_ah / spec.cell_specific_energy_wh_kg
    return PackLimits(
        part_id=spec.part_id,
        cell_count=cells,
        series_count=series,
        parallel_count=strings,
        energy_kwh=nominal_voltage * capacity_ah / 1000.0,
        nominal_voltage_v=nominal_voltage,
        resistance_ohm=f64(series) * spec.cell_resistance_ohm / f64(strings) + f64(spec.module_count) * spec.module_interconnect_resistance_ohm + spec.pack_bus_resistance_ohm,
        charge_power_kw=nominal_voltage * spec.cell_pulse_charge_c_rate * capacity_ah / 1000.0,
        discharge_power_kw=nominal_voltage * spec.cell_pulse_discharge_c_rate * capacity_ah / 1000.0,
        mass_kg=f64(cells) * cell_mass * spec.module_packaging_factor,
    )


def final_drive_ratio():
    """The 9.1:1 reduction is the product of the two declared stage pitch-diameter ratios."""
    gearbox = gearbox_spec()
    return gearbox.first.wheel_pitch_diameter_m / gearbox.first.pinion_pitch_diameter_m * gearbox.second.wheel_pitch_diameter_m / gearbox.second.pinion_pitch_diameter_m


def gear_blank_mass(stage: GearStageSpec):
    """Both gear blanks of one stage as webbed steel discs of their pitch diameter and face width."""
    area = math.pi / 4.0 * (stage.pinion_pitch_diameter_m * stage.pinion_pitch_diameter_m + stage.wheel_pitch_diameter_m * stage.wheel_pitch_diameter_m)
    return area * stage.face_width_m * STEEL_DENSITY_KG_M3 * GEAR_BLANK_WEB_FACTOR


def derived_part_mass(part_id: str) !{}:
    """Mass the declared specification implies for one part, summed over its declared instances."""
    if part_id == "front-motor" or part_id == "rear-motor":
        motor = front_motor_spec() if part_id == "front-motor" else rear_motor_spec()
        return math.pi * motor.stator_outer_radius_m * motor.stator_outer_radius_m * motor.stack_length_m * MOTOR_ACTIVE_DENSITY_KG_M3 * MOTOR_ASSEMBLED_MASS_FACTOR
    if part_id == "front-inverter" or part_id == "rear-inverter":
        inverter = front_inverter_spec() if part_id == "front-inverter" else rear_inverter_spec()
        capacitance_mf = 0.5 * inverter.peak_phase_current_a / (2.0 * INVERTER_SWITCHING_FREQUENCY_HZ * INVERTER_DC_LINK_RIPPLE_V) * 1000.0
        return capacitance_mf * INVERTER_CAPACITOR_KG_PER_MF + INVERTER_SWITCH_COUNT * inverter.peak_phase_current_a / 100.0 * INVERTER_MODULE_KG_PER_100A + inverter.housing_mass_kg
    if part_id == "brakes":
        radius = tire_limits().rolling_radius_m
        front = brake_axle_limits(front_brake_spec(), radius)
        rear = brake_axle_limits(rear_brake_spec(), radius)
        front_spec = front_brake_spec()
        rear_spec = rear_brake_spec()
        return 2.0 * (front.disc_mass_kg + front_spec.caliper_mass_kg + front_spec.pad_mass_kg + front_spec.actuator_mass_kg) + 2.0 * (rear.disc_mass_kg + rear_spec.caliper_mass_kg + rear_spec.pad_mass_kg + rear_spec.actuator_mass_kg)
    if part_id == "tires":
        spec = tire_spec()
        limits = tire_limits()
        rim_radius = spec.rim_diameter_in * INCH_M / 2.0
        tread_area = 2.0 * math.pi * limits.unloaded_radius_m * spec.section_width_m
        sidewall_area = 2.0 * math.pi * (limits.unloaded_radius_m * limits.unloaded_radius_m - rim_radius * rim_radius)
        return 4.0 * (tread_area + sidewall_area) * spec.carcass_thickness_m * spec.carcass_density_kg_m3
    if part_id == "battery-modules":
        return pack_limits().mass_kg
    gearbox = gearbox_spec()
    return f64(gearbox.unit_count) * (gear_blank_mass(gearbox.first) + gear_blank_mass(gearbox.second)) * GEARBOX_HOUSING_MASS_FACTOR


def declared_part(part_id: str):
    """The declared part this specification claims to describe. Fails loudly on an unknown id."""
    for part in vehicle_parts():
        if part.id == part_id:
            return part
    ensure false
    return vehicle_parts()[0]


def part_derivation(part_id: str):
    """One sentence naming what the derived mass for this part is actually built from."""
    if part_id == "front-motor" or part_id == "rear-motor":
        return "active stack volume from the declared stator outer radius and stack length, at the shared active density and assembled-to-active mass ratio"
    if part_id == "front-inverter" or part_id == "rear-inverter":
        return "DC-link capacitance sized from the declared peak phase current and ripple budget, plus six power modules at the declared current rating, plus the declared housing"
    if part_id == "brakes":
        return "four vented cast-iron discs from the declared swept annulus, plus the declared caliper, pad and actuator masses per corner"
    if part_id == "tires":
        return "four carcasses as a tread band and two sidewall annuli at the declared thickness and compound density"
    if part_id == "battery-modules":
        return "432 cells at the declared cell specific energy, times the declared module packaging factor"
    return "four webbed steel gear blanks per unit at their declared pitch diameters and face widths, times the declared housing mass factor"


def reconcile_part(part_id: str, expected_count: int) !{}:
    """Check one specification against the part it claims to describe: identity, count and mass."""
    part = declared_part(part_id)
    derived = derived_part_mass(part_id)
    error = abs(derived - part.mass_kg) / part.mass_kg
    return PartReconciliation(
        part_id=part_id,
        resolved=true,
        declared_count=part.count,
        expected_count=expected_count,
        declared_mass_kg=part.mass_kg,
        derived_mass_kg=derived,
        mass_error_fraction=error,
        tolerance_fraction=MASS_TOLERANCE_FRACTION,
        derivation=part_derivation(part_id),
        passed=part.count == expected_count and error <= MASS_TOLERANCE_FRACTION,
    )


def part_reconciliation() !{}:
    """Every specification checked against `vehicle_parts()`, in the order the drive chain runs."""
    return [
        reconcile_part("front-motor", 1),
        reconcile_part("rear-motor", 1),
        reconcile_part("front-inverter", 1),
        reconcile_part("rear-inverter", 1),
        reconcile_part("reduction-gears", 2),
        reconcile_part("brakes", 4),
        reconcile_part("tires", 4),
        reconcile_part("battery-modules", 24),
    ]


def net_current_limit(net_id: str):
    """Declared maximum current of one high-voltage net, read out of `vehicle_nets()`."""
    for net in vehicle_nets("circuitframe"):
        if net.id == net_id:
            return net.maximum_current_a
    ensure false
    return 0.0


def generating_power(motor: MotorLimits):
    """Electrical power one machine can regenerate: its continuous torque at its own base speed.

    A braking burst is short, but the machines still recover at their continuous rating rather than
    their peak: peak current is a traction-side thermal allowance the pad-and-disc side never needs.
    """
    return motor.corner_power_kw * motor.continuous_torque_nm / motor.peak_torque_nm


def axle_hardware(axle: str, motor: MotorLimits, brake: BrakeAxleLimits, tire: TireLimits, ratio: f64, efficiency: f64):
    """Collapse one axle's declared machine, inverter, calipers and tyres into per-step scalars."""
    return AxleHardware(
        axle=axle,
        brake_axle_force_n=brake.axle_force_n,
        drive_force_per_throttle_n=motor.peak_torque_nm * ratio * efficiency / tire.rolling_radius_m,
        power_force_numerator_w=motor.corner_power_kw * 1000.0 * efficiency,
        power_constraint="inverter-power" if motor.current_limited_by == "inverter-rating" else "motor-power",
        peak_friction=tire.peak_friction,
        slide_friction=tire.slide_friction,
        load_sensitivity=tire.load_sensitivity,
        friction_reference_load_n=tire.friction_reference_load_n,
        cornering_stiffness_per_n=tire.cornering_stiffness_per_n,
        fade_onset_temp_c=FADE_ONSET_TEMP_C,
        fade_rate_per_k=FADE_RATE_PER_K,
        fade_floor_fraction=FADE_FLOOR_FRACTION,
        disc_thermal_capacity_j_k=brake.disc_thermal_capacity_j_k,
    )


pub def hardware_limits() -> HardwareLimits !{}:
    """Derive every performance limit `physics.sema` needs, and refuse to return a drifted set.

    The reconciliation runs first. A specification whose part id no longer resolves, whose instance
    count no longer matches the declared count, or whose derived mass has drifted outside the stated
    tolerance stops the build here rather than quietly feeding a wrong limit into the dynamics.
    """
    reconciliation = part_reconciliation()
    for row in reconciliation:
        ensure row.resolved and row.passed
    front = motor_limits(front_motor_spec(), front_inverter_spec())
    rear = motor_limits(rear_motor_spec(), rear_inverter_spec())
    ensure front.dc_link_peak_current_a <= net_current_limit(front_inverter_spec().net_id)
    ensure rear.dc_link_peak_current_a <= net_current_limit(rear_inverter_spec().net_id)
    tire = tire_limits()
    pack = pack_limits()
    ratio = final_drive_ratio()
    efficiency = GEAR_MESH_EFFICIENCY * GEAR_MESH_EFFICIENCY
    front_brake = brake_axle_limits(front_brake_spec(), tire.rolling_radius_m)
    rear_brake = brake_axle_limits(rear_brake_spec(), tire.rolling_radius_m)
    regenerative_torque = front.continuous_torque_nm + rear.continuous_torque_nm
    driveline = MOTOR_EFFICIENCY * (1.0 - (front.inverter_loss_kw + rear.inverter_loss_kw) / (front.corner_power_kw + rear.corner_power_kw)) * efficiency
    return HardwareLimits(
        front_motor=front,
        rear_motor=rear,
        front_brake=front_brake,
        rear_brake=rear_brake,
        tire=tire,
        pack=pack,
        front_axle=axle_hardware("front", front, front_brake, tire, ratio, driveline),
        rear_axle=axle_hardware("rear", rear, rear_brake, tire, ratio, driveline),
        final_drive_ratio=ratio,
        gearbox_efficiency=efficiency,
        motor_efficiency=MOTOR_EFFICIENCY,
        inverter_efficiency=1.0 - (front.inverter_loss_kw + rear.inverter_loss_kw) / (front.corner_power_kw + rear.corner_power_kw),
        max_motor_torque_nm=front.peak_torque_nm + rear.peak_torque_nm,
        max_brake_force_n=front_brake.axle_force_n + rear_brake.axle_force_n,
        max_drive_power_kw=front.corner_power_kw + rear.corner_power_kw,
        max_regenerative_force_n=regenerative_torque * ratio * efficiency / tire.rolling_radius_m,
        max_regenerative_power_kw=min(pack.charge_power_kw, generating_power(front) + generating_power(rear)),
        regenerative_front_share=front.continuous_torque_nm / regenerative_torque,
        max_line_pressure_pa=MAX_LINE_PRESSURE_PA,
        pad_friction=PAD_FRICTION_COEFFICIENT,
        fade_onset_temp_c=FADE_ONSET_TEMP_C,
        fade_rate_per_k=FADE_RATE_PER_K,
        fade_floor_fraction=FADE_FLOOR_FRACTION,
        disc_cooling_base_w_per_k=DISC_COOLING_BASE_W_PER_K,
        disc_cooling_speed_w_per_k_mps=DISC_COOLING_SPEED_W_PER_K_MPS,
        driveline_top_speed_mps=min(front.max_speed_rpm, rear.max_speed_rpm) / 60.0 / ratio * 2.0 * math.pi * tire.rolling_radius_m,
    )


def parameter_row(part_id: str, key: str, label: str, value: f64, unit: str, source: str, note: str):
    """One declared scalar with the part it belongs to and how it was arrived at."""
    return {"id": part_id + "." + key, "part_id": part_id, "label": label, "value": value, "unit": unit, "source": source, "note": note}


def motor_parameters(motor: TractionMotorSpec):
    """Provenance rows for one machine, read straight off the specification struct."""
    return [
        parameter_row(motor.part_id, "pole-pairs", "Pole pairs", f64(motor.pole_pairs), "1", SOURCE_ASSUMED, "eight-pole rotor, the usual choice for this speed and frequency class"),
        parameter_row(motor.part_id, "rotor-radius", "Rotor radius", motor.rotor_radius_m, "m", SOURCE_ASSUMED, "sized so the derived peak torque sits near 47 kPa of airgap shear stress"),
        parameter_row(motor.part_id, "stack-length", "Active stack length", motor.stack_length_m, "m", SOURCE_ASSUMED, "sets the torque per unit shear stress and, with the stator radius, the machine mass"),
        parameter_row(motor.part_id, "stator-outer-radius", "Stator outer radius", motor.stator_outer_radius_m, "m", SOURCE_ASSUMED, "outer envelope of the active stack; drives the reconciled machine mass"),
        parameter_row(motor.part_id, "magnet-flux-linkage", "Magnet flux linkage", motor.magnet_flux_linkage_wb, "Wb", SOURCE_ASSUMED, "800 V winding, so roughly twice the flux linkage and half the current of a 400 V machine"),
        parameter_row(motor.part_id, "d-axis-inductance", "d-axis inductance", motor.d_axis_inductance_h, "H", SOURCE_ASSUMED, "saturated value at peak current; no current-dependent inductance map is claimed"),
        parameter_row(motor.part_id, "q-axis-inductance", "q-axis inductance", motor.q_axis_inductance_h, "H", SOURCE_ASSUMED, "the declared saliency is what produces the reluctance share of the derived torque"),
        parameter_row(motor.part_id, "peak-phase-current", "Peak phase current", motor.peak_phase_current_a, "A", SOURCE_ASSUMED, "winding thermal and demagnetisation limit for a bounded peak-power burst"),
        parameter_row(motor.part_id, "continuous-phase-current", "Continuous phase current", motor.continuous_phase_current_a, "A", SOURCE_ASSUMED, "liquid-cooled continuous rating; sets the derived regenerative torque"),
        parameter_row(motor.part_id, "current-angle", "MTPA current angle", motor.current_angle_deg, "deg", SOURCE_CORRELATED, "fixed maximum-torque-per-amp angle; a real controller would walk this with speed and temperature"),
        parameter_row(motor.part_id, "max-speed", "Maximum speed", motor.max_speed_rpm, "rpm", SOURCE_ASSUMED, "rotor burst and bearing limit; with the base speed it sets the constant-power region"),
    ]


def inverter_parameters(inverter: InverterSpec):
    """Provenance rows for one inverter, read straight off the specification struct."""
    return [
        parameter_row(inverter.part_id, "dc-link-voltage", "DC link voltage", inverter.dc_link_voltage_v, "V", SOURCE_ASSUMED, "800 V class, consistent with the declared HV net voltage and the derived pack nominal"),
        parameter_row(inverter.part_id, "peak-phase-current", "Peak phase current", inverter.peak_phase_current_a, "A", SOURCE_ASSUMED, "silicon carbide module rating; caps the machine when it is below the winding limit"),
        parameter_row(inverter.part_id, "on-state-resistance", "On-state resistance", inverter.on_state_resistance_ohm, "ohm", SOURCE_ASSUMED, "per-phase conduction path through the paralleled dies"),
        parameter_row(inverter.part_id, "housing-mass", "Housing and coldplate mass", inverter.housing_mass_kg, "kg", SOURCE_ASSUMED, "front unit is standalone in the subframe, rear shares the drive-unit casting"),
    ]


def brake_parameters(corner: BrakeCornerSpec):
    """Provenance rows for one brake corner, read straight off the specification struct."""
    return [
        parameter_row(corner.part_id, corner.axle + ".disc-outer-radius", corner.axle + " disc outer radius", corner.disc_outer_radius_m, "m", SOURCE_ASSUMED, "swept annulus outer edge; with the inner edge it fixes the effective radius"),
        parameter_row(corner.part_id, corner.axle + ".disc-inner-radius", corner.axle + " disc inner radius", corner.disc_inner_radius_m, "m", SOURCE_ASSUMED, "inner edge of the pad sweep"),
        parameter_row(corner.part_id, corner.axle + ".hat-radius", corner.axle + " disc hat radius", corner.hat_radius_m, "m", SOURCE_ASSUMED, "mounting hat; excluded from the thermal mass because it is not swept"),
        parameter_row(corner.part_id, corner.axle + ".disc-thickness", corner.axle + " disc thickness", corner.disc_thickness_m, "m", SOURCE_ASSUMED, "vented disc overall thickness"),
        parameter_row(corner.part_id, corner.axle + ".vent-solid-fraction", corner.axle + " vented solid fraction", corner.vent_solid_fraction, "1", SOURCE_CORRELATED, "solid fraction of a vaned vented disc; sets both the disc mass and its thermal capacity"),
        parameter_row(corner.part_id, corner.axle + ".piston-count", corner.axle + " piston count", f64(corner.piston_count), "1", SOURCE_ASSUMED, "fixed-caliper pistons per side"),
        parameter_row(corner.part_id, corner.axle + ".piston-bore", corner.axle + " piston bore", corner.piston_bore_m, "m", SOURCE_ASSUMED, "with the line pressure this is the whole clamp force"),
        parameter_row(corner.part_id, corner.axle + ".caliper-mass", corner.axle + " caliper mass", corner.caliper_mass_kg, "kg", SOURCE_ASSUMED, "aluminium fixed caliper body"),
        parameter_row(corner.part_id, corner.axle + ".pad-mass", corner.axle + " pad set mass", corner.pad_mass_kg, "kg", SOURCE_ASSUMED, "both pads including backing plates"),
        parameter_row(corner.part_id, corner.axle + ".actuator-mass", corner.axle + " actuator mass", corner.actuator_mass_kg, "kg", SOURCE_ASSUMED, "brake-by-wire actuator and local electronics; the rear also carries the park-brake motor"),
    ]


def shared_parameters():
    """Provenance rows for the constants shared across specifications and reconciliations."""
    spec = tire_spec()
    pack = pack_spec()
    gearbox = gearbox_spec()
    return [
        parameter_row("front-motor", "active-density", "Active stack mean density", MOTOR_ACTIVE_DENSITY_KG_M3, "kg/m^3", SOURCE_CORRELATED, "volume-mean of laminations, copper, magnets and slot air; shared by both machines"),
        parameter_row("front-motor", "assembled-mass-factor", "Assembled to active mass ratio", MOTOR_ASSEMBLED_MASS_FACTOR, "1", SOURCE_ASSUMED, "housing, shaft, bearings and end windings over the active stack; shared by both machines"),
        parameter_row("front-motor", "efficiency", "Shaft efficiency at the corner point", MOTOR_EFFICIENCY, "1", SOURCE_CORRELATED, "lumped copper, iron, magnet and windage loss for a liquid-cooled traction machine; no loss map is claimed, so this stays declared rather than derived"),
        parameter_row("front-inverter", "switching-frequency", "Switching frequency", INVERTER_SWITCHING_FREQUENCY_HZ, "Hz", SOURCE_ASSUMED, "shared silicon carbide switching frequency"),
        parameter_row("front-inverter", "switching-energy", "Switching energy coefficient", INVERTER_SWITCHING_ENERGY_MJ_PER_A, "mJ/A", SOURCE_CORRELATED, "turn-on, turn-off and reverse-recovery energy per amp at the declared DC link voltage"),
        parameter_row("front-inverter", "dc-link-ripple", "DC link ripple budget", INVERTER_DC_LINK_RIPPLE_V, "V", SOURCE_ASSUMED, "sizes the film capacitor, and through it the reconciled inverter mass"),
        parameter_row("front-inverter", "capacitor-specific-mass", "Capacitor specific mass", INVERTER_CAPACITOR_KG_PER_MF, "kg/mF", SOURCE_CORRELATED, "900 V film capacitor mass per millifarad"),
        parameter_row("front-inverter", "module-specific-mass", "Power module specific mass", INVERTER_MODULE_KG_PER_100A, "kg/100A", SOURCE_CORRELATED, "module, substrate and busbar mass per hundred amps per switch"),
        parameter_row("brakes", "pad-friction", "Pad friction coefficient", PAD_FRICTION_COEFFICIENT, "1", SOURCE_CORRELATED, "cold organic pad against cast iron; the fade curve degrades it with disc temperature"),
        parameter_row("brakes", "max-line-pressure", "Maximum line pressure", MAX_LINE_PRESSURE_PA, "Pa", SOURCE_ASSUMED, "160 bar at full pedal demand; the pedal-to-pressure map is taken as linear"),
        parameter_row("brakes", "disc-density", "Disc material density", DISC_DENSITY_KG_M3, "kg/m^3", SOURCE_ASSUMED, "grey cast iron"),
        parameter_row("brakes", "disc-specific-heat", "Disc specific heat", DISC_SPECIFIC_HEAT_J_KG_K, "J/(kg.K)", SOURCE_CORRELATED, "grey cast iron; with the swept mass this is the whole disc thermal capacity"),
        parameter_row("brakes", "fade-onset", "Fade onset temperature", FADE_ONSET_TEMP_C, "degC", SOURCE_CORRELATED, "temperature above which the declared pad compound starts losing friction"),
        parameter_row("brakes", "fade-rate", "Fade rate", FADE_RATE_PER_K, "1/K", SOURCE_CORRELATED, "fractional friction loss per kelvin above onset"),
        parameter_row("brakes", "fade-floor", "Fade floor", FADE_FLOOR_FRACTION, "1", SOURCE_CORRELATED, "residual friction fraction the compound never falls below"),
        parameter_row("brakes", "disc-cooling-base", "Disc cooling at rest", DISC_COOLING_BASE_W_PER_K, "W/K", SOURCE_CORRELATED, "natural convection and radiation from one disc"),
        parameter_row("brakes", "disc-cooling-speed", "Disc cooling per unit speed", DISC_COOLING_SPEED_W_PER_K_MPS, "W/(K.m/s)", SOURCE_CORRELATED, "forced convection through the vanes, linear in road speed"),
        parameter_row(spec.part_id, "section-width", "Section width", spec.section_width_m, "m", SOURCE_ASSUMED, "moulded tyre size 255/45 R21"),
        parameter_row(spec.part_id, "aspect-ratio", "Aspect ratio", spec.aspect_ratio, "1", SOURCE_ASSUMED, "sidewall height as a fraction of section width"),
        parameter_row(spec.part_id, "rim-diameter", "Rim diameter", spec.rim_diameter_in, "in", SOURCE_ASSUMED, "matches the declared alloy wheel"),
        parameter_row(spec.part_id, "rolling-radius-factor", "Rolling radius factor", spec.rolling_radius_factor, "1", SOURCE_CORRELATED, "loaded rolling radius over unloaded radius for a laden radial tyre"),
        parameter_row(spec.part_id, "load-index", "Load index mass", spec.load_index_kg, "kg", SOURCE_ASSUMED, "load index 106; four corners must cover the laden vehicle with margin"),
        parameter_row(spec.part_id, "peak-friction", "Peak friction coefficient", spec.peak_friction, "1", SOURCE_CORRELATED, "dry asphalt summer compound at the reference load"),
        parameter_row(spec.part_id, "slide-friction", "Sliding friction coefficient", spec.slide_friction, "1", SOURCE_CORRELATED, "past the peak of the slip curve; also the floor of the load-sensitivity model"),
        parameter_row(spec.part_id, "load-sensitivity", "Friction load sensitivity", spec.load_sensitivity, "1", SOURCE_CORRELATED, "fractional friction loss per unit of relative overload; this is how added mass costs grip"),
        parameter_row(spec.part_id, "friction-reference-load", "Friction reference load fraction", spec.friction_reference_load_fraction, "1", SOURCE_CORRELATED, "fraction of the load-index maximum at which the friction figures are quoted"),
        parameter_row(spec.part_id, "cornering-stiffness", "Cornering stiffness per unit load", spec.cornering_stiffness_per_n, "1/rad", SOURCE_CORRELATED, "linear-range slip stiffness per newton of vertical load, so an axle stiffens as it is loaded"),
        parameter_row(spec.part_id, "rolling-coefficient", "Rolling resistance coefficient", spec.rolling_coefficient, "1", SOURCE_CORRELATED, "standing-start value; ISO 28580 class for a low-rolling-resistance EV tyre"),
        parameter_row(spec.part_id, "rolling-speed-coefficient", "Rolling resistance speed term", spec.rolling_speed_coefficient, "s^2/m^2", SOURCE_CORRELATED, "quadratic speed growth from carcass hysteresis"),
        parameter_row(spec.part_id, "carcass-thickness", "Carcass thickness", spec.carcass_thickness_m, "m", SOURCE_ASSUMED, "mean rubber and belt thickness used for the reconciled tyre mass"),
        parameter_row(spec.part_id, "carcass-density", "Carcass density", spec.carcass_density_kg_m3, "kg/m^3", SOURCE_ASSUMED, "rubber, steel belt and textile composite"),
        parameter_row(pack.part_id, "module-count", "Module count", f64(pack.module_count), "1", SOURCE_ASSUMED, "checked against the declared part instance count"),
        parameter_row(pack.part_id, "series-per-module", "Cells in series per module", f64(pack.series_per_module), "1", SOURCE_ASSUMED, "24 modules of 9 in series make the 216s string"),
        parameter_row(pack.part_id, "parallel-per-module", "Cells in parallel per module", f64(pack.parallel_per_module), "1", SOURCE_ASSUMED, "sets pack capacity and divides the cell resistance"),
        parameter_row(pack.part_id, "cell-nominal-voltage", "Cell nominal voltage", pack.cell_nominal_voltage_v, "V", SOURCE_ASSUMED, "prismatic nickel-manganese-cobalt chemistry"),
        parameter_row(pack.part_id, "cell-capacity", "Cell capacity", pack.cell_capacity_ah, "Ah", SOURCE_ASSUMED, "with the series and parallel counts this is the whole pack energy"),
        parameter_row(pack.part_id, "cell-resistance", "Cell internal resistance", pack.cell_resistance_ohm, "ohm", SOURCE_CORRELATED, "DC internal resistance of a power-oriented prismatic cell at 25 degrees"),
        parameter_row(pack.part_id, "cell-specific-energy", "Cell specific energy", pack.cell_specific_energy_wh_kg, "Wh/kg", SOURCE_CORRELATED, "cell-level energy density; with the packaging factor it is the reconciled module mass"),
        parameter_row(pack.part_id, "module-packaging-factor", "Module packaging factor", pack.module_packaging_factor, "1", SOURCE_ASSUMED, "busbars, frame, cooling plate and sensing over bare cell mass"),
        parameter_row(pack.part_id, "module-interconnect-resistance", "Module interconnect resistance", pack.module_interconnect_resistance_ohm, "ohm", SOURCE_ASSUMED, "inter-module busbar and joint resistance"),
        parameter_row(pack.part_id, "pack-bus-resistance", "Pack bus resistance", pack.pack_bus_resistance_ohm, "ohm", SOURCE_ASSUMED, "contactors, fuse and pack terminals"),
        parameter_row(pack.part_id, "charge-c-rate", "Cell pulse charge rate", pack.cell_pulse_charge_c_rate, "1/h", SOURCE_CORRELATED, "regenerative charge acceptance for a bounded pulse; caps the derived regenerative power"),
        parameter_row(pack.part_id, "discharge-c-rate", "Cell pulse discharge rate", pack.cell_pulse_discharge_c_rate, "1/h", SOURCE_CORRELATED, "peak discharge pulse the cell will take"),
        parameter_row(gearbox.part_id, "stage-1-pinion", "First stage pinion pitch diameter", gearbox.first.pinion_pitch_diameter_m, "m", SOURCE_ASSUMED, "with the wheel diameter this is the 3.5:1 first stage"),
        parameter_row(gearbox.part_id, "stage-1-wheel", "First stage wheel pitch diameter", gearbox.first.wheel_pitch_diameter_m, "m", SOURCE_ASSUMED, "first reduction wheel"),
        parameter_row(gearbox.part_id, "stage-1-face", "First stage face width", gearbox.first.face_width_m, "m", SOURCE_ASSUMED, "helical face width; enters the reconciled gearbox mass only"),
        parameter_row(gearbox.part_id, "stage-2-pinion", "Second stage pinion pitch diameter", gearbox.second.pinion_pitch_diameter_m, "m", SOURCE_ASSUMED, "with the wheel diameter this is the 2.6:1 second stage"),
        parameter_row(gearbox.part_id, "stage-2-wheel", "Second stage wheel pitch diameter", gearbox.second.wheel_pitch_diameter_m, "m", SOURCE_ASSUMED, "output wheel on the differential carrier"),
        parameter_row(gearbox.part_id, "stage-2-face", "Second stage face width", gearbox.second.face_width_m, "m", SOURCE_ASSUMED, "helical face width; enters the reconciled gearbox mass only"),
        parameter_row(gearbox.part_id, "mesh-efficiency", "Mesh efficiency per stage", GEAR_MESH_EFFICIENCY, "1", SOURCE_CORRELATED, "loaded helical mesh, churning and bearing drag; applied once per stage"),
        parameter_row(gearbox.part_id, "blank-web-factor", "Gear blank web factor", GEAR_BLANK_WEB_FACTOR, "1", SOURCE_ASSUMED, "webbed blank mass over the solid disc of the same pitch diameter"),
        parameter_row(gearbox.part_id, "housing-mass-factor", "Gearbox housing mass factor", GEARBOX_HOUSING_MASS_FACTOR, "1", SOURCE_ASSUMED, "casing, shafts, bearings, oil and differential over the gear blanks"),
    ]


def hardware_parameters():
    """The whole provenance ledger, built from the specification structs so it cannot drift."""
    return (motor_parameters(front_motor_spec())
        + motor_parameters(rear_motor_spec())
        + inverter_parameters(front_inverter_spec())
        + inverter_parameters(rear_inverter_spec())
        + brake_parameters(front_brake_spec())
        + brake_parameters(rear_brake_spec())
        + shared_parameters())


def motor_export(limits: MotorLimits):
    return {
        "part_id": limits.part_id,
        "inverter_part_id": limits.inverter_part_id,
        "effective_peak_current_a": limits.effective_peak_current_a,
        "current_limited_by": limits.current_limited_by,
        "peak_torque_nm": limits.peak_torque_nm,
        "continuous_torque_nm": limits.continuous_torque_nm,
        "base_speed_rpm": limits.base_speed_rpm,
        "max_speed_rpm": limits.max_speed_rpm,
        "constant_power_ratio": limits.constant_power_ratio,
        "corner_power_kw": limits.corner_power_kw,
        "power_factor": limits.power_factor,
        "inverter_apparent_power_kva": limits.inverter_apparent_power_kva,
        "inverter_peak_power_kw": limits.inverter_peak_power_kw,
        "inverter_loss_kw": limits.inverter_loss_kw,
        "dc_link_peak_current_a": limits.dc_link_peak_current_a,
        "airgap_shear_stress_kpa": limits.airgap_shear_stress_kpa,
    }


def brake_export(limits: BrakeAxleLimits):
    return {
        "part_id": limits.part_id,
        "axle": limits.axle,
        "effective_radius_m": limits.effective_radius_m,
        "piston_area_m2": limits.piston_area_m2,
        "clamp_force_n": limits.clamp_force_n,
        "corner_torque_nm": limits.corner_torque_nm,
        "axle_force_n": limits.axle_force_n,
        "disc_mass_kg": limits.disc_mass_kg,
        "disc_thermal_capacity_j_k": limits.disc_thermal_capacity_j_k,
    }


def reconciliation_export(row: PartReconciliation):
    return {
        "part_id": row.part_id,
        "resolved": row.resolved,
        "declared_count": row.declared_count,
        "expected_count": row.expected_count,
        "declared_mass_kg": row.declared_mass_kg,
        "derived_mass_kg": row.derived_mass_kg,
        "mass_error_fraction": row.mass_error_fraction,
        "tolerance_fraction": row.tolerance_fraction,
        "derivation": row.derivation,
        "passed": row.passed,
    }


pub def hardware_export() -> dict[str, any] !{}:
    """API payload: the derived limits, the declared parameters behind them, and the reconciliation."""
    limits = hardware_limits()
    tire = limits.tire
    pack = limits.pack
    return {
        "schema": "sema.circuitframe-hardware/v1",
        "authority": "engineered component assumptions for a reconstruction; no dynamometer, pressure-rig, tyre-rig or cell-cycling evidence exists for any value here",
        "method": "every performance limit is derived from the declared parameters of a part that already exists in vehicle.sema, and every specification is reconciled against that part's declared instance count and mass",
        "front_motor": motor_export(limits.front_motor),
        "rear_motor": motor_export(limits.rear_motor),
        "front_brake": brake_export(limits.front_brake),
        "rear_brake": brake_export(limits.rear_brake),
        "tire": {
            "part_id": tire.part_id,
            "size": "255/45 R21",
            "unloaded_radius_m": tire.unloaded_radius_m,
            "rolling_radius_m": tire.rolling_radius_m,
            "rated_load_n": tire.rated_load_n,
            "friction_reference_load_n": tire.friction_reference_load_n,
            "peak_friction": tire.peak_friction,
            "slide_friction": tire.slide_friction,
            "load_sensitivity": tire.load_sensitivity,
            "cornering_stiffness_per_n": tire.cornering_stiffness_per_n,
            "rolling_coefficient": tire.rolling_coefficient,
            "rolling_speed_coefficient": tire.rolling_speed_coefficient,
        },
        "pack": {
            "part_id": pack.part_id,
            "cell_count": pack.cell_count,
            "series_count": pack.series_count,
            "parallel_count": pack.parallel_count,
            "energy_kwh": pack.energy_kwh,
            "nominal_voltage_v": pack.nominal_voltage_v,
            "resistance_ohm": pack.resistance_ohm,
            "charge_power_kw": pack.charge_power_kw,
            "discharge_power_kw": pack.discharge_power_kw,
            "mass_kg": pack.mass_kg,
        },
        "driveline": {
            "part_id": "reduction-gears",
            "final_drive_ratio": limits.final_drive_ratio,
            "gearbox_efficiency": limits.gearbox_efficiency,
            "motor_efficiency": limits.motor_efficiency,
            "inverter_efficiency": limits.inverter_efficiency,
            "top_speed_mps": limits.driveline_top_speed_mps,
        },
        "limits": {
            "max_motor_torque_nm": limits.max_motor_torque_nm,
            "max_drive_power_kw": limits.max_drive_power_kw,
            "max_brake_force_n": limits.max_brake_force_n,
            "max_regenerative_force_n": limits.max_regenerative_force_n,
            "max_regenerative_power_kw": limits.max_regenerative_power_kw,
            "regenerative_front_share": limits.regenerative_front_share,
            "max_line_pressure_pa": limits.max_line_pressure_pa,
            "pad_friction": limits.pad_friction,
            "fade_onset_temp_c": limits.fade_onset_temp_c,
            "fade_rate_per_k": limits.fade_rate_per_k,
            "fade_floor_fraction": limits.fade_floor_fraction,
        },
        "chain": {
            "drive": "front-motor + rear-motor peak shaft torque at the effective peak current -> reduction-gears final drive and mesh efficiency -> tires rolling radius -> wheel force, held above the machine's own base speed to its corner shaft power and bounded by that axle's tyre friction circle. Where the inverter's current rating is below the winding rating it is the inverter that set the torque, and `current_limited_by` names which one did",
            "brake": "pedal demand -> brakes line pressure -> caliper clamp force -> corner torque at the derived effective radius -> tires rolling radius -> axle force, then bounded by the tyre friction circle on that axle's live vertical load",
            "energy": "battery-modules cells -> pack energy, nominal voltage and internal resistance -> terminal power and regenerative charge acceptance",
        },
        "parameters": hardware_parameters(),
        "reconciliation": [reconciliation_export(row) for row in part_reconciliation()],
        "scientific_validated": false,
    }


pub def hardware_equations() -> list[dict[str, str]] !{}:
    """Manifest rows for the hardware model, in the same shape as physics.sema:dynamics_equations()."""
    return [
        {"id": "ipm-torque", "source": "ipm_shaft_torque", "expression": "T = 3/2 p I cos(gamma) (lambda_m + (L_q - L_d) I sin(gamma))", "unit": "N.m", "owner": "Sema interior-permanent-magnet machine on declared winding parameters (solved on assumed inputs)"},
        {"id": "stator-flux", "source": "stator_flux_linkage", "expression": "psi_s = sqrt((lambda_m - L_d I sin gamma)^2 + (L_q I cos gamma)^2)", "unit": "Wb", "owner": "Sema machine flux at the peak-torque operating point (solved)"},
        {"id": "base-speed", "source": "voltage_limited_speed", "expression": "omega_m = V_dc / (sqrt(3) psi_s p)", "unit": "rad/s", "owner": "Sema space-vector voltage ceiling; start of the constant-power region (solved)"},
        {"id": "inverter-power", "source": "motor_limits", "expression": "P_inv = 3/2 (V_dc/sqrt3) I cos(phi) - 6 f_sw k_sw I - 3 (I/sqrt2)^2 R_on", "unit": "kW", "owner": "Sema inverter apparent power at the derived corner power factor, less switching and conduction loss (solved on correlated loss coefficients)"},
        {"id": "airgap-shear", "source": "airgap_shear_stress", "expression": "sigma = T / (2 pi r_rotor^2 l_stack)", "unit": "Pa", "owner": "Sema machine sizing check; a credible liquid-cooled peak is 40-55 kPa (correlated)"},
        {"id": "brake-effective-radius", "source": "uniform_pressure_radius", "expression": "r_eff = 2/3 (r_o^3 - r_i^3) / (r_o^2 - r_i^2)", "unit": "m", "owner": "Sema uniform-pressure pad sweep (solved)"},
        {"id": "brake-clamp", "source": "caliper_clamp_force", "expression": "F_clamp = p_line n_piston pi (d_bore/2)^2", "unit": "N", "owner": "Sema hydraulic caliper (exact)"},
        {"id": "brake-torque", "source": "corner_brake_torque", "expression": "T_corner = 2 mu_pad F_clamp r_eff", "unit": "N.m", "owner": "Sema friction brake, two pads per disc (exact on a correlated pad friction)"},
        {"id": "brake-fade", "source": "brake_fade_factor", "expression": "mu(T) = max(f_floor, 1 - k_fade max(0, T_disc - T_onset)) mu_pad", "unit": "1", "owner": "Sema pad fade curve above the declared onset temperature (correlated)"},
        {"id": "disc-thermal-mass", "source": "brake_axle_limits", "expression": "C_disc = rho_iron pi (r_o^2 - r_hat^2) t phi_vent c_iron", "unit": "J/K", "owner": "Sema vented swept-volume disc thermal mass (solved on assumed geometry)"},
        {"id": "tire-load-sensitivity", "source": "tire_friction", "expression": "mu(F_z) = clamp(mu_peak (1 - k_load (F_z/F_ref - 1)), mu_slide, mu_peak)", "unit": "1", "owner": "Sema tyre load sensitivity; this is why added mass buys less grip than it costs (correlated)"},
        {"id": "tire-rolling", "source": "rolling_coefficient", "expression": "f_r(v) = f_0 + f_2 v^2", "unit": "1", "owner": "Sema rolling resistance with a quadratic speed term (correlated)"},
        {"id": "tire-radius", "source": "tire_limits", "expression": "r_roll = k_roll (d_rim/2 + w_section a_ratio)", "unit": "m", "owner": "Sema moulded tyre size to loaded rolling radius (solved on a correlated deflection factor)"},
        {"id": "pack-topology", "source": "pack_limits", "expression": "E = n_s n_p V_cell C_cell; V = n_s V_cell; R = n_s R_cell / n_p + n_mod R_link + R_bus", "unit": "kWh, V, ohm", "owner": "Sema series-parallel cell summation over the declared modules (solved)"},
        {"id": "pack-mass-closure", "source": "reconcile_part", "expression": "|m_derived - m_declared| / m_declared <= tol", "unit": "1", "owner": "Sema specification-to-part reconciliation; fails the build on drift (solved)"},
        {"id": "final-drive", "source": "final_drive_ratio", "expression": "i = (d_w1/d_p1) (d_w2/d_p2)", "unit": "1", "owner": "Sema two-stage helical reduction from the declared pitch diameters (exact)"},
    ]


test "every hardware specification resolves to a declared part with the declared count and mass":
    rows = part_reconciliation()
    ensure len(rows) == 8
    for row in rows:
        ensure row.resolved
        ensure row.declared_count == row.expected_count
        ensure row.mass_error_fraction <= row.tolerance_fraction
        ensure row.passed
    ensure declared_part("battery-modules").count == 24
    ensure declared_part("brakes").count == 4
    ensure declared_part("tires").count == 4
    ensure declared_part("reduction-gears").count == 2


test "derived pack energy, nominal voltage and mass land on the declared pack":
    pack = pack_limits()
    ensure pack.cell_count == 432 and pack.series_count == 216 and pack.parallel_count == 2
    ensure pack.energy_kwh > 91.0 and pack.energy_kwh < 93.0
    ensure pack.nominal_voltage_v > 760.0 and pack.nominal_voltage_v < 840.0
    ensure abs(pack.mass_kg - 438.0) < 0.02 * 438.0
    ensure pack.resistance_ohm > 0.0 and pack.resistance_ohm < 0.15
    ensure pack.charge_power_kw > 150.0 and pack.charge_power_kw < 220.0
    ensure pack.discharge_power_kw > pack.charge_power_kw


test "the declared reduction stages make the 9.1 final drive and a credible tyre radius":
    limits = hardware_limits()
    ensure abs(limits.final_drive_ratio - 9.1) < 0.000000001
    ensure abs(limits.tire.rolling_radius_m - 0.3695) < 0.005
    ensure limits.tire.rolling_radius_m < limits.tire.unloaded_radius_m
    ensure limits.driveline_top_speed_mps > 55.0 and limits.driveline_top_speed_mps < 90.0


test "each machine runs at the lower of its own winding limit and its inverter rating":
    limits = hardware_limits()
    ensure limits.front_motor.current_limited_by == "motor-winding"
    ensure limits.rear_motor.current_limited_by == "inverter-rating"
    ensure limits.rear_motor.effective_peak_current_a == rear_inverter_spec().peak_phase_current_a
    ensure limits.front_motor.effective_peak_current_a == front_motor_spec().peak_phase_current_a
    ensure limits.rear_motor.peak_torque_nm > limits.front_motor.peak_torque_nm
    for motor in [limits.front_motor, limits.rear_motor]:
        ensure motor.airgap_shear_stress_kpa > 30.0 and motor.airgap_shear_stress_kpa < 60.0
        ensure motor.constant_power_ratio > 2.5 and motor.constant_power_ratio < 5.0
        ensure motor.base_speed_rpm > 3000.0 and motor.base_speed_rpm < 6500.0
        ensure motor.power_factor > 0.8
        ensure motor.dc_link_peak_current_a < 520.0


test "the derived limits reproduce the literals they replace":
    limits = hardware_limits()
    ensure abs(limits.max_motor_torque_nm - 660.0) < 0.02 * 660.0
    ensure abs(limits.max_drive_power_kw - 320.0) < 0.06 * 320.0
    ensure abs(limits.pack.energy_kwh - 92.0) < 0.02 * 92.0
    ensure limits.max_regenerative_force_n > 5000.0 and limits.max_regenerative_force_n < 7000.0
    # The 180 kW regenerative literal is what the declared cells will accept, and the pack lands on
    # it. The machines cannot deliver it: two liquid-cooled stators generate 133 kW at their
    # continuous rating, so the recovery limit is the drive units, not the pack.
    ensure abs(limits.pack.charge_power_kw - 180.0) < 0.06 * 180.0
    ensure limits.max_regenerative_power_kw < limits.pack.charge_power_kw
    ensure limits.max_regenerative_power_kw > 110.0 and limits.max_regenerative_power_kw < 160.0
    # The brake force is the one limit that must not reproduce its literal: 14.5 kN was below the
    # tyre limit at kerb mass, which would make every stop brake-bound. A real system is sized above
    # the contact patch it feeds.
    ensure limits.max_brake_force_n > 25000.0


test "the provenance ledger covers every specification and carries only known source tags":
    rows = hardware_parameters()
    ensure len(rows) >= 80
    mut seen = []
    for row in rows:
        ensure row["source"] == SOURCE_SOLVED or row["source"] == SOURCE_CORRELATED or row["source"] == SOURCE_ASSUMED
        ensure len(str(row["note"])) > 0
        ensure not contains_id(seen, str(row["id"]))
        seen = seen + [str(row["id"])]
    for part_id in ["front-motor", "rear-motor", "front-inverter", "rear-inverter", "brakes", "tires", "battery-modules", "reduction-gears"]:
        ensure ledger_covers(rows, part_id)


def contains_id(seen: list[str], value: str):
    for item in seen:
        if item == value:
            return true
    return false


def ledger_covers(rows: list[dict[str, any]], part_id: str):
    for row in rows:
        if row["part_id"] == part_id:
            return true
    return false


test "the ledger reports the same numbers the specification structs carry":
    rows = hardware_parameters()
    motor = rear_motor_spec()
    pack = pack_spec()
    mut checked = 0
    for row in rows:
        if row["id"] == "rear-motor.magnet-flux-linkage":
            ensure row["value"] == motor.magnet_flux_linkage_wb
            checked = checked + 1
        if row["id"] == "rear-motor.peak-phase-current":
            ensure row["value"] == motor.peak_phase_current_a
            checked = checked + 1
        if row["id"] == "battery-modules.cell-capacity":
            ensure row["value"] == pack.cell_capacity_ah
            checked = checked + 1
        if row["id"] == "brakes.front.piston-bore":
            ensure row["value"] == front_brake_spec().piston_bore_m
            checked = checked + 1
    ensure checked == 4


test "the export carries the derived limits, the parameters and the reconciliation":
    payload = hardware_export()
    ensure payload["schema"] == "sema.circuitframe-hardware/v1"
    ensure len(payload["reconciliation"]) == 8
    ensure len(payload["parameters"]) >= 80
    ensure payload["limits"]["max_motor_torque_nm"] > 600.0
    ensure payload["front_motor"]["current_limited_by"] == "motor-winding"
    ensure payload["rear_motor"]["current_limited_by"] == "inverter-rating"
    for row in payload["reconciliation"]:
        ensure row["passed"] and row["resolved"]
    rows = hardware_equations()
    ensure len(rows) >= 12
    for row in rows:
        ensure len(row["id"]) > 0 and len(row["source"]) > 0 and len(row["expression"]) > 0 and len(row["unit"]) > 0 and len(row["owner"]) > 0


test "tyre friction falls as the corner it carries is loaded":
    tire = tire_limits()
    light = tire_friction(tire.peak_friction, tire.slide_friction, tire.load_sensitivity, tire.friction_reference_load_n, 0.5 * tire.friction_reference_load_n)
    reference = tire_friction(tire.peak_friction, tire.slide_friction, tire.load_sensitivity, tire.friction_reference_load_n, tire.friction_reference_load_n)
    heavy = tire_friction(tire.peak_friction, tire.slide_friction, tire.load_sensitivity, tire.friction_reference_load_n, 2.0 * tire.friction_reference_load_n)
    ensure abs(reference - tire.peak_friction) < 0.000000001
    ensure light == tire.peak_friction
    ensure heavy < reference
    ensure heavy >= tire.slide_friction
    ensure tire_friction(tire.peak_friction, tire.slide_friction, tire.load_sensitivity, tire.friction_reference_load_n, 20.0 * tire.friction_reference_load_n) == tire.slide_friction


test "pad friction is unaffected below the fade onset and degrades above it":
    limits = hardware_limits()
    cold = brake_fade_factor(limits.fade_onset_temp_c - 100.0, limits.fade_onset_temp_c, limits.fade_rate_per_k, limits.fade_floor_fraction)
    warm = brake_fade_factor(limits.fade_onset_temp_c + 200.0, limits.fade_onset_temp_c, limits.fade_rate_per_k, limits.fade_floor_fraction)
    cooked = brake_fade_factor(limits.fade_onset_temp_c + 2000.0, limits.fade_onset_temp_c, limits.fade_rate_per_k, limits.fade_floor_fraction)
    ensure cold == 1.0
    ensure warm < 1.0 and warm > limits.fade_floor_fraction
    ensure cooked == limits.fade_floor_fraction
```

### `src/live.sema`

```sema
"""Automotive-native Sema HTTP service for CircuitFrame Lab.

Everything expensive is solved once when the service starts: the source-panel aerodynamic basis, both
reduced-order surrogates, the mass and joint budget, and the four printed-circuit boards with their DC
nodal solutions. The 20 Hz drive loop and the on-demand wind-tunnel measurement then reuse those caches.
"""

import http
import math
from std.adaptive_dynamics import RegimeStatus, append_bounded
from std.json import decode as decode_json, encode as encode_json
from magna_ev_digital_twin.adaptation import evaluate_yaw_candidate, initial_aero_detector, regime_label, retained_adaptation, update_aero_detector
from magna_ev_digital_twin.aero import aero_basis_export, aero_equations, aero_solve
from magna_ev_digital_twin.assembly import assembly_equations, assembly_export
from magna_ev_digital_twin.hardware import hardware_equations, hardware_export
from magna_ev_digital_twin.authoring import compile_circuit_description
from magna_ev_digital_twin.candidate import optimize_panel
from magna_ev_digital_twin.pcb import pcb_equations, pcb_export
from magna_ev_digital_twin.physics import ControlInput, PhysicsContext, aero_holdout_metrics, dynamics_equations, initial_vehicle_state, physics_context, step_vehicle
from magna_ev_digital_twin.vehicle import source_gate, vehicle_manifest, vehicle_net_comparison

assure silver


MAX_WIND_MPS = 60.0
AERO_PROVENANCE = {
    "cd": "solved+correlated",
    "cl": "solved+correlated",
    "cy": "solved+correlated",
    "cmz": "solved+correlated",
    "cd_pressure": "solved topology + correlated base pressure",
    "cd_base": "solved topology + correlated base pressure",
    "cd_friction": "correlated",
    "cd_wheels": "correlated",
    "drag_n": "solved+correlated",
    "lift_n": "solved+correlated",
    "side_n": "solved+correlated",
    "yaw_moment_nm": "solved+correlated",
    "front_lift_n": "solved+correlated",
    "rear_lift_n": "solved+correlated",
    "dynamic_pressure_pa": "exact",
    "reynolds_number": "exact",
    "separation_x_m": "solved",
    "cp_min": "solved",
    "cp_max": "solved",
    "base_pressure_coefficient": "correlated",
    "wake_width_m": "solved",
    "wake_deficit_fraction": "correlated",
    "wake_shedding_hz": "correlated",
    "turbulence_intensity": "correlated",
    "wake_recirculation_length_m": "correlated",
    "dalembert_residual_cd": "numerical",
    "solver_residual": "numerical",
    "force_closure_residual": "numerical",
}
AERO_METHOD = "3D constant-strength source-panel solve with an image ground plane, closed by an empirical viscous and base-pressure model; not a governing CFD field"
RESIDUAL_HISTORY_CAPACITY = 120
SWEEP_STEPS = 200
SWEEP_RAMP_STEPS = 40
SWEEP_TARGET_SIDESLIP_DEG = 25.0
SWEEP_MIN_WIND_MPS = 6.0
SWEEP_MAX_WIND_MPS = 30.0
# The candidate's declared validity region, mirrored from `evaluate_yaw_candidate` in adaptation.sema
# so the viewer can state the limits before a demonstration runs instead of hard-coding them.
# `the declared validity region is published and enforced` pins the two together.
ENVELOPE_MAX_SPEED_MPS = 55.0
ENVELOPE_MAX_SIDESLIP_DEG = 35.0
CALM_MODEL_ID = "aero-rom-calm-v1"
SAMPLED_EQUATION_IDS = ["surrogate-fit", "mass-budget", "panel-neumann", "panel-influence", "panel-ground-image", "centre-of-mass", "parallel-axis", "axle-load-split", "four-bar-closure", "motion-ratio", "air-spring", "bump-steer", "ipm-torque", "stator-flux", "base-speed", "inverter-power", "airgap-shear", "brake-effective-radius", "brake-clamp", "brake-torque", "disc-thermal-mass", "tire-radius", "pack-topology", "pack-mass-closure", "final-drive"]
PER_STEP_EQUATION_IDS = ["thermal-network"]
THERMAL_EQUATION_IDS = ["thermal-network", "thermal-tire", "thermal-stability"]


def response(status: int, body: any) !{}:
    return {
        "status": status,
        "content_type": "application/json",
        "headers": {"Cache-Control": "no-store", "X-Content-Type-Options": "nosniff"},
        "body": encode_json(body),
    }


def error_response(status: int, code: str, detail: str) !{}:
    return response(status, {"schema": "sema.circuitframe-error/v1", "error": code, "detail": detail})


def physical_actions_allowed():
    return source_gate()["physical_completeness"]


def evidence_export_allowed():
    return source_gate()["presentation_qualified"]


def initial_session(context: PhysicsContext):
    detector = initial_aero_detector()
    return {
        "generation": 0,
        "sequence": 0,
        "state_version": 0,
        "state": initial_vehicle_state(),
        "frame": None,
        "detector": detector,
        "model_id": "aero-rom-calm-v1",
        "adaptation": retained_adaptation(detector, "aero-rom-calm-v1"),
        "candidate": None,
        "panel_width_mm": 90.0,
        "vehicle_revision": 1,
        "assembly_step": 11,
        "configuration_variant": "circuitframe",
        "context": context,
        "aero_basis_payload": None,
        "pcb_payload": None,
        "assembly_payload": None,
        "hardware_payload": None,
        "residual_history": initial_residual_history(),
        "sweep": idle_sweep(),
    }


def cached_aero_basis(session: dict[str, any]):
    if session["aero_basis_payload"] is None:
        session["aero_basis_payload"] = aero_basis_export(session["context"].basis)
    return session["aero_basis_payload"]


def cached_pcb(session: dict[str, any]):
    if session["pcb_payload"] is None:
        session["pcb_payload"] = pcb_export()
    return session["pcb_payload"]


def cached_assembly(session: dict[str, any]):
    if session["assembly_payload"] is None:
        session["assembly_payload"] = assembly_export()
    return session["assembly_payload"]


def cached_hardware(session: dict[str, any]):
    """The derived component limits and their reconciliation. Solved once with the context."""
    if session["hardware_payload"] is None:
        session["hardware_payload"] = hardware_export()
    return session["hardware_payload"]


def progress_report():
    return [
        {"id": "foundation", "label": "Sema automotive core", "percent": 100, "state": "implemented"},
        {"id": "vehicle", "label": "Evidence-aware EV reconstruction", "percent": 88, "state": "attributed exterior with separated wheel surfaces plus 737-feature FreeCAD/OCCT mechanical assembly, source-measured wheel/body clearances, wheelhouse-cut chassis, axle-bounded battery packaging, mounted controllers, wheel/occupant route collision gates, declared-part semantic ledger, and source-gated browser LODs; OEM manufacturing authority and validation remain open"},
        {"id": "circuit", "label": "Conventional / CircuitFrame E/E", "percent": 78, "state": "four reconstructed boards with pin-level nets, layer stackups, routed copper, vias and DC modified-nodal-analysis solutions, IPC-2221 conductor sizing, IEC 60664-1 creepage gates, generated netlists, and a quantified harness-versus-structural-conductor comparison over the same nine vehicle nets; packaging, EMC, shielding and physical validation remain open"},
        {"id": "motion", "label": "Coupled vehicle dynamics", "percent": 72, "state": "live longitudinal, lateral, yaw, tire, equivalent-circuit battery and eight-node coupled thermal network driven by mass, centre of gravity and yaw inertia summed from the declared part placements; production parameterisation and correlation absent"},
        {"id": "flow", "label": "Aerodynamics", "percent": 74, "state": "240-panel constant-strength source solve with an image ground plane, two exact freestream modes, Stratford separation walks and a calibrated base-pressure closure produce yaw-dependent drag, lift, side force and yaw moment; the browser advects smoke through the same solved singularity field. Governing CFD, wind-tunnel correlation and transient wake resolution remain absent"},
        {"id": "build", "label": "Manufacturing and robot sequence", "percent": 40, "state": "complete canonical assembly by default with operation scrubbing, named parametric robot/cell geometry, and declared-operation replay; reach, collision, tooling, and process feasibility remain unvalidated"},
        {"id": "experience", "label": "Premium web experience", "percent": 94, "state": "attributed metallic exterior, source-gated mechanical LODs, engineering cutaways, model-matched wheel kinematics, configuration comparison, wind-tunnel smoke and surface-pressure modes, live multiphysics, and evidence-aware inspection"},
        {"id": "handoff", "label": "Engineering export", "percent": 58, "state": "source ledger, evidence manifest, selected-configuration GLB, FCStd/STEP engineering reconstruction, generated board netlists, and semantic BOM/BOP declarations available; receiving-tool reconciliation and FMI remain unavailable"},
    ]


def contains(values: list[str], value: str):
    for item in values:
        if item == value:
            return true
    return false


def equation_phase(id: str, live: bool):
    """Where a body is evaluated: inside every 20 Hz step, once at startup, or only when asked."""
    if contains(SAMPLED_EQUATION_IDS, id):
        return "sampled"
    if contains(PER_STEP_EQUATION_IDS, id):
        return "per_step"
    return "on_demand" if not live else "per_step"


def grouped_equations(rows: list[dict[str, str]], group: str, live: bool):
    """Tag each declared equation with the subsystem that owns it and when its body runs."""
    return [{"id": row["id"], "source": row["source"], "expression": row["expression"], "unit": row["unit"], "owner": row["owner"], "group": "thermal network" if contains(THERMAL_EQUATION_IDS, row["id"]) else group, "phase": equation_phase(row["id"], live)} for row in rows]


def initial_residual_history():
    return {"residual": [], "score": [], "count": 0, "last_sequence": 0, "activated_at_sequence": 0}


def pushed_residual_history(history: dict[str, any], residual: f64, score: f64, sequence: int, activated: bool) !{}:
    """Bounded ring of the surrogate-versus-solved residual and the detector score that tracks it."""
    return {
        "residual": append_bounded(history["residual"], residual, RESIDUAL_HISTORY_CAPACITY),
        "score": append_bounded(history["score"], score, RESIDUAL_HISTORY_CAPACITY),
        "count": history["count"] + 1,
        "last_sequence": sequence,
        "activated_at_sequence": sequence if activated else history["activated_at_sequence"],
    }


def idle_sweep():
    return {"active": false, "index": 0, "commanded_wind_mps": 0.0, "commanded_yaw_deg": 0.0, "restarted_from_calm": false}


def sweep_command(index: int, speed_mps: f64):
    """Wind speed and bearing that put the apparent wind at the scripted sideslip. Returns [m/s, deg].

    The demonstration commands a sideslip, not a wind. A fixed crosswind that walks a parked car
    clear of the calm model's window barely moves the apparent angle at motorway speed, so a fixed
    wind would quietly stop demonstrating anything above about 30 m/s. Solving the wind vector for
    the target angle keeps it meaningful at any road speed, and both commanded values are published
    every step so nothing about the operating point is hidden.
    """
    target_rad = SWEEP_TARGET_SIDESLIP_DEG * min(1.0, f64(index) / f64(SWEEP_RAMP_STEPS)) * math.pi / 180.0
    lateral_demand = max(0.0, speed_mps) * math.sin(target_rad)
    wind = max(SWEEP_MIN_WIND_MPS, min(SWEEP_MAX_WIND_MPS, lateral_demand / 0.8))
    bearing = target_rad + math.asin(min(1.0, lateral_demand / wind))
    return [wind, bearing * 180.0 / math.pi]


def advanced_sweep(sweep: dict[str, any], controls: ControlInput):
    if not sweep["active"]:
        return sweep
    index = sweep["index"] + 1
    return {"active": index < SWEEP_STEPS, "index": index, "commanded_wind_mps": controls.wind_mps, "commanded_yaw_deg": controls.wind_yaw_deg, "restarted_from_calm": sweep["restarted_from_calm"]}


def detector_body(session: dict[str, any]):
    detector = session["detector"]
    return {
        "score": detector.score,
        "threshold": detector.score_threshold,
        "status": regime_label(detector),
        "consecutive_high": detector.consecutive_high,
        "required_consecutive": detector.required_consecutive,
        "window_size": detector.window_size,
        "min_samples": detector.min_samples,
        "baseline_mean": detector.baseline_mean,
        "baseline_scale": detector.baseline_scale,
    }


def residual_history_body(session: dict[str, any]):
    """The whole ring, served on status only; a step carries one new sample and the client appends."""
    history = session["residual_history"]
    return {
        "capacity": RESIDUAL_HISTORY_CAPACITY,
        "count": history["count"],
        "last_sequence": history["last_sequence"],
        "activated_at_sequence": history["activated_at_sequence"],
        "threshold": session["detector"].score_threshold,
        "residual": history["residual"],
        "score": history["score"],
    }


def sweep_body(session: dict[str, any]):
    sweep = session["sweep"]
    return {
        "kind": "scripted demonstration: a bounded ramp that walks the apparent-wind sideslip out of the calm surrogate's fitted window and holds it there. The detector, the validation gate and any activation that follow are the ordinary live lifecycle, not a replay",
        "active": sweep["active"],
        "step_index": sweep["index"],
        "total_steps": SWEEP_STEPS,
        "remaining_steps": max(0, SWEEP_STEPS - sweep["index"]),
        "ramp_steps": SWEEP_RAMP_STEPS,
        "target_sideslip_deg": SWEEP_TARGET_SIDESLIP_DEG,
        "min_wind_mps": SWEEP_MIN_WIND_MPS,
        "max_wind_mps": SWEEP_MAX_WIND_MPS,
        "envelope_max_speed_mps": ENVELOPE_MAX_SPEED_MPS,
        "envelope_max_sideslip_deg": ENVELOPE_MAX_SIDESLIP_DEG,
        "drives": ["wind_mps", "wind_yaw_deg"],
        "commanded_wind_mps": sweep["commanded_wind_mps"],
        "commanded_yaw_deg": sweep["commanded_yaw_deg"],
        "restarted_from_calm": sweep["restarted_from_calm"],
    }


def all_equations():
    return (grouped_equations(dynamics_equations(), "dynamics", true)
        + grouped_equations(aero_equations(), "aero panel solve", true)
        + grouped_equations(pcb_equations(), "board analysis", false)
        + grouped_equations(assembly_equations(), "assembly", false)
        + grouped_equations(hardware_equations(), "declared hardware", false))


def status_body(session: dict[str, any]):
    context = session["context"]
    parameters = context.parameters
    return {
        "schema": "sema.circuitframe-live-status/v1",
        "backend": "Sema",
        "configuration_id": "circuitframe_gt_01",
        "configuration_variant": session["configuration_variant"],
        "configuration_revision": session["vehicle_revision"],
        "generation": session["generation"],
        "sequence": session["sequence"],
        "state_version": session["state_version"],
        "update_hz": 20,
        "state": session["state"],
        "frame": session["frame"],
        "model_id": session["model_id"],
        "adaptation": session["adaptation"],
        "detector": detector_body(session),
        "residual_history": residual_history_body(session),
        "sweep": sweep_body(session),
        "equations": all_equations(),
        "vehicle": vehicle_manifest(session["configuration_variant"]),
        "vehicle_nets": vehicle_net_comparison(),
        "panel": {"id": "sill-panel-left", "width_mm": session["panel_width_mm"], "revision": session["vehicle_revision"], "fidelity": "derived"},
        "assembly_step": session["assembly_step"],
        "progress": progress_report(),
        "mass_properties": {
            "total_mass_kg": parameters.mass_kg,
            "cg_to_front_axle_m": parameters.cg_to_front_axle_m,
            "cg_to_rear_axle_m": parameters.cg_to_rear_axle_m,
            "cg_height_m": parameters.cg_height_m,
            "yaw_inertia_kg_m2": parameters.yaw_inertia_kg_m2,
            "wheelbase_m": parameters.wheelbase_m,
            "track_m": parameters.track_m,
            "front_mass_fraction": parameters.front_mass_fraction,
            "source": "mass, centre of gravity and yaw inertia summed from the declared part placements in assembly.sema; track read from the declared suspension hardpoints",
        },
        "hardware": cached_hardware(session),
        "surrogates": {
            "active": {"id": context.calm.model_id, "fit_max_yaw_deg": context.calm.fit_max_yaw_rad * 180.0 / 3.141592653589793, "fit_nodes": context.calm.fit_nodes, "fit_rms_cd": context.calm.fit_rms_cd},
            "candidate": {"id": context.crosswind.model_id, "fit_max_yaw_deg": context.crosswind.fit_max_yaw_rad * 180.0 / 3.141592653589793, "fit_nodes": context.crosswind.fit_nodes, "fit_rms_cd": context.crosswind.fit_rms_cd},
            "holdout": aero_holdout_metrics(context),
        },
        "capabilities": {
            "live_reduced_dynamics": true,
            "coupled_longitudinal_lateral_yaw": true,
            "equivalent_circuit_battery": true,
            "coupled_thermal_network": true,
            "derived_mass_budget": true,
            "derived_hardware_limits": true,
            "component_reconciled_performance": true,
            "source_panel_aerodynamics": true,
            "yaw_dependent_aero_coefficients": true,
            "wind_tunnel_measurement": true,
            "surface_pressure_field": true,
            "pin_level_netlist": true,
            "dc_nodal_analysis": true,
            "ipc_conductor_sizing": true,
            "kinematic_joint_model": true,
            "adaptive_aero_model": true,
            "candidate_search_algorithm": true,
            "physical_candidate_evaluation": false,
            "robot_kinematic_replay": true,
            "parametric_mechanical_assembly": true,
            "step_engineering_reconstruction": true,
            "evidence_export": true,
            "qualification_matrix": true,
            "governing_cfd": false,
            "governing_structural_dynamics": false,
            "manufacturing_cad_authority": false,
            "decision_authority": false,
        },
        "fidelity": {
            "vehicle_geometry": "CC BY community exterior + source-gated FreeCAD/OCCT engineering reconstruction; not OEM manufacturing CAD",
            "dynamics": "live coupled reduced longitudinal/lateral/yaw/tire model on assembly-derived inertia, with every performance limit derived from the declared motors, inverters, brakes, tyres, pack and reduction gears and reconciled against those parts' declared counts and masses; uncorrelated parameters and no dynamometer, pressure-rig, tyre-rig or cell-cycling evidence",
            "aerodynamics": AERO_METHOD,
            "electrical": "live equivalent-circuit battery plus DC modified-nodal-analysis over reconstructed boards; EMC, transient and physical routing validation absent",
            "thermal": "live eight-node coupled coolant network, a lumped tire node and a per-axle brake-disc node integrating the friction share of each stop against the derived disc thermal mass; no 3D thermal solver or correlation",
            "structure": "live axle reactions, joint mobility and static load-path equilibrium only; no stress, deformation, modal, fatigue, or crash solve",
            "robotics": "deterministic kinematic demonstrator",
        },
        "scientific_validated": false,
        "production_complete": false,
        "major_assembly_complete": false,
    }


def number_field_valid(payload: dict[str, any], field: str, minimum: f64, maximum: f64):
    if not payload.has(field) or not (payload[field] is int or payload[field] is float):
        return false
    value = f64(payload[field])
    return value == value and value >= minimum and value <= maximum


def measurement_body(forces: any):
    return {
        "cd": forces.cd,
        "cl": forces.cl,
        "cy": forces.cy,
        "cmz": forces.cmz,
        "cd_pressure": forces.cd_pressure,
        "cd_base": forces.cd_base,
        "cd_friction": forces.cd_friction,
        "cd_wheels": forces.cd_wheels,
        "drag_n": forces.drag_n,
        "lift_n": forces.lift_n,
        "side_n": forces.side_n,
        "yaw_moment_nm": forces.yaw_moment_nm,
        "front_lift_n": forces.front_lift_n,
        "rear_lift_n": forces.rear_lift_n,
        "dynamic_pressure_pa": forces.dynamic_pressure_pa,
        "reynolds_number": forces.reynolds_number,
        "separation_x_m": forces.separation_x_m,
        "cp_min": forces.cp_min,
        "cp_max": forces.cp_max,
        "base_pressure_coefficient": forces.base_pressure_coefficient,
        "wake_width_m": forces.wake_width_m,
        "wake_deficit_fraction": forces.wake_deficit_fraction,
        "wake_shedding_hz": forces.wake_shedding_hz,
        "turbulence_intensity": forces.turbulence_intensity,
        "wake_recirculation_length_m": forces.wake_recirculation_length_m,
        "dalembert_residual_cd": forces.dalembert_residual_cd,
        "solver_residual": forces.solver_residual,
        "force_closure_residual": forces.force_closure_residual,
    }


def tunnel_response(payload: any, session: dict[str, any]) !{}:
    """On-demand wind-tunnel measurement: the car is held, the stream runs, the loads are integrated."""
    if not (payload is dict):
        return error_response(422, "TunnelInvalid", "wind tunnel request must be an object")
    if not number_field_valid(payload, "wind_mps", 0.0, MAX_WIND_MPS) or not number_field_valid(payload, "yaw_deg", -180.0, 180.0):
        return error_response(422, "TunnelInvalid", "wind_mps must be 0-60 m/s and yaw_deg must be -180..180")
    wind_mps = f64(payload["wind_mps"])
    yaw_deg = f64(payload["yaw_deg"])
    yaw_rad = yaw_deg * 3.141592653589793 / 180.0
    forces = aero_solve(session["context"].basis, wind_mps, yaw_rad)
    return response(200, {
        "schema": "sema.circuitframe-wind-tunnel/v1",
        "method": AERO_METHOD,
        "wind_mps": wind_mps,
        "yaw_deg": yaw_deg,
        "yaw_rad": yaw_rad,
        "measurement": measurement_body(forces),
        "provenance": AERO_PROVENANCE,
        "surface_cp": forces.surface_cp,
        "scientific_validated": false,
    })


def step_response(payload: any, session: dict[str, any]) !{}:
    if not (payload is dict):
        return error_response(422, "StepInvalid", "step request must be an object")
    required = ["generation", "sequence", "state_version", "dt_s", "throttle", "brake", "steering", "wind_mps", "wind_yaw_deg"]
    if not all(payload.has(field) for field in required):
        return error_response(422, "StepInvalid", "step request is missing a required field")
    if not (payload["generation"] is int) or not (payload["sequence"] is int) or not (payload["state_version"] is int):
        return error_response(422, "StepInvalid", "generation, sequence and state_version must be integers")
    expected_generation = session["generation"]
    expected_sequence = session["sequence"]
    expected_state_version = session["state_version"]
    expected_state = session["state"]
    expected_detector = session["detector"]
    expected_model_id = session["model_id"]
    if payload["generation"] != expected_generation:
        return error_response(409, "GenerationConflict", "generation does not match Sema-owned state")
    if payload["sequence"] != expected_sequence + 1 or payload["state_version"] != expected_state_version:
        return error_response(409, "StateConflict", "sequence or state version does not match Sema-owned state")
    if not number_field_valid(payload, "dt_s", 0.05, 0.05) or not number_field_valid(payload, "throttle", 0.0, 1.0) or not number_field_valid(payload, "brake", 0.0, 1.0) or not number_field_valid(payload, "steering", -1.0, 1.0) or not number_field_valid(payload, "wind_mps", 0.0, MAX_WIND_MPS) or not number_field_valid(payload, "wind_yaw_deg", -180.0, 180.0):
        return error_response(422, "StepInvalid", "numeric controls must be finite, in range, and use the fixed 0.05 s integration interval")
    dt_s = f64(payload["dt_s"])
    mut controls = ControlInput(
        throttle=f64(payload["throttle"]),
        brake=f64(payload["brake"]),
        steering=f64(payload["steering"]),
        wind_mps=f64(payload["wind_mps"]),
        wind_yaw_deg=f64(payload["wind_yaw_deg"]),
    )
    sweep = session["sweep"]
    if sweep["active"]:
        command = sweep_command(sweep["index"], expected_state.speed_mps)
        controls = ControlInput(throttle=controls.throttle, brake=controls.brake, steering=controls.steering, wind_mps=command[0], wind_yaw_deg=command[1])
    sequence = payload["sequence"]
    context = session["context"]
    frame = step_vehicle(expected_state, controls, dt_s, sequence, expected_model_id, context)
    detector = update_aero_detector(expected_detector, frame.aero_residual_fraction, frame.state.time_s)
    adaptation = retained_adaptation(detector, expected_model_id)
    next_model_id = expected_model_id
    if detector.status == RegimeStatus.shifted and expected_model_id == "aero-rom-calm-v1":
        adaptation = evaluate_yaw_candidate(detector, sequence, frame.state.time_s, frame.state.speed_mps, frame.sideslip_rad * 180.0 / 3.141592653589793, aero_holdout_metrics(context))
        if adaptation.activated:
            next_model_id = adaptation.selected_model_id
    if session["generation"] != expected_generation or session["sequence"] != expected_sequence or session["state_version"] != expected_state_version:
        return error_response(409, "LifecycleConflict", "Sema-owned state changed before the step could commit")
    session["model_id"] = next_model_id
    session["state"] = frame.state
    session["frame"] = frame
    session["detector"] = detector
    session["adaptation"] = adaptation
    session["sequence"] = sequence
    session["state_version"] = expected_state_version + 1
    session["residual_history"] = pushed_residual_history(session["residual_history"], frame.aero_residual_fraction, detector.score, sequence, adaptation.activated)
    session["sweep"] = advanced_sweep(sweep, controls)
    return response(200, {
        "schema": "sema.circuitframe-dynamics-frame/v1",
        "generation": session["generation"],
        "sequence": session["sequence"],
        "state_version": session["state_version"],
        "frame": frame,
        "adaptation": adaptation,
        "model_id_next": session["model_id"],
        "residual_sample": {"sequence": sequence, "residual": frame.aero_residual_fraction, "score": detector.score, "threshold": detector.score_threshold, "status": regime_label(detector), "consecutive_high": detector.consecutive_high, "samples_seen": session["residual_history"]["count"], "activated_at_sequence": session["residual_history"]["activated_at_sequence"]},
        "sweep": {"active": session["sweep"]["active"], "step_index": session["sweep"]["index"], "remaining_steps": max(0, SWEEP_STEPS - session["sweep"]["index"]), "commanded_wind_mps": session["sweep"]["commanded_wind_mps"], "commanded_yaw_deg": session["sweep"]["commanded_yaw_deg"]},
        "provenance": AERO_PROVENANCE,
        "fidelity": AERO_METHOD,
        "scientific_validated": false,
    })


def reset_session(session: dict[str, any]):
    reset = initial_session(session["context"])
    session["generation"] = session["generation"] + 1
    session["sequence"] = reset["sequence"]
    session["state_version"] = reset["state_version"]
    session["state"] = reset["state"]
    session["frame"] = reset["frame"]
    session["detector"] = reset["detector"]
    session["model_id"] = reset["model_id"]
    session["adaptation"] = reset["adaptation"]
    session["candidate"] = None
    session["assembly_step"] = reset["assembly_step"]
    session["residual_history"] = reset["residual_history"]
    session["sweep"] = reset["sweep"]


def reset_response(session: dict[str, any]) !{}:
    reset_session(session)
    session["panel_width_mm"] = 90.0
    session["vehicle_revision"] = 1
    session["configuration_variant"] = "circuitframe"
    return response(200, status_body(session))


def candidate_response(session: dict[str, any]) !{}:
    if not physical_actions_allowed():
        return error_response(409, "SourceVehicleRequired", "physical conductor candidate evaluation requires the reconstructed geometry, loads, routing, materials, and process data")
    preview = optimize_panel(session["vehicle_revision"])
    session["candidate"] = preview["selected"]
    return response(200, preview)


def commit_response(payload: any, session: dict[str, any]) !{}:
    if not physical_actions_allowed():
        return error_response(409, "SourceVehicleRequired", "candidate commit is blocked until the approved reconstruction source stack and physical routing are loaded")
    if not (payload is dict) or not payload.has("candidate_id") or not payload.has("base_revision"):
        return error_response(422, "CandidateCommitInvalid", "candidate_id and base_revision are required")
    if session["candidate"] is None:
        return error_response(409, "CandidateMissing", "create a candidate preview before commit")
    candidate = session["candidate"]
    if payload["candidate_id"] != candidate.id or payload["base_revision"] != session["vehicle_revision"] or candidate.base_revision != session["vehicle_revision"]:
        return error_response(409, "CandidateConflict", "candidate identity or base revision is stale")
    if not candidate.passed:
        return error_response(422, "CandidateRejected", candidate.reason)
    session["panel_width_mm"] = candidate.width_mm
    session["vehicle_revision"] = session["vehicle_revision"] + 1
    session["candidate"] = None
    return response(200, {
        "schema": "sema.circuitframe-candidate-commit/v1",
        "candidate_id": candidate.id,
        "configuration_revision": session["vehicle_revision"],
        "panel_width_mm": session["panel_width_mm"],
        "committed": true,
        "reason": candidate.reason,
    })


def assembly_step_response(payload: any, session: dict[str, any]) !{}:
    if not (payload is dict) or not payload.has("step") or not payload.has("generation") or not (payload["step"] is int) or not (payload["generation"] is int):
        return error_response(422, "AssemblyStepInvalid", "step and generation must be integers")
    expected_generation = session["generation"]
    if payload["generation"] != expected_generation:
        return error_response(409, "GenerationConflict", "assembly generation does not match Sema-owned state")
    manifest = vehicle_manifest(session["configuration_variant"])
    if payload["step"] < 0 or payload["step"] >= len(manifest["operations"]):
        return error_response(422, "AssemblyStepInvalid", "step is outside the operation graph")
    if session["generation"] != expected_generation:
        return error_response(409, "LifecycleConflict", "Sema-owned state changed before the assembly step could commit")
    session["assembly_step"] = payload["step"]
    return response(200, {"schema": "sema.circuitframe-assembly-state/v1", "generation": session["generation"], "step": session["assembly_step"], "operation": manifest["operations"][session["assembly_step"]]})


def sweep_state_body(session: dict[str, any]):
    return {
        "schema": "sema.circuitframe-regime-sweep/v1",
        "sweep": sweep_body(session),
        "detector": detector_body(session),
        "residual_history": residual_history_body(session),
        "adaptation": session["adaptation"],
        "model_id": session["model_id"],
    }


def regime_sweep_response(payload: any, session: dict[str, any]) !{}:
    """Arm or disarm the bounded wind-yaw demonstration; it never advances the causal sequence itself.

    Anything short of an explicit `{"action": "start"}` disarms, so an empty body, `{}`, an explicit
    stop or a cancel flag all reliably stop the demonstration.
    """
    if not (payload is dict):
        return error_response(422, "RegimeSweepInvalid", "regime sweep request must be an object")
    if payload.has("action") and not (payload["action"] is str):
        return error_response(422, "RegimeSweepInvalid", "action must be start or stop")
    action = str(payload["action"]) if payload.has("action") else "stop"
    if action != "start" and action != "stop":
        return error_response(422, "RegimeSweepInvalid", "action must be start or stop")
    cancel = payload.has("cancel") and payload["cancel"] is bool and payload["cancel"]
    if action == "stop" or cancel:
        session["sweep"] = idle_sweep()
        return response(200, sweep_state_body(session))
    restart = payload.has("restart_from_calm") and payload["restart_from_calm"] is bool and payload["restart_from_calm"]
    if restart:
        detector = initial_aero_detector()
        session["detector"] = detector
        session["model_id"] = CALM_MODEL_ID
        session["adaptation"] = retained_adaptation(detector, CALM_MODEL_ID)
        session["residual_history"] = initial_residual_history()
    session["sweep"] = {"active": true, "index": 0, "commanded_wind_mps": SWEEP_MIN_WIND_MPS, "commanded_yaw_deg": 0.0, "restarted_from_calm": restart}
    return response(200, sweep_state_body(session))


def configuration_response(payload: any, session: dict[str, any]) !{}:
    if not (payload is dict) or not payload.has("configuration") or not (payload["configuration"] is str):
        return error_response(422, "ConfigurationInvalid", "configuration must be conventional or circuitframe")
    configuration = str(payload["configuration"])
    if configuration != "conventional" and configuration != "circuitframe":
        return error_response(422, "ConfigurationInvalid", "configuration must be conventional or circuitframe")
    reset_session(session)
    session["configuration_variant"] = configuration
    session["panel_width_mm"] = 90.0
    return response(200, {
        "schema": "sema.circuitframe-configuration-view/v1",
        "configuration": configuration,
        "shared_identity": "circuitframe_gt_01",
        "authority": "configuration-specific semantic routes and process delta over shared reconstruction identities; physical deltas remain engineered assumptions",
        "state_reset": true,
        "generation": session["generation"],
    })


def export_response(session: dict[str, any]) !{}:
    if not evidence_export_allowed():
        return error_response(409, "SourceVehicleRequired", "evidence export is blocked until attributed presentation geometry is loaded")
    return response(200, {
        "schema": "sema.circuitframe-engineering-export/v1",
        "configuration_id": "circuitframe_gt_01",
        "configuration_revision": session["vehicle_revision"],
        "authority": "Sema semantic/evidence manifest with attributed exterior and assumption-derived parametric assembly; no OEM manufacturing authority",
        "vehicle": vehicle_manifest(session["configuration_variant"]),
        "vehicle_nets": vehicle_net_comparison(),
        "assembly": cached_assembly(session),
        "pcb": cached_pcb(session),
        "aerodynamics": cached_aero_basis(session),
        "equations": all_equations(),
        "runtime": {"model_id": session["model_id"], "state": session["state"], "frame": session["frame"], "panel_width_mm": session["panel_width_mm"]},
        "configuration_variant": session["configuration_variant"],
        "representations": [
            {"format": "glTF 2.0", "status": "generated web scene", "authority": "attributed exterior plus byte-verified engineering reconstruction"},
            {"format": "STEP AP242", "status": "generated parametric reconstruction", "authority": "FreeCAD/OCCT engineering assumptions; not OEM manufacturing CAD or certified PMI"},
            {"format": "FreeCAD FCStd", "status": "generated parametric master", "authority": "open-profile engineering reconstruction"},
            {"format": "KiCad flat netlist", "status": "generated per board", "authority": "Sema reconstructed boards; not a manufacturable fabrication package"},
            {"format": "FMI/SSP", "status": "contract only", "authority": "Sema reduced-model implementation"},
            {"format": "BOM/MBOM/BOP", "status": "major-assembly manifest", "authority": "Sema research profile"},
        ],
        "losses": ["no OEM revision-certified manufacturing B-rep or JT", "no homologation evidence", "no governing CFD/FEA/crash", "no wind-tunnel or dynamometer correlation", "no EMC or transient circuit analysis", "supplier internals opaque"],
    })


def authoring_response(payload: any) !{}:
    if not (payload is dict) or not payload.has("description") or not (payload["description"] is str):
        return error_response(422, "AuthoringInvalid", "authoring request requires a description string")
    description = str(payload["description"])
    if len(description) == 0 or len(description) > 2048:
        return error_response(413, "AuthoringInvalid", "description must contain between 1 and 2048 characters")
    result = compile_circuit_description(description)
    return response(200, {"schema": "sema.circuitframe-authoring/v1", "result": result})


def live_response(request: dict[str, any], session: dict[str, any]) !{}:
    if request["method"] == "GET" and request["path"] == "/api/sema/health":
        return response(200, {"status": "ok", "backend": "Sema", "schema": "sema.circuitframe-health/v1"})
    if request["method"] == "GET" and request["path"] == "/api/sema/status":
        return response(200, status_body(session))
    if request["method"] == "GET" and request["path"] == "/api/sema/aero/basis":
        return response(200, cached_aero_basis(session))
    if request["method"] == "GET" and request["path"] == "/api/sema/pcb":
        return response(200, cached_pcb(session))
    if request["method"] == "GET" and request["path"] == "/api/sema/assembly":
        return response(200, cached_assembly(session))
    if request["method"] == "GET" and request["path"] == "/api/sema/export":
        return export_response(session)
    if request["method"] == "POST" and request["path"] == "/api/sema/reset":
        return reset_response(session)
    if request["method"] == "POST" and request["path"] == "/api/sema/tunnel":
        if len(request["body"]) == 0 or len(request["body"]) > 1024:
            return error_response(413, "TunnelInvalid", "wind tunnel request must contain at most 1024 bytes")
        expect payload = decode_json(request["body"]):
            return tunnel_response(payload, session)
        except JsonError as error:
            return error_response(422, "TunnelInvalid", "wind tunnel request must contain valid JSON")
    if request["method"] == "POST" and request["path"] == "/api/sema/configuration":
        if len(request["body"]) == 0 or len(request["body"]) > 1024:
            return error_response(413, "ConfigurationInvalid", "configuration request must contain at most 1024 bytes")
        expect payload = decode_json(request["body"]):
            return configuration_response(payload, session)
        except JsonError as error:
            return error_response(422, "ConfigurationInvalid", "configuration request must contain valid JSON")
    if request["method"] == "POST" and request["path"] == "/api/sema/authoring":
        if len(request["body"]) == 0 or len(request["body"]) > 4096:
            return error_response(413, "AuthoringInvalid", "authoring request must contain at most 4096 bytes")
        expect payload = decode_json(request["body"]):
            return authoring_response(payload)
        except JsonError as error:
            return error_response(422, "AuthoringInvalid", "authoring request must contain valid JSON")
    if request["method"] == "POST" and request["path"] == "/api/sema/candidate":
        return candidate_response(session)
    if request["method"] == "POST" and request["path"] == "/api/sema/candidate/commit":
        if len(request["body"]) == 0 or len(request["body"]) > 2048:
            return error_response(413, "CandidateCommitInvalid", "candidate commit must contain at most 2048 bytes")
        expect payload = decode_json(request["body"]):
            return commit_response(payload, session)
        except JsonError as error:
            return error_response(422, "CandidateCommitInvalid", "candidate commit must contain valid JSON")
    if request["method"] == "POST" and request["path"] == "/api/sema/assembly":
        if len(request["body"]) == 0 or len(request["body"]) > 1024:
            return error_response(413, "AssemblyStepInvalid", "assembly request must contain at most 1024 bytes")
        expect payload = decode_json(request["body"]):
            return assembly_step_response(payload, session)
        except JsonError as error:
            return error_response(422, "AssemblyStepInvalid", "assembly request must contain valid JSON")
    if request["method"] == "POST" and request["path"] == "/api/sema/regime-sweep":
        if len(request["body"]) > 1024:
            return error_response(413, "RegimeSweepInvalid", "regime sweep request must contain at most 1024 bytes")
        if len(request["body"]) == 0:
            return regime_sweep_response({}, session)
        expect payload = decode_json(request["body"]):
            return regime_sweep_response(payload, session)
        except JsonError as error:
            return error_response(422, "RegimeSweepInvalid", "regime sweep request must contain valid JSON")
    if request["method"] == "POST" and request["path"] == "/api/sema/step":
        if len(request["body"]) == 0 or len(request["body"]) > 4096:
            return error_response(413, "StepInvalid", "step request must contain at most 4096 bytes")
        expect payload = decode_json(request["body"]):
            return step_response(payload, session)
        except JsonError as error:
            return error_response(422, "StepInvalid", "step request must contain valid finite JSON")
    return error_response(404, "NotFound", "unknown CircuitFrame Sema endpoint: " + str(request["method"]) + " " + str(request["path"]))


pub def serve_live(port: int) -> None !{net.listen}:
    sem "Serve an automotive-native CircuitFrame vehicle, aerodynamic, circuit, assembly, physics, adaptive-model and evidence API"
    require port >= 1024 and port <= 65535
    context = physics_context()
    session = initial_session(context)
    cached_aero_basis(session)
    cached_pcb(session)
    cached_assembly(session)
    print("circuitframe_sema=http://127.0.0.1:" + str(port)
        + " configuration=circuitframe_gt_01 model=" + session["model_id"]
        + " panels=" + str(context.basis.panel_count)
        + " mass_kg=" + str(context.parameters.mass_kg))
    http.serve(port, request => live_response(request, session))


test "a parked car in the tunnel measures yaw-dependent loads":
    session = initial_session(physics_context())
    head_on = tunnel_response({"wind_mps": 27.8, "yaw_deg": 0.0}, session)
    yawed = tunnel_response({"wind_mps": 27.8, "yaw_deg": 25.0}, session)
    still = tunnel_response({"wind_mps": 0.0, "yaw_deg": 0.0}, session)
    rejected = tunnel_response({"wind_mps": 90.0, "yaw_deg": 0.0}, session)
    head_on_body = decode_json(head_on["body"])
    yawed_body = decode_json(yawed["body"])
    still_body = decode_json(still["body"])
    ensure head_on["status"] == 200 and yawed["status"] == 200 and still["status"] == 200
    ensure rejected["status"] == 422
    ensure head_on_body["measurement"]["drag_n"] > 200.0
    ensure yawed_body["measurement"]["cd"] > head_on_body["measurement"]["cd"]
    ensure yawed_body["measurement"]["side_n"] > 0.0
    ensure still_body["measurement"]["drag_n"] == 0.0
    ensure len(head_on_body["surface_cp"]) == session["context"].basis.panel_count
    ensure head_on_body["provenance"]["cd_friction"] == "correlated"


test "the aerodynamic basis, boards and assembly are served from one solve":
    session = initial_session(physics_context())
    basis = cached_aero_basis(session)
    boards = cached_pcb(session)
    assembly = cached_assembly(session)
    ensure basis["schema"] == "sema.circuitframe-aero-basis/v1"
    ensure boards["schema"] == "sema.circuitframe-pcb/v1"
    ensure assembly["schema"] == "sema.circuitframe-assembly/v1"
    ensure len(basis["panels"]) == session["context"].basis.panel_count
    ensure session["aero_basis_payload"] is not None
    ensure session["pcb_payload"] is not None
    ensure session["assembly_payload"] is not None


test "physical commit fails closed while evidence export stays truthful":
    session = initial_session(physics_context())
    step = step_response({"generation": session["generation"], "sequence": 1, "state_version": session["state_version"], "dt_s": 0.05, "throttle": 0.4, "brake": 0.0, "steering": 0.0, "wind_mps": 4.0, "wind_yaw_deg": 30.0}, session)
    candidate = candidate_response(session)
    commit = commit_response({"candidate_id": "none", "base_revision": 1}, session)
    export = export_response(session)
    payload = decode_json(export["body"])
    ensure candidate["status"] == 409
    ensure commit["status"] == 409
    ensure step["status"] == 200
    ensure export["status"] == 200
    ensure payload["representations"][1]["status"] == "generated parametric reconstruction"
    ensure payload["runtime"]["frame"]["sequence"] == 1


test "the harness comparison quantifies the CircuitFrame conductor delta":
    rows = vehicle_net_comparison()
    mut lighter = 0
    mut fewer_connectors = 0
    for row in rows:
        if row["delta"]["mass_kg"] < 0.0:
            lighter = lighter + 1
        if row["delta"]["connectors"] < 0:
            fewer_connectors = fewer_connectors + 1
        ensure row["variants"]["conventional"]["resistance_mohm"] > 0.0
        ensure row["variants"]["circuitframe"]["length_m"] < row["variants"]["conventional"]["length_m"]
    ensure len(rows) == 9
    ensure lighter == 9
    ensure fewer_connectors == 9


test "assembly selection rejects a stale lifecycle generation":
    session = initial_session(physics_context())
    stale_generation = session["generation"]
    selected = configuration_response({"configuration": "circuitframe"}, session)
    rejected = assembly_step_response({"generation": stale_generation, "step": 3}, session)
    ensure selected["status"] == 200
    ensure rejected["status"] == 409
    ensure session["assembly_step"] == 11


test "configuration comparison accepts only declared variants":
    session = initial_session(physics_context())
    selected = configuration_response({"configuration": "circuitframe"}, session)
    rejected = configuration_response({"configuration": "prototype"}, session)
    ensure selected["status"] == 200
    ensure session["configuration_variant"] == "circuitframe"
    ensure rejected["status"] == 422
    conventional = vehicle_manifest("conventional")
    circuitframe = vehicle_manifest("circuitframe")
    ensure conventional["configuration_delta"]["changed_net_ids"] == circuitframe["configuration_delta"]["changed_net_ids"]
    ensure conventional["nets"][0].route != circuitframe["nets"][0].route
    ensure conventional["operations"][1].name != circuitframe["operations"][1].name
    ensure session["generation"] == 1


test "invalid numeric controls fail as typed client errors":
    session = initial_session(physics_context())
    invalid_range = step_response({"generation": 0, "sequence": 1, "state_version": 0, "dt_s": 0.05, "throttle": 2.0, "brake": 0.0, "steering": 0.0, "wind_mps": 0.0, "wind_yaw_deg": 0.0}, session)
    invalid_type = step_response({"generation": 0, "sequence": 1, "state_version": 0, "dt_s": "0.05", "throttle": 0.4, "brake": 0.0, "steering": 0.0, "wind_mps": 0.0, "wind_yaw_deg": 0.0}, session)
    invalid_rate = step_response({"generation": 0, "sequence": 1, "state_version": 0, "dt_s": 0.1, "throttle": 0.4, "brake": 0.0, "steering": 0.0, "wind_mps": 0.0, "wind_yaw_deg": 0.0}, session)
    invalid_wind = step_response({"generation": 0, "sequence": 1, "state_version": 0, "dt_s": 0.05, "throttle": 0.4, "brake": 0.0, "steering": 0.0, "wind_mps": 75.0, "wind_yaw_deg": 0.0}, session)
    ensure invalid_range["status"] == 422
    ensure invalid_type["status"] == 422
    ensure invalid_rate["status"] == 422
    ensure invalid_wind["status"] == 422


test "generation transition rejects stale step without mutation":
    session = initial_session(physics_context())
    stale_step = {"generation": 0, "sequence": 1, "state_version": 0, "dt_s": 0.05, "throttle": 0.4, "brake": 0.0, "steering": 0.0, "wind_mps": 0.0, "wind_yaw_deg": 0.0}
    selected = configuration_response({"configuration": "circuitframe"}, session)
    rejected = step_response(stale_step, session)
    ensure selected["status"] == 200
    ensure rejected["status"] == 409
    ensure session["generation"] == 1
    ensure session["sequence"] == 0


test "the residual ring is bounded and the newest sample rides on every step":
    session = initial_session(physics_context())
    mut sequence = 0
    mut last = {}
    while sequence < RESIDUAL_HISTORY_CAPACITY + 10:
        sequence = sequence + 1
        last = decode_json(step_response({"generation": 0, "sequence": sequence, "state_version": sequence - 1, "dt_s": 0.05, "throttle": 0.3, "brake": 0.0, "steering": 0.0, "wind_mps": 6.0, "wind_yaw_deg": 12.0}, session)["body"])
    status = status_body(session)
    history = status["residual_history"]
    ensure len(history["residual"]) == RESIDUAL_HISTORY_CAPACITY
    ensure len(history["score"]) == RESIDUAL_HISTORY_CAPACITY
    ensure history["count"] == sequence
    ensure history["last_sequence"] == sequence
    ensure history["threshold"] == status["detector"]["threshold"]
    ensure last["residual_sample"]["residual"] == history["residual"][RESIDUAL_HISTORY_CAPACITY - 1]
    ensure last["residual_sample"]["score"] == history["score"][RESIDUAL_HISTORY_CAPACITY - 1]
    ensure last["residual_sample"]["samples_seen"] == sequence
    ensure not last.has("residual_history")


test "the scripted sweep drives the declared yaw ramp, passes the gate and always disarms":
    session = initial_session(physics_context())
    rejected = regime_sweep_response({"action": "rewind"}, session)
    armed = regime_sweep_response({"action": "start"}, session)
    ensure rejected["status"] == 422
    ensure armed["status"] == 200
    ensure session["sweep"]["active"] and not session["sweep"]["restarted_from_calm"]
    mut sequence = 0
    while sequence < 60 and session["model_id"] == CALM_MODEL_ID:
        sequence = sequence + 1
        step_response({"generation": 0, "sequence": sequence, "state_version": sequence - 1, "dt_s": 0.05, "throttle": 0.35, "brake": 0.0, "steering": 0.0, "wind_mps": 0.0, "wind_yaw_deg": 0.0}, session)
    frame = session["frame"]
    adaptation = session["adaptation"]
    ensure frame.controls.wind_mps >= SWEEP_MIN_WIND_MPS and frame.controls.wind_mps <= SWEEP_MAX_WIND_MPS
    ensure frame.controls.wind_yaw_deg > 0.0
    ensure abs(frame.sideslip_rad * 180.0 / math.pi) <= 35.0
    ensure session["model_id"] == "aero-rom-crosswind-v2"
    ensure adaptation.activated and adaptation.validation_error < 0.04 and adaptation.invariant_violations == 0
    ensure session["residual_history"]["activated_at_sequence"] == sequence
    ensure session["sweep"]["index"] == sequence and session["sweep"]["active"]
    disarmed = regime_sweep_response({}, session)
    ensure disarmed["status"] == 200
    ensure not session["sweep"]["active"] and session["sweep"]["index"] == 0


test "resetting the causal state clears the residual ring and disarms the sweep":
    session = initial_session(physics_context())
    regime_sweep_response({"action": "start"}, session)
    step_response({"generation": 0, "sequence": 1, "state_version": 0, "dt_s": 0.05, "throttle": 0.3, "brake": 0.0, "steering": 0.0, "wind_mps": 0.0, "wind_yaw_deg": 0.0}, session)
    ensure session["residual_history"]["count"] == 1 and session["sweep"]["active"]
    reset_session(session)
    ensure session["residual_history"]["count"] == 0
    ensure len(session["residual_history"]["residual"]) == 0
    ensure not session["sweep"]["active"]


test "every declared equation carries a subsystem group and an evaluation phase":
    rows = all_equations()
    mut per_step = 0
    mut sampled = 0
    mut on_demand = 0
    for row in rows:
        ensure len(row["group"]) > 0 and len(row["owner"]) > 0 and len(row["expression"]) > 0
        if row["phase"] == "per_step":
            per_step = per_step + 1
        elif row["phase"] == "sampled":
            sampled = sampled + 1
        else:
            on_demand = on_demand + 1
    ensure per_step + sampled + on_demand == len(rows)
    ensure sampled == len(SAMPLED_EQUATION_IDS)
    ensure per_step > sampled and on_demand > 0
    ensure contains([row["group"] for row in rows], "thermal network")
    ensure contains([row["group"] for row in rows], "aero panel solve")


test "the declared validity region is published and enforced":
    session = initial_session(physics_context())
    published = sweep_body(session)
    ensure published["envelope_max_speed_mps"] == 55.0
    ensure published["envelope_max_sideslip_deg"] == 35.0
    detector = initial_aero_detector()
    mut shifted = detector
    for index in range(0, 8):
        shifted = update_aero_detector(shifted, 0.4, f64(index) * 0.05)
    inside = evaluate_yaw_candidate(shifted, 24, 1.2, published["envelope_max_speed_mps"] - 1.0, 20.0, aero_holdout_metrics(session["context"]))
    too_fast = evaluate_yaw_candidate(shifted, 24, 1.2, published["envelope_max_speed_mps"] + 1.0, 20.0, aero_holdout_metrics(session["context"]))
    too_yawed = evaluate_yaw_candidate(shifted, 24, 1.2, 20.0, published["envelope_max_sideslip_deg"] + 1.0, aero_holdout_metrics(session["context"]))
    ensure inside.activated
    ensure not too_fast.activated and too_fast.validation_status == "blocked_out_of_scope"
    ensure not too_yawed.activated and too_yawed.validation_status == "blocked_out_of_scope"
```

### `src/pcb.sema`

```sema
"""Printed-circuit design and DC electrical analysis for the CircuitFrame boards.

Four boards are declared at component, pin, net, trace and via level and then analysed:

  * DC modified nodal analysis. Copper branch conductances come from geometry
    (R = rho * L / (w * t), rho corrected by the copper temperature coefficient),
    every net's feed pin is held at its declared operating potential, declared loads
    inject current at their sink pin and return it at their return pin, and the
    reference net pin is the 0 V node. `G v = i` is solved with `solve(matrix, rhs)`.
  * IPC-2221 external/internal conductor sizing plus the inverted temperature-rise
    relation, evaluated per trace against the layer's copper weight.
  * IEC 60664-1 creepage screening on the 800 V board.
  * A geometric design-rule check (track width, annular ring, pads inside outline).

Every board is an *engineered reconstruction* published for the CircuitFrame research
demo. None of it is an OEM design, an OEM netlist, or a released manufacturing data set.
Part numbers name the device *class* the reconstruction is dimensioned against.
"""

from std.json import encode as encode_json
import math

assure silver


enum PcbLayerFunction:
    signal | plane | core | prepreg | soldermask | coverlay


enum PcbNetClass:
    hv | power | ground | signal | can | ethernet | analog


enum PcbPinKind:
    power | ground | signal | analog | thermal | nc


struct PcbLayer:
    id: str
    name: str
    function: PcbLayerFunction
    copper_um: f64
    dielectric_mm: f64
    dielectric_er: f64
    material: str
    invariant len(id) > 0
    invariant len(name) > 0
    invariant copper_um >= 0.0 and copper_um <= 5000.0
    invariant dielectric_mm >= 0.0 and dielectric_mm <= 20.0
    invariant dielectric_er >= 1.0 and dielectric_er <= 20.0
    invariant len(material) > 0


struct PcbStackup:
    id: str
    name: str
    layers: list[PcbLayer]
    total_thickness_mm: f64
    ipc_class: int
    impedance_target_ohm: f64
    invariant len(id) > 0
    invariant len(name) > 0
    invariant len(layers) >= 2 and len(layers) <= 64
    invariant total_thickness_mm > 0.0
    invariant ipc_class >= 1 and ipc_class <= 3
    invariant impedance_target_ohm > 0.0


struct PcbComponent:
    refdes: str
    part_number: str
    description: str
    footprint: str
    pin_count: int
    x_mm: f64
    y_mm: f64
    rotation_deg: f64
    power_dissipation_w: f64
    mass_g: f64
    invariant len(refdes) > 0
    invariant len(part_number) > 0
    invariant len(description) > 0
    invariant len(footprint) > 0
    invariant pin_count >= 2 and pin_count <= 2048
    invariant x_mm >= 0.0
    invariant y_mm >= 0.0
    invariant rotation_deg >= 0.0 and rotation_deg < 360.0
    invariant power_dissipation_w >= 0.0
    invariant mass_g > 0.0


struct PcbPin:
    id: str
    refdes: str
    number: int
    name: str
    net_id: str
    x_mm: f64
    y_mm: f64
    kind: PcbPinKind
    invariant len(id) > 0
    invariant len(refdes) > 0
    invariant number >= 1
    invariant len(name) > 0
    invariant len(net_id) > 0
    invariant x_mm >= 0.0
    invariant y_mm >= 0.0


struct PcbNet:
    id: str
    name: str
    net_class: PcbNetClass
    pin_ids: list[str]
    nominal_voltage_v: f64
    current_a: f64
    is_reference: bool
    invariant len(id) > 0
    invariant len(name) > 0
    invariant len(pin_ids) >= 2 and len(pin_ids) <= 512
    invariant nominal_voltage_v >= 0.0
    invariant current_a >= 0.0


struct PcbTrace:
    id: str
    net_id: str
    layer_id: str
    width_mm: f64
    length_mm: f64
    current_a: f64
    from_pin_id: str
    to_pin_id: str
    resistance_mohm: f64
    voltage_drop_mv: f64
    power_loss_mw: f64
    temperature_rise_c: f64
    ipc_min_width_mm: f64
    compliant: bool
    invariant len(id) > 0
    invariant len(net_id) > 0
    invariant len(layer_id) > 0
    invariant width_mm > 0.0
    invariant length_mm > 0.0
    invariant current_a >= 0.0
    invariant len(from_pin_id) > 0
    invariant len(to_pin_id) > 0
    invariant resistance_mohm >= 0.0
    invariant voltage_drop_mv >= 0.0
    invariant power_loss_mw >= 0.0
    invariant temperature_rise_c >= 0.0
    invariant ipc_min_width_mm >= 0.0


struct PcbVia:
    id: str
    net_id: str
    drill_mm: f64
    pad_mm: f64
    from_layer_id: str
    to_layer_id: str
    current_a: f64
    resistance_mohm: f64
    invariant len(id) > 0
    invariant len(net_id) > 0
    invariant drill_mm > 0.0
    invariant pad_mm > drill_mm
    invariant len(from_layer_id) > 0
    invariant len(to_layer_id) > 0
    invariant current_a >= 0.0
    invariant resistance_mohm >= 0.0


struct PcbBoard:
    id: str
    name: str
    host_part_id: str
    stackup: PcbStackup
    components: list[PcbComponent]
    pins: list[PcbPin]
    nets: list[PcbNet]
    traces: list[PcbTrace]
    vias: list[PcbVia]
    outline_width_mm: f64
    outline_height_mm: f64
    zone: str
    substrate: str
    invariant len(id) > 0
    invariant len(name) > 0
    invariant len(host_part_id) > 0
    invariant len(components) > 0 and len(components) <= 4096
    invariant len(pins) > 0 and len(pins) <= 16384
    invariant len(nets) > 0 and len(nets) <= 4096
    invariant len(traces) > 0 and len(traces) <= 16384
    invariant len(vias) <= 16384
    invariant outline_width_mm > 0.0
    invariant outline_height_mm > 0.0
    invariant len(zone) > 0
    invariant len(substrate) > 0


struct PcbAnalysis:
    board_id: str
    node_count: int
    branch_count: int
    source_node: str
    worst_net_id: str
    worst_voltage_drop_mv: f64
    worst_drop_fraction: f64
    total_copper_loss_w: f64
    max_temperature_rise_c: f64
    ipc_violations: int
    drc_violations: int
    isolation_violations: int
    solver_residual: f64
    kcl_residual_a: f64
    invariant len(board_id) > 0
    invariant node_count > 0
    invariant branch_count > 0
    invariant len(source_node) > 0
    invariant len(worst_net_id) > 0
    invariant worst_voltage_drop_mv >= 0.0
    invariant worst_drop_fraction >= 0.0
    invariant total_copper_loss_w >= 0.0
    invariant max_temperature_rise_c >= 0.0
    invariant ipc_violations >= 0
    invariant drc_violations >= 0
    invariant isolation_violations >= 0
    invariant solver_residual >= 0.0
    invariant kcl_residual_a >= 0.0


struct PcbRoutePlan:
    net_id: str
    layer_id: str
    width_mm: f64
    topology: str
    via_count: int
    via_drill_mm: f64
    via_pad_mm: f64
    via_layer_id: str
    invariant len(net_id) > 0
    invariant len(layer_id) > 0
    invariant width_mm > 0.0
    invariant topology == "chain" or topology == "star"
    invariant via_count >= 0 and via_count <= 32
    invariant via_drill_mm > 0.0
    invariant via_pad_mm > via_drill_mm


struct PcbSegment:
    id: str
    net_id: str
    layer_id: str
    width_mm: f64
    from_pin_id: str
    to_pin_id: str
    via_count: int
    via_drill_mm: f64
    via_pad_mm: f64
    via_layer_id: str
    invariant len(id) > 0
    invariant len(net_id) > 0
    invariant len(layer_id) > 0
    invariant width_mm > 0.0
    invariant via_count >= 0 and via_count <= 32
    invariant via_drill_mm > 0.0
    invariant via_pad_mm > via_drill_mm


struct PcbLoad:
    id: str
    board_id: str
    pin_id: str
    return_pin_id: str
    current_a: f64
    description: str
    invariant len(id) > 0
    invariant len(board_id) > 0
    invariant len(pin_id) > 0
    invariant len(return_pin_id) > 0
    invariant current_a >= 0.0
    invariant len(description) > 0


struct PcbSolution:
    node_ids: list[str]
    node_voltages: list[f64]
    branch_currents: list[f64]
    fixed_count: int
    solver_residual: f64
    kcl_residual_a: f64
    source_current_a: f64
    invariant len(node_ids) == len(node_voltages)
    invariant fixed_count >= 0
    invariant solver_residual >= 0.0
    invariant kcl_residual_a >= 0.0
    invariant source_current_a >= 0.0


struct PcbIsolationCheck:
    id: str
    board_id: str
    category: str
    from_pin_id: str
    to_pin_id: str
    working_voltage_v: f64
    required_mm: f64
    measured_mm: f64
    reinforced: bool
    compliant: bool
    invariant len(id) > 0
    invariant len(board_id) > 0
    invariant len(category) > 0
    invariant working_voltage_v >= 0.0
    invariant required_mm >= 0.0
    invariant measured_mm >= 0.0


# Copper at 20 degC, IACS annealed, with the standard temperature coefficient.
COPPER_RESISTIVITY_OHM_M = 0.00000001724
COPPER_TEMPCO_PER_K = 0.00393
# Declared conductor operating temperature used for the resistance correction.
CONDUCTOR_OPERATING_TEMP_C = 45.0
# IPC-2221 conductor sizing: A_mils2 = (I / (k * dT^0.44))^(1/0.725).
IPC_ALLOWED_RISE_C = 20.0
IPC_K_EXTERNAL = 0.048
IPC_K_INTERNAL = 0.024
VIA_PLATING_UM = 25.0
MIN_TRACK_WIDTH_MM = 0.15
MIN_ANNULAR_RING_MM = 0.125
MIN_SEGMENT_LENGTH_MM = 0.05
# IEC 60664-1 table 4 basic creepage, pollution degree 2, material group IIIa.
IEC60664_VOLTAGE_STEPS = [63.0, 100.0, 125.0, 160.0, 200.0, 250.0, 320.0, 400.0, 500.0, 630.0, 800.0, 1000.0]
IEC60664_CREEPAGE_MM = [0.63, 1.0, 1.25, 1.6, 2.0, 2.5, 3.2, 4.0, 5.0, 6.3, 8.0, 10.0]
# Below 60 V DC no hazardous-voltage creepage screening is applied.
HAZARDOUS_VOLTAGE_V = 60.0
HV_WORKING_VOLTAGE_V = 800.0
# Clearance for a 6 kV rated impulse, pollution degree 2, inhomogeneous field.
HV_CLEARANCE_MM = 5.5
BUCK_EFFICIENCY = 0.94
HV_BUS_CURRENT_A = 160.0
SILL_HV_CURRENT_A = 120.0
FZC_OUT1_A = 3.1
FZC_OUT2_A = 2.2
FZC_OUT3_A = 1.4
FZC_MCU_A = 0.32
FZC_PHY_A = 0.11
FZC_CAN_SUPPLY_A = 0.06
HVJ_COIL_A = 0.42
HVJ_PYRO_MONITOR_A = 0.005
HVJ_ISO_SUPPLY_A = 0.012
HVJ_ISENSE_A = 0.0045
HVJ_IMD_A = 0.005
HVJ_IMD_DIVIDER_A = 0.00035
SILL_ZONE_A = 22.0
SILL_SERVICE_A = 6.0
SILL_BPILLAR_A = 9.0


equation conductor_resistance_ohm(resistivity, length_m, width_m, thickness_m) -> any:
    return resistivity * length_m / (width_m * thickness_m)


equation temperature_corrected_resistivity(rho_20, tempco, temperature_c) -> any:
    return rho_20 * (1.0 + tempco * (temperature_c - 20.0))


equation ipc2221_cross_section_mils2(current_a, k, rise_c) -> any:
    return (current_a / (k * rise_c^0.44))^(1.0 / 0.725)


equation ipc2221_temperature_rise_c(current_a, k, area_mils2) -> any:
    return (current_a / (k * area_mils2^0.725))^(1.0 / 0.44)


equation via_barrel_resistance_ohm(resistivity, height_m, drill_m, plating_m) -> any:
    return resistivity * height_m / (3.141592653589793 * (drill_m + plating_m) * plating_m)


equation joule_loss_w(current_a, resistance_ohm) -> any:
    return current_a^2 * resistance_ohm


def operating_resistivity() !{}:
    return temperature_corrected_resistivity(COPPER_RESISTIVITY_OHM_M, COPPER_TEMPCO_PER_K, CONDUCTOR_OPERATING_TEMP_C)


def net_class_key(net_class: PcbNetClass):
    if net_class == PcbNetClass.hv:
        return "hv"
    if net_class == PcbNetClass.power:
        return "power"
    if net_class == PcbNetClass.ground:
        return "ground"
    if net_class == PcbNetClass.can:
        return "can"
    if net_class == PcbNetClass.ethernet:
        return "ethernet"
    if net_class == PcbNetClass.analog:
        return "analog"
    return "signal"


def layer_function_key(function: PcbLayerFunction):
    if function == PcbLayerFunction.signal:
        return "signal"
    if function == PcbLayerFunction.plane:
        return "plane"
    if function == PcbLayerFunction.core:
        return "core"
    if function == PcbLayerFunction.prepreg:
        return "prepreg"
    if function == PcbLayerFunction.soldermask:
        return "soldermask"
    return "coverlay"


def pin_kind_key(kind: PcbPinKind):
    if kind == PcbPinKind.power:
        return "power"
    if kind == PcbPinKind.ground:
        return "ground"
    if kind == PcbPinKind.analog:
        return "analog"
    if kind == PcbPinKind.thermal:
        return "thermal"
    if kind == PcbPinKind.nc:
        return "nc"
    return "signal"


def pcb_pin(refdes: str, number: int, name: str, net_id: str, x_mm: f64, y_mm: f64, kind: PcbPinKind):
    return PcbPin(id=refdes + "." + str(number), refdes=refdes, number=number, name=name, net_id=net_id, x_mm=x_mm, y_mm=y_mm, kind=kind)


def pcb_load(board_id: str, pin_id: str, return_pin_id: str, current_a: f64, description: str):
    return PcbLoad(id=board_id + ":" + pin_id + ">" + return_pin_id, board_id=board_id, pin_id=pin_id, return_pin_id=return_pin_id, current_a=current_a, description=description)


def route_points(x1: f64, y1: f64, x2: f64, y2: f64):
    # One orthogonal leg then a 45-degree diagonal, the way real copper is routed.
    dx = x2 - x1
    dy = y2 - y1
    if abs(dx) >= abs(dy):
        run = abs(dx) - abs(dy)
        mid_x = x1 + run if dx >= 0.0 else x1 - run
        return [[x1, y1], [mid_x, y1], [x2, y2]]
    rise = abs(dy) - abs(dx)
    mid_y = y1 + rise if dy >= 0.0 else y1 - rise
    return [[x1, y1], [x1, mid_y], [x2, y2]]


def polyline_length_mm(points: list[list[f64]]):
    mut total = 0.0
    for index in range(1, len(points)):
        dx = points[index][0] - points[index - 1][0]
        dy = points[index][1] - points[index - 1][1]
        total = total + math.sqrt(dx * dx + dy * dy)
    return total


def pin_index_map(pins: list[PcbPin]) -> dict[str, int]:
    mut mapping = {}
    for index in range(0, len(pins)):
        mapping[pins[index].id] = index
    return mapping


def layer_depth_mm(stackup: PcbStackup, layer_id: str):
    mut depth = 0.0
    for layer in stackup.layers:
        if layer.id == layer_id:
            return depth
        depth = depth + layer.copper_um * 0.001 + layer.dielectric_mm
    return depth


def layer_copper_um(stackup: PcbStackup, layer_id: str):
    for layer in stackup.layers:
        if layer.id == layer_id:
            return layer.copper_um
    return 35.0


def layer_is_external(stackup: PcbStackup, layer_id: str):
    mut first = ""
    mut last = ""
    for layer in stackup.layers:
        if layer.copper_um > 0.0:
            if len(first) == 0:
                first = layer.id
            last = layer.id
    return layer_id == first or layer_id == last


def stackup_thickness_mm(layers: list[PcbLayer]):
    mut total = 0.0
    for layer in layers:
        total = total + layer.copper_um * 0.001 + layer.dielectric_mm
    return total


def ipc_min_width_mm(current_a: f64, copper_um: f64, external: bool) !{}:
    # A_mils2 = (I / (k * dT^0.44))^(1/0.725); width_mils = A_mils2 / thickness_mils.
    # 1 mil = 0.0254 mm, so copper_um / 25.4 turns a copper weight into mils and
    # the final * 0.0254 turns the required width back into millimetres.
    # IPC-2221B figure 6-4 is fitted for I <= 35 A and A <= 700 mils^2. The traction
    # busbar and the printed sill conductors run well above that, so their numbers are
    # an extrapolation of the published curve, not a standard-validated result.
    if current_a <= 0.0:
        return 0.0
    factor = IPC_K_EXTERNAL if external else IPC_K_INTERNAL
    area_mils2 = ipc2221_cross_section_mils2(current_a, factor, IPC_ALLOWED_RISE_C)
    return area_mils2 / (copper_um / 25.4) * 0.0254


def ipc_temperature_rise_c(current_a: f64, width_mm: f64, copper_um: f64, external: bool) !{}:
    # Same relation inverted: dT = (I / (k * A^0.725))^(1/0.44).
    if current_a <= 0.0:
        return 0.0
    factor = IPC_K_EXTERNAL if external else IPC_K_INTERNAL
    area_mils2 = (width_mm / 0.0254) * (copper_um / 25.4)
    return ipc2221_temperature_rise_c(current_a, factor, area_mils2)


def iec60664_creepage_mm(working_voltage_v: f64):
    # IEC 60664-1 table 4, pollution degree 2, material group IIIa, linear between steps.
    last = len(IEC60664_VOLTAGE_STEPS) - 1
    if working_voltage_v <= IEC60664_VOLTAGE_STEPS[0]:
        return IEC60664_CREEPAGE_MM[0]
    for index in range(1, last + 1):
        if working_voltage_v <= IEC60664_VOLTAGE_STEPS[index]:
            low_v = IEC60664_VOLTAGE_STEPS[index - 1]
            high_v = IEC60664_VOLTAGE_STEPS[index]
            low_mm = IEC60664_CREEPAGE_MM[index - 1]
            high_mm = IEC60664_CREEPAGE_MM[index]
            return low_mm + (high_mm - low_mm) * (working_voltage_v - low_v) / (high_v - low_v)
    return IEC60664_CREEPAGE_MM[last] * working_voltage_v / IEC60664_VOLTAGE_STEPS[last]


def route_plan_for(net: PcbNet, defaults: list[PcbRoutePlan], overrides: list[PcbRoutePlan]):
    for plan in overrides:
        if plan.net_id == net.id:
            return plan
    key = net_class_key(net.net_class)
    for plan in defaults:
        if plan.net_id == key:
            return plan
    return defaults[0]


def build_segments(nets: list[PcbNet], defaults: list[PcbRoutePlan], overrides: list[PcbRoutePlan]):
    mut segments = []
    for net in nets:
        plan = route_plan_for(net, defaults, overrides)
        for index in range(1, len(net.pin_ids)):
            origin = net.pin_ids[0] if plan.topology == "star" else net.pin_ids[index - 1]
            segments.append(PcbSegment(
                id=net.id + "-s" + str(index),
                net_id=net.id,
                layer_id=plan.layer_id,
                width_mm=plan.width_mm,
                from_pin_id=origin,
                to_pin_id=net.pin_ids[index],
                via_count=plan.via_count,
                via_drill_mm=plan.via_drill_mm,
                via_pad_mm=plan.via_pad_mm,
                via_layer_id=plan.via_layer_id,
            ))
    return segments


def segment_polyline(segment: PcbSegment, pins: list[PcbPin], index_of: dict[str, int]):
    origin = pins[index_of.get(segment.from_pin_id, 0)]
    target = pins[index_of.get(segment.to_pin_id, 0)]
    return route_points(origin.x_mm, origin.y_mm, target.x_mm, target.y_mm)


def single_via_resistance_ohm(stackup: PcbStackup, segment: PcbSegment) !{}:
    height_mm = max(0.05, abs(layer_depth_mm(stackup, segment.via_layer_id) - layer_depth_mm(stackup, segment.layer_id)))
    return via_barrel_resistance_ohm(operating_resistivity(), height_mm * 0.001, segment.via_drill_mm * 0.001, VIA_PLATING_UM * 0.000001)


def via_series_resistance_ohm(stackup: PcbStackup, segment: PcbSegment) !{}:
    # A buried run needs one transition down and one back up; each is a parallel group.
    if segment.via_count == 0:
        return 0.0
    return 2.0 * single_via_resistance_ohm(stackup, segment) / float(segment.via_count)


def copper_resistances_ohm(stackup: PcbStackup, segments: list[PcbSegment], lengths_mm: list[f64]) !{}:
    rho = operating_resistivity()
    mut values = []
    for index in range(0, len(segments)):
        segment = segments[index]
        thickness = layer_copper_um(stackup, segment.layer_id) * 0.000001
        values.append(conductor_resistance_ohm(rho, lengths_mm[index] * 0.001, segment.width_mm * 0.001, thickness))
    return values


def solve_linear(matrix: list[list[f64]], rhs: list[f64]):
    equation:
        solution := solve(matrix, rhs)
    mut values = []
    for index in range(0, len(rhs)):
        values.append(float(solution[index]))
    return values


def solve_network(pins: list[PcbPin], nets: list[PcbNet], segments: list[PcbSegment], resistances_ohm: list[f64], loads: list[PcbLoad]):
    # The unknown is the deviation from each net's declared feed potential. Every
    # branch stays inside one net, so the nominal levels cancel out of `G v = i`
    # exactly and the solve runs on millivolt-scale numbers instead of hundreds of
    # volts, which keeps the KCL residual at the floating-point floor on the 800 V
    # busbars. Absolute node potentials are restored after the solve.
    index_of = pin_index_map(pins)
    count = len(pins)
    mut nominal = []
    mut is_fixed = []
    for index in range(0, count):
        nominal.append(0.0)
        is_fixed.append(false)
    for net in nets:
        level = 0.0 if net.is_reference else net.nominal_voltage_v
        for pin_id in net.pin_ids:
            nominal[index_of.get(pin_id, 0)] = level
        is_fixed[index_of.get(net.pin_ids[0], 0)] = true
    mut unknown_of = []
    mut unknowns = 0
    for index in range(0, count):
        if is_fixed[index]:
            unknown_of.append(-1)
        else:
            unknown_of.append(unknowns)
            unknowns = unknowns + 1
    mut conductance = []
    mut rhs = []
    for row in range(0, unknowns):
        mut cells = []
        for column in range(0, unknowns):
            cells.append(0.0)
        conductance.append(cells)
        rhs.append(0.0)
    for index in range(0, len(segments)):
        left = index_of.get(segments[index].from_pin_id, 0)
        right = index_of.get(segments[index].to_pin_id, 0)
        gain = 1.0 / resistances_ohm[index]
        left_unknown = unknown_of[left]
        right_unknown = unknown_of[right]
        if left_unknown >= 0:
            conductance[left_unknown][left_unknown] = conductance[left_unknown][left_unknown] + gain
            if right_unknown >= 0:
                conductance[left_unknown][right_unknown] = conductance[left_unknown][right_unknown] - gain
        if right_unknown >= 0:
            conductance[right_unknown][right_unknown] = conductance[right_unknown][right_unknown] + gain
            if left_unknown >= 0:
                conductance[right_unknown][left_unknown] = conductance[right_unknown][left_unknown] - gain
    mut injected = []
    for index in range(0, count):
        injected.append(0.0)
    for load in loads:
        sink = index_of.get(load.pin_id, 0)
        source = index_of.get(load.return_pin_id, 0)
        injected[sink] = injected[sink] - load.current_a
        injected[source] = injected[source] + load.current_a
        if unknown_of[sink] >= 0:
            rhs[unknown_of[sink]] = rhs[unknown_of[sink]] - load.current_a
        if unknown_of[source] >= 0:
            rhs[unknown_of[source]] = rhs[unknown_of[source]] + load.current_a
    deviations = solve_linear(conductance, rhs)
    mut residual = 0.0
    for row in range(0, unknowns):
        mut accumulated = 0.0
        for column in range(0, unknowns):
            accumulated = accumulated + conductance[row][column] * deviations[column]
        residual = max(residual, abs(accumulated - rhs[row]))
    mut node_ids = []
    mut voltages = []
    mut offsets = []
    mut flow = []
    for index in range(0, count):
        node_ids.append(pins[index].id)
        offset = 0.0 if is_fixed[index] else deviations[unknown_of[index]]
        offsets.append(offset)
        voltages.append(nominal[index] + offset)
        flow.append(0.0)
    mut currents = []
    for index in range(0, len(segments)):
        left = index_of.get(segments[index].from_pin_id, 0)
        right = index_of.get(segments[index].to_pin_id, 0)
        branch = (offsets[left] - offsets[right]) / resistances_ohm[index]
        currents.append(branch)
        flow[left] = flow[left] + branch
        flow[right] = flow[right] - branch
    mut kcl = 0.0
    mut supplied = 0.0
    for index in range(0, count):
        if is_fixed[index]:
            supplied = supplied + max(0.0, flow[index] - injected[index])
        else:
            kcl = max(kcl, abs(flow[index] - injected[index]))
    return PcbSolution(node_ids=node_ids, node_voltages=voltages, branch_currents=currents, fixed_count=count - unknowns, solver_residual=residual, kcl_residual_a=kcl, source_current_a=supplied)


def assemble_board(board_id: str, name: str, host_part_id: str, zone: str, substrate: str, stackup: PcbStackup, components: list[PcbComponent], pins: list[PcbPin], nets: list[PcbNet], defaults: list[PcbRoutePlan], overrides: list[PcbRoutePlan], loads: list[PcbLoad], outline_width_mm: f64, outline_height_mm: f64) !{}:
    index_of = pin_index_map(pins)
    segments = build_segments(nets, defaults, overrides)
    mut lengths = []
    for segment in segments:
        lengths.append(max(MIN_SEGMENT_LENGTH_MM, polyline_length_mm(segment_polyline(segment, pins, index_of))))
    copper_ohm = copper_resistances_ohm(stackup, segments, lengths)
    mut branch_ohm = []
    for index in range(0, len(segments)):
        branch_ohm.append(copper_ohm[index] + via_series_resistance_ohm(stackup, segments[index]))
    solution = solve_network(pins, nets, segments, branch_ohm, loads)
    mut traces = []
    mut vias = []
    for index in range(0, len(segments)):
        segment = segments[index]
        current = abs(solution.branch_currents[index])
        copper_um = layer_copper_um(stackup, segment.layer_id)
        external = layer_is_external(stackup, segment.layer_id)
        required = ipc_min_width_mm(current, copper_um, external)
        traces.append(PcbTrace(
            id=segment.id,
            net_id=segment.net_id,
            layer_id=segment.layer_id,
            width_mm=segment.width_mm,
            length_mm=lengths[index],
            current_a=current,
            from_pin_id=segment.from_pin_id,
            to_pin_id=segment.to_pin_id,
            resistance_mohm=copper_ohm[index] * 1000.0,
            voltage_drop_mv=current * copper_ohm[index] * 1000.0,
            power_loss_mw=joule_loss_w(current, copper_ohm[index]) * 1000.0,
            temperature_rise_c=ipc_temperature_rise_c(current, segment.width_mm, copper_um, external),
            ipc_min_width_mm=required,
            compliant=segment.width_mm >= required,
        ))
        if segment.via_count > 0:
            barrel_mohm = single_via_resistance_ohm(stackup, segment) * 1000.0
            share = current / float(segment.via_count)
            for barrel in range(1, segment.via_count + 1):
                vias.append(PcbVia(id=segment.id + "-vd" + str(barrel), net_id=segment.net_id, drill_mm=segment.via_drill_mm, pad_mm=segment.via_pad_mm, from_layer_id=segment.via_layer_id, to_layer_id=segment.layer_id, current_a=share, resistance_mohm=barrel_mohm))
                vias.append(PcbVia(id=segment.id + "-vu" + str(barrel), net_id=segment.net_id, drill_mm=segment.via_drill_mm, pad_mm=segment.via_pad_mm, from_layer_id=segment.layer_id, to_layer_id=segment.via_layer_id, current_a=share, resistance_mohm=barrel_mohm))
    return PcbBoard(id=board_id, name=name, host_part_id=host_part_id, stackup=stackup, components=components, pins=pins, nets=nets, traces=traces, vias=vias, outline_width_mm=outline_width_mm, outline_height_mm=outline_height_mm, zone=zone, substrate=substrate)


def bms_slave_stackup():
    layers = [
        PcbLayer(id="bms-smt", name="Top soldermask", function=PcbLayerFunction.soldermask, copper_um=0.0, dielectric_mm=0.0254, dielectric_er=3.6, material="LPI soldermask"),
        PcbLayer(id="bms-l1", name="Top signal", function=PcbLayerFunction.signal, copper_um=35.0, dielectric_mm=0.0, dielectric_er=1.0, material="electrodeposited copper 1 oz"),
        PcbLayer(id="bms-pp1", name="Prepreg 1", function=PcbLayerFunction.prepreg, copper_um=0.0, dielectric_mm=0.2032, dielectric_er=4.3, material="2x1080 FR-4 prepreg"),
        PcbLayer(id="bms-l2", name="Inner ground plane", function=PcbLayerFunction.plane, copper_um=17.5, dielectric_mm=0.0, dielectric_er=1.0, material="rolled copper 0.5 oz"),
        PcbLayer(id="bms-core", name="Core", function=PcbLayerFunction.core, copper_um=0.0, dielectric_mm=1.0668, dielectric_er=4.5, material="FR-4 Tg170 core"),
        PcbLayer(id="bms-l3", name="Inner cell-tap plane", function=PcbLayerFunction.plane, copper_um=17.5, dielectric_mm=0.0, dielectric_er=1.0, material="rolled copper 0.5 oz"),
        PcbLayer(id="bms-pp2", name="Prepreg 2", function=PcbLayerFunction.prepreg, copper_um=0.0, dielectric_mm=0.2032, dielectric_er=4.3, material="2x1080 FR-4 prepreg"),
        PcbLayer(id="bms-l4", name="Bottom signal", function=PcbLayerFunction.signal, copper_um=35.0, dielectric_mm=0.0, dielectric_er=1.0, material="electrodeposited copper 1 oz"),
        PcbLayer(id="bms-smb", name="Bottom soldermask", function=PcbLayerFunction.soldermask, copper_um=0.0, dielectric_mm=0.0254, dielectric_er=3.6, material="LPI soldermask"),
    ]
    return PcbStackup(id="stk-bms", name="FR-4 4-layer 1.63 mm, 35 um outer copper", layers=layers, total_thickness_mm=stackup_thickness_mm(layers), ipc_class=3, impedance_target_ohm=100.0)


def bms_slave_components():
    return [
        PcbComponent(refdes="U1", part_number="LTC6811-1", description="12-cell monitor with integrated isoSPI; 3 channels modelled at pin level", footprint="SSOP-48", pin_count=48, x_mm=64.0, y_mm=39.0, rotation_deg=0.0, power_dissipation_w=0.42, mass_g=0.9),
        PcbComponent(refdes="T1", part_number="HX1188NL", description="isoSPI pulse transformer, port A", footprint="SM-6P", pin_count=6, x_mm=22.0, y_mm=58.0, rotation_deg=0.0, power_dissipation_w=0.04, mass_g=0.6),
        PcbComponent(refdes="T2", part_number="HX1188NL", description="isoSPI pulse transformer, port B", footprint="SM-6P", pin_count=6, x_mm=22.0, y_mm=20.0, rotation_deg=0.0, power_dissipation_w=0.04, mass_g=0.6),
        PcbComponent(refdes="J1", part_number="GH-4P", description="Daisy-chain connector, upstream module", footprint="JST-GH-4", pin_count=4, x_mm=6.0, y_mm=58.0, rotation_deg=90.0, power_dissipation_w=0.0, mass_g=0.5),
        PcbComponent(refdes="J2", part_number="GH-4P", description="Daisy-chain connector, downstream module", footprint="JST-GH-4", pin_count=4, x_mm=6.0, y_mm=20.0, rotation_deg=90.0, power_dissipation_w=0.0, mass_g=0.5),
        PcbComponent(refdes="J3", part_number="MX150-7", description="Cell-tap and thermistor harness header", footprint="MOLEX-MX150-7", pin_count=7, x_mm=118.0, y_mm=39.0, rotation_deg=270.0, power_dissipation_w=0.0, mass_g=6.0),
        PcbComponent(refdes="Q1", part_number="2N7002E", description="Passive balancing switch, channel 1", footprint="SOT-23", pin_count=3, x_mm=92.0, y_mm=26.0, rotation_deg=0.0, power_dissipation_w=0.01, mass_g=0.01),
        PcbComponent(refdes="Q2", part_number="2N7002E", description="Passive balancing switch, channel 2", footprint="SOT-23", pin_count=3, x_mm=92.0, y_mm=39.0, rotation_deg=0.0, power_dissipation_w=0.01, mass_g=0.01),
        PcbComponent(refdes="Q3", part_number="2N7002E", description="Passive balancing switch, channel 3", footprint="SOT-23", pin_count=3, x_mm=92.0, y_mm=52.0, rotation_deg=0.0, power_dissipation_w=0.01, mass_g=0.01),
        PcbComponent(refdes="R1", part_number="CRGH1206-33R", description="Balancing bleed resistor, channel 1", footprint="1206", pin_count=2, x_mm=104.0, y_mm=26.0, rotation_deg=0.0, power_dissipation_w=0.53, mass_g=0.02),
        PcbComponent(refdes="R2", part_number="CRGH1206-33R", description="Balancing bleed resistor, channel 2", footprint="1206", pin_count=2, x_mm=104.0, y_mm=39.0, rotation_deg=0.0, power_dissipation_w=0.53, mass_g=0.02),
        PcbComponent(refdes="R3", part_number="CRGH1206-33R", description="Balancing bleed resistor, channel 3", footprint="1206", pin_count=2, x_mm=104.0, y_mm=52.0, rotation_deg=0.0, power_dissipation_w=0.53, mass_g=0.02),
        PcbComponent(refdes="RT1", part_number="NTCG163JF103FT", description="Module thermistor, cell block front", footprint="0603", pin_count=2, x_mm=40.0, y_mm=33.0, rotation_deg=0.0, power_dissipation_w=0.001, mass_g=0.005),
        PcbComponent(refdes="RT2", part_number="NTCG163JF103FT", description="Module thermistor, cell block rear", footprint="0603", pin_count=2, x_mm=40.0, y_mm=27.0, rotation_deg=0.0, power_dissipation_w=0.001, mass_g=0.005),
        PcbComponent(refdes="C1", part_number="GRM188R71H104K", description="Monitor regulator decoupling, 100 nF", footprint="0603", pin_count=2, x_mm=48.0, y_mm=24.0, rotation_deg=0.0, power_dissipation_w=0.0, mass_g=0.005),
        PcbComponent(refdes="C2", part_number="GRM31CR71H105K", description="Cell-group bulk capacitor, 1 uF", footprint="1206", pin_count=2, x_mm=80.0, y_mm=60.0, rotation_deg=0.0, power_dissipation_w=0.0, mass_g=0.02),
    ]


def bms_slave_pins():
    return [
        pcb_pin("U1", 1, "V+", "bmsA-stack-plus", 72.0, 52.0, PcbPinKind.power),
        pcb_pin("U1", 2, "C3", "bmsA-stack-plus", 72.0, 49.0, PcbPinKind.analog),
        pcb_pin("U1", 3, "C2", "bmsA-tap2", 72.0, 46.0, PcbPinKind.analog),
        pcb_pin("U1", 4, "C1", "bmsA-tap1", 72.0, 43.0, PcbPinKind.analog),
        pcb_pin("U1", 5, "V-", "bmsA-gnd", 72.0, 40.0, PcbPinKind.ground),
        pcb_pin("U1", 6, "S3", "bmsA-bal3", 72.0, 37.0, PcbPinKind.signal),
        pcb_pin("U1", 7, "S2", "bmsA-bal2", 72.0, 34.0, PcbPinKind.signal),
        pcb_pin("U1", 8, "S1", "bmsA-bal1", 72.0, 31.0, PcbPinKind.signal),
        pcb_pin("U1", 9, "VREG", "bmsA-vreg", 56.0, 31.0, PcbPinKind.power),
        pcb_pin("U1", 10, "GPIO1", "bmsA-ntc1", 56.0, 34.0, PcbPinKind.analog),
        pcb_pin("U1", 11, "GPIO2", "bmsA-ntc2", 56.0, 37.0, PcbPinKind.analog),
        pcb_pin("U1", 12, "IPA", "bmsA-isoa-p", 56.0, 40.0, PcbPinKind.signal),
        pcb_pin("U1", 13, "IMA", "bmsA-isoa-n", 56.0, 43.0, PcbPinKind.signal),
        pcb_pin("U1", 14, "IPB", "bmsA-isob-p", 56.0, 46.0, PcbPinKind.signal),
        pcb_pin("U1", 15, "IMB", "bmsA-isob-n", 56.0, 49.0, PcbPinKind.signal),
        pcb_pin("T1", 1, "P1", "bmsA-isoa-p", 28.0, 62.0, PcbPinKind.signal),
        pcb_pin("T1", 2, "P2", "bmsA-isoa-n", 28.0, 58.0, PcbPinKind.signal),
        pcb_pin("T1", 3, "PSH", "bmsA-gnd", 28.0, 54.0, PcbPinKind.ground),
        pcb_pin("T1", 4, "S1", "bmsA-cablea-p", 16.0, 62.0, PcbPinKind.signal),
        pcb_pin("T1", 5, "S2", "bmsA-cablea-n", 16.0, 58.0, PcbPinKind.signal),
        pcb_pin("T1", 6, "SSH", "bmsA-gnd", 16.0, 54.0, PcbPinKind.ground),
        pcb_pin("T2", 1, "P1", "bmsA-isob-p", 28.0, 24.0, PcbPinKind.signal),
        pcb_pin("T2", 2, "P2", "bmsA-isob-n", 28.0, 20.0, PcbPinKind.signal),
        pcb_pin("T2", 3, "PSH", "bmsA-gnd", 28.0, 16.0, PcbPinKind.ground),
        pcb_pin("T2", 4, "S1", "bmsA-cableb-p", 16.0, 24.0, PcbPinKind.signal),
        pcb_pin("T2", 5, "S2", "bmsA-cableb-n", 16.0, 20.0, PcbPinKind.signal),
        pcb_pin("T2", 6, "SSH", "bmsA-gnd", 16.0, 16.0, PcbPinKind.ground),
        pcb_pin("J1", 1, "UP_P", "bmsA-cablea-p", 6.0, 64.0, PcbPinKind.signal),
        pcb_pin("J1", 2, "UP_N", "bmsA-cablea-n", 6.0, 60.0, PcbPinKind.signal),
        pcb_pin("J1", 3, "UP_SH", "bmsA-gnd", 6.0, 56.0, PcbPinKind.ground),
        pcb_pin("J1", 4, "UP_DRAIN", "bmsA-gnd", 6.0, 52.0, PcbPinKind.ground),
        pcb_pin("J2", 1, "DN_P", "bmsA-cableb-p", 6.0, 26.0, PcbPinKind.signal),
        pcb_pin("J2", 2, "DN_N", "bmsA-cableb-n", 6.0, 22.0, PcbPinKind.signal),
        pcb_pin("J2", 3, "DN_SH", "bmsA-gnd", 6.0, 18.0, PcbPinKind.ground),
        pcb_pin("J2", 4, "DN_DRAIN", "bmsA-gnd", 6.0, 14.0, PcbPinKind.ground),
        pcb_pin("J3", 1, "STACK+", "bmsA-stack-plus", 118.0, 57.0, PcbPinKind.power),
        pcb_pin("J3", 2, "TAP2", "bmsA-tap2", 118.0, 51.0, PcbPinKind.power),
        pcb_pin("J3", 3, "TAP1", "bmsA-tap1", 118.0, 45.0, PcbPinKind.power),
        pcb_pin("J3", 4, "STACK-", "bmsA-gnd", 118.0, 39.0, PcbPinKind.ground),
        pcb_pin("J3", 5, "NTC1", "bmsA-ntc1", 118.0, 33.0, PcbPinKind.thermal),
        pcb_pin("J3", 6, "NTC2", "bmsA-ntc2", 118.0, 27.0, PcbPinKind.thermal),
        pcb_pin("J3", 7, "NTC_RTN", "bmsA-gnd", 118.0, 21.0, PcbPinKind.ground),
        pcb_pin("Q1", 1, "G", "bmsA-bal1", 89.0, 26.0, PcbPinKind.signal),
        pcb_pin("Q1", 2, "S", "bmsA-gnd", 95.0, 24.0, PcbPinKind.ground),
        pcb_pin("Q1", 3, "D", "bmsA-bleed1", 95.0, 28.0, PcbPinKind.power),
        pcb_pin("Q2", 1, "G", "bmsA-bal2", 89.0, 39.0, PcbPinKind.signal),
        pcb_pin("Q2", 2, "S", "bmsA-tap1", 95.0, 37.0, PcbPinKind.power),
        pcb_pin("Q2", 3, "D", "bmsA-bleed2", 95.0, 41.0, PcbPinKind.power),
        pcb_pin("Q3", 1, "G", "bmsA-bal3", 89.0, 52.0, PcbPinKind.signal),
        pcb_pin("Q3", 2, "S", "bmsA-tap2", 95.0, 50.0, PcbPinKind.power),
        pcb_pin("Q3", 3, "D", "bmsA-bleed3", 95.0, 54.0, PcbPinKind.power),
        pcb_pin("R1", 1, "A", "bmsA-bleed1", 101.0, 26.0, PcbPinKind.power),
        pcb_pin("R1", 2, "B", "bmsA-tap1", 107.0, 26.0, PcbPinKind.power),
        pcb_pin("R2", 1, "A", "bmsA-bleed2", 101.0, 39.0, PcbPinKind.power),
        pcb_pin("R2", 2, "B", "bmsA-tap2", 107.0, 39.0, PcbPinKind.power),
        pcb_pin("R3", 1, "A", "bmsA-bleed3", 101.0, 52.0, PcbPinKind.power),
        pcb_pin("R3", 2, "B", "bmsA-stack-plus", 107.0, 52.0, PcbPinKind.power),
        pcb_pin("RT1", 1, "A", "bmsA-ntc1", 38.2, 33.0, PcbPinKind.thermal),
        pcb_pin("RT1", 2, "B", "bmsA-gnd", 41.8, 33.0, PcbPinKind.ground),
        pcb_pin("RT2", 1, "A", "bmsA-ntc2", 38.2, 27.0, PcbPinKind.thermal),
        pcb_pin("RT2", 2, "B", "bmsA-gnd", 41.8, 27.0, PcbPinKind.ground),
        pcb_pin("C1", 1, "A", "bmsA-vreg", 46.2, 24.0, PcbPinKind.power),
        pcb_pin("C1", 2, "B", "bmsA-gnd", 49.8, 24.0, PcbPinKind.ground),
        pcb_pin("C2", 1, "A", "bmsA-stack-plus", 78.0, 60.0, PcbPinKind.power),
        pcb_pin("C2", 2, "B", "bmsA-gnd", 82.0, 60.0, PcbPinKind.ground),
    ]


def bms_slave_nets():
    return [
        PcbNet(id="bmsA-stack-plus", name="Modelled cell-group positive", net_class=PcbNetClass.power, pin_ids=["J3.1", "U1.1", "U1.2", "C2.1", "R3.2"], nominal_voltage_v=12.6, current_a=0.1405, is_reference=false),
        PcbNet(id="bmsA-tap2", name="Cell tap 2", net_class=PcbNetClass.power, pin_ids=["J3.2", "U1.3", "Q3.2", "R2.2"], nominal_voltage_v=8.4, current_a=0.127, is_reference=false),
        PcbNet(id="bmsA-tap1", name="Cell tap 1", net_class=PcbNetClass.power, pin_ids=["J3.3", "U1.4", "Q2.2", "R1.2"], nominal_voltage_v=4.2, current_a=0.127, is_reference=false),
        PcbNet(id="bmsA-gnd", name="Module reference, cell-group negative", net_class=PcbNetClass.ground, pin_ids=["J3.4", "J3.7", "U1.5", "Q1.2", "C1.2", "C2.2", "RT1.2", "RT2.2", "T1.3", "T1.6", "T2.3", "T2.6", "J1.3", "J1.4", "J2.3", "J2.4"], nominal_voltage_v=0.0, current_a=0.1414, is_reference=true),
        PcbNet(id="bmsA-vreg", name="Monitor regulator rail", net_class=PcbNetClass.power, pin_ids=["U1.9", "C1.1"], nominal_voltage_v=5.0, current_a=0.0, is_reference=false),
        PcbNet(id="bmsA-ntc1", name="Thermistor channel 1", net_class=PcbNetClass.analog, pin_ids=["U1.10", "RT1.1", "J3.5"], nominal_voltage_v=2.5, current_a=0.00025, is_reference=false),
        PcbNet(id="bmsA-ntc2", name="Thermistor channel 2", net_class=PcbNetClass.analog, pin_ids=["U1.11", "RT2.1", "J3.6"], nominal_voltage_v=2.5, current_a=0.00025, is_reference=false),
        PcbNet(id="bmsA-isoa-p", name="isoSPI port A positive", net_class=PcbNetClass.signal, pin_ids=["U1.12", "T1.1"], nominal_voltage_v=1.25, current_a=0.02, is_reference=false),
        PcbNet(id="bmsA-isoa-n", name="isoSPI port A negative", net_class=PcbNetClass.signal, pin_ids=["U1.13", "T1.2"], nominal_voltage_v=0.0, current_a=0.02, is_reference=false),
        PcbNet(id="bmsA-isob-p", name="isoSPI port B positive", net_class=PcbNetClass.signal, pin_ids=["U1.14", "T2.1"], nominal_voltage_v=1.25, current_a=0.02, is_reference=false),
        PcbNet(id="bmsA-isob-n", name="isoSPI port B negative", net_class=PcbNetClass.signal, pin_ids=["U1.15", "T2.2"], nominal_voltage_v=0.0, current_a=0.02, is_reference=false),
        PcbNet(id="bmsA-cablea-p", name="Daisy-chain cable A positive", net_class=PcbNetClass.signal, pin_ids=["T1.4", "J1.1"], nominal_voltage_v=1.25, current_a=0.02, is_reference=false),
        PcbNet(id="bmsA-cablea-n", name="Daisy-chain cable A negative", net_class=PcbNetClass.signal, pin_ids=["T1.5", "J1.2"], nominal_voltage_v=0.0, current_a=0.02, is_reference=false),
        PcbNet(id="bmsA-cableb-p", name="Daisy-chain cable B positive", net_class=PcbNetClass.signal, pin_ids=["T2.4", "J2.1"], nominal_voltage_v=1.25, current_a=0.02, is_reference=false),
        PcbNet(id="bmsA-cableb-n", name="Daisy-chain cable B negative", net_class=PcbNetClass.signal, pin_ids=["T2.5", "J2.2"], nominal_voltage_v=0.0, current_a=0.02, is_reference=false),
        PcbNet(id="bmsA-bal1", name="Balancing gate drive 1", net_class=PcbNetClass.signal, pin_ids=["U1.8", "Q1.1"], nominal_voltage_v=5.0, current_a=0.0002, is_reference=false),
        PcbNet(id="bmsA-bal2", name="Balancing gate drive 2", net_class=PcbNetClass.signal, pin_ids=["U1.7", "Q2.1"], nominal_voltage_v=5.0, current_a=0.0002, is_reference=false),
        PcbNet(id="bmsA-bal3", name="Balancing gate drive 3", net_class=PcbNetClass.signal, pin_ids=["U1.6", "Q3.1"], nominal_voltage_v=5.0, current_a=0.0002, is_reference=false),
        PcbNet(id="bmsA-bleed1", name="Bleed path 1", net_class=PcbNetClass.power, pin_ids=["Q1.3", "R1.1"], nominal_voltage_v=0.0, current_a=0.127, is_reference=false),
        PcbNet(id="bmsA-bleed2", name="Bleed path 2", net_class=PcbNetClass.power, pin_ids=["Q2.3", "R2.1"], nominal_voltage_v=4.2, current_a=0.127, is_reference=false),
        PcbNet(id="bmsA-bleed3", name="Bleed path 3", net_class=PcbNetClass.power, pin_ids=["Q3.3", "R3.1"], nominal_voltage_v=8.4, current_a=0.127, is_reference=false),
    ]


def bms_slave_plans():
    return [
        PcbRoutePlan(net_id="power", layer_id="bms-l1", width_mm=1.2, topology="chain", via_count=0, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id="bms-l1"),
        PcbRoutePlan(net_id="ground", layer_id="bms-l2", width_mm=3.0, topology="star", via_count=1, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id="bms-l1"),
        PcbRoutePlan(net_id="signal", layer_id="bms-l1", width_mm=0.3, topology="chain", via_count=0, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id="bms-l1"),
        PcbRoutePlan(net_id="analog", layer_id="bms-l1", width_mm=0.3, topology="chain", via_count=0, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id="bms-l1"),
        PcbRoutePlan(net_id="hv", layer_id="bms-l1", width_mm=1.2, topology="chain", via_count=0, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id="bms-l1"),
        PcbRoutePlan(net_id="can", layer_id="bms-l1", width_mm=0.3, topology="chain", via_count=0, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id="bms-l1"),
        PcbRoutePlan(net_id="ethernet", layer_id="bms-l1", width_mm=0.25, topology="chain", via_count=0, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id="bms-l1"),
    ]


def bms_slave_overrides():
    return [
        PcbRoutePlan(net_id="bmsA-stack-plus", layer_id="bms-l3", width_mm=1.6, topology="chain", via_count=2, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id="bms-l1"),
        PcbRoutePlan(net_id="bmsA-tap2", layer_id="bms-l3", width_mm=1.6, topology="chain", via_count=2, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id="bms-l1"),
        PcbRoutePlan(net_id="bmsA-tap1", layer_id="bms-l3", width_mm=1.6, topology="chain", via_count=2, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id="bms-l1"),
    ]


def bms_slave_loads():
    board = "bms-slave"
    return [
        pcb_load(board, "U1.1", "U1.5", 0.0135, "stack monitor active supply current"),
        pcb_load(board, "R3.2", "R3.1", 0.127, "bleed resistor body, channel 3"),
        pcb_load(board, "Q3.3", "Q3.2", 0.127, "balancing switch channel, channel 3"),
        pcb_load(board, "R2.2", "R2.1", 0.127, "bleed resistor body, channel 2"),
        pcb_load(board, "Q2.3", "Q2.2", 0.127, "balancing switch channel, channel 2"),
        pcb_load(board, "R1.2", "R1.1", 0.127, "bleed resistor body, channel 1"),
        pcb_load(board, "Q1.3", "Q1.2", 0.127, "balancing switch channel, channel 1"),
        pcb_load(board, "RT1.1", "RT1.2", 0.00025, "thermistor 1 bias current"),
        pcb_load(board, "RT2.1", "RT2.2", 0.00025, "thermistor 2 bias current"),
        pcb_load(board, "T1.1", "T1.2", 0.02, "isoSPI port A transformer primary"),
        pcb_load(board, "J1.1", "J1.2", 0.02, "isoSPI port A cable pair"),
        pcb_load(board, "T2.1", "T2.2", 0.02, "isoSPI port B transformer primary"),
        pcb_load(board, "J2.1", "J2.2", 0.02, "isoSPI port B cable pair"),
        pcb_load(board, "Q1.1", "Q1.2", 0.0002, "gate drive current, channel 1"),
        pcb_load(board, "Q2.1", "Q2.2", 0.0002, "gate drive current, channel 2"),
        pcb_load(board, "Q3.1", "Q3.2", 0.0002, "gate drive current, channel 3"),
    ]


def bms_slave_board() !{}:
    return assemble_board("bms-slave", "BMS cell-monitoring slave board", "battery-management", "battery enclosure, module head 3", "FR-4 Tg170, 4 copper layers, 35 um outer", bms_slave_stackup(), bms_slave_components(), bms_slave_pins(), bms_slave_nets(), bms_slave_plans(), bms_slave_overrides(), bms_slave_loads(), 128.0, 78.0)


def front_zone_stackup():
    layers = [
        PcbLayer(id="fzc-smt", name="Top soldermask", function=PcbLayerFunction.soldermask, copper_um=0.0, dielectric_mm=0.0254, dielectric_er=3.6, material="LPI soldermask"),
        PcbLayer(id="fzc-l1", name="Top signal and power", function=PcbLayerFunction.signal, copper_um=70.0, dielectric_mm=0.0, dielectric_er=1.0, material="electrodeposited copper 2 oz"),
        PcbLayer(id="fzc-pp1", name="Prepreg 1", function=PcbLayerFunction.prepreg, copper_um=0.0, dielectric_mm=0.1016, dielectric_er=4.2, material="1080 FR-4 prepreg"),
        PcbLayer(id="fzc-l2", name="Dedicated ground plane", function=PcbLayerFunction.plane, copper_um=35.0, dielectric_mm=0.0, dielectric_er=1.0, material="rolled copper 1 oz"),
        PcbLayer(id="fzc-core1", name="Core 1", function=PcbLayerFunction.core, copper_um=0.0, dielectric_mm=0.3, dielectric_er=4.5, material="FR-4 Tg170 core"),
        PcbLayer(id="fzc-l3", name="Inner power routing", function=PcbLayerFunction.signal, copper_um=35.0, dielectric_mm=0.0, dielectric_er=1.0, material="rolled copper 1 oz"),
        PcbLayer(id="fzc-pp2", name="Prepreg 2", function=PcbLayerFunction.prepreg, copper_um=0.0, dielectric_mm=0.5, dielectric_er=4.4, material="7628 FR-4 prepreg"),
        PcbLayer(id="fzc-l4", name="Inner signal routing", function=PcbLayerFunction.signal, copper_um=35.0, dielectric_mm=0.0, dielectric_er=1.0, material="rolled copper 1 oz"),
        PcbLayer(id="fzc-core2", name="Core 2", function=PcbLayerFunction.core, copper_um=0.0, dielectric_mm=0.3, dielectric_er=4.5, material="FR-4 Tg170 core"),
        PcbLayer(id="fzc-l5", name="Supply plane", function=PcbLayerFunction.plane, copper_um=35.0, dielectric_mm=0.0, dielectric_er=1.0, material="rolled copper 1 oz"),
        PcbLayer(id="fzc-pp3", name="Prepreg 3", function=PcbLayerFunction.prepreg, copper_um=0.0, dielectric_mm=0.1016, dielectric_er=4.2, material="1080 FR-4 prepreg"),
        PcbLayer(id="fzc-l6", name="Bottom signal and power", function=PcbLayerFunction.signal, copper_um=70.0, dielectric_mm=0.0, dielectric_er=1.0, material="electrodeposited copper 2 oz"),
        PcbLayer(id="fzc-smb", name="Bottom soldermask", function=PcbLayerFunction.soldermask, copper_um=0.0, dielectric_mm=0.0254, dielectric_er=3.6, material="LPI soldermask"),
    ]
    return PcbStackup(id="stk-fzc", name="FR-4 6-layer 1.63 mm, dedicated ground plane", layers=layers, total_thickness_mm=stackup_thickness_mm(layers), ipc_class=3, impedance_target_ohm=100.0)


def front_zone_components():
    return [
        PcbComponent(refdes="U1", part_number="S32K344", description="Lockstep automotive MCU; 12 pins modelled of 172", footprint="LQFP-172", pin_count=172, x_mm=40.0, y_mm=70.0, rotation_deg=0.0, power_dissipation_w=1.05, mass_g=1.4),
        PcbComponent(refdes="U2", part_number="LM5145", description="48 V to 12 V synchronous buck controller", footprint="HTSSOP-20", pin_count=20, x_mm=95.0, y_mm=88.0, rotation_deg=0.0, power_dissipation_w=0.18, mass_g=0.3),
        PcbComponent(refdes="Q1", part_number="BSC098N10NS5", description="Buck high-side switch, 100 V", footprint="PQFN-5x6", pin_count=8, x_mm=108.0, y_mm=96.0, rotation_deg=0.0, power_dissipation_w=0.62, mass_g=0.2),
        PcbComponent(refdes="Q2", part_number="BSC098N10NS5", description="Buck low-side switch, 100 V", footprint="PQFN-5x6", pin_count=8, x_mm=108.0, y_mm=82.0, rotation_deg=0.0, power_dissipation_w=0.88, mass_g=0.2),
        PcbComponent(refdes="L1", part_number="XAL1510-103", description="10 uH shielded power inductor", footprint="IND-15x15", pin_count=2, x_mm=120.0, y_mm=89.0, rotation_deg=0.0, power_dissipation_w=0.71, mass_g=6.4),
        PcbComponent(refdes="C1", part_number="CGA9N3X7S2A106K", description="48 V input bulk capacitor, 10 uF 100 V", footprint="1210", pin_count=2, x_mm=95.0, y_mm=100.0, rotation_deg=0.0, power_dissipation_w=0.02, mass_g=0.1),
        PcbComponent(refdes="C2", part_number="CGA9P1X7R1E476M", description="12 V bulk capacitor, 47 uF 25 V", footprint="1210", pin_count=2, x_mm=130.0, y_mm=100.0, rotation_deg=0.0, power_dissipation_w=0.02, mass_g=0.1),
        PcbComponent(refdes="U6", part_number="TPS7B6950-Q1", description="12 V to 5 V low-dropout regulator", footprint="DPAK", pin_count=3, x_mm=95.0, y_mm=62.0, rotation_deg=0.0, power_dissipation_w=3.85, mass_g=0.4),
        PcbComponent(refdes="C3", part_number="GRM21BR61A106K", description="5 V output capacitor, 10 uF", footprint="0805", pin_count=2, x_mm=105.0, y_mm=55.0, rotation_deg=0.0, power_dissipation_w=0.0, mass_g=0.01),
        PcbComponent(refdes="U7", part_number="TPS7A2033", description="5 V to 3V3 low-dropout regulator", footprint="SOT-23-5", pin_count=5, x_mm=80.0, y_mm=55.0, rotation_deg=0.0, power_dissipation_w=0.73, mass_g=0.02),
        PcbComponent(refdes="C4", part_number="GRM21BR61A106K", description="3V3 output capacitor, 10 uF", footprint="0805", pin_count=2, x_mm=68.0, y_mm=50.0, rotation_deg=0.0, power_dissipation_w=0.0, mass_g=0.01),
        PcbComponent(refdes="U3", part_number="TCAN1462-Q1", description="CAN-FD transceiver, powertrain bus", footprint="SOIC-8", pin_count=8, x_mm=24.0, y_mm=92.0, rotation_deg=0.0, power_dissipation_w=0.21, mass_g=0.07),
        PcbComponent(refdes="U4", part_number="TCAN1462-Q1", description="CAN-FD transceiver, chassis bus", footprint="SOIC-8", pin_count=8, x_mm=24.0, y_mm=76.0, rotation_deg=0.0, power_dissipation_w=0.21, mass_g=0.07),
        PcbComponent(refdes="R1", part_number="CRCW0603120R", description="CAN bus split termination, powertrain", footprint="0603", pin_count=2, x_mm=14.0, y_mm=96.0, rotation_deg=90.0, power_dissipation_w=0.07, mass_g=0.005),
        PcbComponent(refdes="R2", part_number="CRCW0603120R", description="CAN bus split termination, chassis", footprint="0603", pin_count=2, x_mm=14.0, y_mm=72.0, rotation_deg=90.0, power_dissipation_w=0.07, mass_g=0.005),
        PcbComponent(refdes="U5", part_number="TJA1101B", description="100BASE-T1 Ethernet PHY", footprint="HVQFN-36", pin_count=36, x_mm=26.0, y_mm=40.0, rotation_deg=0.0, power_dissipation_w=0.42, mass_g=0.1),
        PcbComponent(refdes="T1", part_number="TP-23015NL", description="100BASE-T1 common-mode choke and magnetics", footprint="SM-4P", pin_count=4, x_mm=12.0, y_mm=40.0, rotation_deg=0.0, power_dissipation_w=0.03, mass_g=0.4),
        PcbComponent(refdes="U8", part_number="VN7016AJ", description="High-side eFuse channel 1, low-beam lamp", footprint="PowerSSO-12", pin_count=12, x_mm=120.0, y_mm=62.0, rotation_deg=0.0, power_dissipation_w=0.24, mass_g=0.3),
        PcbComponent(refdes="U9", part_number="VN7016AJ", description="High-side eFuse channel 2, washer heater", footprint="PowerSSO-12", pin_count=12, x_mm=120.0, y_mm=44.0, rotation_deg=0.0, power_dissipation_w=0.17, mass_g=0.3),
        PcbComponent(refdes="U10", part_number="VN7016AJ", description="High-side eFuse channel 3, grille shutter actuator", footprint="PowerSSO-12", pin_count=12, x_mm=120.0, y_mm=26.0, rotation_deg=0.0, power_dissipation_w=0.11, mass_g=0.3),
        PcbComponent(refdes="J1", part_number="MX150-10", description="Zone supply, CAN and Ethernet header", footprint="MOLEX-MX150-10", pin_count=10, x_mm=139.0, y_mm=86.0, rotation_deg=270.0, power_dissipation_w=0.0, mass_g=8.0),
        PcbComponent(refdes="J2", part_number="MX150-7", description="Switched load output header", footprint="MOLEX-MX150-7", pin_count=7, x_mm=139.0, y_mm=34.0, rotation_deg=270.0, power_dissipation_w=0.0, mass_g=6.0),
    ]


def front_zone_pins():
    return [
        pcb_pin("U1", 1, "VDD_3V3", "fzc-3v3", 33.0, 77.0, PcbPinKind.power),
        pcb_pin("U1", 2, "VSS", "fzc-gnd", 33.0, 73.0, PcbPinKind.ground),
        pcb_pin("U1", 3, "CAN0_TX", "fzc-can0-tx", 33.0, 69.0, PcbPinKind.signal),
        pcb_pin("U1", 4, "CAN0_RX", "fzc-can0-rx", 33.0, 65.0, PcbPinKind.signal),
        pcb_pin("U1", 5, "CAN1_TX", "fzc-can1-tx", 33.0, 63.0, PcbPinKind.signal),
        pcb_pin("U1", 6, "CAN1_RX", "fzc-can1-rx", 37.0, 63.0, PcbPinKind.signal),
        pcb_pin("U1", 7, "MII_TXD", "fzc-phy-txd", 41.0, 63.0, PcbPinKind.signal),
        pcb_pin("U1", 8, "MII_RXD", "fzc-phy-rxd", 45.0, 63.0, PcbPinKind.signal),
        pcb_pin("U1", 9, "PWM_HS1", "fzc-hs1-in", 47.0, 65.0, PcbPinKind.signal),
        pcb_pin("U1", 10, "ADC_ISENSE", "fzc-hs-sense", 47.0, 69.0, PcbPinKind.analog),
        pcb_pin("U1", 11, "PWM_HS2", "fzc-hs2-in", 47.0, 73.0, PcbPinKind.signal),
        pcb_pin("U1", 12, "PWM_HS3", "fzc-hs3-in", 47.0, 77.0, PcbPinKind.signal),
        pcb_pin("U2", 1, "VIN", "fzc-48v", 89.0, 92.0, PcbPinKind.power),
        pcb_pin("U2", 2, "HO", "fzc-gate-hs", 101.0, 92.0, PcbPinKind.signal),
        pcb_pin("U2", 3, "SW", "fzc-sw", 101.0, 88.0, PcbPinKind.power),
        pcb_pin("U2", 4, "LO", "fzc-gate-ls", 101.0, 84.0, PcbPinKind.signal),
        pcb_pin("U2", 5, "AGND", "fzc-gnd", 89.0, 84.0, PcbPinKind.ground),
        pcb_pin("Q1", 1, "D", "fzc-48v", 108.0, 100.0, PcbPinKind.power),
        pcb_pin("Q1", 2, "G", "fzc-gate-hs", 104.0, 96.0, PcbPinKind.signal),
        pcb_pin("Q1", 3, "S", "fzc-sw", 108.0, 92.0, PcbPinKind.power),
        pcb_pin("Q2", 1, "D", "fzc-sw", 108.0, 86.0, PcbPinKind.power),
        pcb_pin("Q2", 2, "G", "fzc-gate-ls", 104.0, 82.0, PcbPinKind.signal),
        pcb_pin("Q2", 3, "S", "fzc-gnd", 108.0, 78.0, PcbPinKind.ground),
        pcb_pin("L1", 1, "A", "fzc-sw", 114.0, 89.0, PcbPinKind.power),
        pcb_pin("L1", 2, "B", "fzc-12v", 126.0, 89.0, PcbPinKind.power),
        pcb_pin("C1", 1, "A", "fzc-48v", 92.0, 100.0, PcbPinKind.power),
        pcb_pin("C1", 2, "B", "fzc-gnd", 98.0, 100.0, PcbPinKind.ground),
        pcb_pin("C2", 1, "A", "fzc-12v", 127.0, 100.0, PcbPinKind.power),
        pcb_pin("C2", 2, "B", "fzc-gnd", 133.0, 100.0, PcbPinKind.ground),
        pcb_pin("U6", 1, "IN", "fzc-12v", 95.0, 66.0, PcbPinKind.power),
        pcb_pin("U6", 2, "GND", "fzc-gnd", 91.0, 60.0, PcbPinKind.ground),
        pcb_pin("U6", 3, "OUT", "fzc-5v", 99.0, 60.0, PcbPinKind.power),
        pcb_pin("C3", 1, "A", "fzc-5v", 103.0, 55.0, PcbPinKind.power),
        pcb_pin("C3", 2, "B", "fzc-gnd", 107.0, 55.0, PcbPinKind.ground),
        pcb_pin("U7", 1, "IN", "fzc-5v", 84.0, 57.0, PcbPinKind.power),
        pcb_pin("U7", 2, "GND", "fzc-gnd", 84.0, 53.0, PcbPinKind.ground),
        pcb_pin("U7", 3, "OUT", "fzc-3v3", 76.0, 53.0, PcbPinKind.power),
        pcb_pin("U7", 4, "EN", "fzc-5v", 76.0, 57.0, PcbPinKind.signal),
        pcb_pin("C4", 1, "A", "fzc-3v3", 66.0, 50.0, PcbPinKind.power),
        pcb_pin("C4", 2, "B", "fzc-gnd", 70.0, 50.0, PcbPinKind.ground),
        pcb_pin("U3", 1, "TXD", "fzc-can0-tx", 29.0, 95.0, PcbPinKind.signal),
        pcb_pin("U3", 2, "GND", "fzc-gnd", 29.0, 92.0, PcbPinKind.ground),
        pcb_pin("U3", 3, "VCC", "fzc-5v", 29.0, 89.0, PcbPinKind.power),
        pcb_pin("U3", 4, "RXD", "fzc-can0-rx", 32.0, 95.0, PcbPinKind.signal),
        pcb_pin("U3", 5, "CANH", "fzc-can0-h", 19.0, 95.0, PcbPinKind.signal),
        pcb_pin("U3", 6, "CANL", "fzc-can0-l", 19.0, 92.0, PcbPinKind.signal),
        pcb_pin("U4", 1, "TXD", "fzc-can1-tx", 29.0, 79.0, PcbPinKind.signal),
        pcb_pin("U4", 2, "GND", "fzc-gnd", 29.0, 76.0, PcbPinKind.ground),
        pcb_pin("U4", 3, "VCC", "fzc-5v", 29.0, 73.0, PcbPinKind.power),
        pcb_pin("U4", 4, "RXD", "fzc-can1-rx", 32.0, 79.0, PcbPinKind.signal),
        pcb_pin("U4", 5, "CANH", "fzc-can1-h", 19.0, 79.0, PcbPinKind.signal),
        pcb_pin("U4", 6, "CANL", "fzc-can1-l", 19.0, 76.0, PcbPinKind.signal),
        pcb_pin("R1", 1, "A", "fzc-can0-h", 14.0, 97.6, PcbPinKind.signal),
        pcb_pin("R1", 2, "B", "fzc-can0-l", 14.0, 94.4, PcbPinKind.signal),
        pcb_pin("R2", 1, "A", "fzc-can1-h", 14.0, 73.6, PcbPinKind.signal),
        pcb_pin("R2", 2, "B", "fzc-can1-l", 14.0, 70.4, PcbPinKind.signal),
        pcb_pin("U5", 1, "VDDD", "fzc-3v3", 31.0, 45.0, PcbPinKind.power),
        pcb_pin("U5", 2, "VSS", "fzc-gnd", 31.0, 41.0, PcbPinKind.ground),
        pcb_pin("U5", 3, "TRX_P", "fzc-eth-p", 21.0, 42.0, PcbPinKind.signal),
        pcb_pin("U5", 4, "TRX_N", "fzc-eth-n", 21.0, 38.0, PcbPinKind.signal),
        pcb_pin("U5", 5, "MII_RXD", "fzc-phy-txd", 31.0, 37.0, PcbPinKind.signal),
        pcb_pin("U5", 6, "MII_TXD", "fzc-phy-rxd", 31.0, 34.0, PcbPinKind.signal),
        pcb_pin("T1", 1, "P1", "fzc-eth-p", 16.0, 42.0, PcbPinKind.signal),
        pcb_pin("T1", 2, "P2", "fzc-eth-n", 16.0, 38.0, PcbPinKind.signal),
        pcb_pin("T1", 3, "S1", "fzc-cable-eth-p", 8.0, 42.0, PcbPinKind.signal),
        pcb_pin("T1", 4, "S2", "fzc-cable-eth-n", 8.0, 38.0, PcbPinKind.signal),
        pcb_pin("U8", 1, "VCC", "fzc-12v", 116.0, 66.0, PcbPinKind.power),
        pcb_pin("U8", 2, "IN", "fzc-hs1-in", 116.0, 62.0, PcbPinKind.signal),
        pcb_pin("U8", 3, "OUT", "fzc-out1", 124.0, 62.0, PcbPinKind.power),
        pcb_pin("U8", 4, "GND", "fzc-gnd", 116.0, 58.0, PcbPinKind.ground),
        pcb_pin("U8", 5, "CS", "fzc-hs-sense", 124.0, 58.0, PcbPinKind.analog),
        pcb_pin("U9", 1, "VCC", "fzc-12v", 116.0, 48.0, PcbPinKind.power),
        pcb_pin("U9", 2, "IN", "fzc-hs2-in", 116.0, 44.0, PcbPinKind.signal),
        pcb_pin("U9", 3, "OUT", "fzc-out2", 124.0, 44.0, PcbPinKind.power),
        pcb_pin("U9", 4, "GND", "fzc-gnd", 116.0, 40.0, PcbPinKind.ground),
        pcb_pin("U10", 1, "VCC", "fzc-12v", 116.0, 30.0, PcbPinKind.power),
        pcb_pin("U10", 2, "IN", "fzc-hs3-in", 116.0, 26.0, PcbPinKind.signal),
        pcb_pin("U10", 3, "OUT", "fzc-out3", 124.0, 26.0, PcbPinKind.power),
        pcb_pin("U10", 4, "GND", "fzc-gnd", 116.0, 22.0, PcbPinKind.ground),
        pcb_pin("J1", 1, "48V_A", "fzc-48v", 136.0, 104.0, PcbPinKind.power),
        pcb_pin("J1", 2, "48V_B", "fzc-48v", 136.0, 100.0, PcbPinKind.power),
        pcb_pin("J1", 3, "GND_A", "fzc-gnd", 136.0, 96.0, PcbPinKind.ground),
        pcb_pin("J1", 4, "GND_B", "fzc-gnd", 136.0, 92.0, PcbPinKind.ground),
        pcb_pin("J1", 5, "CAN0_H", "fzc-can0-h", 136.0, 88.0, PcbPinKind.signal),
        pcb_pin("J1", 6, "CAN0_L", "fzc-can0-l", 136.0, 84.0, PcbPinKind.signal),
        pcb_pin("J1", 7, "CAN1_H", "fzc-can1-h", 136.0, 80.0, PcbPinKind.signal),
        pcb_pin("J1", 8, "CAN1_L", "fzc-can1-l", 136.0, 76.0, PcbPinKind.signal),
        pcb_pin("J1", 9, "ETH_P", "fzc-cable-eth-p", 136.0, 72.0, PcbPinKind.signal),
        pcb_pin("J1", 10, "ETH_N", "fzc-cable-eth-n", 136.0, 68.0, PcbPinKind.signal),
        pcb_pin("J2", 1, "OUT1", "fzc-out1", 136.0, 46.0, PcbPinKind.power),
        pcb_pin("J2", 2, "OUT2", "fzc-out2", 136.0, 42.0, PcbPinKind.power),
        pcb_pin("J2", 3, "OUT3", "fzc-out3", 136.0, 38.0, PcbPinKind.power),
        pcb_pin("J2", 4, "RTN1", "fzc-gnd", 136.0, 34.0, PcbPinKind.ground),
        pcb_pin("J2", 5, "RTN2", "fzc-gnd", 136.0, 30.0, PcbPinKind.ground),
        pcb_pin("J2", 6, "RTN3", "fzc-gnd", 136.0, 26.0, PcbPinKind.ground),
        pcb_pin("J2", 7, "RTN4", "fzc-gnd", 136.0, 22.0, PcbPinKind.ground),
    ]


def front_zone_12v_load_a():
    return FZC_OUT1_A + FZC_OUT2_A + FZC_OUT3_A + front_zone_5v_load_a()


def front_zone_5v_load_a():
    return FZC_CAN_SUPPLY_A + FZC_CAN_SUPPLY_A + front_zone_3v3_load_a()


def front_zone_3v3_load_a():
    return FZC_MCU_A + FZC_PHY_A


def front_zone_48v_load_a():
    return 12.0 * front_zone_12v_load_a() / (48.0 * BUCK_EFFICIENCY)


def front_zone_nets():
    return [
        PcbNet(id="fzc-48v", name="48 V zonal supply input", net_class=PcbNetClass.power, pin_ids=["J1.1", "J1.2", "C1.1", "U2.1", "Q1.1"], nominal_voltage_v=48.0, current_a=front_zone_48v_load_a(), is_reference=false),
        PcbNet(id="fzc-sw", name="Buck switch node", net_class=PcbNetClass.power, pin_ids=["Q1.3", "Q2.1", "U2.3", "L1.1"], nominal_voltage_v=12.0, current_a=front_zone_12v_load_a(), is_reference=false),
        PcbNet(id="fzc-gate-hs", name="High-side gate drive", net_class=PcbNetClass.signal, pin_ids=["U2.2", "Q1.2"], nominal_voltage_v=12.0, current_a=0.012, is_reference=false),
        PcbNet(id="fzc-gate-ls", name="Low-side gate drive", net_class=PcbNetClass.signal, pin_ids=["U2.4", "Q2.2"], nominal_voltage_v=12.0, current_a=0.012, is_reference=false),
        PcbNet(id="fzc-12v", name="12 V distribution rail", net_class=PcbNetClass.power, pin_ids=["L1.2", "C2.1", "U8.1", "U9.1", "U10.1", "U6.1"], nominal_voltage_v=12.0, current_a=front_zone_12v_load_a(), is_reference=false),
        PcbNet(id="fzc-5v", name="5 V transceiver rail", net_class=PcbNetClass.power, pin_ids=["U6.3", "C3.1", "U7.1", "U7.4", "U3.3", "U4.3"], nominal_voltage_v=5.0, current_a=front_zone_5v_load_a(), is_reference=false),
        PcbNet(id="fzc-3v3", name="3V3 logic rail", net_class=PcbNetClass.power, pin_ids=["U7.3", "C4.1", "U1.1", "U5.1"], nominal_voltage_v=3.3, current_a=front_zone_3v3_load_a(), is_reference=false),
        PcbNet(id="fzc-gnd", name="Zone controller ground", net_class=PcbNetClass.ground, pin_ids=["J1.3", "J1.4", "C1.2", "U2.5", "Q2.3", "C2.2", "U6.2", "C3.2", "U7.2", "C4.2", "U1.2", "U3.2", "U4.2", "U5.2", "U8.4", "U9.4", "U10.4", "J2.4", "J2.5", "J2.6", "J2.7"], nominal_voltage_v=0.0, current_a=front_zone_12v_load_a(), is_reference=true),
        PcbNet(id="fzc-can0-tx", name="CAN0 transmit", net_class=PcbNetClass.signal, pin_ids=["U1.3", "U3.1"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="fzc-can0-rx", name="CAN0 receive", net_class=PcbNetClass.signal, pin_ids=["U3.4", "U1.4"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="fzc-can1-tx", name="CAN1 transmit", net_class=PcbNetClass.signal, pin_ids=["U1.5", "U4.1"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="fzc-can1-rx", name="CAN1 receive", net_class=PcbNetClass.signal, pin_ids=["U4.4", "U1.6"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="fzc-can0-h", name="CAN0 bus high", net_class=PcbNetClass.can, pin_ids=["U3.5", "R1.1", "J1.5"], nominal_voltage_v=3.5, current_a=0.0333, is_reference=false),
        PcbNet(id="fzc-can0-l", name="CAN0 bus low", net_class=PcbNetClass.can, pin_ids=["U3.6", "R1.2", "J1.6"], nominal_voltage_v=1.5, current_a=0.0333, is_reference=false),
        PcbNet(id="fzc-can1-h", name="CAN1 bus high", net_class=PcbNetClass.can, pin_ids=["U4.5", "R2.1", "J1.7"], nominal_voltage_v=3.5, current_a=0.0333, is_reference=false),
        PcbNet(id="fzc-can1-l", name="CAN1 bus low", net_class=PcbNetClass.can, pin_ids=["U4.6", "R2.2", "J1.8"], nominal_voltage_v=1.5, current_a=0.0333, is_reference=false),
        PcbNet(id="fzc-phy-txd", name="PHY transmit data", net_class=PcbNetClass.signal, pin_ids=["U1.7", "U5.5"], nominal_voltage_v=3.3, current_a=0.0005, is_reference=false),
        PcbNet(id="fzc-phy-rxd", name="PHY receive data", net_class=PcbNetClass.signal, pin_ids=["U5.6", "U1.8"], nominal_voltage_v=3.3, current_a=0.0005, is_reference=false),
        PcbNet(id="fzc-eth-p", name="100BASE-T1 pair positive", net_class=PcbNetClass.ethernet, pin_ids=["U5.3", "T1.1"], nominal_voltage_v=1.1, current_a=0.011, is_reference=false),
        PcbNet(id="fzc-eth-n", name="100BASE-T1 pair negative", net_class=PcbNetClass.ethernet, pin_ids=["U5.4", "T1.2"], nominal_voltage_v=0.0, current_a=0.011, is_reference=false),
        PcbNet(id="fzc-cable-eth-p", name="Ethernet cable pair positive", net_class=PcbNetClass.ethernet, pin_ids=["T1.3", "J1.9"], nominal_voltage_v=1.1, current_a=0.011, is_reference=false),
        PcbNet(id="fzc-cable-eth-n", name="Ethernet cable pair negative", net_class=PcbNetClass.ethernet, pin_ids=["T1.4", "J1.10"], nominal_voltage_v=0.0, current_a=0.011, is_reference=false),
        PcbNet(id="fzc-hs1-in", name="eFuse channel 1 command", net_class=PcbNetClass.signal, pin_ids=["U1.9", "U8.2"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="fzc-hs2-in", name="eFuse channel 2 command", net_class=PcbNetClass.signal, pin_ids=["U1.11", "U9.2"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="fzc-hs3-in", name="eFuse channel 3 command", net_class=PcbNetClass.signal, pin_ids=["U1.12", "U10.2"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="fzc-hs-sense", name="eFuse current-sense feedback", net_class=PcbNetClass.analog, pin_ids=["U8.5", "U1.10"], nominal_voltage_v=1.5, current_a=0.0012, is_reference=false),
        PcbNet(id="fzc-out1", name="Switched output 1, low-beam lamp", net_class=PcbNetClass.power, pin_ids=["U8.3", "J2.1"], nominal_voltage_v=12.0, current_a=FZC_OUT1_A, is_reference=false),
        PcbNet(id="fzc-out2", name="Switched output 2, washer heater", net_class=PcbNetClass.power, pin_ids=["U9.3", "J2.2"], nominal_voltage_v=12.0, current_a=FZC_OUT2_A, is_reference=false),
        PcbNet(id="fzc-out3", name="Switched output 3, grille shutter actuator", net_class=PcbNetClass.power, pin_ids=["U10.3", "J2.3"], nominal_voltage_v=12.0, current_a=FZC_OUT3_A, is_reference=false),
    ]


def front_zone_plans():
    return [
        PcbRoutePlan(net_id="power", layer_id="fzc-l1", width_mm=2.5, topology="chain", via_count=2, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l5"),
        PcbRoutePlan(net_id="ground", layer_id="fzc-l2", width_mm=6.0, topology="star", via_count=2, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="signal", layer_id="fzc-l4", width_mm=0.25, topology="chain", via_count=1, via_drill_mm=0.25, via_pad_mm=0.5, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="analog", layer_id="fzc-l4", width_mm=0.3, topology="chain", via_count=1, via_drill_mm=0.25, via_pad_mm=0.5, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="can", layer_id="fzc-l1", width_mm=0.35, topology="chain", via_count=0, via_drill_mm=0.25, via_pad_mm=0.5, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="ethernet", layer_id="fzc-l1", width_mm=0.28, topology="chain", via_count=0, via_drill_mm=0.25, via_pad_mm=0.5, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="hv", layer_id="fzc-l1", width_mm=3.0, topology="chain", via_count=2, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l5"),
    ]


def front_zone_overrides():
    return [
        PcbRoutePlan(net_id="fzc-sw", layer_id="fzc-l1", width_mm=5.0, topology="chain", via_count=0, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="fzc-12v", layer_id="fzc-l1", width_mm=4.0, topology="chain", via_count=0, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="fzc-48v", layer_id="fzc-l1", width_mm=2.5, topology="chain", via_count=0, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="fzc-5v", layer_id="fzc-l1", width_mm=1.2, topology="chain", via_count=0, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="fzc-3v3", layer_id="fzc-l1", width_mm=1.2, topology="chain", via_count=0, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l1"),
        # Channel 1 is routed on an inner layer where IPC-2221 halves the sizing
        # constant; 2.35 mm is the deliberate marginal case the DRC has to catch.
        PcbRoutePlan(net_id="fzc-out1", layer_id="fzc-l3", width_mm=2.35, topology="chain", via_count=4, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="fzc-out2", layer_id="fzc-l3", width_mm=2.6, topology="chain", via_count=4, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l1"),
        PcbRoutePlan(net_id="fzc-out3", layer_id="fzc-l3", width_mm=2.2, topology="chain", via_count=4, via_drill_mm=0.35, via_pad_mm=0.7, via_layer_id="fzc-l1"),
    ]


def front_zone_loads():
    board = "front-zone-controller"
    return [
        pcb_load(board, "Q1.1", "Q1.3", front_zone_48v_load_a(), "buck high-side switch, average conduction"),
        pcb_load(board, "Q2.3", "Q2.1", front_zone_12v_load_a() - front_zone_48v_load_a(), "buck low-side switch, average freewheel"),
        pcb_load(board, "L1.1", "L1.2", front_zone_12v_load_a(), "buck output inductor"),
        pcb_load(board, "U6.1", "U6.3", front_zone_5v_load_a(), "12 V to 5 V regulator pass element"),
        pcb_load(board, "U7.1", "U7.3", front_zone_3v3_load_a(), "5 V to 3V3 regulator pass element"),
        pcb_load(board, "U1.1", "U1.2", FZC_MCU_A, "MCU core and I/O supply"),
        pcb_load(board, "U5.1", "U5.2", FZC_PHY_A, "Ethernet PHY supply"),
        pcb_load(board, "U3.3", "U3.2", FZC_CAN_SUPPLY_A, "CAN0 transceiver supply"),
        pcb_load(board, "U4.3", "U4.2", FZC_CAN_SUPPLY_A, "CAN1 transceiver supply"),
        pcb_load(board, "U8.1", "U8.3", FZC_OUT1_A, "eFuse channel 1 pass element"),
        pcb_load(board, "U9.1", "U9.3", FZC_OUT2_A, "eFuse channel 2 pass element"),
        pcb_load(board, "U10.1", "U10.3", FZC_OUT3_A, "eFuse channel 3 pass element"),
        pcb_load(board, "J2.1", "J2.4", FZC_OUT1_A, "low-beam lamp module"),
        pcb_load(board, "J2.2", "J2.5", FZC_OUT2_A, "washer nozzle heater"),
        pcb_load(board, "J2.3", "J2.6", FZC_OUT3_A, "grille shutter actuator"),
        pcb_load(board, "R1.1", "R1.2", 0.0333, "CAN0 split termination, dominant state"),
        pcb_load(board, "R2.1", "R2.2", 0.0333, "CAN1 split termination, dominant state"),
        pcb_load(board, "T1.1", "T1.2", 0.011, "100BASE-T1 magnetics primary"),
        pcb_load(board, "J1.9", "J1.10", 0.011, "100BASE-T1 cable pair"),
        pcb_load(board, "Q1.2", "Q1.3", 0.012, "high-side gate charge, averaged"),
        pcb_load(board, "Q2.2", "Q2.3", 0.012, "low-side gate charge, averaged"),
        pcb_load(board, "U3.1", "U3.2", 0.00025, "CAN0 transmit line switching current"),
        pcb_load(board, "U1.4", "U1.2", 0.00025, "CAN0 receive line switching current"),
        pcb_load(board, "U4.1", "U4.2", 0.00025, "CAN1 transmit line switching current"),
        pcb_load(board, "U1.6", "U1.2", 0.00025, "CAN1 receive line switching current"),
        pcb_load(board, "U5.5", "U5.2", 0.0005, "MII transmit switching current"),
        pcb_load(board, "U1.8", "U1.2", 0.0005, "MII receive switching current"),
        pcb_load(board, "U8.2", "U8.4", 0.00025, "eFuse 1 command input"),
        pcb_load(board, "U9.2", "U9.4", 0.00025, "eFuse 2 command input"),
        pcb_load(board, "U10.2", "U10.4", 0.00025, "eFuse 3 command input"),
        pcb_load(board, "U1.10", "U1.2", 0.0012, "current-sense feedback into the ADC"),
    ]


def front_zone_board() !{}:
    return assemble_board("front-zone-controller", "Front zone controller board", "front-zone-controller", "front-left plenum, behind the wheel-arch liner", "FR-4 Tg170, 6 copper layers, dedicated ground plane", front_zone_stackup(), front_zone_components(), front_zone_pins(), front_zone_nets(), front_zone_plans(), front_zone_overrides(), front_zone_loads(), 145.0, 110.0)


def hv_junction_stackup():
    layers = [
        PcbLayer(id="hvj-bus", name="Bonded HV busbar", function=PcbLayerFunction.signal, copper_um=2000.0, dielectric_mm=0.0, dielectric_er=1.0, material="2.0 mm laminated copper busbar bonded to top copper"),
        PcbLayer(id="hvj-smt", name="Top soldermask", function=PcbLayerFunction.soldermask, copper_um=0.0, dielectric_mm=0.0254, dielectric_er=3.6, material="high-CTI LPI soldermask"),
        PcbLayer(id="hvj-l1", name="Top control and HV sense", function=PcbLayerFunction.signal, copper_um=105.0, dielectric_mm=0.0, dielectric_er=1.0, material="heavy copper 3 oz"),
        PcbLayer(id="hvj-d1", name="Interlayer dielectric", function=PcbLayerFunction.prepreg, copper_um=0.0, dielectric_mm=0.1016, dielectric_er=4.6, material="high-Tg thermal prepreg"),
        PcbLayer(id="hvj-l2", name="LV reference plane", function=PcbLayerFunction.plane, copper_um=105.0, dielectric_mm=0.0, dielectric_er=1.0, material="heavy copper 3 oz"),
        PcbLayer(id="hvj-d2", name="Thermally conductive dielectric", function=PcbLayerFunction.prepreg, copper_um=0.0, dielectric_mm=0.15, dielectric_er=8.0, material="ceramic-filled thermal dielectric"),
        PcbLayer(id="hvj-base", name="Aluminium base plate", function=PcbLayerFunction.core, copper_um=0.0, dielectric_mm=1.5, dielectric_er=1.0, material="5052 aluminium base plate"),
    ]
    return PcbStackup(id="stk-hvj", name="IMS heavy-copper 2-layer with bonded 2 mm busbars", layers=layers, total_thickness_mm=stackup_thickness_mm(layers), ipc_class=3, impedance_target_ohm=100.0)


def hv_junction_components():
    return [
        PcbComponent(refdes="TB1", part_number="BUSBAR-M8-2P", description="HV pack input terminal block", footprint="BUSBAR-M8-2P", pin_count=2, x_mm=172.0, y_mm=60.0, rotation_deg=0.0, power_dissipation_w=0.4, mass_g=48.0),
        PcbComponent(refdes="TB2", part_number="BUSBAR-M8-2P", description="HV output terminal block to the drive units", footprint="BUSBAR-M8-2P", pin_count=2, x_mm=52.0, y_mm=60.0, rotation_deg=0.0, power_dissipation_w=0.4, mass_g=48.0),
        PcbComponent(refdes="K1", part_number="GV12-800-1A", description="Main positive HV contactor, 800 V DC", footprint="CONTACTOR-GV12", pin_count=4, x_mm=140.0, y_mm=100.0, rotation_deg=0.0, power_dissipation_w=5.4, mass_g=340.0),
        PcbComponent(refdes="K2", part_number="GV12-800-1A", description="Main negative HV contactor, 800 V DC", footprint="CONTACTOR-GV12", pin_count=4, x_mm=140.0, y_mm=20.0, rotation_deg=0.0, power_dissipation_w=5.4, mass_g=340.0),
        PcbComponent(refdes="F1", part_number="PYROFUSE-PF800", description="Pyrotechnic disconnect in the positive rail", footprint="PYRO-M8", pin_count=4, x_mm=90.0, y_mm=100.0, rotation_deg=0.0, power_dissipation_w=2.1, mass_g=180.0),
        PcbComponent(refdes="RS1", part_number="FL-100U-800", description="100 uOhm coaxial current shunt in the negative rail", footprint="SHUNT-4T", pin_count=4, x_mm=90.0, y_mm=20.0, rotation_deg=0.0, power_dissipation_w=2.56, mass_g=95.0),
        PcbComponent(refdes="U1", part_number="AMC1311-Q1", description="Reinforced isolation amplifier for shunt current sense", footprint="SOIC-8W", pin_count=8, x_mm=30.0, y_mm=20.0, rotation_deg=0.0, power_dissipation_w=0.05, mass_g=0.08),
        PcbComponent(refdes="U2", part_number="ISO224-Q1", description="Reinforced isolation amplifier, insulation-monitor front end", footprint="SOIC-8W", pin_count=8, x_mm=30.0, y_mm=60.0, rotation_deg=0.0, power_dissipation_w=0.05, mass_g=0.08),
        PcbComponent(refdes="T1", part_number="750313638", description="Isolated supply transformer for the HV-referenced side", footprint="SM-4P", pin_count=4, x_mm=30.0, y_mm=42.0, rotation_deg=0.0, power_dissipation_w=0.09, mass_g=1.8),
        PcbComponent(refdes="U3", part_number="TPS7B6950-Q1", description="12 V to 5 V low-dropout regulator", footprint="DPAK", pin_count=3, x_mm=12.0, y_mm=82.0, rotation_deg=0.0, power_dissipation_w=0.15, mass_g=0.4),
        PcbComponent(refdes="Q1", part_number="IPB017N10N5", description="Contactor 1 low-side coil driver", footprint="D2PAK", pin_count=3, x_mm=12.0, y_mm=92.0, rotation_deg=0.0, power_dissipation_w=0.26, mass_g=1.6),
        PcbComponent(refdes="Q2", part_number="IPB017N10N5", description="Contactor 2 low-side coil driver", footprint="D2PAK", pin_count=3, x_mm=12.0, y_mm=102.0, rotation_deg=0.0, power_dissipation_w=0.26, mass_g=1.6),
        PcbComponent(refdes="Q3", part_number="IAUC100N04S6L", description="Pyrotechnic squib trigger driver", footprint="DPAK", pin_count=3, x_mm=12.0, y_mm=112.0, rotation_deg=0.0, power_dissipation_w=0.02, mass_g=0.4),
        PcbComponent(refdes="R1", part_number="HVD-2M2-4S", description="Insulation-monitor divider, four series HV elements", footprint="RES-HV-2512x4", pin_count=2, x_mm=60.0, y_mm=74.0, rotation_deg=0.0, power_dissipation_w=0.27, mass_g=0.12),
        PcbComponent(refdes="R2", part_number="CRCW2512-100K", description="Insulation-monitor divider low-side element", footprint="2512", pin_count=2, x_mm=57.0, y_mm=66.0, rotation_deg=0.0, power_dissipation_w=0.01, mass_g=0.03),
        PcbComponent(refdes="J1", part_number="MX150-10", description="LV control and diagnostics header", footprint="MOLEX-MX150-10", pin_count=10, x_mm=6.0, y_mm=35.0, rotation_deg=270.0, power_dissipation_w=0.0, mass_g=8.0),
        PcbComponent(refdes="J2", part_number="HVIL-2", description="High-voltage interlock loop connector", footprint="HVIL-2", pin_count=2, x_mm=6.0, y_mm=77.0, rotation_deg=270.0, power_dissipation_w=0.0, mass_g=2.4),
    ]


def hv_junction_pins():
    return [
        pcb_pin("TB1", 1, "PACK+", "hvj-hv-pack-p", 172.0, 100.0, PcbPinKind.power),
        pcb_pin("TB1", 2, "PACK-", "hvj-hv-pack-n", 172.0, 20.0, PcbPinKind.power),
        pcb_pin("K1", 1, "COIL+", "hvj-12v", 132.0, 116.0, PcbPinKind.power),
        pcb_pin("K1", 2, "COIL-", "hvj-coil1-lo", 120.0, 116.0, PcbPinKind.power),
        pcb_pin("K1", 3, "HV_IN", "hvj-hv-pack-p", 156.0, 100.0, PcbPinKind.power),
        pcb_pin("K1", 4, "HV_OUT", "hvj-hv-bus-p", 124.0, 100.0, PcbPinKind.power),
        pcb_pin("K2", 1, "COIL+", "hvj-12v", 132.0, 4.0, PcbPinKind.power),
        pcb_pin("K2", 2, "COIL-", "hvj-coil2-lo", 120.0, 4.0, PcbPinKind.power),
        pcb_pin("K2", 3, "HV_IN", "hvj-hv-pack-n", 156.0, 20.0, PcbPinKind.power),
        pcb_pin("K2", 4, "HV_OUT", "hvj-hv-bus-n", 124.0, 20.0, PcbPinKind.power),
        pcb_pin("F1", 1, "HV_IN", "hvj-hv-bus-p", 108.0, 100.0, PcbPinKind.power),
        pcb_pin("F1", 2, "HV_OUT", "hvj-hv-out-p", 72.0, 100.0, PcbPinKind.power),
        pcb_pin("F1", 3, "TRIG+", "hvj-12v", 90.0, 116.0, PcbPinKind.power),
        pcb_pin("F1", 4, "TRIG-", "hvj-pyro-lo", 78.0, 116.0, PcbPinKind.power),
        pcb_pin("RS1", 1, "HV_IN", "hvj-hv-bus-n", 108.0, 20.0, PcbPinKind.power),
        pcb_pin("RS1", 2, "HV_OUT", "hvj-hv-out-n", 72.0, 20.0, PcbPinKind.power),
        pcb_pin("RS1", 3, "SENSE+", "hvj-shunt-p", 99.0, 9.0, PcbPinKind.analog),
        pcb_pin("RS1", 4, "SENSE-", "hvj-shunt-n", 81.0, 9.0, PcbPinKind.analog),
        pcb_pin("TB2", 1, "OUT+", "hvj-hv-out-p", 52.0, 100.0, PcbPinKind.power),
        pcb_pin("TB2", 2, "OUT-", "hvj-hv-out-n", 52.0, 20.0, PcbPinKind.power),
        pcb_pin("R1", 1, "A", "hvj-hv-out-p", 66.0, 74.0, PcbPinKind.analog),
        pcb_pin("R1", 2, "B", "hvj-imd-node", 54.0, 74.0, PcbPinKind.analog),
        pcb_pin("R2", 1, "A", "hvj-imd-node", 60.0, 66.0, PcbPinKind.analog),
        pcb_pin("R2", 2, "B", "hvj-iso-gnd", 54.0, 66.0, PcbPinKind.ground),
        pcb_pin("U1", 1, "VDD1", "hvj-iso-5v", 38.0, 28.0, PcbPinKind.power),
        pcb_pin("U1", 2, "GND1", "hvj-iso-gnd", 38.0, 24.0, PcbPinKind.ground),
        pcb_pin("U1", 3, "INP", "hvj-shunt-p", 38.0, 20.0, PcbPinKind.analog),
        pcb_pin("U1", 4, "INN", "hvj-shunt-n", 38.0, 16.0, PcbPinKind.analog),
        pcb_pin("U1", 5, "VDD2", "hvj-5v", 22.0, 22.0, PcbPinKind.power),
        pcb_pin("U1", 6, "OUTP", "hvj-isense-out", 22.0, 18.0, PcbPinKind.analog),
        pcb_pin("U1", 7, "GND2", "hvj-gnd", 22.0, 14.0, PcbPinKind.ground),
        pcb_pin("U2", 1, "VDD1", "hvj-iso-5v", 38.0, 66.0, PcbPinKind.power),
        pcb_pin("U2", 2, "GND1", "hvj-iso-gnd", 38.0, 62.0, PcbPinKind.ground),
        pcb_pin("U2", 3, "VIN", "hvj-imd-node", 38.0, 58.0, PcbPinKind.analog),
        pcb_pin("U2", 4, "VDD2", "hvj-5v", 22.0, 64.0, PcbPinKind.power),
        pcb_pin("U2", 5, "GND2", "hvj-gnd", 22.0, 60.0, PcbPinKind.ground),
        pcb_pin("U2", 6, "OUT", "hvj-imd-out", 22.0, 56.0, PcbPinKind.analog),
        pcb_pin("T1", 1, "P1", "hvj-5v", 21.0, 46.0, PcbPinKind.power),
        pcb_pin("T1", 2, "P2", "hvj-gnd", 21.0, 38.0, PcbPinKind.ground),
        pcb_pin("T1", 3, "S1", "hvj-iso-5v", 38.0, 46.0, PcbPinKind.power),
        pcb_pin("T1", 4, "S2", "hvj-iso-gnd", 38.0, 38.0, PcbPinKind.ground),
        pcb_pin("U3", 1, "IN", "hvj-12v", 9.0, 82.0, PcbPinKind.power),
        pcb_pin("U3", 2, "GND", "hvj-gnd", 12.0, 85.0, PcbPinKind.ground),
        pcb_pin("U3", 3, "OUT", "hvj-5v", 15.0, 82.0, PcbPinKind.power),
        pcb_pin("Q1", 1, "G", "hvj-coil1-drv", 9.0, 92.0, PcbPinKind.signal),
        pcb_pin("Q1", 2, "D", "hvj-coil1-lo", 15.0, 95.0, PcbPinKind.power),
        pcb_pin("Q1", 3, "S", "hvj-gnd", 15.0, 89.0, PcbPinKind.ground),
        pcb_pin("Q2", 1, "G", "hvj-coil2-drv", 9.0, 102.0, PcbPinKind.signal),
        pcb_pin("Q2", 2, "D", "hvj-coil2-lo", 15.0, 105.0, PcbPinKind.power),
        pcb_pin("Q2", 3, "S", "hvj-gnd", 15.0, 99.0, PcbPinKind.ground),
        pcb_pin("Q3", 1, "G", "hvj-pyro-drv", 9.0, 112.0, PcbPinKind.signal),
        pcb_pin("Q3", 2, "D", "hvj-pyro-lo", 15.0, 115.0, PcbPinKind.power),
        pcb_pin("Q3", 3, "S", "hvj-gnd", 15.0, 109.0, PcbPinKind.ground),
        pcb_pin("J1", 1, "12V", "hvj-12v", 3.0, 8.0, PcbPinKind.power),
        pcb_pin("J1", 2, "GND", "hvj-gnd", 3.0, 14.0, PcbPinKind.ground),
        pcb_pin("J1", 3, "COIL1_DRV", "hvj-coil1-drv", 3.0, 20.0, PcbPinKind.signal),
        pcb_pin("J1", 4, "COIL2_DRV", "hvj-coil2-drv", 3.0, 26.0, PcbPinKind.signal),
        pcb_pin("J1", 5, "PYRO_DRV", "hvj-pyro-drv", 3.0, 32.0, PcbPinKind.signal),
        pcb_pin("J1", 6, "ISENSE", "hvj-isense-out", 3.0, 38.0, PcbPinKind.analog),
        pcb_pin("J1", 7, "IMD", "hvj-imd-out", 3.0, 44.0, PcbPinKind.analog),
        pcb_pin("J1", 8, "HVIL_OUT", "hvj-hvil-out", 3.0, 50.0, PcbPinKind.signal),
        pcb_pin("J1", 9, "HVIL_IN", "hvj-hvil-in", 3.0, 56.0, PcbPinKind.signal),
        pcb_pin("J1", 10, "GND_RTN", "hvj-gnd", 3.0, 62.0, PcbPinKind.ground),
        pcb_pin("J2", 1, "LOOP_OUT", "hvj-hvil-out", 3.0, 74.0, PcbPinKind.signal),
        pcb_pin("J2", 2, "LOOP_IN", "hvj-hvil-in", 3.0, 80.0, PcbPinKind.signal),
    ]


def hv_junction_12v_load_a():
    return 2.0 * HVJ_COIL_A + HVJ_PYRO_MONITOR_A + hv_junction_5v_load_a()


def hv_junction_5v_load_a():
    return HVJ_ISO_SUPPLY_A + HVJ_ISENSE_A + HVJ_IMD_A


def hv_junction_nets():
    return [
        PcbNet(id="hvj-hv-pack-p", name="Pack positive to contactor", net_class=PcbNetClass.hv, pin_ids=["TB1.1", "K1.3"], nominal_voltage_v=800.0, current_a=HV_BUS_CURRENT_A, is_reference=false),
        PcbNet(id="hvj-hv-bus-p", name="Contactor to pyrofuse positive bus", net_class=PcbNetClass.hv, pin_ids=["K1.4", "F1.1"], nominal_voltage_v=800.0, current_a=HV_BUS_CURRENT_A, is_reference=false),
        PcbNet(id="hvj-hv-out-p", name="Protected HV output positive", net_class=PcbNetClass.hv, pin_ids=["F1.2", "TB2.1", "R1.1"], nominal_voltage_v=800.0, current_a=HV_BUS_CURRENT_A, is_reference=false),
        PcbNet(id="hvj-hv-out-n", name="Protected HV output negative", net_class=PcbNetClass.hv, pin_ids=["TB2.2", "RS1.2"], nominal_voltage_v=0.0, current_a=HV_BUS_CURRENT_A, is_reference=false),
        PcbNet(id="hvj-hv-bus-n", name="Shunt to contactor negative bus", net_class=PcbNetClass.hv, pin_ids=["RS1.1", "K2.4"], nominal_voltage_v=0.0, current_a=HV_BUS_CURRENT_A, is_reference=false),
        PcbNet(id="hvj-hv-pack-n", name="Contactor to pack negative", net_class=PcbNetClass.hv, pin_ids=["K2.3", "TB1.2"], nominal_voltage_v=0.0, current_a=HV_BUS_CURRENT_A, is_reference=false),
        PcbNet(id="hvj-shunt-p", name="Shunt sense positive", net_class=PcbNetClass.analog, pin_ids=["RS1.3", "U1.3"], nominal_voltage_v=0.0, current_a=0.0, is_reference=false),
        PcbNet(id="hvj-shunt-n", name="Shunt sense negative", net_class=PcbNetClass.analog, pin_ids=["RS1.4", "U1.4"], nominal_voltage_v=0.0, current_a=0.0, is_reference=false),
        PcbNet(id="hvj-imd-node", name="Insulation-monitor divider midpoint", net_class=PcbNetClass.analog, pin_ids=["R1.2", "R2.1", "U2.3"], nominal_voltage_v=34.8, current_a=0.00035, is_reference=false),
        PcbNet(id="hvj-iso-5v", name="HV-referenced isolated 5 V rail", net_class=PcbNetClass.power, pin_ids=["T1.3", "U1.1", "U2.1"], nominal_voltage_v=5.0, current_a=0.0075, is_reference=false),
        PcbNet(id="hvj-iso-gnd", name="HV-referenced isolated return", net_class=PcbNetClass.ground, pin_ids=["T1.4", "U1.2", "U2.2", "R2.2"], nominal_voltage_v=0.0, current_a=0.0079, is_reference=false),
        PcbNet(id="hvj-5v", name="LV 5 V rail", net_class=PcbNetClass.power, pin_ids=["U3.3", "T1.1", "U1.5", "U2.4"], nominal_voltage_v=5.0, current_a=hv_junction_5v_load_a(), is_reference=false),
        PcbNet(id="hvj-12v", name="LV 12 V supply and contactor coil feed", net_class=PcbNetClass.power, pin_ids=["J1.1", "U3.1", "K1.1", "K2.1", "F1.3"], nominal_voltage_v=12.0, current_a=hv_junction_12v_load_a(), is_reference=false),
        PcbNet(id="hvj-gnd", name="LV chassis-referenced ground", net_class=PcbNetClass.ground, pin_ids=["J1.2", "J1.10", "U3.2", "Q1.3", "Q2.3", "Q3.3", "T1.2", "U1.7", "U2.5"], nominal_voltage_v=0.0, current_a=hv_junction_12v_load_a(), is_reference=true),
        PcbNet(id="hvj-coil1-lo", name="Contactor 1 coil low side", net_class=PcbNetClass.power, pin_ids=["K1.2", "Q1.2"], nominal_voltage_v=0.6, current_a=HVJ_COIL_A, is_reference=false),
        PcbNet(id="hvj-coil2-lo", name="Contactor 2 coil low side", net_class=PcbNetClass.power, pin_ids=["K2.2", "Q2.2"], nominal_voltage_v=0.6, current_a=HVJ_COIL_A, is_reference=false),
        PcbNet(id="hvj-pyro-lo", name="Pyrotechnic squib low side", net_class=PcbNetClass.power, pin_ids=["F1.4", "Q3.2"], nominal_voltage_v=0.2, current_a=HVJ_PYRO_MONITOR_A, is_reference=false),
        PcbNet(id="hvj-coil1-drv", name="Contactor 1 gate command", net_class=PcbNetClass.signal, pin_ids=["J1.3", "Q1.1"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="hvj-coil2-drv", name="Contactor 2 gate command", net_class=PcbNetClass.signal, pin_ids=["J1.4", "Q2.1"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="hvj-pyro-drv", name="Pyrotechnic trigger command", net_class=PcbNetClass.signal, pin_ids=["J1.5", "Q3.1"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="hvj-isense-out", name="Isolated current-sense output", net_class=PcbNetClass.analog, pin_ids=["U1.6", "J1.6"], nominal_voltage_v=2.5, current_a=0.0002, is_reference=false),
        PcbNet(id="hvj-imd-out", name="Insulation-monitor output", net_class=PcbNetClass.analog, pin_ids=["U2.6", "J1.7"], nominal_voltage_v=2.5, current_a=0.0002, is_reference=false),
        PcbNet(id="hvj-hvil-out", name="Interlock loop drive", net_class=PcbNetClass.signal, pin_ids=["J1.8", "J2.1"], nominal_voltage_v=5.0, current_a=0.008, is_reference=false),
        PcbNet(id="hvj-hvil-in", name="Interlock loop return", net_class=PcbNetClass.signal, pin_ids=["J2.2", "J1.9"], nominal_voltage_v=5.0, current_a=0.008, is_reference=false),
    ]


def hv_junction_plans():
    return [
        PcbRoutePlan(net_id="hv", layer_id="hvj-bus", width_mm=16.0, topology="chain", via_count=0, via_drill_mm=0.5, via_pad_mm=1.0, via_layer_id="hvj-bus"),
        PcbRoutePlan(net_id="power", layer_id="hvj-l1", width_mm=1.5, topology="chain", via_count=0, via_drill_mm=0.4, via_pad_mm=0.8, via_layer_id="hvj-l1"),
        PcbRoutePlan(net_id="ground", layer_id="hvj-l2", width_mm=6.0, topology="star", via_count=2, via_drill_mm=0.4, via_pad_mm=0.8, via_layer_id="hvj-l1"),
        PcbRoutePlan(net_id="signal", layer_id="hvj-l1", width_mm=0.5, topology="chain", via_count=0, via_drill_mm=0.4, via_pad_mm=0.8, via_layer_id="hvj-l1"),
        PcbRoutePlan(net_id="analog", layer_id="hvj-l1", width_mm=0.5, topology="chain", via_count=0, via_drill_mm=0.4, via_pad_mm=0.8, via_layer_id="hvj-l1"),
        PcbRoutePlan(net_id="can", layer_id="hvj-l1", width_mm=0.4, topology="chain", via_count=0, via_drill_mm=0.4, via_pad_mm=0.8, via_layer_id="hvj-l1"),
        PcbRoutePlan(net_id="ethernet", layer_id="hvj-l1", width_mm=0.4, topology="chain", via_count=0, via_drill_mm=0.4, via_pad_mm=0.8, via_layer_id="hvj-l1"),
    ]


def hv_junction_overrides():
    return [
        # The isolated return must stay off the chassis-referenced plane.
        PcbRoutePlan(net_id="hvj-iso-gnd", layer_id="hvj-l1", width_mm=2.0, topology="star", via_count=0, via_drill_mm=0.4, via_pad_mm=0.8, via_layer_id="hvj-l1"),
        PcbRoutePlan(net_id="hvj-coil1-lo", layer_id="hvj-l1", width_mm=2.0, topology="chain", via_count=0, via_drill_mm=0.4, via_pad_mm=0.8, via_layer_id="hvj-l1"),
        PcbRoutePlan(net_id="hvj-coil2-lo", layer_id="hvj-l1", width_mm=2.0, topology="chain", via_count=0, via_drill_mm=0.4, via_pad_mm=0.8, via_layer_id="hvj-l1"),
        PcbRoutePlan(net_id="hvj-12v", layer_id="hvj-l1", width_mm=2.5, topology="chain", via_count=0, via_drill_mm=0.4, via_pad_mm=0.8, via_layer_id="hvj-l1"),
    ]


def hv_junction_loads():
    board = "hv-junction-board"
    return [
        pcb_load(board, "K1.3", "K1.4", HV_BUS_CURRENT_A, "positive contactor main pole"),
        pcb_load(board, "F1.1", "F1.2", HV_BUS_CURRENT_A, "pyrotechnic fuse conductor"),
        pcb_load(board, "TB2.1", "TB2.2", HV_BUS_CURRENT_A, "drive-unit HV load"),
        pcb_load(board, "RS1.2", "RS1.1", HV_BUS_CURRENT_A, "current shunt element"),
        pcb_load(board, "K2.4", "K2.3", HV_BUS_CURRENT_A, "negative contactor main pole"),
        pcb_load(board, "TB1.2", "TB1.1", HV_BUS_CURRENT_A, "traction pack source pair"),
        pcb_load(board, "R1.1", "R1.2", HVJ_IMD_DIVIDER_A, "insulation-monitor divider, HV element"),
        pcb_load(board, "R2.1", "R2.2", HVJ_IMD_DIVIDER_A, "insulation-monitor divider, low element"),
        pcb_load(board, "U3.1", "U3.3", hv_junction_5v_load_a(), "12 V to 5 V regulator pass element"),
        pcb_load(board, "T1.1", "T1.2", HVJ_ISO_SUPPLY_A, "isolated supply transformer primary"),
        pcb_load(board, "U1.1", "U1.2", 0.0035, "isolation amplifier HV-side supply"),
        pcb_load(board, "U2.1", "U2.2", 0.004, "insulation-monitor HV-side supply"),
        pcb_load(board, "U1.5", "U1.7", HVJ_ISENSE_A, "isolation amplifier LV-side supply"),
        pcb_load(board, "U2.4", "U2.5", HVJ_IMD_A, "insulation-monitor LV-side supply"),
        pcb_load(board, "K1.1", "K1.2", HVJ_COIL_A, "contactor 1 coil"),
        pcb_load(board, "Q1.2", "Q1.3", HVJ_COIL_A, "contactor 1 driver channel"),
        pcb_load(board, "K2.1", "K2.2", HVJ_COIL_A, "contactor 2 coil"),
        pcb_load(board, "Q2.2", "Q2.3", HVJ_COIL_A, "contactor 2 driver channel"),
        pcb_load(board, "F1.3", "F1.4", HVJ_PYRO_MONITOR_A, "squib continuity monitor current"),
        pcb_load(board, "Q3.2", "Q3.3", HVJ_PYRO_MONITOR_A, "squib driver channel"),
        pcb_load(board, "J1.6", "J1.10", 0.0002, "current-sense output into the zone controller"),
        pcb_load(board, "J1.7", "J1.10", 0.0002, "insulation-monitor output into the zone controller"),
        pcb_load(board, "J2.1", "J2.2", 0.008, "high-voltage interlock loop"),
        pcb_load(board, "Q1.1", "Q1.3", 0.00025, "contactor 1 gate command"),
        pcb_load(board, "Q2.1", "Q2.3", 0.00025, "contactor 2 gate command"),
        pcb_load(board, "Q3.1", "Q3.3", 0.00025, "pyrotechnic gate command"),
    ]


def hv_junction_board() !{}:
    return assemble_board("hv-junction-board", "HV junction, protection and isolation board", "hv-junction", "HV junction box above the pack front cross-member", "IMS aluminium-core, 105 um heavy copper, bonded 2 mm busbars", hv_junction_stackup(), hv_junction_components(), hv_junction_pins(), hv_junction_nets(), hv_junction_plans(), hv_junction_overrides(), hv_junction_loads(), 180.0, 120.0)


def sill_panel_stackup():
    layers = [
        PcbLayer(id="sill-top", name="Abrasion topcoat", function=PcbLayerFunction.soldermask, copper_um=0.0, dielectric_mm=0.05, dielectric_er=3.6, material="two-component abrasion-resistant topcoat"),
        PcbLayer(id="sill-shield", name="Overprinted shield", function=PcbLayerFunction.plane, copper_um=35.0, dielectric_mm=0.0, dielectric_er=1.0, material="screen-printed silver-copper shield ink"),
        PcbLayer(id="sill-cov", name="Printed coverlay", function=PcbLayerFunction.coverlay, copper_um=0.0, dielectric_mm=0.2, dielectric_er=3.4, material="printed polymer coverlay"),
        PcbLayer(id="sill-data", name="Printed data conductors", function=PcbLayerFunction.signal, copper_um=70.0, dielectric_mm=0.0, dielectric_er=1.0, material="printed and sintered copper paste"),
        PcbLayer(id="sill-iso", name="Interlayer dielectric", function=PcbLayerFunction.prepreg, copper_um=0.0, dielectric_mm=0.15, dielectric_er=4.2, material="printed interlayer dielectric"),
        PcbLayer(id="sill-pwr", name="Printed power conductors", function=PcbLayerFunction.signal, copper_um=400.0, dielectric_mm=0.0, dielectric_er=1.0, material="thick screen-printed and sintered copper"),
        PcbLayer(id="sill-diel", name="Dielectric barrier", function=PcbLayerFunction.prepreg, copper_um=0.0, dielectric_mm=0.25, dielectric_er=4.8, material="ceramic-filled epoxy dielectric barrier"),
        PcbLayer(id="sill-base", name="Structural sill extrusion", function=PcbLayerFunction.core, copper_um=0.0, dielectric_mm=3.5, dielectric_er=1.0, material="anodised 6082-T6 aluminium sill extrusion"),
    ]
    return PcbStackup(id="stk-sill", name="Structural printed conductor panel on the aluminium rocker", layers=layers, total_thickness_mm=stackup_thickness_mm(layers), ipc_class=3, impedance_target_ohm=100.0)


def sill_panel_components():
    return [
        PcbComponent(refdes="TB2", part_number="BONDPAD-M8-4P", description="Rear bond terminal block, pack and junction end", footprint="BONDPAD-M8x4", pin_count=4, x_mm=2560.0, y_mm=98.0, rotation_deg=0.0, power_dissipation_w=0.6, mass_g=96.0),
        PcbComponent(refdes="TB1", part_number="BONDPAD-M8-4P", description="Front bond terminal block, drive-unit end", footprint="BONDPAD-M8x4", pin_count=4, x_mm=40.0, y_mm=98.0, rotation_deg=0.0, power_dissipation_w=0.6, mass_g=96.0),
        PcbComponent(refdes="J2", part_number="MX150-6", description="Rear data and service connector", footprint="MOLEX-MX150-6", pin_count=6, x_mm=2480.0, y_mm=60.0, rotation_deg=0.0, power_dissipation_w=0.0, mass_g=5.0),
        PcbComponent(refdes="J1", part_number="MX150-6", description="Front data and service connector", footprint="MOLEX-MX150-6", pin_count=6, x_mm=120.0, y_mm=60.0, rotation_deg=0.0, power_dissipation_w=0.0, mass_g=5.0),
        PcbComponent(refdes="J3", part_number="MX150-4", description="B-pillar branch tap", footprint="MOLEX-MX150-4", pin_count=4, x_mm=1300.0, y_mm=40.0, rotation_deg=0.0, power_dissipation_w=0.0, mass_g=3.4),
        PcbComponent(refdes="FS1", part_number="PRINTLINK-200A", description="Printed fusible link in the 48 V branch", footprint="PRINTLINK-60x25", pin_count=2, x_mm=1400.0, y_mm=62.0, rotation_deg=0.0, power_dissipation_w=1.9, mass_g=2.2),
        PcbComponent(refdes="TP1", part_number="NTC-PRINTED-10K", description="Printed thermistor, front conductor run", footprint="PRINTNTC-6x4", pin_count=2, x_mm=900.0, y_mm=25.0, rotation_deg=0.0, power_dissipation_w=0.001, mass_g=0.2),
        PcbComponent(refdes="TP2", part_number="NTC-PRINTED-10K", description="Printed thermistor, rear conductor run", footprint="PRINTNTC-6x4", pin_count=2, x_mm=1900.0, y_mm=27.0, rotation_deg=0.0, power_dissipation_w=0.001, mass_g=0.2),
        PcbComponent(refdes="SH1", part_number="SHIELDBOND-4P", description="Shield bond pad group at the B-pillar node", footprint="BONDPAD-6x4", pin_count=4, x_mm=1330.0, y_mm=121.0, rotation_deg=0.0, power_dissipation_w=0.0, mass_g=1.1),
    ]


def sill_panel_pins():
    return [
        pcb_pin("TB2", 1, "HV+", "sill-hv-p", 2560.0, 185.0, PcbPinKind.power),
        pcb_pin("TB2", 2, "HV-", "sill-hv-n", 2560.0, 115.0, PcbPinKind.power),
        pcb_pin("TB2", 3, "48V", "sill-48v", 2560.0, 62.0, PcbPinKind.power),
        pcb_pin("TB2", 4, "RTN", "sill-gnd", 2560.0, 32.0, PcbPinKind.ground),
        pcb_pin("TB1", 1, "HV+", "sill-hv-p", 40.0, 185.0, PcbPinKind.power),
        pcb_pin("TB1", 2, "HV-", "sill-hv-n", 40.0, 115.0, PcbPinKind.power),
        pcb_pin("TB1", 3, "48V", "sill-48v", 40.0, 62.0, PcbPinKind.power),
        pcb_pin("TB1", 4, "RTN", "sill-gnd", 40.0, 32.0, PcbPinKind.ground),
        pcb_pin("J2", 1, "DATA_P", "sill-data-p", 2480.0, 8.0, PcbPinKind.signal),
        pcb_pin("J2", 2, "DATA_N", "sill-data-n", 2480.0, 12.0, PcbPinKind.signal),
        pcb_pin("J2", 3, "SHIELD", "sill-shield", 2480.0, 210.0, PcbPinKind.ground),
        pcb_pin("J2", 4, "48V", "sill-48v", 2480.0, 62.0, PcbPinKind.power),
        pcb_pin("J2", 5, "RTN_A", "sill-gnd", 2480.0, 32.0, PcbPinKind.ground),
        pcb_pin("J2", 6, "RTN_B", "sill-gnd", 2480.0, 28.0, PcbPinKind.ground),
        pcb_pin("J1", 1, "DATA_P", "sill-data-p", 120.0, 8.0, PcbPinKind.signal),
        pcb_pin("J1", 2, "DATA_N", "sill-data-n", 120.0, 12.0, PcbPinKind.signal),
        pcb_pin("J1", 3, "SHIELD", "sill-shield", 120.0, 210.0, PcbPinKind.ground),
        pcb_pin("J1", 4, "48V", "sill-48v", 120.0, 62.0, PcbPinKind.power),
        pcb_pin("J1", 5, "RTN_A", "sill-gnd", 120.0, 32.0, PcbPinKind.ground),
        pcb_pin("J1", 6, "RTN_B", "sill-gnd", 120.0, 28.0, PcbPinKind.ground),
        pcb_pin("J3", 1, "48V_B", "sill-48v-b", 1300.0, 62.0, PcbPinKind.power),
        pcb_pin("J3", 2, "RTN", "sill-gnd", 1300.0, 32.0, PcbPinKind.ground),
        pcb_pin("J3", 3, "TEMP1", "sill-temp", 1300.0, 18.0, PcbPinKind.thermal),
        pcb_pin("J3", 4, "TEMP2", "sill-temp2", 1300.0, 22.0, PcbPinKind.thermal),
        pcb_pin("FS1", 1, "IN", "sill-48v", 1370.0, 62.0, PcbPinKind.power),
        pcb_pin("FS1", 2, "OUT", "sill-48v-b", 1430.0, 62.0, PcbPinKind.power),
        pcb_pin("TP1", 1, "A", "sill-temp", 900.0, 18.0, PcbPinKind.thermal),
        pcb_pin("TP1", 2, "B", "sill-gnd", 900.0, 32.0, PcbPinKind.ground),
        pcb_pin("TP2", 1, "A", "sill-temp2", 1900.0, 22.0, PcbPinKind.thermal),
        pcb_pin("TP2", 2, "B", "sill-gnd", 1900.0, 32.0, PcbPinKind.ground),
        pcb_pin("SH1", 1, "SH_A", "sill-shield", 1300.0, 210.0, PcbPinKind.ground),
        pcb_pin("SH1", 2, "SH_B", "sill-shield", 1340.0, 210.0, PcbPinKind.ground),
        pcb_pin("SH1", 3, "BOND_A", "sill-gnd", 1320.0, 32.0, PcbPinKind.ground),
        pcb_pin("SH1", 4, "BOND_B", "sill-gnd", 1360.0, 32.0, PcbPinKind.ground),
    ]


def sill_panel_nets():
    return [
        PcbNet(id="sill-hv-p", name="Structural HV positive conductor", net_class=PcbNetClass.hv, pin_ids=["TB2.1", "TB1.1"], nominal_voltage_v=800.0, current_a=SILL_HV_CURRENT_A, is_reference=false),
        PcbNet(id="sill-hv-n", name="Structural HV negative conductor", net_class=PcbNetClass.hv, pin_ids=["TB2.2", "TB1.2"], nominal_voltage_v=0.0, current_a=SILL_HV_CURRENT_A, is_reference=false),
        PcbNet(id="sill-48v", name="Structural 48 V conductor", net_class=PcbNetClass.power, pin_ids=["TB2.3", "J2.4", "FS1.1", "J1.4", "TB1.3"], nominal_voltage_v=48.0, current_a=SILL_ZONE_A + SILL_SERVICE_A + SILL_BPILLAR_A, is_reference=false),
        PcbNet(id="sill-48v-b", name="B-pillar protected branch", net_class=PcbNetClass.power, pin_ids=["FS1.2", "J3.1"], nominal_voltage_v=48.0, current_a=SILL_BPILLAR_A, is_reference=false),
        PcbNet(id="sill-gnd", name="Structural return conductor", net_class=PcbNetClass.ground, pin_ids=["TB2.4", "J2.5", "J2.6", "TP2.2", "SH1.3", "SH1.4", "J3.2", "TP1.2", "J1.5", "J1.6", "TB1.4"], nominal_voltage_v=0.0, current_a=SILL_ZONE_A + SILL_SERVICE_A + SILL_BPILLAR_A, is_reference=true),
        PcbNet(id="sill-data-p", name="Printed differential pair positive", net_class=PcbNetClass.ethernet, pin_ids=["J2.1", "J1.1"], nominal_voltage_v=1.1, current_a=0.011, is_reference=false),
        PcbNet(id="sill-data-n", name="Printed differential pair negative", net_class=PcbNetClass.ethernet, pin_ids=["J1.2", "J2.2"], nominal_voltage_v=0.0, current_a=0.011, is_reference=false),
        PcbNet(id="sill-shield", name="Overprinted shield conductor", net_class=PcbNetClass.signal, pin_ids=["SH1.1", "SH1.2", "J2.3", "J1.3"], nominal_voltage_v=0.0, current_a=0.0, is_reference=false),
        PcbNet(id="sill-temp", name="Printed thermistor channel, front run", net_class=PcbNetClass.analog, pin_ids=["J3.3", "TP1.1"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
        PcbNet(id="sill-temp2", name="Printed thermistor channel, rear run", net_class=PcbNetClass.analog, pin_ids=["J3.4", "TP2.1"], nominal_voltage_v=3.3, current_a=0.00025, is_reference=false),
    ]


def sill_panel_plans():
    return [
        PcbRoutePlan(net_id="hv", layer_id="sill-pwr", width_mm=60.0, topology="chain", via_count=0, via_drill_mm=1.0, via_pad_mm=2.0, via_layer_id="sill-pwr"),
        PcbRoutePlan(net_id="power", layer_id="sill-pwr", width_mm=25.0, topology="chain", via_count=0, via_drill_mm=1.0, via_pad_mm=2.0, via_layer_id="sill-pwr"),
        PcbRoutePlan(net_id="ground", layer_id="sill-pwr", width_mm=25.0, topology="chain", via_count=0, via_drill_mm=1.0, via_pad_mm=2.0, via_layer_id="sill-pwr"),
        PcbRoutePlan(net_id="ethernet", layer_id="sill-data", width_mm=0.8, topology="chain", via_count=0, via_drill_mm=0.6, via_pad_mm=1.2, via_layer_id="sill-data"),
        PcbRoutePlan(net_id="analog", layer_id="sill-data", width_mm=0.5, topology="chain", via_count=0, via_drill_mm=0.6, via_pad_mm=1.2, via_layer_id="sill-data"),
        PcbRoutePlan(net_id="signal", layer_id="sill-shield", width_mm=12.0, topology="chain", via_count=0, via_drill_mm=0.6, via_pad_mm=1.2, via_layer_id="sill-shield"),
        PcbRoutePlan(net_id="can", layer_id="sill-data", width_mm=0.8, topology="chain", via_count=0, via_drill_mm=0.6, via_pad_mm=1.2, via_layer_id="sill-data"),
    ]


def sill_panel_overrides():
    return [
        PcbRoutePlan(net_id="sill-48v-b", layer_id="sill-pwr", width_mm=12.0, topology="chain", via_count=0, via_drill_mm=1.0, via_pad_mm=2.0, via_layer_id="sill-pwr"),
    ]


def sill_panel_loads():
    board = "sill-conductor-panel"
    return [
        pcb_load(board, "TB1.1", "TB1.2", SILL_HV_CURRENT_A, "front drive-unit traction feed, continuous rating"),
        pcb_load(board, "TB1.3", "TB1.4", SILL_ZONE_A, "front zone controller 48 V supply"),
        pcb_load(board, "J1.4", "J1.5", SILL_SERVICE_A, "front service branch"),
        pcb_load(board, "FS1.1", "FS1.2", SILL_BPILLAR_A, "printed fusible link element"),
        pcb_load(board, "J3.1", "J3.2", SILL_BPILLAR_A, "B-pillar branch load"),
        pcb_load(board, "J1.1", "J1.2", 0.011, "100BASE-T1 structural pair"),
        pcb_load(board, "TP1.1", "TP1.2", 0.00025, "front printed thermistor bias"),
        pcb_load(board, "TP2.1", "TP2.2", 0.00025, "rear printed thermistor bias"),
    ]


def sill_panel_board() !{}:
    return assemble_board("sill-conductor-panel", "Structural sill printed conductor panel", "sill-panels", "left structural rocker, A-pillar to C-pillar", "printed conductor panel on an anodised 6082-T6 aluminium sill extrusion", sill_panel_stackup(), sill_panel_components(), sill_panel_pins(), sill_panel_nets(), sill_panel_plans(), sill_panel_overrides(), sill_panel_loads(), 2600.0, 220.0)


def board_loads(board_id: str):
    if board_id == "bms-slave":
        return bms_slave_loads()
    if board_id == "front-zone-controller":
        return front_zone_loads()
    if board_id == "hv-junction-board":
        return hv_junction_loads()
    return sill_panel_loads()


def trace_branch_resistance_ohm(board: PcbBoard, trace: PcbTrace):
    # Vias are emitted as two parallel groups of n barrels around one buried run,
    # so the series contribution is 2 R1 / n = 4 * sum / count^2.
    mut group_sum = 0.0
    mut group_count = 0
    prefix = trace.id + "-v"
    for via in board.vias:
        if via.id.startswith(prefix):
            group_sum = group_sum + via.resistance_mohm
            group_count = group_count + 1
    series_mohm = 0.0 if group_count == 0 else 4.0 * group_sum / float(group_count * group_count)
    return (trace.resistance_mohm + series_mohm) / 1000.0


def board_solution(board: PcbBoard):
    mut solve_segments = []
    mut solve_resistances = []
    for trace in board.traces:
        solve_segments.append(PcbSegment(id=trace.id, net_id=trace.net_id, layer_id=trace.layer_id, width_mm=trace.width_mm, from_pin_id=trace.from_pin_id, to_pin_id=trace.to_pin_id, via_count=0, via_drill_mm=0.3, via_pad_mm=0.6, via_layer_id=trace.layer_id))
        solve_resistances.append(trace_branch_resistance_ohm(board, trace))
    return solve_network(board.pins, board.nets, solve_segments, solve_resistances, board_loads(board.id))


def node_voltage(solution: PcbSolution, pin_id: str):
    for index in range(0, len(solution.node_ids)):
        if solution.node_ids[index] == pin_id:
            return solution.node_voltages[index]
    return 0.0


def net_drop_v(board: PcbBoard, solution: PcbSolution, net: PcbNet):
    feed = node_voltage(solution, net.pin_ids[0])
    mut spread = 0.0
    for pin_id in net.pin_ids:
        spread = max(spread, abs(feed - node_voltage(solution, pin_id)))
    return spread


def board_drc_violations(board: PcbBoard):
    mut findings = []
    mut seen_pins = {}
    for pin in board.pins:
        if pin.x_mm > board.outline_width_mm or pin.y_mm > board.outline_height_mm:
            findings.append("pad outside outline: " + pin.id)
        seen_pins[pin.id] = seen_pins.get(pin.id, 0) + 1
        if seen_pins[pin.id] > 1:
            findings.append("duplicate pad identifier: " + pin.id)
    for trace in board.traces:
        if trace.width_mm < MIN_TRACK_WIDTH_MM:
            findings.append("track below the minimum manufacturable width: " + trace.id)
    for via in board.vias:
        if (via.pad_mm - via.drill_mm) / 2.0 < MIN_ANNULAR_RING_MM:
            findings.append("annular ring below the class 3 minimum: " + via.id)
    return findings


def isolated_domain_nets(board_id: str):
    # Nets that float at traction-battery potential relative to the LV chassis domain.
    if board_id == "hv-junction-board":
        return ["hvj-hv-pack-p", "hvj-hv-pack-n", "hvj-hv-bus-p", "hvj-hv-bus-n", "hvj-hv-out-p", "hvj-hv-out-n", "hvj-shunt-p", "hvj-shunt-n", "hvj-imd-node", "hvj-iso-5v", "hvj-iso-gnd"]
    if board_id == "sill-conductor-panel":
        return ["sill-hv-p", "sill-hv-n"]
    return []


def isolation_required_mm(same_domain: bool, potential_difference_v: f64):
    # Creepage and clearance must both hold, so the binding requirement is the larger.
    # A barrier crossing gets reinforced insulation (twice the basic creepage) at the
    # full 800 V working voltage; inside one domain the local difference sets creepage
    # while the system impulse withstand still sets the clearance floor.
    if not same_domain:
        return max(2.0 * iec60664_creepage_mm(HV_WORKING_VOLTAGE_V), 2.0 * HV_CLEARANCE_MM)
    if potential_difference_v <= HAZARDOUS_VOLTAGE_V:
        return 0.0
    return max(iec60664_creepage_mm(potential_difference_v), HV_CLEARANCE_MM)


def net_lookup(nets: list[PcbNet]) -> dict[str, int]:
    mut net_index = {}
    for index in range(0, len(nets)):
        net_index[nets[index].id] = index
    return net_index


def pcb_isolation_checks(board: PcbBoard):
    isolated = isolated_domain_nets(board.id)
    if len(isolated) == 0:
        return []
    net_index = net_lookup(board.nets)
    mut in_domain = {}
    for net_id in isolated:
        in_domain[net_id] = 1
    mut checks = []
    mut tightest_barrier = 1000000.0
    mut barrier_pair = ["", ""]
    mut tightest_basic = 1000000.0
    mut basic_pair = ["", ""]
    mut basic_delta = 0.0
    for left in range(0, len(board.pins)):
        for right in range(left + 1, len(board.pins)):
            first = board.pins[left]
            second = board.pins[right]
            if first.net_id == second.net_id:
                continue
            first_hv = in_domain.get(first.net_id, 0) == 1
            second_hv = in_domain.get(second.net_id, 0) == 1
            if not first_hv and not second_hv:
                continue
            delta = abs(board.nets[net_index.get(first.net_id, 0)].nominal_voltage_v - board.nets[net_index.get(second.net_id, 0)].nominal_voltage_v)
            required = isolation_required_mm(first_hv == second_hv, delta)
            if required <= 0.0:
                continue
            gap = math.sqrt((first.x_mm - second.x_mm) * (first.x_mm - second.x_mm) + (first.y_mm - second.y_mm) * (first.y_mm - second.y_mm))
            if first_hv != second_hv:
                if gap < tightest_barrier:
                    tightest_barrier = gap
                    barrier_pair = [first.id, second.id]
            elif gap < tightest_basic:
                tightest_basic = gap
                basic_pair = [first.id, second.id]
                basic_delta = delta
            if gap < required:
                checks.append(PcbIsolationCheck(id=board.id + ":" + first.id + "/" + second.id, board_id=board.id, category="violation", from_pin_id=first.id, to_pin_id=second.id, working_voltage_v=delta, required_mm=required, measured_mm=gap, reinforced=first_hv != second_hv, compliant=false))
    if len(barrier_pair[0]) > 0:
        barrier_required = isolation_required_mm(false, HV_WORKING_VOLTAGE_V)
        checks.append(PcbIsolationCheck(id=board.id + ":tightest-barrier", board_id=board.id, category="reinforced barrier, IEC 60664-1 PD2 group IIIa", from_pin_id=barrier_pair[0], to_pin_id=barrier_pair[1], working_voltage_v=HV_WORKING_VOLTAGE_V, required_mm=barrier_required, measured_mm=tightest_barrier, reinforced=true, compliant=tightest_barrier >= barrier_required))
    if len(basic_pair[0]) > 0:
        basic_required = isolation_required_mm(true, basic_delta)
        checks.append(PcbIsolationCheck(id=board.id + ":tightest-basic", board_id=board.id, category="basic creepage inside the HV domain", from_pin_id=basic_pair[0], to_pin_id=basic_pair[1], working_voltage_v=basic_delta, required_mm=basic_required, measured_mm=tightest_basic, reinforced=false, compliant=tightest_basic >= basic_required))
    return checks


def analyze_solved(board: PcbBoard, field: PcbSolution, gates: list[PcbIsolationCheck]):
    mut copper_loss_w = 0.0
    mut max_rise = 0.0
    mut ipc_violations = 0
    for trace in board.traces:
        copper_loss_w = copper_loss_w + trace.power_loss_mw / 1000.0
        max_rise = max(max_rise, trace.temperature_rise_c)
        if not trace.compliant:
            ipc_violations = ipc_violations + 1
    for via in board.vias:
        copper_loss_w = copper_loss_w + via.current_a * via.current_a * via.resistance_mohm / 1000.0
    mut worst_net = board.nets[0].id
    mut worst_drop = 0.0
    mut worst_fraction = 0.0
    mut source_node = board.nets[0].pin_ids[0]
    mut source_voltage = 0.0
    for net in board.nets:
        drop = net_drop_v(board, field, net)
        if net.nominal_voltage_v > source_voltage:
            source_voltage = net.nominal_voltage_v
            source_node = net.pin_ids[0]
        if net.nominal_voltage_v >= 1.0:
            fraction = drop / net.nominal_voltage_v
            if fraction > worst_fraction:
                worst_fraction = fraction
                worst_drop = drop
                worst_net = net.id
    mut isolation_violations = 0
    for check in gates:
        if not check.compliant:
            isolation_violations = isolation_violations + 1
    return PcbAnalysis(
        board_id=board.id,
        node_count=len(field.node_ids),
        branch_count=len(board.traces),
        source_node=source_node,
        worst_net_id=worst_net,
        worst_voltage_drop_mv=worst_drop * 1000.0,
        worst_drop_fraction=worst_fraction,
        total_copper_loss_w=copper_loss_w,
        max_temperature_rise_c=max_rise,
        ipc_violations=ipc_violations,
        drc_violations=len(board_drc_violations(board)),
        isolation_violations=isolation_violations,
        solver_residual=field.solver_residual,
        kcl_residual_a=field.kcl_residual_a,
    )


def pcb_analyze(board: PcbBoard):
    return analyze_solved(board, board_solution(board), pcb_isolation_checks(board))


def pcb_netlist_text(board: PcbBoard):
    mut netlist_rows = ["(export (version D)"]
    netlist_rows.append("  (design (source \"" + board.id + "\") (tool \"Sema pcb.sema engineered reconstruction\") (substrate \"" + board.substrate + "\"))")
    netlist_rows.append("  (components")
    for component in board.components:
        netlist_rows.append("    (comp (ref " + component.refdes + ") (value " + component.part_number + ") (footprint " + component.footprint + "))")
    netlist_rows.append("  )")
    netlist_rows.append("  (nets")
    mut code = 0
    for net in board.nets:
        code = code + 1
        netlist_rows.append("    (net (code " + str(code) + ") (name \"/" + net.id + "\")")
        for pin in board.pins:
            if pin.net_id == net.id:
                netlist_rows.append("      (node (ref " + pin.refdes + ") (pin " + str(pin.number) + "))")
        netlist_rows.append("    )")
    netlist_rows.append("  )")
    netlist_rows.append(")")
    return "\n".join(netlist_rows)


def pcb_boards() !{}:
    return [bms_slave_board(), front_zone_board(), hv_junction_board(), sill_panel_board()]


def component_kind(refdes: str):
    if refdes.startswith("RT") or refdes.startswith("TP"):
        return "sensor"
    if refdes.startswith("RS"):
        return "shunt"
    if refdes.startswith("SH"):
        return "shield"
    if refdes.startswith("FS") or refdes.startswith("F"):
        return "fuse"
    if refdes.startswith("TB") or refdes.startswith("J"):
        return "connector"
    if refdes.startswith("U"):
        return "ic"
    if refdes.startswith("Q"):
        return "transistor"
    if refdes.startswith("R"):
        return "resistor"
    if refdes.startswith("C"):
        return "capacitor"
    if refdes.startswith("L"):
        return "inductor"
    if refdes.startswith("K"):
        return "relay"
    if refdes.startswith("D"):
        return "diode"
    return "magnetics"


def component_extent(board: PcbBoard, refdes: str):
    mut low_x = board.outline_width_mm
    mut low_y = board.outline_height_mm
    mut high_x = 0.0
    mut high_y = 0.0
    mut found = false
    for pin in board.pins:
        if pin.refdes == refdes:
            found = true
            low_x = min(low_x, pin.x_mm)
            low_y = min(low_y, pin.y_mm)
            high_x = max(high_x, pin.x_mm)
            high_y = max(high_y, pin.y_mm)
    if not found:
        return [1.6, 0.8]
    return [max(1.6, high_x - low_x + 1.6), max(0.8, high_y - low_y + 1.6)]


def pad_extent(footprint: str):
    if footprint.startswith("BUSBAR") or footprint.startswith("BONDPAD"):
        return [9.0, 9.0]
    if footprint.startswith("CONTACTOR") or footprint.startswith("PYRO") or footprint.startswith("SHUNT") or footprint.startswith("PRINTLINK"):
        return [6.0, 6.0]
    if footprint.startswith("MOLEX") or footprint.startswith("JST") or footprint.startswith("HVIL"):
        return [1.6, 2.4]
    if footprint.startswith("D2PAK") or footprint.startswith("DPAK") or footprint.startswith("IND") or footprint.startswith("PQFN"):
        return [2.2, 2.2]
    if footprint.startswith("1206") or footprint.startswith("2512") or footprint.startswith("RES-HV") or footprint.startswith("PRINTNTC"):
        return [1.4, 1.6]
    return [0.9, 0.5]


def layer_kind(layer: PcbLayer):
    if layer.copper_um <= 0.0:
        return "base"
    return "plane" if layer.function == PcbLayerFunction.plane else "signal"


def layer_color(index: int, layer: PcbLayer):
    if layer.copper_um <= 0.0:
        return "#2b2f36"
    palette = ["#e0803a", "#3f8fd0", "#6fbf5a", "#c05fc0", "#d8c04a", "#5ac4c0", "#c85050", "#8f7ad8"]
    return palette[index % len(palette)]


def substrate_kind(board_id: str):
    if board_id == "hv-junction-board":
        return "ims"
    if board_id == "sill-conductor-panel":
        return "structural-sill"
    return "fr4"


def viewer_net_class(net_class: PcbNetClass):
    if net_class == PcbNetClass.can or net_class == PcbNetClass.ethernet:
        return "data"
    if net_class == PcbNetClass.analog:
        return "sensor"
    return net_class_key(net_class)


def vehicle_net_for(net_id: str):
    if net_id == "sill-hv-p" or net_id == "sill-hv-n":
        return "hv-pack-front"
    if net_id == "hvj-hv-out-p" or net_id == "hvj-hv-out-n" or net_id == "hvj-hv-pack-p" or net_id == "hvj-hv-pack-n" or net_id == "hvj-hv-bus-p" or net_id == "hvj-hv-bus-n":
        return "hv-pack-rear"
    if net_id == "sill-48v" or net_id == "sill-48v-b" or net_id == "fzc-48v":
        return "lv-zones"
    if net_id == "sill-data-p" or net_id == "sill-data-n" or net_id == "fzc-cable-eth-p" or net_id == "fzc-cable-eth-n":
        return "data-backbone"
    if net_id == "fzc-can0-h" or net_id == "fzc-can0-l" or net_id == "fzc-can1-h" or net_id == "fzc-can1-l":
        return "front-drive-control"
    if net_id == "bmsA-ntc1" or net_id == "bmsA-ntc2" or net_id == "sill-temp" or net_id == "sill-temp2":
        return "thermal-sensors"
    return ""


def board_pours(board: PcbBoard):
    inset = min(2.0, board.outline_width_mm * 0.02)
    mut pours = []
    for layer in board.stackup.layers:
        if layer.function != PcbLayerFunction.plane or layer.copper_um <= 0.0:
            continue
        mut owner = ""
        mut best = 0
        mut counts = {}
        for trace in board.traces:
            if trace.layer_id != layer.id:
                continue
            tally = counts.get(trace.net_id, 0) + 1
            counts[trace.net_id] = tally
            if tally > best:
                best = tally
                owner = trace.net_id
        if len(owner) == 0:
            owner = board.nets[0].id
        pours.append({
            "id": board.id + ":" + layer.id + ":pour",
            "layer": layer.id,
            "net_id": owner,
            "label": layer.name,
            "points": [[inset, inset], [board.outline_width_mm - inset, inset], [board.outline_width_mm - inset, board.outline_height_mm - inset], [inset, board.outline_height_mm - inset]],
        })
    return pours


def board_export(board: PcbBoard):
    index_of = pin_index_map(board.pins)
    solution = board_solution(board)
    gates = pcb_isolation_checks(board)
    report = analyze_solved(board, solution, gates)
    netlist = pcb_netlist_text(board)
    mut layers = []
    for index in range(0, len(board.stackup.layers)):
        layer = board.stackup.layers[index]
        layers.append({
            "id": layer.id,
            "name": layer.name,
            "index": index,
            "kind": layer_kind(layer),
            "function": layer_function_key(layer.function),
            "copper_oz": layer.copper_um / 34.79,
            "copper_um": layer.copper_um,
            "dielectric_mm": layer.dielectric_mm,
            "dielectric_er": layer.dielectric_er,
            "material": layer.material,
            "color": layer_color(index, layer),
            "internal": index > 0 and index < len(board.stackup.layers) - 1,
        })
    mut components = []
    mut pins = []
    for component in board.components:
        extent = component_extent(board, component.refdes)
        pad = pad_extent(component.footprint)
        mut component_pins = []
        for pin in board.pins:
            if pin.refdes != component.refdes:
                continue
            record = {
                "id": pin.id,
                "refdes": pin.refdes,
                "number": pin.number,
                "name": pin.name,
                "kind": pin_kind_key(pin.kind),
                "net_id": pin.net_id,
                "at": [pin.x_mm, pin.y_mm],
                "size": pad,
                "voltage_v": solution.node_voltages[index_of.get(pin.id, 0)],
            }
            component_pins.append(record)
            pins.append(record)
        components.append({
            "refdes": component.refdes,
            "part_number": component.part_number,
            "description": component.description,
            "footprint": component.footprint,
            "kind": component_kind(component.refdes),
            "center": [component.x_mm, component.y_mm],
            "rotation": component.rotation_deg,
            "size": extent,
            "layer": board.stackup.layers[0].id,
            "pin_count": component.pin_count,
            "modelled_pin_count": len(component_pins),
            "dissipation_w": component.power_dissipation_w,
            "mass_g": component.mass_g,
            "pins": component_pins,
        })
    mut traces = []
    mut violations = []
    mut routed_length_mm = 0.0
    mut copper_area_mm2 = 0.0
    mut midpoint_of = {}
    for trace in board.traces:
        origin = board.pins[index_of.get(trace.from_pin_id, 0)]
        target = board.pins[index_of.get(trace.to_pin_id, 0)]
        points = route_points(origin.x_mm, origin.y_mm, target.x_mm, target.y_mm)
        routed_length_mm = routed_length_mm + trace.length_mm
        copper_area_mm2 = copper_area_mm2 + trace.length_mm * trace.width_mm
        midpoint_of[trace.id] = len(traces)
        traces.append({
            "id": trace.id,
            "net_id": trace.net_id,
            "layer": trace.layer_id,
            "width_mm": trace.width_mm,
            "points": points,
            "current_a": trace.current_a,
            "delta_t_c": trace.temperature_rise_c,
            "length_mm": trace.length_mm,
            "resistance_mohm": trace.resistance_mohm,
            "drop_mv": trace.voltage_drop_mv,
            "power_loss_mw": trace.power_loss_mw,
            "required_width_mm": trace.ipc_min_width_mm,
            "compliant": trace.compliant,
            "from_pin_id": trace.from_pin_id,
            "to_pin_id": trace.to_pin_id,
        })
        if not trace.compliant:
            violations.append({
                "trace_id": trace.id,
                "net_id": trace.net_id,
                "layer": trace.layer_id,
                "width_mm": trace.width_mm,
                "required_width_mm": trace.ipc_min_width_mm,
                "current_a": trace.current_a,
                "delta_t_c": trace.temperature_rise_c,
                "deficit_mm": trace.ipc_min_width_mm - trace.width_mm,
                "severity": "major" if trace.ipc_min_width_mm > trace.width_mm * 1.25 else "marginal",
            })
    mut vias = []
    for via in board.vias:
        parent = via.id.substring(0, via.id.rfind("-v"))
        anchor = traces[midpoint_of.get(parent, 0)]["points"][1]
        vias.append({
            "id": via.id,
            "net_id": via.net_id,
            "at": anchor,
            "drill_mm": via.drill_mm,
            "pad_mm": via.pad_mm,
            "from_layer": via.from_layer_id,
            "to_layer": via.to_layer_id,
            "current_a": via.current_a,
            "resistance_mohm": via.resistance_mohm,
            "count": 1,
        })
    mut nets = []
    mut worst_drop_mv = 0.0
    for net in board.nets:
        mut trace_count = 0
        for trace in board.traces:
            if trace.net_id == net.id:
                trace_count = trace_count + 1
        drop_mv = net_drop_v(board, solution, net) * 1000.0
        worst_drop_mv = max(worst_drop_mv, drop_mv)
        nets.append({
            "id": net.id,
            "name": net.name,
            "net_class": viewer_net_class(net.net_class),
            "net_class_raw": net_class_key(net.net_class),
            "nominal_voltage_v": net.nominal_voltage_v,
            "current_a": net.current_a,
            "is_reference": net.is_reference,
            "feed_pin_id": net.pin_ids[0],
            "pin_ids": net.pin_ids,
            "note": "feed pin held at the declared operating potential; every other pad solved",
            "worst_drop_mv": drop_mv,
            "trace_count": trace_count,
            "vehicle_net_id": vehicle_net_for(net.id),
        })
    mut isolation = []
    for check in gates:
        isolation.append({"id": check.id, "category": check.category, "from_pin_id": check.from_pin_id, "to_pin_id": check.to_pin_id, "working_voltage_v": check.working_voltage_v, "required_mm": check.required_mm, "measured_mm": check.measured_mm, "reinforced": check.reinforced, "compliant": check.compliant})
    mut dissipation_w = report.total_copper_loss_w
    for component in board.components:
        dissipation_w = dissipation_w + component.power_dissipation_w
    analysis = {
        "board_id": report.board_id,
        "layer_count": len(board.stackup.layers),
        "component_count": len(board.components),
        "pin_count": len(board.pins),
        "net_count": len(board.nets),
        "trace_count": len(board.traces),
        "via_count": len(board.vias),
        "node_count": report.node_count,
        "branch_count": report.branch_count,
        "source_node": report.source_node,
        "routed_length_mm": routed_length_mm,
        "copper_area_mm2": copper_area_mm2,
        "total_dissipation_w": dissipation_w,
        "total_copper_loss_w": report.total_copper_loss_w,
        "worst_drop_mv": report.worst_voltage_drop_mv,
        "worst_drop_fraction": report.worst_drop_fraction,
        "worst_drop_net_id": report.worst_net_id,
        "max_temperature_rise_c": report.max_temperature_rise_c,
        "ipc_violations": report.ipc_violations,
        "drc_violations": report.drc_violations,
        "isolation_violations": report.isolation_violations,
        "solver_residual": report.solver_residual,
        "kcl_residual_a": report.kcl_residual_a,
        "rule": "IPC-2221B conductor sizing at " + str(IPC_ALLOWED_RISE_C) + " K rise, IPC-6012 class " + str(board.stackup.ipc_class),
        "violations": violations,
        "isolation": isolation,
        "verdict": "pass" if report.ipc_violations + report.drc_violations + report.isolation_violations == 0 else "review",
    }
    return {
        "id": board.id,
        "name": board.name,
        "host_part_id": board.host_part_id,
        "part_id": board.host_part_id,
        "zone": board.zone,
        "substrate": substrate_kind(board.id),
        "substrate_label": board.substrate,
        "outline_width_mm": board.outline_width_mm,
        "outline_height_mm": board.outline_height_mm,
        "size": [board.outline_width_mm, board.outline_height_mm],
        "corner_radius": min(4.0, board.outline_width_mm * 0.02),
        "stackup": {"id": board.stackup.id, "name": board.stackup.name, "total_thickness_mm": board.stackup.total_thickness_mm, "ipc_class": board.stackup.ipc_class, "impedance_target_ohm": board.stackup.impedance_target_ohm},
        "summary": board.name + " on " + board.substrate,
        "layers": layers,
        "components": components,
        "pins": pins,
        "traces": traces,
        "vias": vias,
        "pours": board_pours(board),
        "nets": nets,
        "analysis": analysis,
        "netlist": netlist,
        "netlist_sha_free_text_length": len(netlist),
        "board_worst_drop_mv": worst_drop_mv,
    }


pub def pcb_export() -> dict[str, any] !{}:
    mut boards = []
    mut component_count = 0
    mut pin_count = 0
    mut net_count = 0
    mut trace_count = 0
    mut via_count = 0
    mut copper_loss_w = 0.0
    mut violation_count = 0
    mut peak_drop_mv = 0.0
    for item in pcb_boards():
        exported = board_export(item)
        boards.append(exported)
        summary = exported["analysis"]
        component_count = component_count + summary["component_count"]
        pin_count = pin_count + summary["pin_count"]
        net_count = net_count + summary["net_count"]
        trace_count = trace_count + summary["trace_count"]
        via_count = via_count + summary["via_count"]
        copper_loss_w = copper_loss_w + summary["total_copper_loss_w"]
        violation_count = violation_count + summary["ipc_violations"] + summary["drc_violations"] + summary["isolation_violations"]
        peak_drop_mv = max(peak_drop_mv, exported["board_worst_drop_mv"])
    totals = {
        "board_count": len(boards),
        "component_count": component_count,
        "pin_count": pin_count,
        "net_count": net_count,
        "trace_count": trace_count,
        "via_count": via_count,
        "copper_loss_w": copper_loss_w,
        "violations": violation_count,
        "peak_conductor_drop_mv": peak_drop_mv,
    }
    return {
        "schema": "sema.circuitframe-pcb/v1",
        "units": "mm, A, V, W, degC",
        "standard": "IPC-2221B conductor sizing, IPC-6012 class 3 fabrication, IEC 60664-1 creepage screening",
        "provenance": "engineered reconstruction; not an OEM design, netlist or manufacturing data set",
        "conductor_model": "copper resistivity 1.724e-8 ohm.m at 20 degC, temperature coefficient 0.00393 /K, evaluated at " + str(CONDUCTOR_OPERATING_TEMP_C) + " degC",
        "boards": boards,
        "totals": totals,
        "summary": totals,
        "equations": pcb_equations(),
    }


pub def pcb_equations() -> list[dict[str, str]] !{}:
    return [
        {"id": "pcb-resistivity", "source": "conductor_resistance_ohm", "expression": "R = rho_20 (1 + alpha (T - 20)) L / (w t), rho_20 = 1.724e-8 ohm.m, alpha = 0.00393 /K", "unit": "ohm", "owner": "Sema printed-conductor model"},
        {"id": "pcb-nodal-analysis", "source": "solve_network", "expression": "G v = i, feed pin of every net held at its declared potential, loads injected at sink and returned at the return pad", "unit": "V", "owner": "Sema DC modified nodal analysis"},
        {"id": "pcb-joule-loss", "source": "joule_loss_w", "expression": "P = I^2 R with I = (v_a - v_b) / R from the solved node voltages", "unit": "W", "owner": "Sema DC loss model"},
        {"id": "pcb-via-barrel", "source": "via_barrel_resistance_ohm", "expression": "R_via = rho h / (pi (d + t) t), t = 25 um plating", "unit": "ohm", "owner": "Sema plated-barrel model"},
        {"id": "pcb-ipc2221-width", "source": "ipc2221_cross_section_mils2", "expression": "A_mils2 = (I / (k dT^0.44))^(1/0.725), k = 0.048 external and 0.024 internal; w_mm = A_mils2 / (t_um / 25.4) * 0.0254; the published curve is fitted to I <= 35 A, so busbar and printed-sill currents are extrapolated", "unit": "mm", "owner": "IPC-2221B conductor sizing"},
        {"id": "pcb-temperature-rise", "source": "ipc2221_temperature_rise_c", "expression": "dT = (I / (k A^0.725))^(1/0.44), the same relation inverted for the declared cross-section", "unit": "degC", "owner": "IPC-2221B conductor sizing"},
        {"id": "pcb-creepage", "source": "iec60664_creepage_mm", "expression": "creepage = table4(V_working) at pollution degree 2 and material group IIIa; reinforced insulation = 2 x basic", "unit": "mm", "owner": "IEC 60664-1 screening"},
    ]


test "every pad resolves to a declared component and net, and every trace stays on its net":
    mut orphan_pads = 0
    mut thin_nets = 0
    mut stray_endpoints = 0
    mut missing_members = 0
    for subject in pcb_boards():
        mut known_nets = {}
        for subject_net in subject.nets:
            known_nets[subject_net.id] = len(subject_net.pin_ids)
            if len(subject_net.pin_ids) < 2:
                thin_nets = thin_nets + 1
        mut known_refdes = {}
        for subject_component in subject.components:
            known_refdes[subject_component.refdes] = 1
        mut owner_of = {}
        for subject_pad in subject.pins:
            owner_of[subject_pad.id] = subject_pad.net_id
            if known_nets.get(subject_pad.net_id, 0) == 0 or known_refdes.get(subject_pad.refdes, 0) == 0:
                orphan_pads = orphan_pads + 1
        for subject_net in subject.nets:
            for member in subject_net.pin_ids:
                if owner_of.get(member, "") != subject_net.id:
                    missing_members = missing_members + 1
        for subject_wire in subject.traces:
            if owner_of.get(subject_wire.from_pin_id, "") != subject_wire.net_id:
                stray_endpoints = stray_endpoints + 1
            if owner_of.get(subject_wire.to_pin_id, "") != subject_wire.net_id:
                stray_endpoints = stray_endpoints + 1
    ensure orphan_pads == 0
    ensure thin_nets == 0
    ensure stray_endpoints == 0
    ensure missing_members == 0


test "the solved rail falls monotonically away from its feed and closes Kirchhoff":
    panel = sill_panel_board()
    field = board_solution(panel)
    ladder = ["TB2.3", "J2.4", "FS1.1", "J1.4", "TB1.3"]
    mut upstream = node_voltage(field, ladder[0])
    for rung in range(1, len(ladder)):
        downstream = node_voltage(field, ladder[rung])
        ensure downstream < upstream
        upstream = downstream
    ensure abs(node_voltage(field, ladder[0]) - 48.0) < 0.000000001
    ensure node_voltage(field, ladder[0]) - node_voltage(field, ladder[4]) > 0.05
    ensure field.kcl_residual_a < 0.000000001
    ensure field.solver_residual < 0.000000001
    ensure pcb_analyze(front_zone_board()).kcl_residual_a < 0.000000001
    ensure pcb_analyze(hv_junction_board()).kcl_residual_a < 0.000000001


test "copper loss equals I squared R over every routed conductor":
    mut reported_mw = 0.0
    mut recomputed_mw = 0.0
    for subject in pcb_boards():
        for subject_wire in subject.traces:
            reported_mw = reported_mw + subject_wire.power_loss_mw
            recomputed_mw = recomputed_mw + subject_wire.current_a * subject_wire.current_a * subject_wire.resistance_mohm
    ensure reported_mw > 60000.0
    ensure abs(reported_mw - recomputed_mw) / reported_mw < 0.000000001


test "node-wise and branch-wise power sums agree on the solved zone controller":
    card = front_zone_board()
    field = board_solution(card)
    mut node_side = 0.0
    mut branch_side = 0.0
    for rung in range(0, len(card.traces)):
        subject_wire = card.traces[rung]
        drive = field.branch_currents[rung]
        branch_side = branch_side + drive * drive * trace_branch_resistance_ohm(card, subject_wire)
        node_side = node_side + (node_voltage(field, subject_wire.from_pin_id) - node_voltage(field, subject_wire.to_pin_id)) * drive
    ensure branch_side > 0.0
    ensure abs(node_side - branch_side) / branch_side < 0.000000001


test "IPC-2221 sizing clears the sound eFuse channel and rejects the marginal one":
    card = front_zone_board()
    mut marginal = 0
    mut sound = 0
    for subject_wire in card.traces:
        if subject_wire.id == "fzc-out1-s1":
            marginal = marginal + 1
            ensure not subject_wire.compliant
            ensure subject_wire.ipc_min_width_mm > subject_wire.width_mm
            ensure subject_wire.ipc_min_width_mm < subject_wire.width_mm * 1.1
            ensure subject_wire.temperature_rise_c > IPC_ALLOWED_RISE_C
        if subject_wire.id == "fzc-out2-s1":
            sound = sound + 1
            ensure subject_wire.compliant
            ensure subject_wire.ipc_min_width_mm < subject_wire.width_mm
            ensure subject_wire.temperature_rise_c < IPC_ALLOWED_RISE_C
    ensure marginal == 1 and sound == 1
    ensure pcb_analyze(card).ipc_violations == 1
    ensure ipc_min_width_mm(3.1, 35.0, true) < ipc_min_width_mm(3.1, 35.0, false)
    ensure abs(ipc_temperature_rise_c(3.1, ipc_min_width_mm(3.1, 35.0, false), 35.0, false) - IPC_ALLOWED_RISE_C) < 0.00000001
    ensure ipc_min_width_mm(0.0, 35.0, true) == 0.0


test "the generated netlist enumerates exactly one node per connected pad":
    for subject in pcb_boards():
        rendered = pcb_netlist_text(subject)
        mut connected = 0
        for subject_pad in subject.pins:
            if len(subject_pad.net_id) > 0:
                connected = connected + 1
        ensure rendered.startswith("(export (version D)")
        ensure rendered.count("(node ") == connected
        ensure rendered.count("(comp (ref ") == len(subject.components)
        ensure rendered.count("(net (code ") == len(subject.nets)


test "the 800 V board holds IEC 60664-1 creepage and an under-spec gap is rejected":
    card = hv_junction_board()
    mut barrier_mm = 0.0
    mut basic_mm = 0.0
    for gate in pcb_isolation_checks(card):
        ensure gate.compliant
        if gate.reinforced:
            barrier_mm = gate.measured_mm
        else:
            basic_mm = gate.measured_mm
    ensure abs(iec60664_creepage_mm(800.0) - 8.0) < 0.000001
    ensure abs(isolation_required_mm(false, 800.0) - 16.0) < 0.000001
    ensure isolation_required_mm(true, 12.0) == 0.0
    ensure barrier_mm >= isolation_required_mm(false, 800.0)
    ensure basic_mm >= isolation_required_mm(true, 800.0)
    understated = PcbIsolationCheck(id="synthetic-under-spec", board_id=card.id, category="synthetic barrier gap", from_pin_id="U1.4", to_pin_id="U1.5", working_voltage_v=800.0, required_mm=isolation_required_mm(false, 800.0), measured_mm=6.0, reinforced=true, compliant=6.0 >= isolation_required_mm(false, 800.0))
    ensure not understated.compliant
    ensure pcb_analyze(card).isolation_violations == 0


test "conductor resistance follows the temperature-corrected copper law":
    rho = operating_resistivity()
    ensure abs(rho - COPPER_RESISTIVITY_OHM_M * (1.0 + COPPER_TEMPCO_PER_K * 25.0)) < 0.0000000000000001
    ensure abs(conductor_resistance_ohm(rho, 1.0, 0.001, 0.000035) - rho / 0.000000035) < 0.000000001
    corner = route_points(0.0, 0.0, 10.0, 4.0)
    ensure len(corner) == 3
    ensure abs(polyline_length_mm(corner) - (6.0 + math.sqrt(32.0))) < 0.000000001


test "the exported payload carries four boards with drawable geometry and analysis":
    payload = pcb_export()
    encoded = encode_json(payload)
    ensure len(encoded) > 200000 and len(encoded) < 400000
    ensure payload["schema"] == "sema.circuitframe-pcb/v1"
    ensure payload["totals"]["board_count"] == 4
    ensure payload["totals"]["component_count"] == 64
    ensure payload["totals"]["pin_count"] == 260
    ensure payload["totals"]["net_count"] == 84
    ensure payload["totals"]["trace_count"] == 176
    ensure payload["totals"]["via_count"] == 230
    ensure payload["totals"]["violations"] == 1
    ensure payload["totals"]["copper_loss_w"] > 69.0 and payload["totals"]["copper_loss_w"] < 70.0
    ensure payload["boards"][0]["id"] == "bms-slave"
    ensure payload["boards"][1]["id"] == "front-zone-controller"
    ensure payload["boards"][2]["id"] == "hv-junction-board"
    ensure payload["boards"][3]["id"] == "sill-conductor-panel"
    ensure payload["boards"][3]["substrate"] == "structural-sill"
    ensure payload["boards"][2]["substrate"] == "ims"
    mut drawable = 0
    for card in payload["boards"]:
        ensure card["size"][0] > 0.0 and card["size"][1] > 0.0
        ensure card["layers"][0]["index"] == 0
        ensure len(card["netlist"]) == card["netlist_sha_free_text_length"]
        for subject_wire in card["traces"]:
            ensure len(subject_wire["points"]) >= 2
            if subject_wire["compliant"]:
                ensure subject_wire["width_mm"] >= subject_wire["required_width_mm"]
            else:
                ensure subject_wire["width_mm"] < subject_wire["required_width_mm"]
            drawable = drawable + 1
    ensure drawable == 176


test "the equation manifest publishes the same row shape as the dynamics manifest":
    rows = pcb_equations()
    ensure len(rows) == 7
    mut described = 0
    for row in rows:
        ensure len(row["id"]) > 0 and len(row["source"]) > 0
        ensure len(row["expression"]) > 0 and len(row["unit"]) > 0 and len(row["owner"]) > 0
        described = described + 1
    ensure described == 7
```

### `src/physics.sema`

```sema
"""Coupled electric-vehicle dynamics over the panel-solved aerodynamic field.

Aerodynamic loads come from `aero.sema`: a constant-strength source-panel solve with an image
ground plane, closed by an empirical viscous and base-pressure model. Mass, centre of gravity and
yaw inertia come from `assembly.sema`, summed from the declared part placements rather than typed
in as literals. The thermal state is the eight-node coolant network from the same module, so heat
moves between components instead of each one sinking independently to ambient.

Two reduced-order surrogates are fitted to the panel solve at startup and are continuously scored
against it while the vehicle runs; that residual is what drives the model-adaptation lifecycle.
The dynamics themselves always integrate the solved loads, never the surrogate.
"""

import math

from magna_ev_digital_twin.aero import AeroBasis, AeroForces, aero_basis, aero_solve
from magna_ev_digital_twin.assembly import MassProperties, SuspensionState, air_spring_reference_pressure, arm_rotation_limits, corner_pose, mass_properties, suspension_travel, thermal_initial_temps, thermal_step, wheel_force
from magna_ev_digital_twin.hardware import AxleHardware, HardwareLimits, brake_fade_factor, hardware_limits, rolling_coefficient, tire_friction

assure silver


CALM_MODEL_ID = "aero-rom-calm-v1"
CROSSWIND_MODEL_ID = "aero-rom-crosswind-v2"
THERMAL_NODE_COUNT = 8
MAX_WIND_MPS = 60.0


pub struct ControlInput:
    throttle: f64
    brake: f64
    steering: f64
    wind_mps: f64
    wind_yaw_deg: f64
    invariant throttle >= 0.0 and throttle <= 1.0
    invariant brake >= 0.0 and brake <= 1.0
    invariant steering >= -1.0 and steering <= 1.0
    invariant wind_mps >= 0.0 and wind_mps <= MAX_WIND_MPS
    invariant wind_yaw_deg >= -180.0 and wind_yaw_deg <= 180.0


pub struct VehicleState:
    time_s: f64
    distance_m: f64
    speed_mps: f64
    lateral_speed_mps: f64
    acceleration_mps2: f64
    lateral_acceleration_mps2: f64
    yaw_rate_rps: f64
    yaw_angle_rad: f64
    steering_angle_rad: f64
    battery_soc: f64
    thermal_temps_c: list[f64]
    battery_temp_c: f64
    motor_temp_c: f64
    inverter_temp_c: f64
    coolant_temp_c: f64
    cabin_temp_c: f64
    tire_temp_c: f64
    front_disc_temp_c: f64
    rear_disc_temp_c: f64
    motor_rpm: f64
    model_id: str
    invariant time_s >= 0.0
    invariant distance_m >= 0.0
    invariant speed_mps >= 0.0 and speed_mps <= 90.0
    invariant lateral_speed_mps >= -40.0 and lateral_speed_mps <= 40.0
    invariant yaw_rate_rps >= -3.0 and yaw_rate_rps <= 3.0
    invariant battery_soc >= 0.0 and battery_soc <= 1.0
    invariant len(thermal_temps_c) == THERMAL_NODE_COUNT
    invariant battery_temp_c >= -40.0 and battery_temp_c <= 120.0
    invariant motor_temp_c >= -40.0 and motor_temp_c <= 220.0
    invariant inverter_temp_c >= -40.0 and inverter_temp_c <= 180.0
    invariant coolant_temp_c >= -40.0 and coolant_temp_c <= 140.0
    invariant cabin_temp_c >= -40.0 and cabin_temp_c <= 90.0
    invariant tire_temp_c >= -40.0 and tire_temp_c <= 180.0
    invariant front_disc_temp_c >= -40.0 and front_disc_temp_c <= MAX_DISC_TEMP_C
    invariant rear_disc_temp_c >= -40.0 and rear_disc_temp_c <= MAX_DISC_TEMP_C
    invariant len(model_id) > 0


pub struct AeroLoads:
    """Body-axis aerodynamic loads. Positive drag resists forward travel; positive lift is upward."""
    air_speed_mps: f64
    sideslip_rad: f64
    dynamic_pressure_pa: f64
    cd: f64
    cy: f64
    cl: f64
    cmz: f64
    drag_n: f64
    side_n: f64
    lift_n: f64
    front_lift_n: f64
    rear_lift_n: f64
    yaw_moment_nm: f64
    invariant air_speed_mps >= 0.0
    invariant dynamic_pressure_pa >= 0.0


pub struct AeroSurrogate:
    """Even-in-yaw drag and lift, odd-in-yaw side force and yaw moment, fitted to the panel solve."""
    model_id: str
    cd0: f64
    cd2: f64
    cd4: f64
    cy1: f64
    cy3: f64
    cl0: f64
    cl2: f64
    cl4: f64
    cmz1: f64
    cmz3: f64
    fit_max_yaw_rad: f64
    fit_nodes: int
    fit_rms_cd: f64
    invariant len(model_id) > 0
    invariant cd0 > 0.0
    invariant fit_max_yaw_rad > 0.0
    invariant fit_nodes >= 2
    invariant fit_rms_cd >= 0.0


pub struct VehicleParameters:
    """Inertial and geometric parameters derived from the declared assembly, not typed in."""
    mass_kg: f64
    cg_to_front_axle_m: f64
    cg_to_rear_axle_m: f64
    cg_height_m: f64
    yaw_inertia_kg_m2: f64
    wheelbase_m: f64
    front_mass_fraction: f64
    track_m: f64
    invariant mass_kg > 0.0
    invariant cg_to_front_axle_m > 0.0
    invariant cg_to_rear_axle_m > 0.0
    invariant cg_height_m > 0.0
    invariant yaw_inertia_kg_m2 > 0.0
    invariant wheelbase_m > 0.0
    invariant front_mass_fraction > 0.0 and front_mass_fraction < 1.0
    invariant track_m > 0.0


pub struct AeroHoldout:
    active_training_error: f64
    candidate_training_error: f64
    active_validation_error: f64
    candidate_validation_error: f64
    invariant active_training_error >= 0.0
    invariant candidate_training_error >= 0.0
    invariant active_validation_error >= 0.0
    invariant candidate_validation_error >= 0.0


pub struct CornerSuspension:
    """One wheel station read out of the declared double-wishbone closure."""
    vertical_load_n: f64
    wheel_travel_m: f64
    camber_deg: f64
    toe_deg: f64
    damper_length_m: f64
    spring_force_n: f64
    travel_utilisation: f64
    invariant vertical_load_n >= 0.0
    invariant damper_length_m > 0.0
    invariant travel_utilisation >= 0.0 and travel_utilisation <= 1.0


pub struct SuspensionCorners:
    """The four wheel stations, each solved at its own vertical load.

    Lateral load transfer moves normal force onto the outside pair, so the closure returns different
    travel, camber and toe per side. With no lateral acceleration the two sides are identical.
    """
    front_left: CornerSuspension
    front_right: CornerSuspension
    rear_left: CornerSuspension
    rear_right: CornerSuspension


pub struct SuspensionTable:
    """Corner closures pre-solved on a load grid, in strictly increasing load order."""
    loads_n: list[f64]
    corners: list[CornerSuspension]
    invariant len(loads_n) == len(corners)
    invariant len(loads_n) >= 2


pub struct AxleLongitudinal:
    """One axle's longitudinal state: what its hardware could do, and what the tyres allowed.

    Both the brake and the drive side report which declared component set the number. At the tyre
    limit a stop is mass-independent; the moment the caliper is the binding constraint it is not,
    and the difference is visible here rather than folded into one force.
    """
    axle: str
    normal_force_n: f64
    friction_coefficient: f64
    tire_limit_n: f64
    brake_capability_n: f64
    brake_force_n: f64
    brake_constraint: str
    motor_force_n: f64
    power_force_n: f64
    drive_force_n: f64
    drive_constraint: str
    cornering_stiffness_n_rad: f64
    disc_temp_c: f64
    fade_fraction: f64
    invariant len(axle) > 0
    invariant normal_force_n >= 0.0
    invariant friction_coefficient > 0.0
    invariant tire_limit_n >= 0.0
    invariant brake_capability_n >= 0.0
    invariant brake_force_n >= 0.0
    invariant drive_force_n >= 0.0
    invariant cornering_stiffness_n_rad >= 0.0
    invariant fade_fraction > 0.0 and fade_fraction <= 1.0
    invariant len(brake_constraint) > 0 and len(drive_constraint) > 0


struct LongitudinalDemand:
    """The live inputs one longitudinal solve needs, flattened out of the state and the aero solve.

    Built once per step and handed to the four axle solves. Everything on it is a scalar so that the
    per-call copy the interpreter makes stays cheap.
    """
    throttle: f64
    brake: f64
    power_limit: f64
    speed_mps: f64
    previous_acceleration_mps2: f64
    front_disc_temp_c: f64
    rear_disc_temp_c: f64
    front_lift_n: f64
    rear_lift_n: f64
    lift_n: f64
    drag_n: f64
    invariant throttle >= 0.0 and throttle <= 1.0
    invariant brake >= 0.0 and brake <= 1.0
    invariant power_limit >= 0.0 and power_limit <= 1.0
    invariant speed_mps >= 0.0


struct LongitudinalSolution:
    """Both axles solved together under one load transfer, with the resulting body acceleration."""
    front: AxleLongitudinal
    rear: AxleLongitudinal
    rolling_force_n: f64
    brake_line_pressure_pa: f64
    brake_force_n: f64
    drive_force_n: f64
    motor_torque_nm: f64
    acceleration_mps2: f64
    limiting_constraint: str
    invariant rolling_force_n >= 0.0
    invariant brake_line_pressure_pa >= 0.0
    invariant brake_force_n >= 0.0
    invariant drive_force_n >= 0.0
    invariant motor_torque_nm >= 0.0
    invariant len(limiting_constraint) > 0


pub struct PhysicsContext:
    """Everything expensive is built once here: the panel basis, the mass budget and both surrogates."""
    basis: AeroBasis
    parameters: VehicleParameters
    calm: AeroSurrogate
    crosswind: AeroSurrogate
    holdout: AeroHoldout
    suspension: SuspensionTable
    hardware: HardwareLimits


pub struct DynamicsFrame:
    state: VehicleState
    controls: ControlInput
    sequence: int
    apparent_air_speed_mps: f64
    sideslip_rad: f64
    dynamic_pressure_pa: f64
    solved: AeroLoads
    surrogate: AeroLoads
    model_cd: f64
    reference_cd: f64
    cd_pressure: f64
    cd_base: f64
    cd_friction: f64
    cd_wheels: f64
    drag_force_n: f64
    lift_force_n: f64
    side_force_n: f64
    aero_yaw_moment_nm: f64
    rolling_force_n: f64
    brake_force_n: f64
    regenerative_force_n: f64
    drive_force_n: f64
    wheel_torque_nm: f64
    tire_slip_ratio: f64
    front_axle_normal_force_n: f64
    rear_axle_normal_force_n: f64
    tire_utilization_fraction: f64
    front_axle: AxleLongitudinal
    rear_axle: AxleLongitudinal
    limiting_constraint: str
    brake_line_pressure_pa: f64
    brake_fade_fraction: f64
    front_disc_temp_c: f64
    rear_disc_temp_c: f64
    suspension: SuspensionCorners
    mechanical_power_kw: f64
    electrical_power_kw: f64
    regenerative_power_kw: f64
    battery_power_kw: f64
    battery_loss_kw: f64
    motor_loss_kw: f64
    inverter_loss_kw: f64
    pack_open_circuit_voltage_v: f64
    pack_voltage_v: f64
    pack_current_a: f64
    pack_resistance_ohm: f64
    power_limit_fraction: f64
    power_residual_kw: f64
    aero_residual_fraction: f64
    reynolds_number: f64
    separation_x_m: f64
    cp_min: f64
    cp_max: f64
    base_pressure_coefficient: f64
    wake_width_m: f64
    wake_shedding_hz: f64
    turbulence_intensity: f64
    wake_velocity_deficit_fraction: f64
    wake_recirculation_length_m: f64
    dalembert_residual_cd: f64
    aero_solver_residual: f64
    aero_force_closure_residual: f64
    thermal_energy_residual_w: f64
    energy_residual_fraction: f64
    invariant sequence >= 0
    invariant apparent_air_speed_mps >= 0.0
    invariant dynamic_pressure_pa >= 0.0
    invariant rolling_force_n >= 0.0
    invariant brake_force_n >= 0.0
    invariant regenerative_force_n >= 0.0
    invariant drive_force_n >= 0.0
    invariant tire_slip_ratio >= 0.0
    invariant front_axle_normal_force_n >= 0.0
    invariant rear_axle_normal_force_n >= 0.0
    invariant tire_utilization_fraction >= 0.0
    invariant len(limiting_constraint) > 0
    invariant brake_line_pressure_pa >= 0.0
    invariant brake_fade_fraction > 0.0 and brake_fade_fraction <= 1.0
    invariant front_disc_temp_c >= -40.0 and front_disc_temp_c <= MAX_DISC_TEMP_C
    invariant rear_disc_temp_c >= -40.0 and rear_disc_temp_c <= MAX_DISC_TEMP_C
    invariant regenerative_power_kw >= 0.0
    invariant battery_loss_kw >= 0.0
    invariant motor_loss_kw >= 0.0
    invariant inverter_loss_kw >= 0.0
    invariant pack_open_circuit_voltage_v > 0.0
    invariant pack_voltage_v > 0.0
    invariant pack_resistance_ohm > 0.0
    invariant power_limit_fraction >= 0.0 and power_limit_fraction <= 1.0
    invariant aero_residual_fraction >= 0.0
    invariant reynolds_number >= 0.0
    invariant wake_shedding_hz >= 0.0
    invariant turbulence_intensity >= 0.0 and turbulence_intensity <= 1.0
    invariant wake_velocity_deficit_fraction >= 0.0 and wake_velocity_deficit_fraction <= 1.0
    invariant wake_recirculation_length_m >= 0.0
    invariant energy_residual_fraction >= 0.0


AIR_DENSITY_KG_M3 = 1.225
FRONTAL_AREA_M2 = 2.48
SIDE_AREA_M2 = 4.32
PLANFORM_AREA_M2 = 7.95
VEHICLE_LENGTH_M = 4.751
GRAVITY_MPS2 = 9.80665
MOTOR_EFFICIENCY = 0.95
INVERTER_EFFICIENCY = 0.975
AUXILIARY_POWER_KW = 0.9
MAX_DISC_TEMP_C = 1200.0
FRONT_MOTOR_LOSS_SHARE = 0.42
AMBIENT_TEMP_C = 24.0
TIRE_THERMAL_CAPACITY_KJ_K = 28.0
TIRE_COOLING_KW_PER_K = 0.08
CALM_FIT_MAX_DEG = 8.0
CROSSWIND_FIT_MAX_DEG = 35.0
CALM_FIT_NODES = 5
CROSSWIND_FIT_NODES = 8
SURROGATE_FIT_SPEED_MPS = 27.8
SUSPENSION_TABLE_NODES = 48
SUSPENSION_MAX_LOAD_N = 40000.0


equation aerodynamic_force(air_density, coefficient, reference_area, relative_speed) -> any:
    return 0.5 * air_density * coefficient * reference_area * relative_speed^2


equation dynamic_pressure(air_density, speed) -> any:
    return 0.5 * air_density * speed^2


equation traction_force(motor_torque, gear_ratio, efficiency, wheel_radius) -> any:
    return motor_torque * gear_ratio * efficiency / wheel_radius


equation longitudinal_acceleration(drive_force, drag_force, rolling_force, brake_force, mass) -> any:
    return (drive_force - drag_force - rolling_force - brake_force) / mass


equation battery_soc_step(soc, electrical_power_kw, duration_s, capacity_kwh) -> any:
    return soc - electrical_power_kw * duration_s / (capacity_kwh * 3600.0)


equation battery_open_circuit_voltage(soc, temperature_c) -> any:
    return 700.0 + 150.0 * soc - max(0.0, 15.0 - temperature_c) * 0.7


equation battery_internal_resistance(base_resistance, soc, temperature_c) -> any:
    return base_resistance * (1.0 + max(0.0, 0.2 - soc) * 2.5 + max(0.0, 15.0 - temperature_c) * 0.018)


equation lumped_thermal_step(temperature_c, heat_kw, cooling_kw_per_k, ambient_c, thermal_capacity_kj_k, duration_s) -> any:
    return temperature_c + (heat_kw - cooling_kw_per_k * (temperature_c - ambient_c)) * duration_s / thermal_capacity_kj_k


equation lateral_acceleration(front_force, rear_force, aero_side_force, speed, yaw_rate, mass) -> any:
    return (front_force + rear_force + aero_side_force) / mass - speed * yaw_rate


equation yaw_acceleration(front_force, rear_force, aero_yaw_moment, front_arm, rear_arm, yaw_inertia) -> any:
    return (front_force * front_arm - rear_force * rear_arm + aero_yaw_moment) / yaw_inertia


equation normal_equations_2x2(s11, s12, s22, r1, r2) -> any:
    return solve([[s11, s12], [s12, s22]], [r1, r2])


equation axle_load_transfer(mass, acceleration, cg_height, wheelbase) -> any:
    return mass * acceleration * cg_height / wheelbase


equation disc_cooling_conductance(base_w_per_k, speed_w_per_k_mps, speed) -> any:
    return base_w_per_k + speed_w_per_k_mps * speed


def vehicle_parameters():
    """Inertia and geometry summed from the declared part placements in `assembly.sema`."""
    mass = mass_properties()
    return VehicleParameters(
        mass_kg=mass.total_mass_kg,
        cg_to_front_axle_m=0.5 * mass.wheelbase_m - mass.cg_x_m,
        cg_to_rear_axle_m=0.5 * mass.wheelbase_m + mass.cg_x_m,
        cg_height_m=mass.cg_height_m,
        yaw_inertia_kg_m2=mass.izz_kg_m2,
        wheelbase_m=mass.wheelbase_m,
        front_mass_fraction=mass.front_mass_fraction,
        track_m=2.0 * corner_pose(0.0)[5],
    )


def least_squares_pair(u: list[f64], y: list[f64]) !{}:
    """Fit y ~ a*u + b*u^2 through the origin by the 2x2 normal equations."""
    require len(u) == len(y) and len(u) >= 2
    mut s11 = 0.0
    mut s12 = 0.0
    mut s22 = 0.0
    mut r1 = 0.0
    mut r2 = 0.0
    for i in range(0, len(u)):
        a = u[i]
        b = a * a
        s11 = s11 + a * a
        s12 = s12 + a * b
        s22 = s22 + b * b
        r1 = r1 + a * y[i]
        r2 = r2 + b * y[i]
    if s11 * s22 - s12 * s12 <= 0.0:
        return [0.0, 0.0]
    coefficients = normal_equations_2x2(s11, s12, s22, r1, r2)
    return [coefficients[0], coefficients[1]]


def least_squares_single(u: list[f64], y: list[f64]):
    """Fit y ~ a*u through the origin."""
    require len(u) == len(y) and len(u) >= 1
    mut numerator = 0.0
    mut denominator = 0.0
    for i in range(0, len(u)):
        numerator = numerator + u[i] * y[i]
        denominator = denominator + u[i] * u[i]
    if denominator <= 0.0:
        return 0.0
    return numerator / denominator


def yaw_fit_nodes(count: int, maximum_deg: f64):
    """Yaw stations in radians, evenly spaced from zero to the declared fit boundary."""
    require count >= 2
    mut nodes = []
    for i in range(0, count):
        nodes = nodes + [maximum_deg * float(i) / float(count - 1) * math.pi / 180.0]
    return nodes


def fit_surrogate(basis: AeroBasis, model_id: str, count: int, maximum_deg: f64, quartic: bool) !{}:
    """Least-squares fit of the panel solve over a bounded yaw window.

    Drag and lift are even in sideslip, side force and yaw moment are odd, so the basis functions are
    beta^2/beta^4 and beta/beta^3 respectively. The zero-yaw intercepts are taken exactly from the
    solve so the calibrated `C_d(0)` is preserved without relying on the fit.
    """
    require count >= 2
    nodes = yaw_fit_nodes(count, maximum_deg)
    zero = aero_solve(basis, SURROGATE_FIT_SPEED_MPS, 0.0)
    mut even = []
    mut odd = []
    mut drag_residual = []
    mut lift_residual = []
    mut side = []
    mut moment = []
    for beta in nodes:
        forces = aero_solve(basis, SURROGATE_FIT_SPEED_MPS, beta)
        even = even + [beta * beta]
        odd = odd + [beta]
        drag_residual = drag_residual + [forces.cd - zero.cd]
        lift_residual = lift_residual + [forces.cl - zero.cl]
        side = side + [forces.cy]
        moment = moment + [forces.cmz]
    mut cd2 = 0.0
    mut cd4 = 0.0
    mut cl2 = 0.0
    mut cl4 = 0.0
    mut cy1 = 0.0
    mut cy3 = 0.0
    mut cmz1 = 0.0
    mut cmz3 = 0.0
    if quartic:
        drag = least_squares_pair(even, drag_residual)
        lift = least_squares_pair(even, lift_residual)
        lateral = least_squares_pair(odd, side)
        yaw = least_squares_pair(odd, moment)
        cd2 = drag[0]
        cd4 = drag[1]
        cl2 = lift[0]
        cl4 = lift[1]
        cy1 = lateral[0]
        cy3 = lateral[1]
        cmz1 = yaw[0]
        cmz3 = yaw[1]
    else:
        cd2 = least_squares_single(even, drag_residual)
        cl2 = least_squares_single(even, lift_residual)
        cy1 = least_squares_single(odd, side)
        cmz1 = least_squares_single(odd, moment)
    mut squared = 0.0
    for i in range(0, len(nodes)):
        u = even[i]
        predicted = cd2 * u + cd4 * u * u
        error = predicted - drag_residual[i]
        squared = squared + error * error
    return AeroSurrogate(
        model_id=model_id,
        cd0=zero.cd,
        cd2=cd2,
        cd4=cd4,
        cy1=cy1,
        cy3=cy3,
        cl0=zero.cl,
        cl2=cl2,
        cl4=cl4,
        cmz1=cmz1,
        cmz3=cmz3,
        fit_max_yaw_rad=maximum_deg * math.pi / 180.0,
        fit_nodes=count,
        fit_rms_cd=math.sqrt(squared / float(len(nodes))),
    )


def surrogate_loads(surrogate: AeroSurrogate, air_speed_mps: f64, sideslip_rad: f64) !{}:
    """Evaluate the reduced-order coefficients. Cheap enough to run inside any control loop."""
    speed = max(0.0, air_speed_mps)
    q = dynamic_pressure(AIR_DENSITY_KG_M3, speed)
    even = sideslip_rad * sideslip_rad
    cd = surrogate.cd0 + surrogate.cd2 * even + surrogate.cd4 * even * even
    cl = surrogate.cl0 + surrogate.cl2 * even + surrogate.cl4 * even * even
    cy = surrogate.cy1 * sideslip_rad + surrogate.cy3 * sideslip_rad * even
    cmz = surrogate.cmz1 * sideslip_rad + surrogate.cmz3 * sideslip_rad * even
    lift = cl * q * PLANFORM_AREA_M2
    return AeroLoads(
        air_speed_mps=speed,
        sideslip_rad=sideslip_rad,
        dynamic_pressure_pa=q,
        cd=cd,
        cy=cy,
        cl=cl,
        cmz=cmz,
        drag_n=cd * q * FRONTAL_AREA_M2,
        side_n=cy * q * SIDE_AREA_M2,
        lift_n=lift,
        front_lift_n=0.5 * lift,
        rear_lift_n=0.5 * lift,
        yaw_moment_nm=cmz * q * SIDE_AREA_M2 * VEHICLE_LENGTH_M,
    )


def solved_loads(forces: AeroForces):
    return AeroLoads(
        air_speed_mps=forces.air_speed_mps,
        sideslip_rad=forces.yaw_rad,
        dynamic_pressure_pa=forces.dynamic_pressure_pa,
        cd=forces.cd,
        cy=forces.cy,
        cl=forces.cl,
        cmz=forces.cmz,
        drag_n=forces.drag_n,
        side_n=forces.side_n,
        lift_n=forces.lift_n,
        front_lift_n=forces.front_lift_n,
        rear_lift_n=forces.rear_lift_n,
        yaw_moment_nm=forces.yaw_moment_nm,
    )


def surrogate_for(context: PhysicsContext, model_id: str):
    return context.crosswind if model_id == CROSSWIND_MODEL_ID else context.calm


def relative_drag_error(basis: AeroBasis, surrogate: AeroSurrogate, degrees: list[f64]) !{}:
    """Mean relative drag-coefficient error of a surrogate against the panel solve."""
    require len(degrees) >= 1
    mut total = 0.0
    for value in degrees:
        beta = value * math.pi / 180.0
        reference = aero_solve(basis, SURROGATE_FIT_SPEED_MPS, beta)
        predicted = surrogate_loads(surrogate, SURROGATE_FIT_SPEED_MPS, beta)
        total = total + abs(reference.cd - predicted.cd) / max(0.001, abs(reference.cd))
    return total / float(len(degrees))


pub def physics_context() -> PhysicsContext !{}:
    """Build the panel basis, the mass budget and both surrogates. Call once at startup."""
    basis = aero_basis()
    calm = fit_surrogate(basis, CALM_MODEL_ID, CALM_FIT_NODES, CALM_FIT_MAX_DEG, false)
    crosswind = fit_surrogate(basis, CROSSWIND_MODEL_ID, CROSSWIND_FIT_NODES, CROSSWIND_FIT_MAX_DEG, true)
    training = [4.0, 8.0]
    validation = [22.0, 32.0]
    return PhysicsContext(
        basis=basis,
        parameters=vehicle_parameters(),
        calm=calm,
        crosswind=crosswind,
        holdout=AeroHoldout(
            active_training_error=relative_drag_error(basis, calm, training),
            candidate_training_error=relative_drag_error(basis, crosswind, training),
            active_validation_error=relative_drag_error(basis, calm, validation),
            candidate_validation_error=relative_drag_error(basis, crosswind, validation),
        ),
        suspension=suspension_table(),
        hardware=hardware_limits(),
    )


pub def aero_holdout_metrics(context: PhysicsContext) -> dict[str, f64] !{}:
    """Training and held-out surrogate errors measured against the live panel solve."""
    return {
        "active_training_error": context.holdout.active_training_error,
        "candidate_training_error": context.holdout.candidate_training_error,
        "active_validation_error": context.holdout.active_validation_error,
        "candidate_validation_error": context.holdout.candidate_validation_error,
        "invariant_violations": 0.0,
    }


pub def initial_vehicle_state() -> VehicleState !{}:
    temps = thermal_initial_temps()
    return VehicleState(
        time_s=0.0,
        distance_m=0.0,
        speed_mps=0.0,
        lateral_speed_mps=0.0,
        acceleration_mps2=0.0,
        lateral_acceleration_mps2=0.0,
        yaw_rate_rps=0.0,
        yaw_angle_rad=0.0,
        steering_angle_rad=0.0,
        battery_soc=0.82,
        thermal_temps_c=temps,
        battery_temp_c=temps[0],
        motor_temp_c=max(temps[1], temps[2]),
        inverter_temp_c=max(temps[3], temps[4]),
        coolant_temp_c=temps[6],
        cabin_temp_c=temps[5],
        tire_temp_c=AMBIENT_TEMP_C,
        front_disc_temp_c=AMBIENT_TEMP_C,
        rear_disc_temp_c=AMBIENT_TEMP_C,
        motor_rpm=0.0,
        model_id=CALM_MODEL_ID,
    )


def state_at(speed_mps: f64, battery_soc: f64):
    """A settled state at one road speed and state of charge, with cold discs and cold tyres."""
    base = initial_vehicle_state()
    return VehicleState(
        time_s=base.time_s,
        distance_m=base.distance_m,
        speed_mps=speed_mps,
        lateral_speed_mps=base.lateral_speed_mps,
        acceleration_mps2=base.acceleration_mps2,
        lateral_acceleration_mps2=base.lateral_acceleration_mps2,
        yaw_rate_rps=base.yaw_rate_rps,
        yaw_angle_rad=base.yaw_angle_rad,
        steering_angle_rad=base.steering_angle_rad,
        battery_soc=battery_soc,
        thermal_temps_c=base.thermal_temps_c,
        battery_temp_c=base.battery_temp_c,
        motor_temp_c=base.motor_temp_c,
        inverter_temp_c=base.inverter_temp_c,
        coolant_temp_c=base.coolant_temp_c,
        cabin_temp_c=base.cabin_temp_c,
        tire_temp_c=base.tire_temp_c,
        front_disc_temp_c=base.front_disc_temp_c,
        rear_disc_temp_c=base.rear_disc_temp_c,
        motor_rpm=base.motor_rpm,
        model_id=base.model_id,
    )


def state_carrying(state: VehicleState, speed_mps: f64):
    """The same thermal and electrical state brought back to speed, for a repeated-stop sequence.

    Only the motion resets. Disc, tyre, pack and coolant temperatures carry over, which is the whole
    point: the heat a stop leaves behind is what the next one starts from.
    """
    return VehicleState(
        time_s=state.time_s,
        distance_m=state.distance_m,
        speed_mps=speed_mps,
        lateral_speed_mps=0.0,
        acceleration_mps2=0.0,
        lateral_acceleration_mps2=0.0,
        yaw_rate_rps=0.0,
        yaw_angle_rad=state.yaw_angle_rad,
        steering_angle_rad=0.0,
        battery_soc=state.battery_soc,
        thermal_temps_c=state.thermal_temps_c,
        battery_temp_c=state.battery_temp_c,
        motor_temp_c=state.motor_temp_c,
        inverter_temp_c=state.inverter_temp_c,
        coolant_temp_c=state.coolant_temp_c,
        cabin_temp_c=state.cabin_temp_c,
        tire_temp_c=state.tire_temp_c,
        front_disc_temp_c=state.front_disc_temp_c,
        rear_disc_temp_c=state.rear_disc_temp_c,
        motor_rpm=state.motor_rpm,
        model_id=state.model_id,
    )


def with_added_mass(context: PhysicsContext, added_kg: f64):
    """The same vehicle carrying more mass: everything else declared stays exactly as it is.

    Only the inertial parameters move. The motors, calipers, discs and tyres are the ones the parts
    list declares, which is the whole point of the comparison: what does the declared hardware do
    when it has more mass to move and to stop.
    """
    parameters = context.parameters
    return PhysicsContext(
        basis=context.basis,
        parameters=VehicleParameters(
            mass_kg=parameters.mass_kg + added_kg,
            cg_to_front_axle_m=parameters.cg_to_front_axle_m,
            cg_to_rear_axle_m=parameters.cg_to_rear_axle_m,
            cg_height_m=parameters.cg_height_m,
            yaw_inertia_kg_m2=parameters.yaw_inertia_kg_m2 * (parameters.mass_kg + added_kg) / parameters.mass_kg,
            wheelbase_m=parameters.wheelbase_m,
            front_mass_fraction=parameters.front_mass_fraction,
            track_m=parameters.track_m,
        ),
        calm=context.calm,
        crosswind=context.crosswind,
        holdout=context.holdout,
        suspension=context.suspension,
        hardware=context.hardware,
    )


def apparent_wind(state: VehicleState, controls: ControlInput):
    """Apparent-wind magnitude and sideslip in the vehicle frame.

    A head-on tunnel wind and forward road speed add; `wind_yaw_deg` rotates the tunnel wind around
    the vehicle, so a parked car in a 27.8 m/s stream reports 27.8 m/s of apparent wind at that yaw.
    """
    yaw = controls.wind_yaw_deg * math.pi / 180.0
    axial = state.speed_mps + controls.wind_mps * math.cos(yaw)
    lateral = state.lateral_speed_mps + controls.wind_mps * math.sin(yaw)
    speed = math.sqrt(axial * axial + lateral * lateral)
    sideslip = math.atan2(lateral, axial) if speed > 0.01 else 0.0
    return [speed, sideslip]


def corner_state(state: SuspensionState):
    """Narrow a solved corner to the fields the dynamics frame publishes."""
    return CornerSuspension(
        vertical_load_n=state.vertical_load_n,
        wheel_travel_m=state.wheel_travel_m,
        camber_deg=state.camber_deg,
        toe_deg=state.toe_deg,
        damper_length_m=state.damper_length_m,
        spring_force_n=state.spring_force_n,
        travel_utilisation=state.travel_utilisation,
    )


def suspension_table():
    """Pre-solve the declared corner closure on a grid uniform in lower-arm rotation.

    `suspension_travel` bisects the arm rotation against the polytropic air spring, which costs far
    more than the whole rest of a 20 Hz step. Sampling it once at startup and interpolating keeps the
    loop inside its budget without inventing a second suspension model. Rotation-uniform spacing puts
    the nodes where the linkage actually moves: the air spring stiffens so sharply that a load-uniform
    grid would collapse the working range into a couple of intervals. Rotations that clamp to the same
    load at a stop are dropped, so the lookup never meets a zero-width interval.
    """
    limits = arm_rotation_limits()
    pressure = air_spring_reference_pressure()
    mut loads = []
    mut corners = []
    for index in range(0, SUSPENSION_TABLE_NODES):
        rotation = limits[0] + (limits[1] - limits[0]) * f64(index) / f64(SUSPENSION_TABLE_NODES - 1)
        load = max(0.0, min(SUSPENSION_MAX_LOAD_N, wheel_force(rotation, pressure)))
        if len(loads) == 0 or load > loads[len(loads) - 1]:
            loads.append(load)
            corners.append(corner_state(suspension_travel(load)))
    return SuspensionTable(loads_n=loads, corners=corners)


def interpolate_corner(table: SuspensionTable, vertical_load_n: f64):
    """Read the pre-solved corner grid at one vertical wheel load, linearly between nodes."""
    load = max(0.0, min(SUSPENSION_MAX_LOAD_N, vertical_load_n))
    mut index = 1
    while index < len(table.loads_n) - 1 and table.loads_n[index] < load:
        index = index + 1
    lower = table.corners[index - 1]
    upper = table.corners[index]
    span = table.loads_n[index] - table.loads_n[index - 1]
    blend = max(0.0, min(1.0, (load - table.loads_n[index - 1]) / span))
    return CornerSuspension(
        vertical_load_n=load,
        wheel_travel_m=lower.wheel_travel_m + (upper.wheel_travel_m - lower.wheel_travel_m) * blend,
        camber_deg=lower.camber_deg + (upper.camber_deg - lower.camber_deg) * blend,
        toe_deg=lower.toe_deg + (upper.toe_deg - lower.toe_deg) * blend,
        damper_length_m=lower.damper_length_m + (upper.damper_length_m - lower.damper_length_m) * blend,
        spring_force_n=lower.spring_force_n + (upper.spring_force_n - lower.spring_force_n) * blend,
        travel_utilisation=lower.travel_utilisation + (upper.travel_utilisation - lower.travel_utilisation) * blend,
    )


def clamped_disc_temp(value: f64):
    """Hold an integrated disc temperature inside the range the state invariant declares."""
    return max(-40.0, min(MAX_DISC_TEMP_C, value))


def axle_longitudinal(hardware: AxleHardware, demand: LongitudinalDemand, normal_force_n: f64, disc_temp_c: f64) !{}:
    """One axle solved against its own hardware and its own live vertical load.

    The brake side walks the real chain: pedal demand sets line pressure, pressure and piston area
    set clamp force, pad friction and the effective radius set corner torque, and the rolling radius
    turns that into a wheel force. All of that is fixed by the declared components, so `hardware`
    arrives with it already solved and only the pedal fraction and the fade factor are applied here.
    The tyre side is the friction circle on this axle's live load, with the declared load
    sensitivity, so a heavier corner returns less grip per newton. Whichever of the two is smaller
    is the force the axle makes, and which one it was is published.
    """
    load = max(0.0, normal_force_n)
    friction = tire_friction(hardware.peak_friction, hardware.slide_friction, hardware.load_sensitivity, hardware.friction_reference_load_n, 0.5 * load)
    grip = friction * load
    fade = brake_fade_factor(disc_temp_c, hardware.fade_onset_temp_c, hardware.fade_rate_per_k, hardware.fade_floor_fraction)
    capability = hardware.brake_axle_force_n * demand.brake * fade
    braking = min(capability, grip)
    motor_force = hardware.drive_force_per_throttle_n * demand.throttle * demand.power_limit
    # The power term is the machine's shaft corner power carried through the same driveline
    # efficiency as the torque term, so the two meet exactly at the base speed. Which component set
    # the current behind that envelope is already resolved into `power_constraint`.
    power_force = hardware.power_force_numerator_w * demand.power_limit / max(2.0, demand.speed_mps)
    headroom = max(0.0, grip - braking)
    drive = min(motor_force, min(power_force, headroom))
    mut drive_constraint = "coasting"
    if demand.throttle > 0.0:
        drive_constraint = "tyre-grip"
        if motor_force <= power_force and motor_force <= headroom:
            drive_constraint = "motor-torque"
        elif power_force <= headroom:
            drive_constraint = hardware.power_constraint
    return AxleLongitudinal(
        axle=hardware.axle,
        normal_force_n=load,
        friction_coefficient=friction,
        tire_limit_n=grip,
        brake_capability_n=capability,
        brake_force_n=braking,
        brake_constraint="released" if demand.brake <= 0.0 else ("brake-torque" if capability < grip else "tyre-grip"),
        motor_force_n=motor_force,
        power_force_n=power_force,
        drive_force_n=drive,
        drive_constraint=drive_constraint,
        cornering_stiffness_n_rad=hardware.cornering_stiffness_per_n * load,
        disc_temp_c=disc_temp_c,
        fade_fraction=fade,
    )


def axle_pass(front_hardware: AxleHardware, rear_hardware: AxleHardware, demand: LongitudinalDemand, static_front_n: f64, static_rear_n: f64, transfer_n: f64) !{}:
    """Both axles solved at the load transfer one candidate acceleration implies."""
    return [
        axle_longitudinal(front_hardware, demand, static_front_n - transfer_n, demand.front_disc_temp_c),
        axle_longitudinal(rear_hardware, demand, static_rear_n + transfer_n, demand.rear_disc_temp_c),
    ]


def longitudinal_solution(parameters: VehicleParameters, hardware: HardwareLimits, demand: LongitudinalDemand) !{}:
    """Solve both axles and the body acceleration together, since each one sets the other.

    Load transfer depends on the acceleration, and the acceleration depends on the per-axle limits
    that the transferred loads produce. One predictor from the previous step's acceleration and one
    corrector from this step's is enough at 20 Hz: the transfer moves by well under a percent over
    the second pass, and a fixed point is not worth the extra arithmetic inside the loop.

    Nothing larger than a flat scalar struct crosses these call boundaries. The interpreter copies a
    struct argument by value, and `PhysicsContext` carries the whole panel basis, so handing it to a
    helper that runs four times a step costs milliseconds out of a fifty-millisecond budget.
    """
    front_hardware = hardware.front_axle
    rear_hardware = hardware.rear_axle
    mass = parameters.mass_kg
    weight = mass * GRAVITY_MPS2
    static_front = weight * parameters.cg_to_rear_axle_m / parameters.wheelbase_m - demand.front_lift_n
    static_rear = weight * parameters.cg_to_front_axle_m / parameters.wheelbase_m - demand.rear_lift_n
    coefficient = rolling_coefficient(hardware.tire.rolling_coefficient, hardware.tire.rolling_speed_coefficient, demand.speed_mps)
    rolling = coefficient * max(0.0, weight - demand.lift_n) if demand.speed_mps > 0.05 or demand.throttle > 0.0 else 0.0
    predicted = axle_pass(front_hardware, rear_hardware, demand, static_front, static_rear, axle_load_transfer(mass, demand.previous_acceleration_mps2, parameters.cg_height_m, parameters.wheelbase_m))
    trial = longitudinal_acceleration(predicted[0].drive_force_n + predicted[1].drive_force_n, demand.drag_n, rolling, predicted[0].brake_force_n + predicted[1].brake_force_n, mass)
    axles = axle_pass(front_hardware, rear_hardware, demand, static_front, static_rear, axle_load_transfer(mass, trial, parameters.cg_height_m, parameters.wheelbase_m))
    front = axles[0]
    rear = axles[1]
    braking = front.brake_force_n + rear.brake_force_n
    drive = front.drive_force_n + rear.drive_force_n
    mut limiting = front.drive_constraint if front.drive_force_n >= rear.drive_force_n else rear.drive_constraint
    if braking > 0.0:
        limiting = front.brake_constraint if front.brake_force_n >= rear.brake_force_n else rear.brake_constraint
    return LongitudinalSolution(
        front=front,
        rear=rear,
        rolling_force_n=rolling,
        brake_line_pressure_pa=hardware.max_line_pressure_pa * demand.brake,
        brake_force_n=braking,
        drive_force_n=drive,
        motor_torque_nm=hardware.max_motor_torque_nm * demand.throttle * demand.power_limit,
        acceleration_mps2=longitudinal_acceleration(drive, demand.drag_n, rolling, braking, mass),
        limiting_constraint=limiting,
    )


pub def step_vehicle(state: VehicleState, controls: ControlInput, duration_s: f64, sequence: int, model_id: str, context: PhysicsContext) -> DynamicsFrame !{}:
    require duration_s == 0.05
    require sequence >= 1
    parameters = context.parameters
    mass = parameters.mass_kg
    wheelbase = parameters.wheelbase_m
    wind = apparent_wind(state, controls)
    apparent_air_speed = wind[0]
    sideslip = wind[1]
    forces = aero_solve(context.basis, apparent_air_speed, sideslip)
    solved = solved_loads(forces)
    surrogate = surrogate_loads(surrogate_for(context, model_id), apparent_air_speed, sideslip)
    pressure = forces.dynamic_pressure_pa
    drag = solved.drag_n
    lift = solved.lift_n
    aero_side_force = 0.0 - solved.side_n
    aero_yaw_moment = 0.0 - solved.yaw_moment_nm
    battery_derate = max(0.25, 1.0 - max(0.0, state.battery_temp_c - 55.0) / 55.0)
    motor_derate = max(0.30, 1.0 - max(0.0, state.motor_temp_c - 120.0) / 100.0)
    soc_derate = min(1.0, max(0.0, state.battery_soc / 0.12))
    power_limit = min(battery_derate, min(motor_derate, soc_derate))
    longitudinal = longitudinal_solution(parameters, context.hardware, LongitudinalDemand(
        throttle=controls.throttle * (1.0 - controls.brake),
        brake=controls.brake,
        power_limit=power_limit,
        speed_mps=state.speed_mps,
        previous_acceleration_mps2=state.acceleration_mps2,
        front_disc_temp_c=state.front_disc_temp_c,
        rear_disc_temp_c=state.rear_disc_temp_c,
        front_lift_n=solved.front_lift_n,
        rear_lift_n=solved.rear_lift_n,
        lift_n=lift,
        drag_n=drag,
    ))
    front_axle = longitudinal.front
    rear_axle = longitudinal.rear
    rolling = longitudinal.rolling_force_n
    brake_force = longitudinal.brake_force_n
    drive_force = longitudinal.drive_force_n
    motor_torque = longitudinal.motor_torque_nm
    requested_drive_force = front_axle.motor_force_n + rear_axle.motor_force_n
    tire_force_limit = front_axle.tire_limit_n + rear_axle.tire_limit_n
    acceleration = longitudinal.acceleration_mps2
    next_speed = max(0.0, min(90.0, state.speed_mps + acceleration * duration_s))
    average_speed = (state.speed_mps + next_speed) * 0.5
    front_axle_load = front_axle.normal_force_n
    rear_axle_load = rear_axle.normal_force_n
    # Regeneration is a share of the same pedal demand, split across the axles by the continuous
    # torque each machine can generate and clamped to what that axle is actually braking with. The
    # discs only take the friction remainder, so a stop with a warm, receptive pack heats them less.
    charge_acceptance = min(1.0, max(0.0, (1.0 - state.battery_soc) / 0.02))
    limits = context.hardware
    regenerative_request = min(brake_force, min(limits.max_regenerative_force_n, limits.max_regenerative_power_kw * 1000.0 / max(2.0, state.speed_mps))) * charge_acceptance
    front_regenerative = min(front_axle.brake_force_n, regenerative_request * limits.regenerative_front_share)
    rear_regenerative = min(rear_axle.brake_force_n, regenerative_request * (1.0 - limits.regenerative_front_share))
    regenerative_force = front_regenerative + rear_regenerative
    disc_cooling_kw_per_k = disc_cooling_conductance(limits.disc_cooling_base_w_per_k, limits.disc_cooling_speed_w_per_k_mps, average_speed) / 1000.0
    next_front_disc_temp = clamped_disc_temp(lumped_thermal_step(state.front_disc_temp_c, 0.5 * (front_axle.brake_force_n - front_regenerative) * average_speed / 1000.0, disc_cooling_kw_per_k, AMBIENT_TEMP_C, limits.front_brake.disc_thermal_capacity_j_k / 1000.0, duration_s))
    next_rear_disc_temp = clamped_disc_temp(lumped_thermal_step(state.rear_disc_temp_c, 0.5 * (rear_axle.brake_force_n - rear_regenerative) * average_speed / 1000.0, disc_cooling_kw_per_k, AMBIENT_TEMP_C, limits.rear_brake.disc_thermal_capacity_j_k / 1000.0, duration_s))
    mechanical_power = max(0.0, drive_force * average_speed / 1000.0)
    regenerative_mechanical_power = regenerative_force * average_speed / 1000.0
    regenerative_power = regenerative_mechanical_power * limits.motor_efficiency * limits.inverter_efficiency
    traction_electrical_power = mechanical_power / (limits.motor_efficiency * limits.inverter_efficiency)
    auxiliary_power = AUXILIARY_POWER_KW * min(1.0, max(0.0, state.battery_soc / 0.01))
    electric_power = traction_electrical_power + auxiliary_power - regenerative_power
    open_circuit_voltage = battery_open_circuit_voltage(state.battery_soc, state.battery_temp_c)
    pack_resistance = battery_internal_resistance(limits.pack.resistance_ohm, state.battery_soc, state.battery_temp_c)
    current_discriminant = max(1.0, open_circuit_voltage * open_circuit_voltage - 4.0 * pack_resistance * electric_power * 1000.0)
    pack_current = (open_circuit_voltage - math.sqrt(current_discriminant)) / (2.0 * pack_resistance)
    pack_voltage = open_circuit_voltage - pack_current * pack_resistance
    battery_loss = pack_current * pack_current * pack_resistance / 1000.0
    battery_power = open_circuit_voltage * pack_current / 1000.0
    next_soc = max(0.0, min(1.0, battery_soc_step(state.battery_soc, battery_power, duration_s, limits.pack.energy_kwh)))
    motor_loss = mechanical_power * (1.0 / limits.motor_efficiency - 1.0) + regenerative_mechanical_power * (1.0 - limits.motor_efficiency)
    inverter_loss = mechanical_power / limits.motor_efficiency * (1.0 / limits.inverter_efficiency - 1.0) + regenerative_mechanical_power * limits.motor_efficiency * (1.0 - limits.inverter_efficiency)
    heat_w = [
        battery_loss * 1000.0,
        motor_loss * 1000.0 * FRONT_MOTOR_LOSS_SHARE,
        motor_loss * 1000.0 * (1.0 - FRONT_MOTOR_LOSS_SHARE),
        inverter_loss * 1000.0 * FRONT_MOTOR_LOSS_SHARE,
        inverter_loss * 1000.0 * (1.0 - FRONT_MOTOR_LOSS_SHARE),
        auxiliary_power * 1000.0 * 0.35,
        0.0,
        0.0,
    ]
    next_temps = thermal_step(state.thermal_temps_c, heat_w, AMBIENT_TEMP_C, duration_s)
    mut injected = 0.0
    for value in heat_w:
        injected = injected + value
    mut stored = 0.0
    for index in range(0, THERMAL_NODE_COUNT):
        stored = stored + (next_temps[index] - state.thermal_temps_c[index])
    steering_angle = controls.steering * 0.42
    speed_floor = max(2.0, next_speed)
    front_slip_angle = steering_angle - (state.lateral_speed_mps + parameters.cg_to_front_axle_m * state.yaw_rate_rps) / speed_floor
    rear_slip_angle = -(state.lateral_speed_mps - parameters.cg_to_rear_axle_m * state.yaw_rate_rps) / speed_floor
    total_axle_load = max(1.0, front_axle_load + rear_axle_load)
    front_longitudinal_force = front_axle.drive_force_n - front_axle.brake_force_n
    rear_longitudinal_force = rear_axle.drive_force_n - rear_axle.brake_force_n
    front_grip = front_axle.tire_limit_n
    rear_grip = rear_axle.tire_limit_n
    front_lateral_limit = math.sqrt(max(0.0, front_grip * front_grip - front_longitudinal_force * front_longitudinal_force))
    rear_lateral_limit = math.sqrt(max(0.0, rear_grip * rear_grip - rear_longitudinal_force * rear_longitudinal_force))
    front_tire_force = max(-front_lateral_limit, min(front_lateral_limit, front_axle.cornering_stiffness_n_rad * front_slip_angle))
    rear_tire_force = max(-rear_lateral_limit, min(rear_lateral_limit, rear_axle.cornering_stiffness_n_rad * rear_slip_angle))
    lateral_acceleration_value = lateral_acceleration(front_tire_force, rear_tire_force, aero_side_force, next_speed, state.yaw_rate_rps, mass)
    # The load transfer is driven by the total lateral force the contact patches react, not by the
    # body-frame velocity derivative: in a settled corner that derivative goes to zero while the car
    # is still leaning on its outside wheels.
    lateral_force_n = front_tire_force + rear_tire_force + aero_side_force
    lateral_transfer_n = lateral_force_n * parameters.cg_height_m / parameters.track_m
    front_transfer_n = lateral_transfer_n * parameters.front_mass_fraction
    rear_transfer_n = lateral_transfer_n * (1.0 - parameters.front_mass_fraction)
    corners = SuspensionCorners(
        front_left=interpolate_corner(context.suspension, front_axle_load * 0.5 - front_transfer_n),
        front_right=interpolate_corner(context.suspension, front_axle_load * 0.5 + front_transfer_n),
        rear_left=interpolate_corner(context.suspension, rear_axle_load * 0.5 - rear_transfer_n),
        rear_right=interpolate_corner(context.suspension, rear_axle_load * 0.5 + rear_transfer_n),
    )
    next_lateral_speed = max(-40.0, min(40.0, state.lateral_speed_mps + lateral_acceleration_value * duration_s))
    yaw_acceleration_value = yaw_acceleration(front_tire_force, rear_tire_force, aero_yaw_moment, parameters.cg_to_front_axle_m, parameters.cg_to_rear_axle_m, parameters.yaw_inertia_kg_m2)
    grip_yaw_rate_limit = min(3.0, 0.5 * (front_axle.friction_coefficient + rear_axle.friction_coefficient) * GRAVITY_MPS2 * total_axle_load / (mass * GRAVITY_MPS2 * max(2.0, next_speed)))
    next_yaw_rate = max(0.0 - grip_yaw_rate_limit, min(grip_yaw_rate_limit, state.yaw_rate_rps + yaw_acceleration_value * duration_s))
    next_yaw_angle = state.yaw_angle_rad + (state.yaw_rate_rps + next_yaw_rate) * 0.5 * duration_s
    tire_slip = min(1.0, abs(front_slip_angle) + abs(rear_slip_angle) + max(0.0, requested_drive_force - tire_force_limit) / max(1.0, tire_force_limit))
    front_tire_utilization = math.sqrt(front_longitudinal_force * front_longitudinal_force + front_tire_force * front_tire_force) / max(1.0, front_grip)
    rear_tire_utilization = math.sqrt(rear_longitudinal_force * rear_longitudinal_force + rear_tire_force * rear_tire_force) / max(1.0, rear_grip)
    tire_utilization = max(front_tire_utilization, rear_tire_utilization)
    tire_heat = rolling * average_speed / 1000.0 * 0.12 + brake_force * average_speed / 1000.0 * 0.08 + abs(lateral_acceleration_value) * average_speed * 0.03
    next_tire_temp = lumped_thermal_step(state.tire_temp_c, tire_heat, TIRE_COOLING_KW_PER_K, AMBIENT_TEMP_C, TIRE_THERMAL_CAPACITY_KJ_K, duration_s)
    wheel_rps = next_speed / (2.0 * math.pi * limits.tire.rolling_radius_m)
    next_state = VehicleState(
        time_s=state.time_s + duration_s,
        distance_m=state.distance_m + average_speed * duration_s,
        speed_mps=next_speed,
        lateral_speed_mps=next_lateral_speed,
        acceleration_mps2=acceleration,
        lateral_acceleration_mps2=lateral_acceleration_value,
        yaw_rate_rps=next_yaw_rate,
        yaw_angle_rad=next_yaw_angle,
        steering_angle_rad=steering_angle,
        battery_soc=next_soc,
        thermal_temps_c=next_temps,
        battery_temp_c=next_temps[0],
        motor_temp_c=max(next_temps[1], next_temps[2]),
        inverter_temp_c=max(next_temps[3], next_temps[4]),
        coolant_temp_c=next_temps[6],
        cabin_temp_c=next_temps[5],
        tire_temp_c=next_tire_temp,
        front_disc_temp_c=next_front_disc_temp,
        rear_disc_temp_c=next_rear_disc_temp,
        motor_rpm=wheel_rps * 60.0 * limits.final_drive_ratio,
        model_id=model_id,
    )
    aero_residual = abs(solved.cd - surrogate.cd) / max(0.001, abs(solved.cd))
    power_residual = electric_power - pack_voltage * pack_current / 1000.0
    energy_residual = abs(power_residual) / max(1.0, abs(electric_power))
    return DynamicsFrame(
        state=next_state,
        controls=controls,
        sequence=sequence,
        apparent_air_speed_mps=apparent_air_speed,
        sideslip_rad=sideslip,
        dynamic_pressure_pa=pressure,
        solved=solved,
        surrogate=surrogate,
        model_cd=surrogate.cd,
        reference_cd=solved.cd,
        cd_pressure=forces.cd_pressure,
        cd_base=forces.cd_base,
        cd_friction=forces.cd_friction,
        cd_wheels=forces.cd_wheels,
        drag_force_n=drag,
        lift_force_n=lift,
        side_force_n=solved.side_n,
        aero_yaw_moment_nm=solved.yaw_moment_nm,
        rolling_force_n=rolling,
        brake_force_n=brake_force,
        regenerative_force_n=regenerative_force,
        drive_force_n=drive_force,
        wheel_torque_nm=motor_torque,
        tire_slip_ratio=tire_slip,
        front_axle_normal_force_n=front_axle_load,
        rear_axle_normal_force_n=rear_axle_load,
        tire_utilization_fraction=tire_utilization,
        front_axle=front_axle,
        rear_axle=rear_axle,
        limiting_constraint=longitudinal.limiting_constraint,
        brake_line_pressure_pa=longitudinal.brake_line_pressure_pa,
        brake_fade_fraction=min(front_axle.fade_fraction, rear_axle.fade_fraction),
        front_disc_temp_c=next_front_disc_temp,
        rear_disc_temp_c=next_rear_disc_temp,
        suspension=corners,
        mechanical_power_kw=mechanical_power,
        electrical_power_kw=electric_power,
        regenerative_power_kw=regenerative_power,
        battery_power_kw=battery_power,
        battery_loss_kw=battery_loss,
        motor_loss_kw=motor_loss,
        inverter_loss_kw=inverter_loss,
        pack_open_circuit_voltage_v=open_circuit_voltage,
        pack_voltage_v=pack_voltage,
        pack_current_a=pack_current,
        pack_resistance_ohm=pack_resistance,
        power_limit_fraction=power_limit,
        power_residual_kw=power_residual,
        aero_residual_fraction=aero_residual,
        reynolds_number=forces.reynolds_number,
        separation_x_m=forces.separation_x_m,
        cp_min=forces.cp_min,
        cp_max=forces.cp_max,
        base_pressure_coefficient=forces.base_pressure_coefficient,
        wake_width_m=forces.wake_width_m,
        wake_shedding_hz=forces.wake_shedding_hz,
        turbulence_intensity=forces.turbulence_intensity,
        wake_velocity_deficit_fraction=forces.wake_deficit_fraction,
        wake_recirculation_length_m=forces.wake_recirculation_length_m,
        dalembert_residual_cd=forces.dalembert_residual_cd,
        aero_solver_residual=forces.solver_residual,
        aero_force_closure_residual=forces.force_closure_residual,
        thermal_energy_residual_w=injected,
        energy_residual_fraction=energy_residual,
    )


struct StopMeasurement:
    """One full-pedal stop, integrated at the same 20 Hz the live loop runs at."""
    distance_m: f64
    duration_s: f64
    mean_deceleration_mps2: f64
    front_constraint: str
    rear_constraint: str
    front_disc_temp_c: f64
    rear_disc_temp_c: f64
    peak_brake_force_n: f64
    invariant distance_m > 0.0
    invariant duration_s > 0.0
    invariant mean_deceleration_mps2 > 0.0


struct LaunchMeasurement:
    """One full-throttle launch to a target speed, integrated at the same 20 Hz."""
    duration_s: f64
    distance_m: f64
    launch_constraint: str
    final_constraint: str
    peak_drive_force_n: f64
    invariant duration_s > 0.0
    invariant distance_m > 0.0


def measured_stop(context: PhysicsContext, initial_speed_mps: f64):
    """Brake from one speed to rest at full pedal and report what the vehicle actually did."""
    require initial_speed_mps > 1.0
    controls = ControlInput(throttle=0.0, brake=1.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0)
    start = state_at(initial_speed_mps, 0.82)
    mut frame = step_vehicle(start, controls, 0.05, 1, CALM_MODEL_ID, context)
    first = frame
    mut sequence = 1
    while frame.state.speed_mps > 0.0 and sequence < 400:
        sequence = sequence + 1
        frame = step_vehicle(frame.state, controls, 0.05, sequence, CALM_MODEL_ID, context)
    return StopMeasurement(
        distance_m=frame.state.distance_m,
        duration_s=frame.state.time_s,
        mean_deceleration_mps2=initial_speed_mps * initial_speed_mps / (2.0 * frame.state.distance_m),
        front_constraint=first.front_axle.brake_constraint,
        rear_constraint=first.rear_axle.brake_constraint,
        front_disc_temp_c=frame.state.front_disc_temp_c,
        rear_disc_temp_c=frame.state.rear_disc_temp_c,
        peak_brake_force_n=first.brake_force_n,
    )


def measured_launch(context: PhysicsContext, target_speed_mps: f64):
    """Accelerate from rest to one speed at full throttle and report what the hardware allowed."""
    require target_speed_mps > 1.0
    controls = ControlInput(throttle=1.0, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0)
    mut frame = step_vehicle(initial_vehicle_state(), controls, 0.05, 1, CALM_MODEL_ID, context)
    first = frame
    mut sequence = 1
    while frame.state.speed_mps < target_speed_mps and sequence < 600:
        sequence = sequence + 1
        frame = step_vehicle(frame.state, controls, 0.05, sequence, CALM_MODEL_ID, context)
    return LaunchMeasurement(
        duration_s=frame.state.time_s,
        distance_m=frame.state.distance_m,
        launch_constraint=first.limiting_constraint,
        final_constraint=frame.limiting_constraint,
        peak_drive_force_n=first.drive_force_n,
    )


pub def dynamics_equations() -> list[dict[str, str]] !{}:
    return [
        {"id": "apparent-wind", "source": "apparent_wind", "expression": "V = |v_road + v_wind|; beta = atan2(V_y, V_x)", "unit": "m/s", "owner": "Sema vehicle-frame apparent wind (exact)"},
        {"id": "aero-pressure", "source": "dynamic_pressure", "expression": "q = 0.5 rho |V_rel|^2", "unit": "Pa", "owner": "Sema aero (exact)"},
        {"id": "aero-drag", "source": "aerodynamic_force", "expression": "F_d = q C_d(beta) A_frontal", "unit": "N", "owner": "Sema panel solve + viscous closure (solved+correlated)"},
        {"id": "aero-surrogate", "source": "surrogate_loads", "expression": "C_d ~= C_d0 + c_2 beta^2 + c_4 beta^4; C_y ~= c_1 beta + c_3 beta^3", "unit": "1", "owner": "Sema reduced-order surrogate fitted to the panel solve (correlated)"},
        {"id": "surrogate-fit", "source": "fit_surrogate", "expression": "min_c ||Phi c - (C(beta) - C(0))||_2 via the 2x2 normal equations", "unit": "1", "owner": "Sema least-squares fit (solved)"},
        {"id": "traction", "source": "traction_force", "expression": "F_x = T_m i_g eta / r_w, with T_m from the declared machines and i_g from the declared reduction stages", "unit": "N", "owner": "Sema longitudinal dynamics over hardware.sema limits (exact on solved inputs)"},
        {"id": "axle-drive-limit", "source": "axle_longitudinal", "expression": "F_x,axle = min(T_peak i_g eta / r_w, P_corner eta / v, mu(F_z) F_z - F_brake)", "unit": "N", "owner": "Sema per-axle drive limit; the binding term is published per axle (solved)"},
        {"id": "axle-brake-chain", "source": "axle_longitudinal", "expression": "F_b,axle = min(2 T_corner(p_line, mu_pad(T_disc)) / r_w, mu(F_z) F_z)", "unit": "N", "owner": "Sema pedal-to-pressure-to-clamp-to-torque chain against the tyre friction circle (solved on correlated pad and tyre closures)"},
        {"id": "brake-thermal", "source": "step_vehicle", "expression": "C_disc dT/dt = (F_b - F_regen) v / 2 - (h_0 + h_1 v)(T - T_amb)", "unit": "degC", "owner": "Sema per-disc lumped node; half m v squared less the recovered share (solved on a correlated cooling law)"},
        {"id": "load-transfer-closure", "source": "longitudinal_solution", "expression": "one predictor at a_x(k-1) and one corrector at a_x(k) over Delta F_z = m a_x h_cg / L", "unit": "N", "owner": "Sema two-pass longitudinal closure; the axle limits and the acceleration each set the other (exact)"},
        {"id": "rolling-resistance", "source": "rolling_coefficient", "expression": "F_rr = (f_0 + f_2 v^2) max(0, m g - F_lift)", "unit": "N", "owner": "Sema tyre rolling resistance on the live normal load, not on kerb weight (correlated)"},
        {"id": "cornering-stiffness", "source": "axle_longitudinal", "expression": "C_alpha,axle = c_z F_z,axle", "unit": "N/rad", "owner": "Sema tyre slip stiffness per unit vertical load, so an axle stiffens as it is loaded (correlated)"},
        {"id": "longitudinal", "source": "longitudinal_acceleration", "expression": "a_x = (F_x - F_d - F_rr - F_b) / m", "unit": "m/s^2", "owner": "Sema longitudinal dynamics (exact)"},
        {"id": "mass-budget", "source": "vehicle_parameters", "expression": "m, r_cg, I_zz summed from the declared part placements", "unit": "kg, m, kg m^2", "owner": "Sema assembly mass budget (solved)"},
        {"id": "axle-load-transfer", "source": "step_vehicle", "expression": "Delta F_z = m a_x h_cg / L; F_z,front = F_z,static - Delta F_z - F_lift,front", "unit": "N", "owner": "Sema reduced load-path model (exact)"},
        {"id": "corner-load-split", "source": "step_vehicle", "expression": "Delta F_z,lat = (F_yf + F_yr + F_aero,y) h_cg / t, split front/rear by the static axle share; F_z,corner = F_z,axle / 2 -+ Delta F_z,lat", "unit": "N", "owner": "Sema steady-state algebraic lateral transfer at centre-of-gravity height; no roll-stiffness distribution and no transient (exact, uncorrelated)"},
        {"id": "corner-closure", "source": "interpolate_corner", "expression": "travel, camber, toe and damper length read from the declared four-bar closure at F_z,corner", "unit": "m, deg", "owner": "Sema declared double-wishbone closure, startup-sampled and linearly interpolated (solved+interpolated)"},
        {"id": "tire-friction-circle", "source": "step_vehicle", "expression": "F_y,max = sqrt((mu F_z)^2 - F_x^2)", "unit": "N", "owner": "Sema coupled tire limit (correlated)"},
        {"id": "lateral", "source": "lateral_acceleration", "expression": "a_y = (F_yf + F_yr + F_aero,y) / m - u r", "unit": "m/s^2", "owner": "Sema reduced bicycle model (exact)"},
        {"id": "yaw", "source": "yaw_acceleration", "expression": "yaw_accel = (a F_yf - b F_yr + N_aero) / I_z", "unit": "rad/s^2", "owner": "Sema reduced bicycle model with solved aero moment (exact+solved)"},
        {"id": "battery-energy", "source": "battery_soc_step", "expression": "SoC_(k+1) = SoC_k - P_batt dt / E_pack", "unit": "fraction", "owner": "Sema energy model (exact)"},
        {"id": "battery-voltage", "source": "battery_open_circuit_voltage", "expression": "V_terminal = V_oc(SoC,T) - I R(SoC,T)", "unit": "V", "owner": "Sema equivalent-circuit model (correlated)"},
        {"id": "battery-terminal-power", "source": "step_vehicle", "expression": "P_terminal = I (V_oc - I R)", "unit": "kW", "owner": "Sema equivalent-circuit model (exact)"},
        {"id": "thermal-network", "source": "thermal_step", "expression": "C_i dT_i/dt = Q_i + sum_j k_ij (T_j - T_i) - h_i (T_i - T_amb)", "unit": "degC", "owner": "Sema coupled eight-node coolant network (exact)"},
        {"id": "thermal-tire", "source": "lumped_thermal_step", "expression": "T_(k+1) = T_k + (Q - h(T-T_amb)) dt / C_th", "unit": "degC", "owner": "Sema lumped tire node (correlated)"},
    ]


test "a stationary vehicle in a wind tunnel still measures drag":
    context = physics_context()
    state = initial_vehicle_state()
    controls = ControlInput(throttle=0.0, brake=1.0, steering=0.0, wind_mps=27.8, wind_yaw_deg=0.0)
    frame = step_vehicle(state, controls, 0.05, 1, CALM_MODEL_ID, context)
    ensure frame.state.speed_mps == 0.0
    ensure frame.apparent_air_speed_mps > 27.7 and frame.apparent_air_speed_mps < 27.9
    ensure frame.drag_force_n > 200.0
    ensure frame.dynamic_pressure_pa > 400.0


test "wind yaw drives a signed side force and yaw moment that vanish head-on":
    context = physics_context()
    state = initial_vehicle_state()
    head_on = step_vehicle(state, ControlInput(throttle=0.0, brake=1.0, steering=0.0, wind_mps=27.8, wind_yaw_deg=0.0), 0.05, 1, CALM_MODEL_ID, context)
    from_left = step_vehicle(state, ControlInput(throttle=0.0, brake=1.0, steering=0.0, wind_mps=27.8, wind_yaw_deg=20.0), 0.05, 1, CALM_MODEL_ID, context)
    from_right = step_vehicle(state, ControlInput(throttle=0.0, brake=1.0, steering=0.0, wind_mps=27.8, wind_yaw_deg=-20.0), 0.05, 1, CALM_MODEL_ID, context)
    ensure abs(head_on.side_force_n) < 0.001 * head_on.drag_force_n
    ensure abs(head_on.aero_yaw_moment_nm) < 0.001 * head_on.drag_force_n
    ensure from_left.side_force_n > 0.0
    ensure abs(from_left.side_force_n + from_right.side_force_n) < 0.000001 * abs(from_left.side_force_n)
    ensure from_left.drag_force_n > head_on.drag_force_n
    ensure abs(from_left.drag_force_n - from_right.drag_force_n) < 0.000001 * from_left.drag_force_n
    ensure from_left.state.lateral_acceleration_mps2 < 0.0


test "vehicle step conserves the declared power balance and advances causal state":
    context = physics_context()
    state = initial_vehicle_state()
    controls = ControlInput(throttle=0.7, brake=0.0, steering=0.1, wind_mps=0.0, wind_yaw_deg=0.0)
    frame = step_vehicle(state, controls, 0.05, 1, CALM_MODEL_ID, context)
    ensure frame.state.time_s == 0.05
    ensure frame.state.speed_mps > 0.0
    ensure frame.drive_force_n > frame.drag_force_n
    ensure frame.energy_residual_fraction < 0.000000001
    ensure frame.state.battery_soc < state.battery_soc


test "inertial parameters come from the declared assembly, not from literals":
    parameters = vehicle_parameters()
    ensure parameters.mass_kg > 1900.0 and parameters.mass_kg < 2300.0
    ensure abs(parameters.cg_to_front_axle_m + parameters.cg_to_rear_axle_m - parameters.wheelbase_m) < 0.000000001
    ensure parameters.cg_height_m > 0.45 and parameters.cg_height_m < 0.60
    ensure parameters.yaw_inertia_kg_m2 > 2800.0 and parameters.yaw_inertia_kg_m2 < 4500.0
    ensure parameters.front_mass_fraction > 0.47 and parameters.front_mass_fraction < 0.53
    ensure parameters.track_m > 1.5 and parameters.track_m < 1.8
    ensure parameters.track_m < parameters.wheelbase_m


test "the crosswind surrogate beats the calm surrogate outside the calm fit window":
    context = physics_context()
    holdout = aero_holdout_metrics(context)
    ensure context.calm.fit_rms_cd < 0.03 * context.calm.cd0
    ensure context.crosswind.fit_rms_cd < 0.03 * context.crosswind.cd0
    ensure holdout["active_validation_error"] > 0.10
    ensure holdout["candidate_validation_error"] < 0.04
    ensure holdout["candidate_validation_error"] < 0.25 * holdout["active_validation_error"]


test "surrogate coefficients respect the symmetry of the solved field":
    context = physics_context()
    left = surrogate_loads(context.crosswind, 27.8, 0.35)
    right = surrogate_loads(context.crosswind, 27.8, -0.35)
    ensure abs(left.cd - right.cd) < 0.000000001
    ensure abs(left.cy + right.cy) < 0.000000001
    ensure abs(left.cmz + right.cmz) < 0.000000001


test "aerodynamic loads scale with the square of the apparent wind":
    context = physics_context()
    state = initial_vehicle_state()
    slow = step_vehicle(state, ControlInput(throttle=0.0, brake=1.0, steering=0.0, wind_mps=15.0, wind_yaw_deg=10.0), 0.05, 1, CALM_MODEL_ID, context)
    fast = step_vehicle(state, ControlInput(throttle=0.0, brake=1.0, steering=0.0, wind_mps=30.0, wind_yaw_deg=10.0), 0.05, 1, CALM_MODEL_ID, context)
    ensure abs(fast.side_force_n - 4.0 * slow.side_force_n) < 0.000001 * abs(fast.side_force_n)
    ensure abs(fast.cd_pressure - slow.cd_pressure) < 0.000000000001
    ensure abs(fast.cd_base - slow.cd_base) < 0.000000000001
    ensure fast.cd_friction < slow.cd_friction
    ensure abs(fast.drag_force_n - 4.0 * slow.drag_force_n) < 0.02 * fast.drag_force_n


test "the coupled thermal network moves heat into the coolant loop":
    context = physics_context()
    mut state = initial_vehicle_state()
    mut frame = step_vehicle(state, ControlInput(throttle=1.0, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, 1, CALM_MODEL_ID, context)
    for sequence in range(2, 60):
        state = frame.state
        frame = step_vehicle(state, ControlInput(throttle=1.0, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, sequence, CALM_MODEL_ID, context)
    ensure frame.state.battery_temp_c > 24.0
    ensure frame.state.coolant_temp_c > 24.0
    ensure frame.state.motor_temp_c > frame.state.coolant_temp_c
    ensure len(frame.state.thermal_temps_c) == THERMAL_NODE_COUNT
    ensure frame.energy_residual_fraction < 0.000000001


test "equivalent circuit exposes voltage sag losses and thermal coupling":
    context = physics_context()
    state = initial_vehicle_state()
    frame = step_vehicle(state, ControlInput(throttle=0.9, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, 1, CALM_MODEL_ID, context)
    ensure frame.pack_voltage_v < frame.pack_open_circuit_voltage_v
    ensure frame.battery_loss_kw > 0.0
    ensure frame.battery_power_kw > frame.electrical_power_kw
    coasting = step_vehicle(state, ControlInput(throttle=0.0, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, 1, CALM_MODEL_ID, context)
    ensure frame.state.battery_temp_c > coasting.state.battery_temp_c
    ensure frame.energy_residual_fraction < 0.000000001


test "friction and regenerative braking share one bounded request":
    context = physics_context()
    mut state = initial_vehicle_state()
    mut frame = step_vehicle(state, ControlInput(throttle=0.8, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, 1, CALM_MODEL_ID, context)
    for sequence in range(2, 22):
        state = frame.state
        frame = step_vehicle(state, ControlInput(throttle=0.8, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, sequence, CALM_MODEL_ID, context)
    braking = step_vehicle(frame.state, ControlInput(throttle=0.0, brake=0.8, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, 22, CALM_MODEL_ID, context)
    ensure braking.regenerative_force_n > 0.0
    ensure braking.regenerative_force_n <= braking.brake_force_n
    ensure braking.regenerative_power_kw > 0.0
    ensure braking.state.acceleration_mps2 < 0.0


test "depleted battery cannot provide traction or auxiliary power":
    context = physics_context()
    depleted = state_at(0.0, 0.0)
    frame = step_vehicle(depleted, ControlInput(throttle=1.0, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, 1, CALM_MODEL_ID, context)
    ensure frame.drive_force_n == 0.0
    ensure frame.electrical_power_kw == 0.0
    ensure frame.state.battery_soc == 0.0


test "the pre-sampled corner table reproduces the bisected suspension closure":
    context = physics_context()
    mut previous = -1.0
    for load in context.suspension.loads_n:
        ensure load > previous
        previous = load
    for load in [1500.0, 3800.0, 4820.0, 5680.0, 7200.0, 11000.0, 18000.0]:
        solved = suspension_travel(load)
        read = interpolate_corner(context.suspension, load)
        ensure abs(read.wheel_travel_m - solved.wheel_travel_m) < 0.0003
        ensure abs(read.camber_deg - solved.camber_deg) < 0.01
        ensure abs(read.toe_deg - solved.toe_deg) < 0.005
        ensure abs(read.damper_length_m - solved.damper_length_m) < 0.0005
        ensure abs(read.spring_force_n - solved.spring_force_n) < 0.01 * solved.spring_force_n
    ensure len(context.suspension.loads_n) >= 24


test "a straight-line step loads both sides of an axle identically":
    context = physics_context()
    state = initial_vehicle_state()
    frame = step_vehicle(state, ControlInput(throttle=0.0, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, 1, CALM_MODEL_ID, context)
    corners = frame.suspension
    ensure abs(corners.front_left.vertical_load_n - frame.front_axle_normal_force_n * 0.5) < 0.000001
    ensure abs(corners.rear_left.vertical_load_n - frame.rear_axle_normal_force_n * 0.5) < 0.000001
    ensure corners.front_left.vertical_load_n == corners.front_right.vertical_load_n
    ensure corners.rear_left.vertical_load_n == corners.rear_right.vertical_load_n
    heavier = corners.rear_left if corners.rear_left.vertical_load_n > corners.front_left.vertical_load_n else corners.front_left
    lighter = corners.front_left if corners.rear_left.vertical_load_n > corners.front_left.vertical_load_n else corners.rear_left
    ensure heavier.wheel_travel_m > lighter.wheel_travel_m
    ensure heavier.camber_deg < lighter.camber_deg
    ensure heavier.damper_length_m < lighter.damper_length_m
    ensure heavier.spring_force_n > lighter.spring_force_n
    ensure corners.front_left.travel_utilisation >= 0.0 and corners.front_left.travel_utilisation <= 1.0
    ensure corners.rear_left.travel_utilisation >= 0.0 and corners.rear_left.travel_utilisation <= 1.0


test "cornering moves normal force onto the outside corners":
    context = physics_context()
    mut frame = step_vehicle(initial_vehicle_state(), ControlInput(throttle=0.6, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, 1, CALM_MODEL_ID, context)
    for sequence in range(2, 40):
        frame = step_vehicle(frame.state, ControlInput(throttle=0.35, brake=0.0, steering=0.8, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, sequence, CALM_MODEL_ID, context)
    corners = frame.suspension
    total = corners.front_left.vertical_load_n + corners.front_right.vertical_load_n + corners.rear_left.vertical_load_n + corners.rear_right.vertical_load_n
    ensure frame.state.yaw_rate_rps > 0.01
    ensure abs(total - frame.front_axle_normal_force_n - frame.rear_axle_normal_force_n) < 0.000000001
    ensure corners.front_right.vertical_load_n - corners.front_left.vertical_load_n > 200.0
    ensure corners.rear_right.vertical_load_n - corners.rear_left.vertical_load_n > 200.0
    ensure corners.front_right.wheel_travel_m > corners.front_left.wheel_travel_m
    ensure corners.front_right.camber_deg < corners.front_left.camber_deg
    ensure corners.front_right.damper_length_m < corners.front_left.damper_length_m
    ensure corners.rear_right.travel_utilisation > corners.rear_left.travel_utilisation


test "the brakes out-torque the tyres at kerb mass, so a cold stop is grip limited":
    context = physics_context()
    limits = context.hardware
    parameters = context.parameters
    grip = limits.tire.peak_friction * parameters.mass_kg * GRAVITY_MPS2
    ensure limits.max_brake_force_n > grip
    stop = measured_stop(context, 27.7778)
    ensure stop.front_constraint == "tyre-grip"
    ensure stop.rear_constraint == "tyre-grip"
    ensure stop.peak_brake_force_n < limits.max_brake_force_n


test "a 100-0 km/h stop lands in a credible band at close to mu times g":
    context = physics_context()
    stop = measured_stop(context, 27.7778)
    ensure stop.distance_m > 32.0 and stop.distance_m < 42.0
    ensure stop.duration_s > 2.3 and stop.duration_s < 3.3
    friction = context.hardware.tire.peak_friction
    ensure abs(stop.mean_deceleration_mps2 - friction * GRAVITY_MPS2) < 0.10 * friction * GRAVITY_MPS2
    ensure stop.front_disc_temp_c > AMBIENT_TEMP_C + 20.0
    ensure stop.front_disc_temp_c > stop.rear_disc_temp_c


test "0-100 km/h lands in a credible band for the derived torque and power":
    context = physics_context()
    launch = measured_launch(context, 27.7778)
    ensure launch.duration_s > 3.2 and launch.duration_s < 5.5
    ensure launch.distance_m > 30.0 and launch.distance_m < 90.0
    ensure launch.launch_constraint == "motor-torque"
    ensure launch.final_constraint == "inverter-power" or launch.final_constraint == "motor-power"
    torque = context.hardware.max_motor_torque_nm
    ceiling = torque * context.hardware.final_drive_ratio / context.hardware.tire.rolling_radius_m
    ensure launch.peak_drive_force_n > 0.85 * ceiling and launch.peak_drive_force_n < ceiling


test "mass lengthens the stop, heats the discs and cuts acceleration in proportion":
    context = physics_context()
    heavy = with_added_mass(context, 300.0)
    light_stop = measured_stop(context, 27.7778)
    heavy_stop = measured_stop(heavy, 27.7778)
    light_launch = measured_launch(context, 27.7778)
    heavy_launch = measured_launch(heavy, 27.7778)
    # At kerb both axles are grip limited, so the stop is very nearly mass independent. Add 300 kg
    # and the rear tyre asks for more than the rear caliper can make: from there the extra mass is
    # no longer paid for by extra grip, and the stop gets longer.
    ensure light_stop.rear_constraint == "tyre-grip"
    ensure heavy_stop.rear_constraint == "brake-torque"
    ensure heavy_stop.distance_m > light_stop.distance_m
    ensure heavy_stop.mean_deceleration_mps2 < light_stop.mean_deceleration_mps2
    ensure heavy_stop.front_disc_temp_c > light_stop.front_disc_temp_c
    ensure heavy_stop.rear_disc_temp_c > light_stop.rear_disc_temp_c
    # The launch is motor limited, so a = F/m and the time stretches with the mass ratio.
    ratio = heavy.parameters.mass_kg / context.parameters.mass_kg
    ensure heavy_launch.duration_s > light_launch.duration_s
    ensure abs(heavy_launch.duration_s / light_launch.duration_s - ratio) < 0.05 * ratio


test "repeated stops heat the discs until the declared pad compound fades":
    context = physics_context()
    limits = context.hardware
    controls = ControlInput(throttle=0.0, brake=1.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0)
    mut frame = step_vehicle(state_at(27.7778, 0.82), controls, 0.05, 1, CALM_MODEL_ID, context)
    cold = frame.front_axle.brake_capability_n
    mut sequence = 1
    # Brake to rest, put the car straight back to 100 km/h with the discs exactly as hot as it left
    # them, and repeat. Nothing else changes, so the only thing accumulating is disc temperature.
    while sequence < 900:
        sequence = sequence + 1
        relaunched = frame.state if frame.state.speed_mps > 0.1 else state_carrying(frame.state, 27.7778)
        frame = step_vehicle(relaunched, controls, 0.05, sequence, CALM_MODEL_ID, context)
    ensure frame.state.front_disc_temp_c > limits.fade_onset_temp_c
    ensure frame.brake_fade_fraction < 1.0
    ensure frame.front_axle.brake_capability_n < cold
    ensure frame.front_axle.brake_constraint == "brake-torque"
    ensure frame.brake_fade_fraction >= limits.fade_floor_fraction


test "every published limit is read from the declared hardware, not from a literal":
    context = physics_context()
    limits = context.hardware
    frame = step_vehicle(state_at(20.0, 0.82), ControlInput(throttle=1.0, brake=0.0, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, 1, CALM_MODEL_ID, context)
    ensure frame.front_axle.axle == "front" and frame.rear_axle.axle == "rear"
    ensure frame.rear_axle.motor_force_n > frame.front_axle.motor_force_n
    ensure abs(frame.front_axle.cornering_stiffness_n_rad - limits.tire.cornering_stiffness_per_n * frame.front_axle_normal_force_n) < 0.000000001
    ensure abs(frame.brake_line_pressure_pa) < 0.000000001
    ensure frame.front_axle.brake_constraint == "released" and frame.rear_axle.brake_constraint == "released"
    ensure frame.limiting_constraint == frame.rear_axle.drive_constraint
    braking = step_vehicle(state_at(20.0, 0.82), ControlInput(throttle=0.0, brake=0.5, steering=0.0, wind_mps=0.0, wind_yaw_deg=0.0), 0.05, 1, CALM_MODEL_ID, context)
    ensure abs(braking.brake_line_pressure_pa - 0.5 * limits.max_line_pressure_pa) < 0.000000001
    ensure braking.front_axle.brake_constraint == "brake-torque"
    ensure braking.energy_residual_fraction < 0.000000001
```

### `src/vehicle.sema`

```sema
"""Automotive-native major-assembly vehicle, E/E, and manufacturing definition."""

from magna_ev_digital_twin.candidate import conductor_mass, conductor_resistance
from magna_ev_digital_twin.domain import AutomationClass, CompletenessReport, EvidenceClass, EvidenceRecord, Fidelity, NetDefinition, OperationDefinition, PartDefinition, PartGroup, QualificationStatus, SourceAuthority, SourceBinding, SourceRecord, ValidationCaseDefinition, VehicleIdentity

assure silver


CONFIGURATION_ID = "circuitframe_gt_01"
CONFIGURATION_REVISION = 1
EVIDENCE_EXTERIOR = "model-y-community-reference-exterior-v1"
EVIDENCE_CONCEPT = "engineered-gap-reconstruction-v1"
EVIDENCE_ELECTRICAL = "circuitframe-electrical-v1"
EVIDENCE_PROCESS = "robot-process-kinematic-v1"
EXTERIOR_SOURCE_ID = "printables-model-1111101-stl-4641564"
ASSUMPTION_SOURCE_ID = "source-circuitframe-engineered-gap-v1"
TESLA_SERVICE_SOURCE_ID = "tesla-model-y-sop3-berlin-public-docs"
OPEN_BMS_SOURCE_ID = "ennoid-bms-daf72c9"
TABBY_CAD_SOURCE_ID = "tabby-evo-2015-open-cad"
EVIDENCE_TESLA_SERVICE = "model-y-official-service-reference-v1"
EVIDENCE_OPEN_BMS = "ennoid-open-bms-reference-v1"
EVIDENCE_TABBY_CAD = "tabby-open-cad-candidate-v1"
COPPER_RESISTIVITY_OHM_M = 0.0000000168
COPPER_DENSITY_KG_M3 = 8960.0
CONNECTOR_MASS_KG = 0.045
SPLICE_MASS_KG = 0.012
HARNESS_JACKET_FACTOR = 1.85
STRUCTURAL_OVERPRINT_FACTOR = 1.25
STRUCTURAL_CURRENT_DENSITY_A_MM2 = 7.0
MINIMUM_CONDUCTOR_AREA_MM2 = 0.35


def variant_value(configuration_variant: str, conventional: str, circuitframe: str):
    require configuration_variant == "conventional" or configuration_variant == "circuitframe"
    return conventional if configuration_variant == "conventional" else circuitframe


pub def vehicle_identity() -> VehicleIdentity !{}:
    return VehicleIdentity(
        id=CONFIGURATION_ID,
        revision=CONFIGURATION_REVISION,
        name="CircuitFrame Model Y reference reconstruction",
        category="Evidence-aware electric crossover reconstruction",
        seats=5,
        fidelity=Fidelity.derived,
    )


pub def vehicle_parts() -> list[PartDefinition] !{}:
    return [
        PartDefinition(id="body-shell", name="Painted body shell", group=PartGroup.body, count=1, material="aluminium/composite", mass_kg=236.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_EXTERIOR]),
        PartDefinition(id="floor-pan", name="Structural floor pan", group=PartGroup.chassis, count=1, material="aluminium extrusion and sheet", mass_kg=82.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="front-subframe", name="Front subframe", group=PartGroup.chassis, count=1, material="aluminium", mass_kg=31.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="rear-subframe", name="Rear subframe", group=PartGroup.chassis, count=1, material="aluminium", mass_kg=34.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="sill-panels", name="Variant-specific structural sill panels", group=PartGroup.electrical, count=2, material="configuration-specific aluminium/dielectric/conductor stack", mass_kg=26.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_CONCEPT, EVIDENCE_ELECTRICAL]),
        PartDefinition(id="battery-enclosure", name="Structural battery enclosure", group=PartGroup.energy, count=1, material="aluminium", mass_kg=118.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="battery-modules", name="Battery modules", group=PartGroup.energy, count=24, material="cell/module supplier assembly", mass_kg=438.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="battery-management", name="Battery management system", group=PartGroup.control, count=1, material="PCB/electronics", mass_kg=6.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="hv-junction", name="HV junction and protection unit", group=PartGroup.electrical, count=1, material="copper/polymer/electronics", mass_kg=13.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="charge-port", name="CCS charge port", group=PartGroup.energy, count=1, material="supplier assembly", mass_kg=4.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="onboard-charger", name="On-board charger", group=PartGroup.energy, count=1, material="power electronics", mass_kg=14.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="dc-dc", name="High-to-low voltage converter", group=PartGroup.energy, count=1, material="power electronics", mass_kg=8.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="front-inverter", name="Front inverter", group=PartGroup.drive, count=1, material="power electronics", mass_kg=11.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="rear-inverter", name="Rear inverter", group=PartGroup.drive, count=1, material="power electronics", mass_kg=12.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="front-motor", name="Front electric drive motor", group=PartGroup.drive, count=1, material="steel/copper/magnets", mass_kg=58.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="rear-motor", name="Rear electric drive motor", group=PartGroup.drive, count=1, material="steel/copper/magnets", mass_kg=71.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="reduction-gears", name="Single-speed reduction gears", group=PartGroup.drive, count=2, material="steel/aluminium", mass_kg=42.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="wheels", name="Aerodynamic alloy wheels", group=PartGroup.motion, count=4, material="forged aluminium", mass_kg=52.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_EXTERIOR, EVIDENCE_CONCEPT]),
        PartDefinition(id="tires", name="Road tires", group=PartGroup.motion, count=4, material="supplier tire assembly", mass_kg=48.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_EXTERIOR, EVIDENCE_CONCEPT]),
        PartDefinition(id="suspension", name="Air spring and multi-link suspension", group=PartGroup.motion, count=4, material="steel/aluminium/elastomer", mass_kg=104.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="steering", name="Electric steering system", group=PartGroup.motion, count=1, material="supplier mechatronics", mass_kg=23.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="brakes", name="Brake-by-wire corner modules", group=PartGroup.motion, count=4, material="steel/aluminium/electronics", mass_kg=76.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="thermal-loop", name="Battery and powertrain thermal loop", group=PartGroup.thermal, count=1, material="coolant/aluminium/polymer", mass_kg=31.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="heat-pump", name="Cabin and battery heat pump", group=PartGroup.thermal, count=1, material="supplier thermal assembly", mass_kg=24.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="front-zone-controller", name="Front zone controller", group=PartGroup.control, count=1, material="PCB/electronics", mass_kg=2.2, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="rear-zone-controller", name="Rear zone controller", group=PartGroup.control, count=1, material="PCB/electronics", mass_kg=2.2, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="cabin-zone-controller", name="Cabin zone controller", group=PartGroup.control, count=1, material="PCB/electronics", mass_kg=1.8, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="central-compute", name="Central vehicle compute", group=PartGroup.control, count=1, material="supplier compute assembly", mass_kg=5.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="lv-battery", name="Low-voltage battery", group=PartGroup.energy, count=1, material="lithium-ion assembly", mass_kg=12.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="sensors", name="Vehicle sensing suite", group=PartGroup.control, count=28, material="supplier sensors", mass_kg=18.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_ELECTRICAL]),
        PartDefinition(id="lamps", name="Exterior lighting", group=PartGroup.electrical, count=6, material="polymer/LED/electronics", mass_kg=16.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_EXTERIOR, EVIDENCE_CONCEPT]),
        PartDefinition(id="glazing", name="Laminated glazing", group=PartGroup.body, count=6, material="laminated glass", mass_kg=47.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_EXTERIOR, EVIDENCE_CONCEPT]),
        PartDefinition(id="closures", name="Doors and closures", group=PartGroup.body, count=6, material="aluminium/composite", mass_kg=112.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_EXTERIOR, EVIDENCE_CONCEPT]),
        PartDefinition(id="seats", name="Seats and occupant structures", group=PartGroup.cabin, count=5, material="steel/foam/textile", mass_kg=96.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="interior", name="Interior, HMI and trim", group=PartGroup.cabin, count=1, material="mixed interior system", mass_kg=118.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="restraints", name="Restraints and airbags", group=PartGroup.safety, count=1, material="supplier safety system", mass_kg=34.0, fidelity=Fidelity.derived, opaque=true, evidence_ids=[EVIDENCE_CONCEPT]),
        PartDefinition(id="fasteners-seals", name="Fasteners, adhesives and seals", group=PartGroup.manufacturing, count=620, material="mixed joining system", mass_kg=63.0, fidelity=Fidelity.reduced, opaque=false, evidence_ids=[EVIDENCE_PROCESS]),
        PartDefinition(id="service-links", name="Variant-specific harness and serviceable connector set", group=PartGroup.electrical, count=18, material="copper/polymer/connectors", mass_kg=19.0, fidelity=Fidelity.derived, opaque=false, evidence_ids=[EVIDENCE_ELECTRICAL]),
    ]


pub def vehicle_nets(configuration_variant: str) -> list[NetDefinition] !{}:
    require configuration_variant == "conventional" or configuration_variant == "circuitframe"
    return [
        NetDefinition(id="hv-pack-front", name="Pack to front inverter", domain="HV", source_part_id="hv-junction", target_part_ids=["front-inverter"], nominal_voltage_v=800.0, maximum_current_a=420.0, route=variant_value(configuration_variant, "serviceable shielded HV cable", "left CircuitFrame sill protected busbar"), fidelity=Fidelity.derived, evidence_ids=[EVIDENCE_ELECTRICAL]),
        NetDefinition(id="hv-pack-rear", name="Pack to rear inverter", domain="HV", source_part_id="hv-junction", target_part_ids=["rear-inverter"], nominal_voltage_v=800.0, maximum_current_a=520.0, route=variant_value(configuration_variant, "serviceable shielded HV cable", "battery enclosure rear protected busbar"), fidelity=Fidelity.derived, evidence_ids=[EVIDENCE_ELECTRICAL]),
        NetDefinition(id="hv-charge", name="Charge path", domain="HV", source_part_id="charge-port", target_part_ids=["onboard-charger", "hv-junction"], nominal_voltage_v=800.0, maximum_current_a=500.0, route="serviceable shielded cable", fidelity=Fidelity.derived, evidence_ids=[EVIDENCE_ELECTRICAL]),
        NetDefinition(id="hv-dcdc", name="DC/DC supply", domain="HV", source_part_id="hv-junction", target_part_ids=["dc-dc"], nominal_voltage_v=800.0, maximum_current_a=25.0, route=variant_value(configuration_variant, "shielded HV cable", "protected panel trace"), fidelity=Fidelity.derived, evidence_ids=[EVIDENCE_ELECTRICAL]),
        NetDefinition(id="lv-zones", name="48 V zonal supply", domain="LV", source_part_id="dc-dc", target_part_ids=["front-zone-controller", "rear-zone-controller", "cabin-zone-controller", "central-compute"], nominal_voltage_v=48.0, maximum_current_a=180.0, route=variant_value(configuration_variant, "branched copper wiring harness", "dual protected structural conductors"), fidelity=Fidelity.derived, evidence_ids=[EVIDENCE_ELECTRICAL]),
        NetDefinition(id="data-backbone", name="Redundant Ethernet backbone", domain="DATA", source_part_id="central-compute", target_part_ids=["front-zone-controller", "rear-zone-controller", "cabin-zone-controller"], nominal_voltage_v=1.0, maximum_current_a=0.2, route=variant_value(configuration_variant, "shielded twisted-pair harness", "shielded differential structural links"), fidelity=Fidelity.derived, evidence_ids=[EVIDENCE_ELECTRICAL]),
        NetDefinition(id="front-drive-control", name="Front drive control", domain="DATA", source_part_id="front-zone-controller", target_part_ids=["front-inverter", "brakes", "steering"], nominal_voltage_v=1.0, maximum_current_a=0.2, route=variant_value(configuration_variant, "conventional branch harness", "short local links"), fidelity=Fidelity.derived, evidence_ids=[EVIDENCE_ELECTRICAL]),
        NetDefinition(id="rear-drive-control", name="Rear drive control", domain="DATA", source_part_id="rear-zone-controller", target_part_ids=["rear-inverter", "brakes"], nominal_voltage_v=1.0, maximum_current_a=0.2, route=variant_value(configuration_variant, "conventional branch harness", "short local links"), fidelity=Fidelity.derived, evidence_ids=[EVIDENCE_ELECTRICAL]),
        NetDefinition(id="thermal-sensors", name="Thermal sensor network", domain="SENSOR", source_part_id="cabin-zone-controller", target_part_ids=["battery-management", "thermal-loop", "heat-pump"], nominal_voltage_v=5.0, maximum_current_a=2.0, route=variant_value(configuration_variant, "conventional sensor harness", "local printed links and service harness"), fidelity=Fidelity.derived, evidence_ids=[EVIDENCE_ELECTRICAL]),
    ]


def net_routing() -> list[dict[str, any]]:
    """Per-net conductor routing for both configurations.

    Lengths, terminations and splice counts are bounded engineered assumptions derived from the
    packaging of the reconstructed vehicle; they are not measured production harness data. The
    CircuitFrame column routes through the structure, so it is shorter and needs fewer separable
    interfaces, which is the claim this comparison exists to quantify.
    """
    return [
        {"id": "hv-pack-front", "density": 5.0, "conventional": [3.10, 4.0, 1.0], "circuitframe": [2.35, 2.0, 0.0]},
        {"id": "hv-pack-rear", "density": 5.0, "conventional": [1.90, 4.0, 1.0], "circuitframe": [1.35, 2.0, 0.0]},
        {"id": "hv-charge", "density": 5.0, "conventional": [4.40, 6.0, 2.0], "circuitframe": [4.00, 4.0, 1.0]},
        {"id": "hv-dcdc", "density": 5.0, "conventional": [1.20, 4.0, 0.0], "circuitframe": [0.90, 2.0, 0.0]},
        {"id": "lv-zones", "density": 6.0, "conventional": [11.60, 14.0, 6.0], "circuitframe": [7.20, 6.0, 1.0]},
        {"id": "data-backbone", "density": 6.0, "conventional": [9.80, 12.0, 4.0], "circuitframe": [6.40, 6.0, 0.0]},
        {"id": "front-drive-control", "density": 6.0, "conventional": [5.30, 9.0, 3.0], "circuitframe": [2.90, 4.0, 0.0]},
        {"id": "rear-drive-control", "density": 6.0, "conventional": [4.70, 8.0, 3.0], "circuitframe": [2.60, 4.0, 0.0]},
        {"id": "thermal-sensors", "density": 6.0, "conventional": [7.90, 16.0, 5.0], "circuitframe": [4.80, 8.0, 1.0]},
    ]


def conductor_variant(net: NetDefinition, route: list[f64], density_a_mm2: f64, jacket_factor: f64, conductor: str, route_text: str) !{}:
    """Size one conductor run from its declared current and report the resulting electrical loads."""
    length_m = route[0]
    connectors = route[1]
    splices = route[2]
    area_mm2 = max(MINIMUM_CONDUCTOR_AREA_MM2, net.maximum_current_a / density_a_mm2)
    resistance_ohm = conductor_resistance(COPPER_RESISTIVITY_OHM_M, length_m, area_mm2, 1.0)
    copper_kg = conductor_mass(COPPER_DENSITY_KG_M3, length_m, area_mm2, 1.0)
    return {
        "conductor": conductor,
        "section_mm2": area_mm2,
        "length_m": length_m,
        "mass_kg": copper_kg * jacket_factor + connectors * CONNECTOR_MASS_KG + splices * SPLICE_MASS_KG,
        "resistance_mohm": resistance_ohm * 1000.0,
        "drop_v": net.maximum_current_a * resistance_ohm,
        "loss_w": net.maximum_current_a * net.maximum_current_a * resistance_ohm,
        "connectors": int(connectors),
        "splices": int(splices),
        "route": route_text,
    }


pub def vehicle_net_comparison() -> list[dict[str, any]] !{}:
    """Conventional harness versus CircuitFrame structural conductors over the same nine nets."""
    conventional_nets = vehicle_nets("conventional")
    circuitframe_nets = vehicle_nets("circuitframe")
    routing = net_routing()
    mut rows = []
    for index in range(0, len(routing)):
        entry = routing[index]
        harness_net = conventional_nets[index]
        structural_net = circuitframe_nets[index]
        ensure entry["id"] == harness_net.id and entry["id"] == structural_net.id
        harness = conductor_variant(harness_net, entry["conventional"], entry["density"], HARNESS_JACKET_FACTOR, "stranded copper cable with jacket and shield", harness_net.route)
        structural = conductor_variant(structural_net, entry["circuitframe"], STRUCTURAL_CURRENT_DENSITY_A_MM2, STRUCTURAL_OVERPRINT_FACTOR, "printed structural conductor with dielectric barrier and overprinted shield", structural_net.route)
        rows = rows + [{
            "id": harness_net.id,
            "name": harness_net.name,
            "domain": harness_net.domain,
            "voltage_v": harness_net.nominal_voltage_v,
            "current_a": harness_net.maximum_current_a,
            "source_part_id": harness_net.source_part_id,
            "target_part_ids": harness_net.target_part_ids,
            "variants": {"conventional": harness, "circuitframe": structural},
            "delta": {
                "mass_kg": structural["mass_kg"] - harness["mass_kg"],
                "connectors": structural["connectors"] - harness["connectors"],
                "drop_v": structural["drop_v"] - harness["drop_v"],
                "length_m": structural["length_m"] - harness["length_m"],
            },
        }]
    return rows


def manufacturing_operations(configuration_variant: str):
    require configuration_variant == "conventional" or configuration_variant == "circuitframe"
    return [
        OperationDefinition(id="op-010", name="Locate structural floor and subframes", station="body-01", resource="fixture-cell", consumed_part_ids=["floor-pan", "front-subframe", "rear-subframe"], created_features=["vehicle datum frame"], automation=AutomationClass.unknown, cycle_time_s=68.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-020", name=variant_value(configuration_variant, "Install and inspect conventional harnesses", "Print and inspect protected sill conductors"), station="circuit-01", resource=variant_value(configuration_variant, "harness routing and connector cell", "multi-material deposition cell"), consumed_part_ids=["sill-panels", "service-links"], created_features=[variant_value(configuration_variant, "routed HV/LV/data harnesses", "HV/LV/data conductors with dielectric and coating")], automation=AutomationClass.unknown, cycle_time_s=142.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-030", name="Place zone controllers and power terminals", station="circuit-02", resource="six-axis robot and vision tool", consumed_part_ids=["front-zone-controller", "rear-zone-controller", "cabin-zone-controller", "hv-junction"], created_features=["robotic placements", "bonded terminals"], automation=AutomationClass.unknown, cycle_time_s=94.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-040", name="Continuity and isolation test", station="test-01", resource="automated electrical tester", consumed_part_ids=["sill-panels", "hv-junction"], created_features=["signed continuity record", "signed isolation record"], automation=AutomationClass.unknown, cycle_time_s=52.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-050", name="Install and seal battery pack", station="battery-01", resource="AGV lift and fastening robots", consumed_part_ids=["battery-enclosure", "battery-modules", "battery-management"], created_features=["sealed structural pack joint"], automation=AutomationClass.unknown, cycle_time_s=126.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-060", name="Install drive units and thermal loop", station="drive-01", resource="dual robot cell", consumed_part_ids=["front-motor", "rear-motor", "front-inverter", "rear-inverter", "thermal-loop"], created_features=["drive mounts", "thermal connections"], automation=AutomationClass.assisted, cycle_time_s=155.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-070", name="Install suspension, steering, brakes, wheels and tires", station="chassis-01", resource="four-corner robot cell", consumed_part_ids=["suspension", "steering", "brakes", "wheels", "tires"], created_features=["aligned corner modules"], automation=AutomationClass.assisted, cycle_time_s=188.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-080", name="Install cabin, glazing and restraints", station="trim-01", resource="robots plus manual safety work", consumed_part_ids=["seats", "interior", "glazing", "restraints"], created_features=["trimmed cabin"], automation=AutomationClass.manual, cycle_time_s=420.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-090", name="Join and seal body shell", station="body-02", resource="joining and metrology cell", consumed_part_ids=["body-shell", "closures", "fasteners-seals"], created_features=["closed body", "measured gaps"], automation=AutomationClass.unknown, cycle_time_s=176.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-100", name="Connect serviceable exceptions", station="electrical-01", resource="robot-assisted operator", consumed_part_ids=["service-links", "lamps", "sensors"], created_features=["service disconnects", "sensor connections"], automation=AutomationClass.manual, cycle_time_s=255.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-110", name="Coat and paint exterior", station="paint-01", resource="robot paint line", consumed_part_ids=["body-shell"], created_features=["corrosion protection", "pearl exterior finish"], automation=AutomationClass.unknown, cycle_time_s=310.0, evidence_ids=[EVIDENCE_PROCESS]),
        OperationDefinition(id="op-120", name="Software, calibration and end-of-line test", station="eol-01", resource="automated dynamometer and diagnostics", consumed_part_ids=["central-compute", "battery-management"], created_features=["configuration record", "EOL evidence bundle"], automation=AutomationClass.assisted, cycle_time_s=360.0, evidence_ids=[EVIDENCE_PROCESS]),
    ]


def validation_cases():
    return [
        ValidationCaseDefinition(id="dvp-drive-energy", name="Longitudinal drive, brake, regeneration, and energy balance", domain="vehicle-dynamics", authority="Sema internal verification; not homologation evidence", method="20 Hz coupled reduced-model step and exact terminal-power residual", status=QualificationStatus.executable_reduced, fidelity=Fidelity.reduced, evidence_ids=[EVIDENCE_CONCEPT, EVIDENCE_ELECTRICAL]),
        ValidationCaseDefinition(id="dvp-lateral-yaw", name="Steering and crosswind lateral/yaw response", domain="vehicle-dynamics", authority="Sema internal verification; external maneuver correlation required", method="reduced bicycle model with axle load transfer and friction-circle limits", status=QualificationStatus.executable_reduced, fidelity=Fidelity.reduced, evidence_ids=[EVIDENCE_CONCEPT]),
        ValidationCaseDefinition(id="dvp-aero-crosswind", name="Apparent-wind aerodynamic loads", domain="aerodynamics", authority="Sema internal verification; governing CFD absent", method="analytic drag, lift, side force, pressure, and held-out coefficient residual", status=QualificationStatus.executable_reduced, fidelity=Fidelity.reduced, evidence_ids=[EVIDENCE_CONCEPT]),
        ValidationCaseDefinition(id="dvp-pack-electrothermal", name="Pack equivalent-circuit and component thermal duty", domain="battery", authority="Sema internal verification; cell and pack correlation required", method="open-circuit voltage, internal resistance, loss, SoC, and lumped thermal integration", status=QualificationStatus.executable_reduced, fidelity=Fidelity.reduced, evidence_ids=[EVIDENCE_ELECTRICAL, EVIDENCE_OPEN_BMS]),
        ValidationCaseDefinition(id="dvp-body-torsion", name="Body-in-white static torsional stiffness", domain="structure", authority="OEM target and solver practice must be supplied", method="implicit structural FEA with suspension hard-point reactions and mesh convergence", status=QualificationStatus.blocked_missing_evidence, fidelity=Fidelity.unknown, evidence_ids=[EVIDENCE_CONCEPT]),
        ValidationCaseDefinition(id="dvp-modal-nvh", name="Body and full-vehicle modal/NVH response", domain="structure", authority="OEM target and correlation measurements must be supplied", method="normal modes, frequency response, road-input transfer paths, and test correlation", status=QualificationStatus.blocked_missing_evidence, fidelity=Fidelity.unknown, evidence_ids=[EVIDENCE_CONCEPT]),
        ValidationCaseDefinition(id="dvp-frontal-crash", name="Frontal crash and restraint response", domain="crash", authority="Candidate baseline: UNECE frontal-impact requirements and Euro NCAP 2026 Crash Protection", method="explicit nonlinear FE vehicle, dummy, restraint, intrusion, and post-crash HV analysis", status=QualificationStatus.planned_external, fidelity=Fidelity.unknown, evidence_ids=[EVIDENCE_TESLA_SERVICE]),
        ValidationCaseDefinition(id="dvp-side-pole-crash", name="Side and pole impact", domain="crash", authority="Candidate baseline: UNECE R95/R135 and Euro NCAP 2026 Crash Protection", method="explicit nonlinear FE side structure, occupant, battery intrusion, and HV isolation analysis", status=QualificationStatus.planned_external, fidelity=Fidelity.unknown, evidence_ids=[EVIDENCE_TESLA_SERVICE]),
        ValidationCaseDefinition(id="dvp-roof-rollover", name="Roof strength and rollover protection", domain="crash", authority="Applicable market and vehicle-category requirements must be frozen", method="quasi-static roof load plus explicit rollover and restraint analysis", status=QualificationStatus.planned_external, fidelity=Fidelity.unknown, evidence_ids=[EVIDENCE_CONCEPT]),
        ValidationCaseDefinition(id="dvp-post-crash-hv", name="Post-crash electrical safety and rescue", domain="electrical-safety", authority="Candidate baseline: FMVSS 305a/GTR 20 and Euro NCAP 2026 Post-Crash Safety", method="HV isolation, electrical shock, electrolyte, thermal-event warning, door access, and emergency-response evidence", status=QualificationStatus.planned_external, fidelity=Fidelity.unknown, evidence_ids=[EVIDENCE_ELECTRICAL]),
        ValidationCaseDefinition(id="dvp-reess-safety", name="Rechargeable energy storage system safety", domain="battery", authority="Candidate baseline: UNECE R100; applicable revision and approval scope must be frozen", method="mechanical integrity, vibration, thermal shock, fire, external short, overcharge, over-discharge, over-temperature, and propagation evidence", status=QualificationStatus.planned_external, fidelity=Fidelity.unknown, evidence_ids=[EVIDENCE_OPEN_BMS]),
        ValidationCaseDefinition(id="dvp-emc", name="Vehicle and component EMC", domain="electrical-safety", authority="Candidate baseline: UNECE R10/CISPR 25 plus customer limits", method="emissions, immunity, transient, grounding, shielding, and communication robustness tests", status=QualificationStatus.planned_external, fidelity=Fidelity.unknown, evidence_ids=[EVIDENCE_ELECTRICAL]),
        ValidationCaseDefinition(id="dvp-durability", name="Road-load and environmental durability", domain="durability", authority="Customer duty cycle, proving-ground spectrum, and life targets must be supplied", method="multiaxial road-load acquisition, fatigue, thermal cycling, corrosion, ingress, and accelerated-life correlation", status=QualificationStatus.blocked_missing_evidence, fidelity=Fidelity.unknown, evidence_ids=[EVIDENCE_CONCEPT]),
        ValidationCaseDefinition(id="dvp-manufacturing-cell", name="Robot reach, collision, joining, and process capability", domain="manufacturing", authority="Supplier cell, tooling, tolerance, and process-capability evidence must be supplied", method="offline robot programming, reach/collision, line balance, joining coupons, metrology, and capability study", status=QualificationStatus.blocked_missing_evidence, fidelity=Fidelity.unknown, evidence_ids=[EVIDENCE_PROCESS]),
        ValidationCaseDefinition(id="dvp-functional-safety", name="Functional-safety lifecycle and safety validation", domain="governance", authority="Candidate baseline: ISO 26262; item definition and HARA remain owner work", method="requirements traceability, HARA, ASIL decomposition, dependent-failure analysis, verification, and safety case", status=QualificationStatus.reference_only, fidelity=Fidelity.excluded, evidence_ids=[EVIDENCE_ELECTRICAL]),
    ]


def evidence_records():
    return [
        EvidenceRecord(id=EVIDENCE_EXTERIOR, kind=EvidenceClass.provenance, source="Printables model 1111101 / STL 4641564", summary="CC BY 4.0 community-reference exterior normalized into bounded presentation LODs; not OEM CAD", fidelity=Fidelity.derived, accepted=true),
        EvidenceRecord(id=EVIDENCE_CONCEPT, kind=EvidenceClass.provenance, source="CircuitFrame explicit engineered assumptions", summary="Visible subsystem reconstruction with non-authoritative geometry, materials, mass, interfaces, and placement", fidelity=Fidelity.reduced, accepted=true),
        EvidenceRecord(id=EVIDENCE_ELECTRICAL, kind=EvidenceClass.computational, source="Sema electrical equations", summary="Bounded concept topology without source-validated physical routing", fidelity=Fidelity.reduced, accepted=true),
        EvidenceRecord(id=EVIDENCE_PROCESS, kind=EvidenceClass.manufacturing, source="CircuitFrame deterministic process demonstrator", summary="Scripted operation graph without sourced production process or robot feasibility", fidelity=Fidelity.reduced, accepted=false),
        EvidenceRecord(id=EVIDENCE_TESLA_SERVICE, kind=EvidenceClass.provenance, source="Tesla Service Model Y SOP3 Berlin public documentation", summary="Official electrical, connector, collision-repair, and dimensional discovery source; remote reference only and not OEM CAD authority", fidelity=Fidelity.derived, accepted=false),
        EvidenceRecord(id=EVIDENCE_OPEN_BMS, kind=EvidenceClass.computational, source="ENNOID-BMS commit daf72c927d4134cad95bb57da6d85807adf948b0", summary="Ingested GPL-3.0 repository snapshot with modular up-to-400-V BMS datasheet and KiCad schematics; separate comparator, not an 800 V vehicle design", fidelity=Fidelity.derived, accepted=false),
        EvidenceRecord(id=EVIDENCE_TABBY_CAD, kind=EvidenceClass.provenance, source="Open Motors TABBY EVO 2015 open CAD release metadata", summary="CC-BY-SA-4.0 open-EV CAD candidate kept separate; historical binaries were unavailable and therefore were not ingested", fidelity=Fidelity.illustrative, accepted=false),
    ]


def source_records():
    return [
        SourceRecord(
            id=EXTERIOR_SOURCE_ID,
            authority=SourceAuthority.licensed_benchmark,
            provider="Spark Models and SueRoger via Printables",
            vehicle_model="Tesla Model Y community-reference exterior",
            source_revision="printables-model-1111101-stl-4641564",
            artifact_kind="attributed CC BY 4.0 binary STL and deterministic web GLB LODs",
            rights_scope="rendering, adaptation, and attributed visualization export under CC BY 4.0; no Tesla endorsement, OEM CAD, manufacturing, or safety authority",
            digest="aaf8a0da316b913b4559bcaa84f5a8cb9ae3e306b1297ee5d6e53b0c9ce0561b",
            fidelity=Fidelity.derived,
            render_allowed=true,
            simulation_allowed=false,
            export_allowed=true,
        ),
        SourceRecord(
            id=ASSUMPTION_SOURCE_ID,
            authority=SourceAuthority.engineered_assumption,
            provider="CircuitFrame Sema reconstruction",
            vehicle_model="CircuitFrame Model Y reference reconstruction",
            source_revision="configuration-revision-1",
            artifact_kind="explicit subsystem geometry, property, E/E, and process assumptions",
            rights_scope="evidence-labelled demo rendering, reduced simulation, and manifest export only; no production, homologation, or safety claim",
            digest="circuitframe-gt-01-revision-1",
            fidelity=Fidelity.reduced,
            render_allowed=true,
            simulation_allowed=true,
            export_allowed=true,
        ),
        SourceRecord(
            id=TESLA_SERVICE_SOURCE_ID,
            authority=SourceAuthority.open_research,
            provider="Tesla Service",
            vehicle_model="Model Y SOP3 Berlin public service reference",
            source_revision="non-structural-pack-2022-01-06--2024-05-06",
            artifact_kind="remote electrical, connector, collision-repair, and dimensional indexes",
            rights_scope="reference discovery only; documents are not redistributed and confer no OEM CAD, production, or homologation authority",
            digest="86c98870e71b10d06f481d24eab75201796d623b973a8395c7b3ccfded3ecb40",
            fidelity=Fidelity.derived,
            render_allowed=false,
            simulation_allowed=false,
            export_allowed=false,
        ),
        SourceRecord(
            id=OPEN_BMS_SOURCE_ID,
            authority=SourceAuthority.open_research,
            provider="ENNOID-BMS open-source project",
            vehicle_model="Modular BMS reference for packs up to 400 V",
            source_revision="daf72c927d4134cad95bb57da6d85807adf948b0",
            artifact_kind="metadata and digest manifest for an optional external KiCad source checkout",
            rights_scope="repository declares GPL-3.0; source artifacts are not redistributed here; explicit external source root plus legal and engineering review required",
            digest="b393ebda17485f3b123c64f6a081241a070665726e24864e152d8a8aef7e8cac",
            fidelity=Fidelity.derived,
            render_allowed=false,
            simulation_allowed=false,
            export_allowed=false,
        ),
        SourceRecord(
            id=TABBY_CAD_SOURCE_ID,
            authority=SourceAuthority.open_research,
            provider="Open Motors, formerly OSVehicle",
            vehicle_model="TABBY EVO open EV platform",
            source_revision="2-seat-v0.1-and-4-seat-v0.1-2015",
            artifact_kind="CC-BY-SA-4.0 STEP/2D CAD release metadata; binaries unavailable",
            rights_scope="separate CAD/process comparator only; no Model Y or CircuitFrame identity, geometry, or validation authority",
            digest="678563eec503b99ddfadd78253e82a24dd5d67c69031494601230dd113f7c9ae",
            fidelity=Fidelity.illustrative,
            render_allowed=false,
            simulation_allowed=false,
            export_allowed=false,
        ),
    ]


def source_bindings():
    mut bindings = []
    for part in vehicle_parts():
        if part.id == "body-shell" or part.id == "wheels" or part.id == "tires" or part.id == "lamps" or part.id == "glazing" or part.id == "closures":
            bindings.append(SourceBinding(entity_id=part.id, source_record_id=EXTERIOR_SOURCE_ID, source_entity_id="monolithic-exterior-envelope", fidelity=Fidelity.derived))
        else:
            bindings.append(SourceBinding(entity_id=part.id, source_record_id=ASSUMPTION_SOURCE_ID, source_entity_id=part.id, fidelity=Fidelity.reduced))
    return bindings


pub def source_gate() -> dict[str, any] !{}:
    bindings = source_bindings()
    ensure len(bindings) == len(vehicle_parts())
    mut accepted_source_records = 0
    mut assumption_records = 0
    mut reference_only_records = 0
    for source in source_records():
        if source.authority == SourceAuthority.licensed_benchmark:
            accepted_source_records = accepted_source_records + 1
        elif source.authority == SourceAuthority.engineered_assumption:
            assumption_records = assumption_records + 1
        else:
            reference_only_records = reference_only_records + 1
    return {
        "status": "partial",
        "presentation_qualified": true,
        "physical_completeness": false,
        "reason": "attributed CC BY exterior and FreeCAD/OCCT mechanical assembly loaded; product authority, certified materials and mass, process capability, and validation remain assumption-tagged or incomplete",
        "current_asset_role": "community-reference exterior plus source-gated parametric mechanical reconstruction",
        "accepted_source_records": accepted_source_records,
        "assumption_records": assumption_records,
        "reference_only_records": reference_only_records,
        "bound_fixture_groups": 0,
        "bound_reconstruction_groups": len(bindings),
        "planned_configurations": ["conventional", "circuitframe"],
        "shared_reference_identity_required": true,
        "exterior_asset_manifest": "/assets/vehicle/manifest.json",
        "source_mesh_sha256": "aaf8a0da316b913b4559bcaa84f5a8cb9ae3e306b1297ee5d6e53b0c9ce0561b",
        "exterior_lods": [
            {"id": "hero", "path": "/assets/vehicle/model-y-hero.glb", "sha256": "4d6657c313118c7978398ba2e8d6f5888208e03f626848ec5c6c3db90facd088"},
            {"id": "medium", "path": "/assets/vehicle/model-y-medium.glb", "sha256": "c41f343f29a838f9bc11b494bfeb830b29d64794fe929349574a6f7b4ba9af3e"},
            {"id": "compact", "path": "/assets/vehicle/model-y-compact.glb", "sha256": "c6070775ae904dd238457583e70f84b3514092cf6661fd3d2103419e625257c1"},
        ],
        "mechanical_asset_manifest": "/assets/mechanical/manifest.json",
        "mechanical_authority": "FreeCAD/OCCT parametric engineering reconstruction from declared dimensional assumptions; not OEM manufacturing CAD",
        "mechanical_features": 1005,
        "mechanical_lods": [
            {"id": "hero", "path": "/assets/mechanical/circuitframe-mechanical-hero.glb", "sha256": "47533dcfe24eaa7191e84b5064fc82163ac49fcf3c18eae6f768ca4d3d9e440f"},
            {"id": "medium", "path": "/assets/mechanical/circuitframe-mechanical-medium.glb", "sha256": "121910ada7a5c05ab46b89be9ca597258b5342bd968d8464428e51f3203af189"},
            {"id": "compact", "path": "/assets/mechanical/circuitframe-mechanical-compact.glb", "sha256": "b92d4bd80740c7cee2bf4b485dbb1398f850e03cd09019e2d99d1e182722b156"},
        ],
        "attribution": "Tesla Model Y (high detail) by Spark Models, remixed from SueRoger, CC BY 4.0",
        "branding_review_status": "front and rear badge triangles removed and replaced by neutral surfaces; final legal review open",
    }


pub def completeness_report(configuration_variant: str) -> CompletenessReport !{}:
    parts = vehicle_parts()
    operations = manufacturing_operations(configuration_variant)
    mut instances = 0
    mut unknown = 0
    for part in parts:
        instances = instances + part.count
    for operation in operations:
        if operation.automation == AutomationClass.unknown:
            unknown = unknown + 1
    return CompletenessReport(
        profile="major_assembly_incomplete",
        declared_parts=len(parts),
        declared_instances=instances,
        declared_nets=len(vehicle_nets(configuration_variant)),
        declared_operations=len(operations),
        unterminated_required_ports=0,
        required_port_inventory_complete=false,
        unknown_operations=unknown,
        major_assembly_complete=false,
        production_complete=false,
        exclusions=["required mechanical, electrical, thermal, fluid, control, and service ports are not yet inventoried; zero unterminated ports is therefore not claimed", "CC BY community exterior is dimensionally normalized presentation geometry, not OEM CAD", "all non-exterior geometry, mass properties, interfaces, and placements remain bounded engineered assumptions", "physical circuit routing and manufacturing feasibility are not validated", "fasteners represented as counted families", "airbag inflator internals opaque", "semiconductor die internals opaque", "tire compound formulation opaque", "manufacturing CAD authority not claimed"],
    )


pub def vehicle_manifest(configuration_variant: str) -> dict[str, any] !{}:
    identity = vehicle_identity()
    report = completeness_report(configuration_variant)
    ensure identity.id == CONFIGURATION_ID
    ensure not report.major_assembly_complete and not report.production_complete
    return {
        "schema": "sema.circuitframe-vehicle-manifest/v3",
        "configuration_variant": configuration_variant,
        "configuration_delta": {
            "shared_identity": CONFIGURATION_ID,
            "changed_part_ids": ["sill-panels", "service-links"],
            "changed_net_ids": ["hv-pack-front", "hv-pack-rear", "hv-dcdc", "lv-zones", "data-backbone", "front-drive-control", "rear-drive-control", "thermal-sensors"],
            "removed_features": ["structural conductors", "printed short links"] if configuration_variant == "conventional" else ["branched copper harness routes"],
            "added_features": ["branched copper harness routes"] if configuration_variant == "conventional" else ["protected structural conductors", "printed short links"],
            "authority": "configuration delta is a bounded engineered comparison, not production routing",
        },
        "identity": identity,
        "parts": vehicle_parts(),
        "nets": vehicle_nets(configuration_variant),
        "operations": manufacturing_operations(configuration_variant),
        "validation_cases": validation_cases(),
        "evidence": evidence_records(),
        "sources": source_records(),
        "source_bindings": source_bindings(),
        "source_gate": source_gate(),
        "completeness": report,
    }


test "major assembly profile remains incomplete without required port inventory":
    report = completeness_report("circuitframe")
    ensure not report.major_assembly_complete
    ensure not report.required_port_inventory_complete
    ensure not report.production_complete
    ensure report.declared_parts >= 35
    ensure report.declared_instances > report.declared_parts
    ensure report.declared_nets >= 8
    ensure report.declared_operations >= 10
    ensure report.unknown_operations > 0


test "sourced exterior qualifies presentation without physical authority":
    gate = source_gate()
    ensure gate["status"] == "partial"
    ensure gate["presentation_qualified"] == true
    ensure gate["physical_completeness"] == false
    ensure gate["accepted_source_records"] == 1
    ensure gate["bound_fixture_groups"] == 0
    ensure len(source_bindings()) == len(vehicle_parts())


test "qualification matrix separates executable reduced checks from external evidence":
    cases = validation_cases()
    mut executable = 0
    mut external = 0
    for validation_case in cases:
        if validation_case.status == QualificationStatus.executable_reduced:
            executable = executable + 1
        if validation_case.status == QualificationStatus.planned_external or validation_case.status == QualificationStatus.blocked_missing_evidence:
            external = external + 1
    ensure len(cases) >= 15
    ensure executable == 4
    ensure external >= 10
```

## Reflected API

# `adaptation`

Automotive adaptive-aerodynamics lifecycle over Sema standard-library gates.

# `struct AeroAdaptationSummary`

**Fields**

| field | type | descriptor |
|---|---|---|
| `active_model_id` | `str` |  |
| `candidate_model_id` | `str` |  |
| `selected_model_id` | `str` |  |
| `lifecycle` | `str` |  |
| `regime` | `str` |  |
| `mode` | `str` |  |
| `validation_status` | `str` |  |
| `detector_score` | `f64` |  |
| `training_error` | `f64` |  |
| `validation_error` | `f64` |  |
| `invariant_violations` | `int` |  |
| `dwell_steps` | `int` |  |
| `hysteresis_margin` | `f64` |  |
| `activated` | `bool` |  |
| `reason` | `str` |  |
| `evidence_ids` | `list[str]` |  |

# `def initial_aero_detector`

```sema
def initial_aero_detector() -> ResidualDetector !{}
```

**Returns** `ResidualDetector`

**Effects** `!{}`

# `def update_aero_detector`

```sema
def update_aero_detector(detector: ResidualDetector, residual: f64, observed_at: f64) -> ResidualDetector !{}
```

**Parameters**

| name | type |
|---|---|
| `detector` | `ResidualDetector` |
| `residual` | `f64` |
| `observed_at` | `f64` |

**Returns** `ResidualDetector`

**Effects** `!{}`

# `def regime_label`

```sema
def regime_label(detector: ResidualDetector) -> str !{}
```

**Parameters**

| name | type |
|---|---|
| `detector` | `ResidualDetector` |

**Returns** `str`

**Effects** `!{}`

# `def retained_adaptation`

```sema
def retained_adaptation(detector: ResidualDetector, active_model_id: str) -> AeroAdaptationSummary !{}
```

**Parameters**

| name | type |
|---|---|
| `detector` | `ResidualDetector` |
| `active_model_id` | `str` |

**Returns** `AeroAdaptationSummary`

**Effects** `!{}`

# `def evaluate_yaw_candidate`

```sema
def evaluate_yaw_candidate(detector: ResidualDetector, step: int, observed_at: f64, speed_mps: f64, sideslip_deg: f64, holdout: dict[str, f64]) -> AeroAdaptationSummary !{}
```

**Parameters**

| name | type |
|---|---|
| `detector` | `ResidualDetector` |
| `step` | `int` |
| `observed_at` | `f64` |
| `speed_mps` | `f64` |
| `sideslip_deg` | `f64` |
| `holdout` | `dict[str, f64]` |

**Returns** `AeroAdaptationSummary`

**Effects** `!{}`



# `aero`

Vehicle aerodynamics by a 3D constant-strength source-panel method with an image ground plane.

Method: Hess-Smith constant-strength source panels over a closed lofted body, with every panel
mirrored through the road surface (z = 0) so the ground boundary condition is satisfied exactly.
The Neumann condition (zero normal velocity on every panel) is closed by two dense linear solves,
one per unit freestream direction; the boundary-value problem is linear in the freestream, so
sigma(V, beta) = V (cos beta sigma_x + sin beta sigma_y) is exact for every wind speed and yaw and
no matrix work is needed per frame.

Honesty: this is NOT a CFD run and no Navier-Stokes equation is integrated. Potential flow alone
yields exactly zero drag and zero side force on a closed body (d'Alembert's paradox), so every
drag and side-force number here comes from the empirical viscous and base-pressure closure layered
on top of the solved pressure field. Each published quantity is tagged `solved` (out of the linear
system), `correlated` (empirical closure with a cited form), or `numerical` (a discretisation
diagnostic). See `aero_equations()` for the per-quantity provenance.

# `struct AeroPanel`

**Fields**

| field | type | descriptor |
|---|---|---|
| `index` | `int` |  |
| `station` | `int` |  |
| `ring_index` | `int` |  |
| `center_x` | `f64` |  |
| `center_y` | `f64` |  |
| `center_z` | `f64` |  |
| `normal_x` | `f64` |  |
| `normal_y` | `f64` |  |
| `normal_z` | `f64` |  |
| `area_m2` | `f64` |  |

# `struct AeroBasis`

**Fields**

| field | type | descriptor |
|---|---|---|
| `panels` | `list[AeroPanel]` |  |
| `source_x` | `list[f64]` |  |
| `source_y` | `list[f64]` |  |
| `mode_x` | `list[f64]` |  |
| `mode_y` | `list[f64]` |  |
| `wake_forward` | `list[f64]` |  |
| `wake_forward_cp` | `list[f64]` |  |
| `wake_reverse` | `list[f64]` |  |
| `wake_reverse_cp` | `list[f64]` |  |
| `wake_crossflow` | `list[f64]` |  |
| `wake_crossflow_cp` | `list[f64]` |  |
| `base_face` | `list[f64]` |  |
| `panel_count` | `int` |  |
| `wetted_area_m2` | `f64` |  |
| `frontal_area_m2` | `f64` |  |
| `side_area_m2` | `f64` |  |
| `planform_area_m2` | `f64` |  |
| `closure_residual_m2` | `f64` |  |
| `residual_x` | `f64` |  |
| `residual_y` | `f64` |  |
| `separation_x_m` | `f64` |  |
| `calibrated_wheel_cd` | `f64` |  |
| `calibration_speed_mps` | `f64` |  |
| `calibration_target_cd` | `f64` |  |

# `struct AeroForces`

**Fields**

| field | type | descriptor |
|---|---|---|
| `air_speed_mps` | `f64` |  |
| `yaw_rad` | `f64` |  |
| `dynamic_pressure_pa` | `f64` |  |
| `reynolds_number` | `f64` |  |
| `cd` | `f64` |  |
| `cd_pressure` | `f64` |  |
| `cd_base` | `f64` |  |
| `cd_friction` | `f64` |  |
| `cd_wheels` | `f64` |  |
| `cy` | `f64` |  |
| `cl` | `f64` |  |
| `cmz` | `f64` |  |
| `drag_n` | `f64` |  |
| `side_n` | `f64` |  |
| `lift_n` | `f64` |  |
| `yaw_moment_nm` | `f64` |  |
| `front_lift_n` | `f64` |  |
| `rear_lift_n` | `f64` |  |
| `pressure_drag_n` | `f64` |  |
| `cp_min` | `f64` |  |
| `cp_max` | `f64` |  |
| `base_pressure_coefficient` | `f64` |  |
| `separation_x_m` | `f64` |  |
| `wake_width_m` | `f64` |  |
| `wake_deficit_fraction` | `f64` |  |
| `wake_shedding_hz` | `f64` |  |
| `turbulence_intensity` | `f64` |  |
| `wake_recirculation_length_m` | `f64` |  |
| `dalembert_residual_cd` | `f64` |  |
| `solver_residual` | `f64` |  |
| `force_closure_residual` | `f64` |  |
| `surface_cp` | `list[f64]` |  |

# `def smooth_step`

```sema
def smooth_step(low: f64, high: f64, value: f64)
```

**Parameters**

| name | type |
|---|---|
| `low` | `f64` |
| `high` | `f64` |
| `value` | `f64` |

Cubic Hermite ramp, clamped to [0, 1] outside the interval.

# `def roof_height`

```sema
def roof_height(u: f64)
```

**Parameters**

| name | type |
|---|---|
| `u` | `f64` |

Upper silhouette: low pointed nose, hood, raked windscreen, roof peak, fastback taper.

# `def floor_height`

```sema
def floor_height(u: f64)
```

**Parameters**

| name | type |
|---|---|
| `u` | `f64` |

Lower silhouette: approach ramp, flat underbody at ride height, rear diffuser.

# `def half_width`

```sema
def half_width(u: f64)
```

**Parameters**

| name | type |
|---|---|
| `u` | `f64` |

Plan-view half width: blunt nose growth to full width, mild fastback taper aft.

# `def station_ring`

```sema
def station_ring(u: f64, x_m: f64)
```

**Parameters**

| name | type |
|---|---|
| `u` | `f64` |
| `x_m` | `f64` |

One superelliptic cross-section as AROUND flattened x,y,z points, mirror symmetric in y.

# `def panel_mesh`

```sema
def panel_mesh()
```

Closed lofted body: two degenerate end caps plus RING_COUNT - 3 superelliptic stations.

Panels are ordered index = station * AROUND + ring_index, and ring_index k mirrors to
AROUND - 1 - k through the y = 0 plane, which the symmetry-reduced solve relies on.

# `def influence_blocks`

```sema
def influence_blocks(tx: list[f64], ty: list[f64], tz: list[f64], sx: list[f64], sy: list[f64], sz: list[f64], area: list[f64], epsilon: list[f64]) -> any
```

**Parameters**

| name | type |
|---|---|
| `tx` | `list[f64]` |
| `ty` | `list[f64]` |
| `tz` | `list[f64]` |
| `sx` | `list[f64]` |
| `sy` | `list[f64]` |
| `sz` | `list[f64]` |
| `area` | `list[f64]` |
| `epsilon` | `list[f64]` |

**Returns** `any`

Velocity induced at every target by unit constant sources plus their z = 0 ground images.

Returns the three dense component matrices [Kx, Ky, Kz]; the near field is regularised with
r_eps = REGULARISATION_FACTOR * sqrt(area_j) exactly as the browser-side field evaluator does.

# `def normal_projection`

```sema
def normal_projection(blocks: any, nx: list[f64], ny: list[f64], nz: list[f64], columns: int)
```

**Parameters**

| name | type |
|---|---|
| `blocks` | `any` |
| `nx` | `list[f64]` |
| `ny` | `list[f64]` |
| `nz` | `list[f64]` |
| `columns` | `int` |

Contract the three influence blocks against the target normals into one dense matrix.

# `def identity_matrix`

```sema
def identity_matrix(n: int)
```

**Parameters**

| name | type |
|---|---|
| `n` | `int` |

# `def dense_solve`

```sema
def dense_solve(matrix: any, rhs: list[f64])
```

**Parameters**

| name | type |
|---|---|
| `matrix` | `any` |
| `rhs` | `list[f64]` |

# `def dense_apply`

```sema
def dense_apply(matrix: any, vector: list[f64], rows: int)
```

**Parameters**

| name | type |
|---|---|
| `matrix` | `any` |
| `vector` | `list[f64]` |
| `rows` | `int` |

# `def stratford_limit`

```sema
def stratford_limit(recovery: f64, run_m: f64, gradient: f64, constant: f64)
```

**Parameters**

| name | type |
|---|---|
| `recovery` | `f64` |
| `run_m` | `f64` |
| `gradient` | `f64` |
| `constant` | `f64` |

Stratford (1959) turbulent separation: Cbar (xi dCbar/dxi)^1/2 &gt;= k (1e-6 Re_xi)^1/10.

Re_xi is taken at the calibration speed so the separation topology stays geometry locked, which
is what road vehicles show above Re_L ~ 4e6 where separation sits on fixed edges. `correlated`.

# `def line_walk`

```sema
def line_walk(panels: list[AeroPanel], cp: list[f64], side_line: list[f64], forward: bool)
```

**Parameters**

| name | type |
|---|---|
| `panels` | `list[AeroPanel]` |
| `cp` | `list[f64]` |
| `side_line` | `list[f64]` |
| `forward` | `bool` |

March each longitudinal surface line and freeze the pressure downstream of separation.

Returns 2 * PANEL_COUNT values: a 0/1 separation flag per panel followed by the frozen
separation pressure coefficient carried into the wake.

# `def ring_walk`

```sema
def ring_walk(panels: list[AeroPanel], cp: list[f64])
```

**Parameters**

| name | type |
|---|---|
| `panels` | `list[AeroPanel]` |
| `cp` | `list[f64]` |

March each cross-section from its crossflow stagnation point and mark the leeward wake.

Crossflow-analogy separation (Allen & Perkins): the pressure field driving it is the unit
lateral mode alone, so the topology is yaw independent and can be precomputed once.

# `def aero_basis`

```sema
def aero_basis() -> AeroBasis !{}
```

**Returns** `AeroBasis`

**Effects** `!{}`

Build the panel mesh, run both dense source solves once, and freeze the wake topology.

# `def aero_solve`

```sema
def aero_solve(basis: AeroBasis, air_speed_mps: f64, yaw_rad: f64) -> AeroForces !{}
```

**Parameters**

| name | type |
|---|---|
| `basis` | `AeroBasis` |
| `air_speed_mps` | `f64` |
| `yaw_rad` | `f64` |

**Returns** `AeroForces`

**Effects** `!{}`

Integrate the surface pressure for one wind speed and yaw. O(panels), no matrix work.

The apparent wind in the vehicle frame is U = -V (cos beta, sin beta, 0): air arrives at the
nose and leaves past the tail at zero yaw, and beta &gt; 0 puts the wind on the vehicle's left.
The solved Cp field is invariant under freestream reversal, so it is evaluated straight from
the exported +x and +y modes; only the viscous closure needs the physical flow direction.

# `def rounded`

```sema
def rounded(value: f64)
```

**Parameters**

| name | type |
|---|---|
| `value` | `f64` |

Half-up rounding to five decimals so the exported panel payload stays compact.

# `def aero_basis_export`

```sema
def aero_basis_export(basis: AeroBasis) -> dict[str, any] !{}
```

**Parameters**

| name | type |
|---|---|
| `basis` | `AeroBasis` |

**Returns** `dict[str, any]`

**Effects** `!{}`

`sema.circuitframe-aero-basis/v1`: geometry, source strengths and the solver provenance.

# `def aero_equations`

```sema
def aero_equations() -> list[dict[str, str]] !{}
```

**Returns** `list[dict[str, str]]`

**Effects** `!{}`

Equation manifest rows; every owner states solved, correlated or numerical provenance.

# `def yaw_forces`

```sema
def yaw_forces(basis: AeroBasis, degrees: f64)
```

**Parameters**

| name | type |
|---|---|
| `basis` | `AeroBasis` |
| `degrees` | `f64` |

Evaluate one yaw station at the calibration wind speed. Used by the tests below.



# `assembly`

Physical assembly model: ports, mates, joints, mass properties, load paths, and the coupled thermal network.

# `enum JointKind`

**Variants**

- `fixed`
- `revolute`
- `prismatic`
- `spherical`
- `cylindrical`
- `gear`
- `rigid_bond`

# `enum PlacementPattern`

**Variants**

- `single`
- `distributed`
- `lateral_pair`
- `longitudinal_pair`
- `corner_quad`

# `enum LoadPathKind`

**Variants**

- `vertical`
- `longitudinal`
- `lateral`
- `structural`
- `crash`
- `restraint`

# `struct MateDefinition`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `port_a_id` | `str` |  |
| `port_b_id` | `str` |  |
| `kind` | `PortKind` |  |
| `preload_n` | `f64` |  |
| `sealed` | `bool` |  |
| `serviceable` | `bool` |  |
| `robot_accessible` | `bool` |  |

# `struct JointDefinition`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `name` | `str` |  |
| `kind` | `JointKind` |  |
| `parent_part_id` | `str` |  |
| `child_part_id` | `str` |  |
| `parent_link` | `str` |  |
| `child_link` | `str` |  |
| `axis_x` | `f64` |  |
| `axis_y` | `f64` |  |
| `axis_z` | `f64` |  |
| `origin_x` | `f64` |  |
| `origin_y` | `f64` |  |
| `origin_z` | `f64` |  |
| `limit_lower` | `f64` |  |
| `limit_upper` | `f64` |  |
| `ratio` | `f64` |  |
| `dof` | `int` |  |
| `idle_dof` | `int` |  |
| `corner` | `str` |  |
| `loop_closing` | `bool` |  |

# `struct PartPlacement`

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `pattern` | `PlacementPattern` |  |
| `pattern_instances` | `int` |  |
| `mass_kg` | `f64` |  |
| `centre_x` | `f64` |  |
| `centre_y` | `f64` |  |
| `centre_z` | `f64` |  |
| `extent_x` | `f64` |  |
| `extent_y` | `f64` |  |
| `extent_z` | `f64` |  |
| `span_x` | `f64` |  |
| `span_y` | `f64` |  |
| `unsprung_fraction` | `f64` |  |

# `struct MassProperties`

**Fields**

| field | type | descriptor |
|---|---|---|
| `total_mass_kg` | `f64` |  |
| `sprung_mass_kg` | `f64` |  |
| `unsprung_mass_kg` | `f64` |  |
| `cg_x_m` | `f64` |  |
| `cg_y_m` | `f64` |  |
| `cg_z_m` | `f64` |  |
| `ixx_kg_m2` | `f64` |  |
| `iyy_kg_m2` | `f64` |  |
| `izz_kg_m2` | `f64` |  |
| `front_axle_load_kg` | `f64` |  |
| `rear_axle_load_kg` | `f64` |  |
| `front_mass_fraction` | `f64` |  |
| `wheelbase_m` | `f64` |  |
| `cg_height_m` | `f64` |  |
| `part_count` | `int` |  |
| `instance_count` | `int` |  |
| `closure_residual_kg` | `f64` |  |

# `struct LoadPath`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `name` | `str` |  |
| `kind` | `LoadPathKind` |  |
| `part_ids` | `list[str]` |  |
| `rated_load_n` | `f64` |  |
| `utilisation` | `f64` |  |

# `struct LoadPathReaction`

**Fields**

| field | type | descriptor |
|---|---|---|
| `path_id` | `str` |  |
| `element_index` | `int` |  |
| `part_id` | `str` |  |
| `role` | `str` |  |
| `force_n` | `f64` |  |
| `moment_nm` | `f64` |  |

# `struct LoadPathResult`

**Fields**

| field | type | descriptor |
|---|---|---|
| `path_id` | `str` |  |
| `name` | `str` |  |
| `kind` | `LoadPathKind` |  |
| `applied_n` | `f64` |  |
| `reaction_n` | `f64` |  |
| `residual_n` | `f64` |  |
| `peak_force_n` | `f64` |  |
| `rated_load_n` | `f64` |  |
| `utilisation` | `f64` |  |
| `reactions` | `list[LoadPathReaction]` |  |

# `struct LoadCase`

**Fields**

| field | type | descriptor |
|---|---|---|
| `vertical_n` | `f64` |  |
| `longitudinal_n` | `f64` |  |
| `lateral_n` | `f64` |  |
| `results` | `list[LoadPathResult]` |  |
| `equilibrium_residual_n` | `f64` |  |
| `moment_residual_nm` | `f64` |  |
| `worst_path_id` | `str` |  |
| `worst_utilisation` | `f64` |  |

# `struct SuspensionState`

**Fields**

| field | type | descriptor |
|---|---|---|
| `corner_id` | `str` |  |
| `vertical_load_n` | `f64` |  |
| `arm_rotation_rad` | `f64` |  |
| `wheel_travel_m` | `f64` |  |
| `spring_stroke_m` | `f64` |  |
| `damper_length_m` | `f64` |  |
| `spring_force_n` | `f64` |  |
| `wheel_rate_n_m` | `f64` |  |
| `motion_ratio` | `f64` |  |
| `camber_deg` | `f64` |  |
| `camber_change_deg` | `f64` |  |
| `toe_deg` | `f64` |  |
| `toe_change_deg` | `f64` |  |
| `kingpin_rotation_rad` | `f64` |  |
| `jounce_limit_m` | `f64` |  |
| `rebound_limit_m` | `f64` |  |
| `travel_utilisation` | `f64` |  |
| `at_limit` | `bool` |  |

# `struct ThermalNode`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `part_id` | `str` |  |
| `capacity_kj_k` | `f64` |  |
| `ambient_coupling_w_k` | `f64` |  |
| `initial_temp_c` | `f64` |  |

# `struct ThermalLink`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `from_node_id` | `str` |  |
| `to_node_id` | `str` |  |
| `conductance_w_k` | `f64` |  |
| `medium` | `str` |  |

# `struct ThermalNetwork`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `nodes` | `list[ThermalNode]` |  |
| `links` | `list[ThermalLink]` |  |

# `struct MobilityReport`

**Fields**

| field | type | descriptor |
|---|---|---|
| `corner_id` | `str` |  |
| `link_count` | `int` |  |
| `joint_count` | `int` |  |
| `joint_dof_sum` | `int` |  |
| `gross_mobility` | `int` |  |
| `idle_dof` | `int` |  |
| `effective_mobility` | `int` |  |
| `expected_mobility` | `int` |  |
| `independent_loops` | `int` |  |
| `loop_closing_joint_ids` | `list[str]` |  |
| `ok` | `bool` |  |

# `def interface_specs`

```sema
def interface_specs() -> list[dict[str, any]]
```

**Returns** `list[dict[str, any]]`

Declared part-to-part interfaces. Every row expands into two ports and one terminating mate.

# `def open_port_specs`

```sema
def open_port_specs() -> list[dict[str, any]]
```

**Returns** `list[dict[str, any]]`

Declared but intentionally unmated interfaces. None of them is a required port.

# `def vehicle_ports`

```sema
def vehicle_ports()
```

Full port inventory: two terminated ports per declared interface plus the open optional ports.

# `def vehicle_mates`

```sema
def vehicle_mates()
```

One mate per declared interface, carrying its preload, sealing, and service classification.

# `def port_termination_report`

```sema
def port_termination_report()
```

Counts required, terminated, open, and multiply-mated ports by scanning the inventory.

# `def part_placements`

```sema
def part_placements()
```

Placement of every declared part in vehicle coordinates, in the same order as vehicle_parts().

# `def placement_instances`

```sema
def placement_instances(placement: PartPlacement)
```

**Parameters**

| name | type |
|---|---|
| `placement` | `PartPlacement` |

Expands a placement pattern into its instance centres. Each row is [mass, x, y, z].

# `def mass_properties`

```sema
def mass_properties() -> MassProperties !{}
```

**Returns** `MassProperties`

**Effects** `!{}`

Total mass, centre of gravity, and inertia tensor summed from the part placements.

Every part is treated as a uniform rectangular prism of its declared extent, transferred to the
vehicle centre of mass by the parallel-axis theorem. The closure residual compares the declared
part masses against the placement masses and must be exactly zero.

# `def corner_table`

```sema
def corner_table()
```

The four suspension corners: mirror signs, steering capability, and the subframe they hang from.

# `def joint`

```sema
def joint(id: str, name: str, kind: JointKind, parent_part_id: str, child_part_id: str, parent_link: str, child_link: str, axis: list[f64], origin: list[f64], limits: list[f64], ratio: f64, dof: int, idle_dof: int, corner: str, loop_closing: bool)
```

**Parameters**

| name | type |
|---|---|
| `id` | `str` |
| `name` | `str` |
| `kind` | `JointKind` |
| `parent_part_id` | `str` |
| `child_part_id` | `str` |
| `parent_link` | `str` |
| `child_link` | `str` |
| `axis` | `list[f64]` |
| `origin` | `list[f64]` |
| `limits` | `list[f64]` |
| `ratio` | `f64` |
| `dof` | `int` |
| `idle_dof` | `int` |
| `corner` | `str` |
| `loop_closing` | `bool` |

Constructs a joint from packed axis, origin, and limit triples.

# `def corner_joints`

```sema
def corner_joints(entry: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `entry` | `dict[str, any]` |

Every joint of one suspension corner, mirrored from the reference hard points.

# `def variant_joint_kind`

```sema
def variant_joint_kind(configuration_variant: str)
```

**Parameters**

| name | type |
|---|---|
| `configuration_variant` | `str` |

Subframe and pack mounts are elastomeric bolted joints conventionally and bonded structural joints in the CircuitFrame variant.

# `def structural_joints`

```sema
def structural_joints(configuration_variant: str)
```

**Parameters**

| name | type |
|---|---|
| `configuration_variant` | `str` |

Driveline, body, pack, and equipment joints outside the four suspension corners.

# `def vehicle_joints_for`

```sema
def vehicle_joints_for(configuration_variant: str)
```

**Parameters**

| name | type |
|---|---|
| `configuration_variant` | `str` |

Complete kinematic joint set for one configuration variant.

# `def vehicle_joints`

```sema
def vehicle_joints()
```

Complete kinematic joint set for the default CircuitFrame configuration.

# `def corner_loop_joint_ids`

```sema
def corner_loop_joint_ids(entry: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `entry` | `dict[str, any]` |

The joints that form the closed kinematic loop of one corner.

Wheel bearing, brake, wheel, tire, halfshaft, and anti-roll-bar joints are excluded: they extend
the corner as an open chain or couple the two corners of an axle, and are analysed separately.

# `def corner_mobility`

```sema
def corner_mobility(entry: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `entry` | `dict[str, any]` |

**Effects** `!{}`

Grubler-Kutzbach mobility of one suspension corner loop.

Gross mobility counts every joint freedom; the idle freedoms are the spins of the
spherical-spherical steering link and of the spherical-prismatic-spherical damper unit, which
move no wheel state. A steered corner must retain one wheel-travel and one steer freedom; a
rear corner must retain wheel travel only.

# `def mobility_reports`

```sema
def mobility_reports() !{}
```

**Effects** `!{}`

Mobility of all four suspension corner loops.

# `def arm_rotation_limits`

```sema
def arm_rotation_limits() -> list[f64] !{}
```

**Returns** `list[f64]`

**Effects** `!{}`

Lower control arm rotation at the rebound and jounce stops, from the declared wheel travel.

# `def corner_pose`

```sema
def corner_pose(arm_rotation_rad: f64) -> list[f64] !{}
```

**Parameters**

| name | type |
|---|---|
| `arm_rotation_rad` | `f64` |

**Returns** `list[f64]`

**Effects** `!{}`

Closes the planar double-wishbone four-bar for a given lower-arm rotation.

Returns [lbj_y, lbj_z, ubj_y, ubj_z, knuckle_rotation_rad, wheel_y, wheel_z, wheel_travel_m].
The upper ball joint is the circle-circle intersection of the upper arm sweep and the rigid
knuckle, so wheel travel and camber are consequences of the joint set, not of a fitted curve.

# `def damper_length`

```sema
def damper_length(arm_rotation_rad: f64)
```

**Parameters**

| name | type |
|---|---|
| `arm_rotation_rad` | `f64` |

Installed length of the air spring and damper unit for a given lower-arm rotation.

# `def motion_ratio`

```sema
def motion_ratio(arm_rotation_rad: f64)
```

**Parameters**

| name | type |
|---|---|
| `arm_rotation_rad` | `f64` |

Spring shortening per unit wheel rise, differentiated through the linkage.

# `def static_corner_load_n`

```sema
def static_corner_load_n()
```

Static vertical load at one wheel with the vehicle at its declared kerb mass.

# `def air_spring_reference_pressure`

```sema
def air_spring_reference_pressure() -> f64 !{}
```

**Returns** `f64`

**Effects** `!{}`

Absolute bellows pressure that balances the static corner load at design height.

# `def air_spring_force`

```sema
def air_spring_force(arm_rotation_rad: f64, reference_pressure: f64) !{}
```

**Parameters**

| name | type |
|---|---|
| `arm_rotation_rad` | `f64` |
| `reference_pressure` | `f64` |

**Effects** `!{}`

Axial air spring force from the polytropic gas state of the bellows.

# `def wheel_force`

```sema
def wheel_force(arm_rotation_rad: f64, reference_pressure: f64) -> f64 !{}
```

**Parameters**

| name | type |
|---|---|
| `arm_rotation_rad` | `f64` |
| `reference_pressure` | `f64` |

**Returns** `f64`

**Effects** `!{}`

Vertical wheel force supported by the corner, by virtual work through the motion ratio.

# `def vector_rotate`

```sema
def vector_rotate(vector: list[f64], axis: list[f64], angle: f64)
```

**Parameters**

| name | type |
|---|---|
| `vector` | `list[f64]` |
| `axis` | `list[f64]` |
| `angle` | `f64` |

Rodrigues rotation of a vector about a unit axis.

# `def tie_rod_residual`

```sema
def tie_rod_residual(pose: list[f64], steer_rotation_rad: f64)
```

**Parameters**

| name | type |
|---|---|
| `pose` | `list[f64]` |
| `steer_rotation_rad` | `f64` |

Length error of the steering link when the knuckle is rotated about its kingpin axis.

# `def bump_steer`

```sema
def bump_steer(pose: list[f64])
```

**Parameters**

| name | type |
|---|---|
| `pose` | `list[f64]` |

Kingpin rotation forced by the fixed-length steering link, and the toe change it produces.

Returns [kingpin_rotation_rad, toe_change_deg]. Solved with a damped Newton iteration seeded at
the design position, so it tracks the physically near root of the length constraint.

# `def suspension_travel`

```sema
def suspension_travel(vertical_load_n: f64) -> SuspensionState !{}
```

**Parameters**

| name | type |
|---|---|
| `vertical_load_n` | `f64` |

**Returns** `SuspensionState`

**Effects** `!{}`

Solves the corner for a vertical wheel load and reports the resulting wheel motion.

The load is balanced against the polytropic air spring through the linkage motion ratio; the
lower-arm rotation that satisfies it is bisected between the rebound and jounce stops. Camber and
toe are then read out of the four-bar closure and the steering-link length constraint, so the
wheel pose is a consequence of the declared joints.

# `def load_path_declarations`

```sema
def load_path_declarations()
```

Declared load paths from load introduction to structural reaction, in transmission order.

Utilisation is left at zero here; load_paths() fills it from the reference static load case.

# `def corner_statics`

```sema
def corner_statics(lateral_n: f64, vertical_n: f64) !{}
```

**Parameters**

| name | type |
|---|---|
| `lateral_n` | `f64` |
| `vertical_n` | `f64` |

**Effects** `!{}`

Static equilibrium of one corner at the design position.

Two 3x3 solves: the knuckle against the lower ball joint reaction and the axial upper-arm force,
then the lower arm against the damper force and its inboard pivot reaction. Returns
[lower_ball_y, lower_ball_z, upper_arm_axial, damper_axial, pivot_y, pivot_z,
 chassis_y, chassis_z, force_residual, moment_residual].

# `def load_paths`

```sema
def load_paths() !{}
```

**Effects** `!{}`

Declared load paths with the utilisation each reaches under the reference static load case.

# `def reaction`

```sema
def reaction(path_id: str, index: int, part_id: str, role: str, force_n: f64, moment_nm: f64)
```

**Parameters**

| name | type |
|---|---|
| `path_id` | `str` |
| `index` | `int` |
| `part_id` | `str` |
| `role` | `str` |
| `force_n` | `f64` |
| `moment_nm` | `f64` |

One element of a load path chain.

# `def shared_path_result`

```sema
def shared_path_result(path: LoadPath, applied_n: f64, shares: list[f64], roles: list[str])
```

**Parameters**

| name | type |
|---|---|
| `path` | `LoadPath` |
| `applied_n` | `f64` |
| `shares` | `list[f64]` |
| `roles` | `list[str]` |

Distributes an applied load over a branching path whose shares sum to one.

# `def load_path_reactions`

```sema
def load_path_reactions(vertical_n: f64, longitudinal_n: f64, lateral_n: f64) !{}
```

**Parameters**

| name | type |
|---|---|
| `vertical_n` | `f64` |
| `longitudinal_n` | `f64` |
| `lateral_n` | `f64` |

**Effects** `!{}`

Reactions along every declared load path for one static load case.

The vertical and lateral corner paths are closed by the two 3x3 equilibrium solves in
corner_statics; the remaining paths distribute their input over branch shares that sum to one.
Every path reports the residual between the applied load and the summed reaction.

# `def thermal_network`

```sema
def thermal_network()
```

Coupled lumped thermal network: the coolant loop is a shared node, not a fixed sink.

Heat leaves a component into the glycol loop, travels to the chiller and the cabin through the
heat pump, and only then reaches ambient through the radiator and the body skin.

# `def isolated_thermal_network`

```sema
def isolated_thermal_network()
```

The same graph with every ambient coupling removed, for closed-system energy checks.

# `def node_index`

```sema
def node_index(network: ThermalNetwork, node_id: str)
```

**Parameters**

| name | type |
|---|---|
| `network` | `ThermalNetwork` |
| `node_id` | `str` |

Position of a node in the network ordering, or -1 when it is absent.

# `def thermal_step_network`

```sema
def thermal_step_network(network: ThermalNetwork, temps: list[f64], heat_w: list[f64], ambient_c: f64, dt_s: f64)
```

**Parameters**

| name | type |
|---|---|
| `network` | `ThermalNetwork` |
| `temps` | `list[f64]` |
| `heat_w` | `list[f64]` |
| `ambient_c` | `f64` |
| `dt_s` | `f64` |

Advances an explicit lumped-capacitance network one step.

C_i dT_i/dt = Q_i + sum_j k_ij (T_j - T_i) - h_i (T_i - T_amb). The link term is antisymmetric, so
with the ambient couplings removed the total stored energy changes by exactly the injected heat.

# `def thermal_step`

```sema
def thermal_step(temps: list[f64], heat_w: list[f64], ambient_c: f64, dt_s: f64) -> list[f64] !{}
```

**Parameters**

| name | type |
|---|---|
| `temps` | `list[f64]` |
| `heat_w` | `list[f64]` |
| `ambient_c` | `f64` |
| `dt_s` | `f64` |

**Returns** `list[f64]`

**Effects** `!{}`

Advances the vehicle thermal network one step.

# `def thermal_initial_temps`

```sema
def thermal_initial_temps() -> list[f64] !{}
```

**Returns** `list[f64]`

**Effects** `!{}`

Declared soak temperatures of the network, in node order.

# `def thermal_stability_limit_s`

```sema
def thermal_stability_limit_s()
```

Largest explicit step the network tolerates: min over nodes of 2 C / (sum k + h).

# `def assembly_export`

```sema
def assembly_export() -> dict[str, any] !{}
```

**Returns** `dict[str, any]`

**Effects** `!{}`

Full assembly payload: placements, interfaces, kinematics, load paths, and the thermal graph.

# `def assembly_equations`

```sema
def assembly_equations() -> list[dict[str, str]] !{}
```

**Returns** `list[dict[str, str]]`

**Effects** `!{}`

Manifest rows for the assembly model, in the same shape as physics.sema:dynamics_equations().



# `authoring`

Bounded natural-language circuit authoring compiled into typed Sema net definitions.

# `enum AuthoringIntent`

**Variants**

- `connect`
- `constrain`
- `unknown`

# `struct CircuitAuthoringResult`

**Fields**

| field | type | descriptor |
|---|---|---|
| `intent` | `AuthoringIntent` |  |
| `accepted` | `bool` |  |
| `natural_description` | `str` |  |
| `formal_sema` | `str` |  |
| `net` | `NetDefinition` |  |
| `requirements` | `list[str]` |  |
| `assumptions` | `list[str]` |  |
| `diagnostics` | `list[str]` |  |

# `def formal_hv_drive_net`

```sema
def formal_hv_drive_net()
```

# `def formal_zonal_supply_net`

```sema
def formal_zonal_supply_net()
```

# `def formal_data_backbone_net`

```sema
def formal_data_backbone_net()
```

# `def unsupported_net`

```sema
def unsupported_net()
```

# `def compile_circuit_description`

```sema
def compile_circuit_description(description: str) -> CircuitAuthoringResult !{}
```

**Parameters**

| name | type |
|---|---|
| `description` | `str` |

**Returns** `CircuitAuthoringResult`

**Effects** `!{}`



# `candidate`

Bounded structural-conductor candidate search with immutable gate results.

# `struct PanelCandidate`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `base_revision` | `int` |  |
| `width_mm` | `f64` |  |
| `thickness_mm` | `f64` |  |
| `length_m` | `f64` |  |
| `mass_kg` | `f64` |  |
| `current_density_a_mm2` | `f64` |  |
| `voltage_drop_v` | `f64` |  |
| `loss_kw` | `f64` |  |
| `estimated_temp_c` | `f64` |  |
| `mass_reduction_fraction` | `f64` |  |
| `passed` | `bool` |  |
| `reason` | `str` |  |

# `def evaluate_panel_candidate`

```sema
def evaluate_panel_candidate(width_mm: f64, base_revision: int) !{}
```

**Parameters**

| name | type |
|---|---|
| `width_mm` | `f64` |
| `base_revision` | `int` |

**Effects** `!{}`

# `def optimize_panel`

```sema
def optimize_panel(base_revision: int) -> dict[str, any] !{}
```

**Parameters**

| name | type |
|---|---|
| `base_revision` | `int` |

**Returns** `dict[str, any]`

**Effects** `!{}`



# `domain`

Canonical automotive identities, completeness, evidence, and interface contracts.

# `enum Fidelity`

**Variants**

- `canonical`
- `derived`
- `reduced`
- `illustrative`
- `unknown`
- `excluded`

# `enum PartGroup`

**Variants**

- `body`
- `chassis`
- `energy`
- `drive`
- `motion`
- `thermal`
- `electrical`
- `control`
- `cabin`
- `safety`
- `manufacturing`

# `enum PortKind`

**Variants**

- `mechanical`
- `high_voltage`
- `low_voltage`
- `data`
- `thermal`
- `fluid`
- `service`

# `enum AutomationClass`

**Variants**

- `robot_validated`
- `assisted`
- `manual`
- `failed`
- `unknown`

# `enum QualificationStatus`

**Variants**

- `executable_reduced`
- `planned_external`
- `reference_only`
- `blocked_missing_evidence`

# `enum EvidenceClass`

**Variants**

- `computational`
- `structural`
- `direct_parity`
- `reduced_model`
- `manufacturing`
- `negative`
- `provenance`

# `enum SourceAuthority`

**Variants**

- `customer_oem`
- `licensed_benchmark`
- `open_research`
- `engineered_assumption`
- `unqualified_fixture`

# `struct SourceRecord`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `authority` | `SourceAuthority` |  |
| `provider` | `str` |  |
| `vehicle_model` | `str` |  |
| `source_revision` | `str` |  |
| `artifact_kind` | `str` |  |
| `rights_scope` | `str` |  |
| `digest` | `str` |  |
| `fidelity` | `Fidelity` |  |
| `render_allowed` | `bool` |  |
| `simulation_allowed` | `bool` |  |
| `export_allowed` | `bool` |  |

# `struct SourceBinding`

**Fields**

| field | type | descriptor |
|---|---|---|
| `entity_id` | `str` |  |
| `source_record_id` | `str` |  |
| `source_entity_id` | `str` |  |
| `fidelity` | `Fidelity` |  |

# `struct ConfigurationDelta`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `base_configuration` | `str` |  |
| `target_configuration` | `str` |  |
| `unchanged_part_ids` | `list[str]` |  |
| `removed_route_ids` | `list[str]` |  |
| `added_feature_ids` | `list[str]` |  |
| `changed_operation_ids` | `list[str]` |  |

# `struct VehicleIdentity`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `revision` | `int` |  |
| `name` | `str` |  |
| `category` | `str` |  |
| `seats` | `int` |  |
| `fidelity` | `Fidelity` |  |

# `struct PartDefinition`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `name` | `str` |  |
| `group` | `PartGroup` |  |
| `count` | `int` |  |
| `material` | `str` |  |
| `mass_kg` | `f64` |  |
| `fidelity` | `Fidelity` |  |
| `opaque` | `bool` |  |
| `evidence_ids` | `list[str]` |  |

# `struct PortDefinition`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `owner_part_id` | `str` |  |
| `kind` | `PortKind` |  |
| `mate_port_id` | `str` |  |
| `required` | `bool` |  |
| `terminated` | `bool` |  |

# `struct NetDefinition`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `name` | `str` |  |
| `domain` | `str` |  |
| `source_part_id` | `str` |  |
| `target_part_ids` | `list[str]` |  |
| `nominal_voltage_v` | `f64` |  |
| `maximum_current_a` | `f64` |  |
| `route` | `str` |  |
| `fidelity` | `Fidelity` |  |
| `evidence_ids` | `list[str]` |  |

# `struct OperationDefinition`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `name` | `str` |  |
| `station` | `str` |  |
| `resource` | `str` |  |
| `consumed_part_ids` | `list[str]` |  |
| `created_features` | `list[str]` |  |
| `automation` | `AutomationClass` |  |
| `cycle_time_s` | `f64` |  |
| `evidence_ids` | `list[str]` |  |

# `struct ValidationCaseDefinition`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `name` | `str` |  |
| `domain` | `str` |  |
| `authority` | `str` |  |
| `method` | `str` |  |
| `status` | `QualificationStatus` |  |
| `fidelity` | `Fidelity` |  |
| `evidence_ids` | `list[str]` |  |

# `struct EvidenceRecord`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `kind` | `EvidenceClass` |  |
| `source` | `str` |  |
| `summary` | `str` |  |
| `fidelity` | `Fidelity` |  |
| `accepted` | `bool` |  |

# `struct CompletenessReport`

**Fields**

| field | type | descriptor |
|---|---|---|
| `profile` | `str` |  |
| `declared_parts` | `int` |  |
| `declared_instances` | `int` |  |
| `declared_nets` | `int` |  |
| `declared_operations` | `int` |  |
| `unterminated_required_ports` | `int` |  |
| `required_port_inventory_complete` | `bool` |  |
| `unknown_operations` | `int` |  |
| `major_assembly_complete` | `bool` |  |
| `production_complete` | `bool` |  |
| `exclusions` | `list[str]` |  |



# `hardware`

Engineering specification of the declared drive, brake, tyre, pack and gear hardware.

The vehicle's mass is summed from the declared part placements in `assembly.sema`, but until now
nothing that accelerated or stopped that mass was declared: peak torque, drive power, brake force,
regenerative force, pack energy, pack resistance, tyre grip, cornering stiffness and rolling
resistance were all bare literals in `physics.sema`. This module declares the engineering
parameters of the components `vehicle.sema` already lists and derives those limits from them, so
the performance follows from the hardware rather than sitting beside it.

Every specification is keyed to the part id it represents, and `part_reconciliation()` checks that
each id resolves in `vehicle_parts()`, that the declared instance counts agree, and that the mass
the specification implies matches the mass the part already declares. `hardware_limits()` fails
loudly on any mismatch, so a specification cannot quietly drift away from the part it describes.

Honesty: these are engineered assumptions for a reconstruction, not OEM data. No motor was
dynamometer tested, no caliper was pressure tested and no cell was cycled. Each declared parameter
carries a `solved`, `correlated` or `assumed` tag in `hardware_parameters()`, using the same
vocabulary as the aerodynamic and assembly modules: `assumed` for a bounded engineering choice,
`correlated` for an empirical closure with a cited form, `solved` for a value that falls out of a
closed derivation over the declared inputs.

# `struct TractionMotorSpec`

One interior-permanent-magnet traction machine, keyed to its declared drive part.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `pole_pairs` | `int` |  |
| `rotor_radius_m` | `f64` |  |
| `stack_length_m` | `f64` |  |
| `stator_outer_radius_m` | `f64` |  |
| `magnet_flux_linkage_wb` | `f64` |  |
| `d_axis_inductance_h` | `f64` |  |
| `q_axis_inductance_h` | `f64` |  |
| `peak_phase_current_a` | `f64` |  |
| `continuous_phase_current_a` | `f64` |  |
| `current_angle_deg` | `f64` |  |
| `max_speed_rpm` | `f64` |  |

# `struct InverterSpec`

One traction inverter, keyed to its declared power-electronics part.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `dc_link_voltage_v` | `f64` |  |
| `peak_phase_current_a` | `f64` |  |
| `on_state_resistance_ohm` | `f64` |  |
| `housing_mass_kg` | `f64` |  |
| `net_id` | `str` |  |

# `struct BrakeCornerSpec`

One brake-by-wire corner module. Both axles are instances of the declared `brakes` part.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `axle` | `str` |  |
| `disc_outer_radius_m` | `f64` |  |
| `disc_inner_radius_m` | `f64` |  |
| `hat_radius_m` | `f64` |  |
| `disc_thickness_m` | `f64` |  |
| `vent_solid_fraction` | `f64` |  |
| `piston_count` | `int` |  |
| `piston_bore_m` | `f64` |  |
| `caliper_mass_kg` | `f64` |  |
| `pad_mass_kg` | `f64` |  |
| `actuator_mass_kg` | `f64` |  |

# `struct TireSpec`

The declared road tyre, in the size notation moulded on its sidewall.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `section_width_m` | `f64` |  |
| `aspect_ratio` | `f64` |  |
| `rim_diameter_in` | `f64` |  |
| `rolling_radius_factor` | `f64` |  |
| `load_index_kg` | `f64` |  |
| `peak_friction` | `f64` |  |
| `slide_friction` | `f64` |  |
| `load_sensitivity` | `f64` |  |
| `friction_reference_load_fraction` | `f64` |  |
| `cornering_stiffness_per_n` | `f64` |  |
| `rolling_coefficient` | `f64` |  |
| `rolling_speed_coefficient` | `f64` |  |
| `carcass_thickness_m` | `f64` |  |
| `carcass_density_kg_m3` | `f64` |  |

# `struct PackSpec`

The declared battery modules, described at the cell that fills them.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `module_count` | `int` |  |
| `series_per_module` | `int` |  |
| `parallel_per_module` | `int` |  |
| `cell_nominal_voltage_v` | `f64` |  |
| `cell_capacity_ah` | `f64` |  |
| `cell_resistance_ohm` | `f64` |  |
| `cell_specific_energy_wh_kg` | `f64` |  |
| `module_packaging_factor` | `f64` |  |
| `module_interconnect_resistance_ohm` | `f64` |  |
| `pack_bus_resistance_ohm` | `f64` |  |
| `cell_pulse_charge_c_rate` | `f64` |  |
| `cell_pulse_discharge_c_rate` | `f64` |  |

# `struct GearStageSpec`

One helical reduction stage, described by the two pitch diameters that set its ratio.

**Fields**

| field | type | descriptor |
|---|---|---|
| `pinion_pitch_diameter_m` | `f64` |  |
| `wheel_pitch_diameter_m` | `f64` |  |
| `face_width_m` | `f64` |  |

# `struct GearboxSpec`

The declared single-speed reduction gearset, one unit per drive axle.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `unit_count` | `int` |  |
| `first` | `GearStageSpec` |  |
| `second` | `GearStageSpec` |  |

# `struct MotorLimits`

What one traction machine can actually deliver through its own inverter.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `inverter_part_id` | `str` |  |
| `effective_peak_current_a` | `f64` |  |
| `current_limited_by` | `str` |  |
| `peak_torque_nm` | `f64` |  |
| `continuous_torque_nm` | `f64` |  |
| `base_speed_rpm` | `f64` |  |
| `max_speed_rpm` | `f64` |  |
| `constant_power_ratio` | `f64` |  |
| `corner_power_kw` | `f64` |  |
| `stator_flux_linkage_wb` | `f64` |  |
| `power_factor` | `f64` |  |
| `inverter_apparent_power_kva` | `f64` |  |
| `inverter_peak_power_kw` | `f64` |  |
| `inverter_loss_kw` | `f64` |  |
| `dc_link_peak_current_a` | `f64` |  |
| `airgap_shear_stress_kpa` | `f64` |  |

# `struct BrakeAxleLimits`

Both corner modules of one axle, reduced to the numbers the dynamics needs.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `axle` | `str` |  |
| `effective_radius_m` | `f64` |  |
| `piston_area_m2` | `f64` |  |
| `clamp_force_n` | `f64` |  |
| `corner_torque_nm` | `f64` |  |
| `axle_force_n` | `f64` |  |
| `disc_mass_kg` | `f64` |  |
| `disc_thermal_capacity_j_k` | `f64` |  |

# `struct TireLimits`

The contact-patch model the dynamics reads, evaluated against live corner loads.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `unloaded_radius_m` | `f64` |  |
| `rolling_radius_m` | `f64` |  |
| `rated_load_n` | `f64` |  |
| `friction_reference_load_n` | `f64` |  |
| `peak_friction` | `f64` |  |
| `slide_friction` | `f64` |  |
| `load_sensitivity` | `f64` |  |
| `cornering_stiffness_per_n` | `f64` |  |
| `rolling_coefficient` | `f64` |  |
| `rolling_speed_coefficient` | `f64` |  |

# `struct PackLimits`

The traction pack, summed from the declared cell out to the terminals.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `cell_count` | `int` |  |
| `series_count` | `int` |  |
| `parallel_count` | `int` |  |
| `energy_kwh` | `f64` |  |
| `nominal_voltage_v` | `f64` |  |
| `resistance_ohm` | `f64` |  |
| `charge_power_kw` | `f64` |  |
| `discharge_power_kw` | `f64` |  |
| `mass_kg` | `f64` |  |

# `struct PartReconciliation`

One declared part, checked against the specification that claims to describe it.

**Fields**

| field | type | descriptor |
|---|---|---|
| `part_id` | `str` |  |
| `resolved` | `bool` |  |
| `declared_count` | `int` |  |
| `expected_count` | `int` |  |
| `declared_mass_kg` | `f64` |  |
| `derived_mass_kg` | `f64` |  |
| `mass_error_fraction` | `f64` |  |
| `tolerance_fraction` | `f64` |  |
| `derivation` | `str` |  |
| `passed` | `bool` |  |

# `struct AxleHardware`

Everything one axle's 20 Hz longitudinal solve needs, pre-solved into flat scalars.

Nothing on this struct depends on the live vehicle state, so it is built once with the rest of
the hardware. The loop is then left with only the arithmetic that genuinely varies: the friction
circle on this axle's live vertical load, the fade factor at its live disc temperature, and the
comparison that decides which of the two is binding. Keeping it flat also keeps it cheap to pass
around: the interpreter copies a struct argument by value, so the per-step helpers must never be
handed anything that carries the panel basis or a pre-sampled table.

**Fields**

| field | type | descriptor |
|---|---|---|
| `axle` | `str` |  |
| `brake_axle_force_n` | `f64` |  |
| `drive_force_per_throttle_n` | `f64` |  |
| `power_force_numerator_w` | `f64` |  |
| `power_constraint` | `str` |  |
| `peak_friction` | `f64` |  |
| `slide_friction` | `f64` |  |
| `load_sensitivity` | `f64` |  |
| `friction_reference_load_n` | `f64` |  |
| `cornering_stiffness_per_n` | `f64` |  |
| `fade_onset_temp_c` | `f64` |  |
| `fade_rate_per_k` | `f64` |  |
| `fade_floor_fraction` | `f64` |  |
| `disc_thermal_capacity_j_k` | `f64` |  |

# `struct HardwareLimits`

Everything `physics.sema` used to hardcode, derived from the declared components.

**Fields**

| field | type | descriptor |
|---|---|---|
| `front_motor` | `MotorLimits` |  |
| `rear_motor` | `MotorLimits` |  |
| `front_brake` | `BrakeAxleLimits` |  |
| `rear_brake` | `BrakeAxleLimits` |  |
| `tire` | `TireLimits` |  |
| `pack` | `PackLimits` |  |
| `front_axle` | `AxleHardware` |  |
| `rear_axle` | `AxleHardware` |  |
| `final_drive_ratio` | `f64` |  |
| `gearbox_efficiency` | `f64` |  |
| `motor_efficiency` | `f64` |  |
| `inverter_efficiency` | `f64` |  |
| `max_motor_torque_nm` | `f64` |  |
| `max_drive_power_kw` | `f64` |  |
| `max_brake_force_n` | `f64` |  |
| `max_regenerative_force_n` | `f64` |  |
| `max_regenerative_power_kw` | `f64` |  |
| `regenerative_front_share` | `f64` |  |
| `max_line_pressure_pa` | `f64` |  |
| `pad_friction` | `f64` |  |
| `fade_onset_temp_c` | `f64` |  |
| `fade_rate_per_k` | `f64` |  |
| `fade_floor_fraction` | `f64` |  |
| `disc_cooling_base_w_per_k` | `f64` |  |
| `disc_cooling_speed_w_per_k_mps` | `f64` |  |
| `driveline_top_speed_mps` | `f64` |  |

# `def front_motor_spec`

```sema
def front_motor_spec()
```

Front machine: smaller rotor, more turns, sized for the lighter front axle duty.

# `def rear_motor_spec`

```sema
def rear_motor_spec()
```

Rear machine: longer, wider rotor and roughly twice the current for the primary drive axle.

# `def front_inverter_spec`

```sema
def front_inverter_spec()
```

Front inverter: standalone subframe housing, rated above the machine it feeds.

# `def rear_inverter_spec`

```sema
def rear_inverter_spec()
```

Rear inverter: shares the drive-unit casting, and is the binding current limit of that axle.

# `def front_brake_spec`

```sema
def front_brake_spec()
```

Front corner: 355 mm vented disc, four-piston fixed caliper.

# `def rear_brake_spec`

```sema
def rear_brake_spec()
```

Rear corner: 330 mm vented disc, two-piston caliper carrying the park-brake actuator.

# `def tire_spec`

```sema
def tire_spec()
```

255/45 R21 summer tyre on all four corners.

# `def pack_spec`

```sema
def pack_spec()
```

Twenty-four 9s2p prismatic NMC modules wired in series into one 216s2p traction pack.

# `def gearbox_spec`

```sema
def gearbox_spec()
```

Two helical stages per drive unit; their pitch diameters are what make the 9.1:1 final drive.

# `def motor_limits`

```sema
def motor_limits(motor: TractionMotorSpec, inverter: InverterSpec) !{}
```

**Parameters**

| name | type |
|---|---|
| `motor` | `TractionMotorSpec` |
| `inverter` | `InverterSpec` |

**Effects** `!{}`

Solve one machine at its own current limit, then at whichever limit its inverter imposes.

Torque is the interior-permanent-magnet expression: magnet torque plus the reluctance term the
declared saliency provides at the declared current angle. The base speed is where the stator
flux linkage the same operating point produces can no longer be driven at the space-vector
voltage ceiling, and the constant-power region runs from there to the declared maximum speed.
Power factor comes out of the corner-point voltage and current phasors rather than being
declared, so the inverter's apparent-power rating and the machine's shaft power stay consistent.

# `def brake_axle_limits`

```sema
def brake_axle_limits(corner: BrakeCornerSpec, rolling_radius_m: f64) !{}
```

**Parameters**

| name | type |
|---|---|
| `corner` | `BrakeCornerSpec` |
| `rolling_radius_m` | `f64` |

**Effects** `!{}`

Pressure, clamp force, corner torque and the wheel force both corners of one axle can make.

The effective radius is the uniform-pressure integral over the swept annulus, and the disc
thermal mass is the vented swept volume in cast iron. That thermal mass is what turns half m v
squared into a disc temperature, so a heavier car heats the same disc further on the same stop.

# `def tire_limits`

```sema
def tire_limits()
```

Turn the moulded tyre size into the radius, rated load and grip model the dynamics reads.

# `def pack_limits`

```sema
def pack_limits()
```

Sum 24 declared modules of cells into pack energy, voltage, resistance, power and mass.

# `def final_drive_ratio`

```sema
def final_drive_ratio()
```

The 9.1:1 reduction is the product of the two declared stage pitch-diameter ratios.

# `def gear_blank_mass`

```sema
def gear_blank_mass(stage: GearStageSpec)
```

**Parameters**

| name | type |
|---|---|
| `stage` | `GearStageSpec` |

Both gear blanks of one stage as webbed steel discs of their pitch diameter and face width.

# `def derived_part_mass`

```sema
def derived_part_mass(part_id: str) !{}
```

**Parameters**

| name | type |
|---|---|
| `part_id` | `str` |

**Effects** `!{}`

Mass the declared specification implies for one part, summed over its declared instances.

# `def declared_part`

```sema
def declared_part(part_id: str)
```

**Parameters**

| name | type |
|---|---|
| `part_id` | `str` |

The declared part this specification claims to describe. Fails loudly on an unknown id.

# `def part_derivation`

```sema
def part_derivation(part_id: str)
```

**Parameters**

| name | type |
|---|---|
| `part_id` | `str` |

One sentence naming what the derived mass for this part is actually built from.

# `def reconcile_part`

```sema
def reconcile_part(part_id: str, expected_count: int) !{}
```

**Parameters**

| name | type |
|---|---|
| `part_id` | `str` |
| `expected_count` | `int` |

**Effects** `!{}`

Check one specification against the part it claims to describe: identity, count and mass.

# `def part_reconciliation`

```sema
def part_reconciliation() !{}
```

**Effects** `!{}`

Every specification checked against `vehicle_parts()`, in the order the drive chain runs.

# `def net_current_limit`

```sema
def net_current_limit(net_id: str)
```

**Parameters**

| name | type |
|---|---|
| `net_id` | `str` |

Declared maximum current of one high-voltage net, read out of `vehicle_nets()`.

# `def generating_power`

```sema
def generating_power(motor: MotorLimits)
```

**Parameters**

| name | type |
|---|---|
| `motor` | `MotorLimits` |

Electrical power one machine can regenerate: its continuous torque at its own base speed.

A braking burst is short, but the machines still recover at their continuous rating rather than
their peak: peak current is a traction-side thermal allowance the pad-and-disc side never needs.

# `def axle_hardware`

```sema
def axle_hardware(axle: str, motor: MotorLimits, brake: BrakeAxleLimits, tire: TireLimits, ratio: f64, efficiency: f64)
```

**Parameters**

| name | type |
|---|---|
| `axle` | `str` |
| `motor` | `MotorLimits` |
| `brake` | `BrakeAxleLimits` |
| `tire` | `TireLimits` |
| `ratio` | `f64` |
| `efficiency` | `f64` |

Collapse one axle's declared machine, inverter, calipers and tyres into per-step scalars.

# `def hardware_limits`

```sema
def hardware_limits() -> HardwareLimits !{}
```

**Returns** `HardwareLimits`

**Effects** `!{}`

Derive every performance limit `physics.sema` needs, and refuse to return a drifted set.

The reconciliation runs first. A specification whose part id no longer resolves, whose instance
count no longer matches the declared count, or whose derived mass has drifted outside the stated
tolerance stops the build here rather than quietly feeding a wrong limit into the dynamics.

# `def parameter_row`

```sema
def parameter_row(part_id: str, key: str, label: str, value: f64, unit: str, source: str, note: str)
```

**Parameters**

| name | type |
|---|---|
| `part_id` | `str` |
| `key` | `str` |
| `label` | `str` |
| `value` | `f64` |
| `unit` | `str` |
| `source` | `str` |
| `note` | `str` |

One declared scalar with the part it belongs to and how it was arrived at.

# `def motor_parameters`

```sema
def motor_parameters(motor: TractionMotorSpec)
```

**Parameters**

| name | type |
|---|---|
| `motor` | `TractionMotorSpec` |

Provenance rows for one machine, read straight off the specification struct.

# `def inverter_parameters`

```sema
def inverter_parameters(inverter: InverterSpec)
```

**Parameters**

| name | type |
|---|---|
| `inverter` | `InverterSpec` |

Provenance rows for one inverter, read straight off the specification struct.

# `def brake_parameters`

```sema
def brake_parameters(corner: BrakeCornerSpec)
```

**Parameters**

| name | type |
|---|---|
| `corner` | `BrakeCornerSpec` |

Provenance rows for one brake corner, read straight off the specification struct.

# `def shared_parameters`

```sema
def shared_parameters()
```

Provenance rows for the constants shared across specifications and reconciliations.

# `def hardware_parameters`

```sema
def hardware_parameters()
```

The whole provenance ledger, built from the specification structs so it cannot drift.

# `def motor_export`

```sema
def motor_export(limits: MotorLimits)
```

**Parameters**

| name | type |
|---|---|
| `limits` | `MotorLimits` |

# `def brake_export`

```sema
def brake_export(limits: BrakeAxleLimits)
```

**Parameters**

| name | type |
|---|---|
| `limits` | `BrakeAxleLimits` |

# `def reconciliation_export`

```sema
def reconciliation_export(row: PartReconciliation)
```

**Parameters**

| name | type |
|---|---|
| `row` | `PartReconciliation` |

# `def hardware_export`

```sema
def hardware_export() -> dict[str, any] !{}
```

**Returns** `dict[str, any]`

**Effects** `!{}`

API payload: the derived limits, the declared parameters behind them, and the reconciliation.

# `def hardware_equations`

```sema
def hardware_equations() -> list[dict[str, str]] !{}
```

**Returns** `list[dict[str, str]]`

**Effects** `!{}`

Manifest rows for the hardware model, in the same shape as physics.sema:dynamics_equations().

# `def contains_id`

```sema
def contains_id(seen: list[str], value: str)
```

**Parameters**

| name | type |
|---|---|
| `seen` | `list[str]` |
| `value` | `str` |

# `def ledger_covers`

```sema
def ledger_covers(rows: list[dict[str, any]], part_id: str)
```

**Parameters**

| name | type |
|---|---|
| `rows` | `list[dict[str, any]]` |
| `part_id` | `str` |



# `live`

Automotive-native Sema HTTP service for CircuitFrame Lab.

Everything expensive is solved once when the service starts: the source-panel aerodynamic basis, both
reduced-order surrogates, the mass and joint budget, and the four printed-circuit boards with their DC
nodal solutions. The 20 Hz drive loop and the on-demand wind-tunnel measurement then reuse those caches.

# `def response`

```sema
def response(status: int, body: any) !{}
```

**Parameters**

| name | type |
|---|---|
| `status` | `int` |
| `body` | `any` |

**Effects** `!{}`

# `def error_response`

```sema
def error_response(status: int, code: str, detail: str) !{}
```

**Parameters**

| name | type |
|---|---|
| `status` | `int` |
| `code` | `str` |
| `detail` | `str` |

**Effects** `!{}`

# `def physical_actions_allowed`

```sema
def physical_actions_allowed()
```

# `def evidence_export_allowed`

```sema
def evidence_export_allowed()
```

# `def initial_session`

```sema
def initial_session(context: PhysicsContext)
```

**Parameters**

| name | type |
|---|---|
| `context` | `PhysicsContext` |

# `def cached_aero_basis`

```sema
def cached_aero_basis(session: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

# `def cached_pcb`

```sema
def cached_pcb(session: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

# `def cached_assembly`

```sema
def cached_assembly(session: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

# `def cached_hardware`

```sema
def cached_hardware(session: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

The derived component limits and their reconciliation. Solved once with the context.

# `def progress_report`

```sema
def progress_report()
```

# `def contains`

```sema
def contains(values: list[str], value: str)
```

**Parameters**

| name | type |
|---|---|
| `values` | `list[str]` |
| `value` | `str` |

# `def equation_phase`

```sema
def equation_phase(id: str, live: bool)
```

**Parameters**

| name | type |
|---|---|
| `id` | `str` |
| `live` | `bool` |

Where a body is evaluated: inside every 20 Hz step, once at startup, or only when asked.

# `def grouped_equations`

```sema
def grouped_equations(rows: list[dict[str, str]], group: str, live: bool)
```

**Parameters**

| name | type |
|---|---|
| `rows` | `list[dict[str, str]]` |
| `group` | `str` |
| `live` | `bool` |

Tag each declared equation with the subsystem that owns it and when its body runs.

# `def initial_residual_history`

```sema
def initial_residual_history()
```

# `def pushed_residual_history`

```sema
def pushed_residual_history(history: dict[str, any], residual: f64, score: f64, sequence: int, activated: bool) !{}
```

**Parameters**

| name | type |
|---|---|
| `history` | `dict[str, any]` |
| `residual` | `f64` |
| `score` | `f64` |
| `sequence` | `int` |
| `activated` | `bool` |

**Effects** `!{}`

Bounded ring of the surrogate-versus-solved residual and the detector score that tracks it.

# `def idle_sweep`

```sema
def idle_sweep()
```

# `def sweep_command`

```sema
def sweep_command(index: int, speed_mps: f64)
```

**Parameters**

| name | type |
|---|---|
| `index` | `int` |
| `speed_mps` | `f64` |

Wind speed and bearing that put the apparent wind at the scripted sideslip. Returns [m/s, deg].

The demonstration commands a sideslip, not a wind. A fixed crosswind that walks a parked car
clear of the calm model's window barely moves the apparent angle at motorway speed, so a fixed
wind would quietly stop demonstrating anything above about 30 m/s. Solving the wind vector for
the target angle keeps it meaningful at any road speed, and both commanded values are published
every step so nothing about the operating point is hidden.

# `def advanced_sweep`

```sema
def advanced_sweep(sweep: dict[str, any], controls: ControlInput)
```

**Parameters**

| name | type |
|---|---|
| `sweep` | `dict[str, any]` |
| `controls` | `ControlInput` |

# `def detector_body`

```sema
def detector_body(session: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

# `def residual_history_body`

```sema
def residual_history_body(session: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

The whole ring, served on status only; a step carries one new sample and the client appends.

# `def sweep_body`

```sema
def sweep_body(session: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

# `def all_equations`

```sema
def all_equations()
```

# `def status_body`

```sema
def status_body(session: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

# `def number_field_valid`

```sema
def number_field_valid(payload: dict[str, any], field: str, minimum: f64, maximum: f64)
```

**Parameters**

| name | type |
|---|---|
| `payload` | `dict[str, any]` |
| `field` | `str` |
| `minimum` | `f64` |
| `maximum` | `f64` |

# `def measurement_body`

```sema
def measurement_body(forces: any)
```

**Parameters**

| name | type |
|---|---|
| `forces` | `any` |

# `def tunnel_response`

```sema
def tunnel_response(payload: any, session: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `payload` | `any` |
| `session` | `dict[str, any]` |

**Effects** `!{}`

On-demand wind-tunnel measurement: the car is held, the stream runs, the loads are integrated.

# `def step_response`

```sema
def step_response(payload: any, session: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `payload` | `any` |
| `session` | `dict[str, any]` |

**Effects** `!{}`

# `def reset_session`

```sema
def reset_session(session: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

# `def reset_response`

```sema
def reset_response(session: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

**Effects** `!{}`

# `def candidate_response`

```sema
def candidate_response(session: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

**Effects** `!{}`

# `def commit_response`

```sema
def commit_response(payload: any, session: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `payload` | `any` |
| `session` | `dict[str, any]` |

**Effects** `!{}`

# `def assembly_step_response`

```sema
def assembly_step_response(payload: any, session: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `payload` | `any` |
| `session` | `dict[str, any]` |

**Effects** `!{}`

# `def sweep_state_body`

```sema
def sweep_state_body(session: dict[str, any])
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

# `def regime_sweep_response`

```sema
def regime_sweep_response(payload: any, session: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `payload` | `any` |
| `session` | `dict[str, any]` |

**Effects** `!{}`

Arm or disarm the bounded wind-yaw demonstration; it never advances the causal sequence itself.

Anything short of an explicit `{"action": "start"}` disarms, so an empty body, `{}`, an explicit
stop or a cancel flag all reliably stop the demonstration.

# `def configuration_response`

```sema
def configuration_response(payload: any, session: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `payload` | `any` |
| `session` | `dict[str, any]` |

**Effects** `!{}`

# `def export_response`

```sema
def export_response(session: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `session` | `dict[str, any]` |

**Effects** `!{}`

# `def authoring_response`

```sema
def authoring_response(payload: any) !{}
```

**Parameters**

| name | type |
|---|---|
| `payload` | `any` |

**Effects** `!{}`

# `def live_response`

```sema
def live_response(request: dict[str, any], session: dict[str, any]) !{}
```

**Parameters**

| name | type |
|---|---|
| `request` | `dict[str, any]` |
| `session` | `dict[str, any]` |

**Effects** `!{}`

# `def serve_live`

```sema
def serve_live(port: int) -> None !{net.listen}
```

**Parameters**

| name | type |
|---|---|
| `port` | `int` |

**Returns** `None`

**Effects** `!{net.listen}`



# `main`

CircuitFrame Lab automotive digital-twin entrypoint.

# `def main`

```sema
def main() !{net.listen}
```

**Effects** `!{net.listen}`



# `pcb`

Printed-circuit design and DC electrical analysis for the CircuitFrame boards.

Four boards are declared at component, pin, net, trace and via level and then analysed:

  * DC modified nodal analysis. Copper branch conductances come from geometry
    (R = rho * L / (w * t), rho corrected by the copper temperature coefficient),
    every net's feed pin is held at its declared operating potential, declared loads
    inject current at their sink pin and return it at their return pin, and the
    reference net pin is the 0 V node. `G v = i` is solved with `solve(matrix, rhs)`.
  * IPC-2221 external/internal conductor sizing plus the inverted temperature-rise
    relation, evaluated per trace against the layer's copper weight.
  * IEC 60664-1 creepage screening on the 800 V board.
  * A geometric design-rule check (track width, annular ring, pads inside outline).

Every board is an *engineered reconstruction* published for the CircuitFrame research
demo. None of it is an OEM design, an OEM netlist, or a released manufacturing data set.
Part numbers name the device *class* the reconstruction is dimensioned against.

# `enum PcbLayerFunction`

**Variants**

- `signal`
- `plane`
- `core`
- `prepreg`
- `soldermask`
- `coverlay`

# `enum PcbNetClass`

**Variants**

- `hv`
- `power`
- `ground`
- `signal`
- `can`
- `ethernet`
- `analog`

# `enum PcbPinKind`

**Variants**

- `power`
- `ground`
- `signal`
- `analog`
- `thermal`
- `nc`

# `struct PcbLayer`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `name` | `str` |  |
| `function` | `PcbLayerFunction` |  |
| `copper_um` | `f64` |  |
| `dielectric_mm` | `f64` |  |
| `dielectric_er` | `f64` |  |
| `material` | `str` |  |

# `struct PcbStackup`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `name` | `str` |  |
| `layers` | `list[PcbLayer]` |  |
| `total_thickness_mm` | `f64` |  |
| `ipc_class` | `int` |  |
| `impedance_target_ohm` | `f64` |  |

# `struct PcbComponent`

**Fields**

| field | type | descriptor |
|---|---|---|
| `refdes` | `str` |  |
| `part_number` | `str` |  |
| `description` | `str` |  |
| `footprint` | `str` |  |
| `pin_count` | `int` |  |
| `x_mm` | `f64` |  |
| `y_mm` | `f64` |  |
| `rotation_deg` | `f64` |  |
| `power_dissipation_w` | `f64` |  |
| `mass_g` | `f64` |  |

# `struct PcbPin`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `refdes` | `str` |  |
| `number` | `int` |  |
| `name` | `str` |  |
| `net_id` | `str` |  |
| `x_mm` | `f64` |  |
| `y_mm` | `f64` |  |
| `kind` | `PcbPinKind` |  |

# `struct PcbNet`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `name` | `str` |  |
| `net_class` | `PcbNetClass` |  |
| `pin_ids` | `list[str]` |  |
| `nominal_voltage_v` | `f64` |  |
| `current_a` | `f64` |  |
| `is_reference` | `bool` |  |

# `struct PcbTrace`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `net_id` | `str` |  |
| `layer_id` | `str` |  |
| `width_mm` | `f64` |  |
| `length_mm` | `f64` |  |
| `current_a` | `f64` |  |
| `from_pin_id` | `str` |  |
| `to_pin_id` | `str` |  |
| `resistance_mohm` | `f64` |  |
| `voltage_drop_mv` | `f64` |  |
| `power_loss_mw` | `f64` |  |
| `temperature_rise_c` | `f64` |  |
| `ipc_min_width_mm` | `f64` |  |
| `compliant` | `bool` |  |

# `struct PcbVia`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `net_id` | `str` |  |
| `drill_mm` | `f64` |  |
| `pad_mm` | `f64` |  |
| `from_layer_id` | `str` |  |
| `to_layer_id` | `str` |  |
| `current_a` | `f64` |  |
| `resistance_mohm` | `f64` |  |

# `struct PcbBoard`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `name` | `str` |  |
| `host_part_id` | `str` |  |
| `stackup` | `PcbStackup` |  |
| `components` | `list[PcbComponent]` |  |
| `pins` | `list[PcbPin]` |  |
| `nets` | `list[PcbNet]` |  |
| `traces` | `list[PcbTrace]` |  |
| `vias` | `list[PcbVia]` |  |
| `outline_width_mm` | `f64` |  |
| `outline_height_mm` | `f64` |  |
| `zone` | `str` |  |
| `substrate` | `str` |  |

# `struct PcbAnalysis`

**Fields**

| field | type | descriptor |
|---|---|---|
| `board_id` | `str` |  |
| `node_count` | `int` |  |
| `branch_count` | `int` |  |
| `source_node` | `str` |  |
| `worst_net_id` | `str` |  |
| `worst_voltage_drop_mv` | `f64` |  |
| `worst_drop_fraction` | `f64` |  |
| `total_copper_loss_w` | `f64` |  |
| `max_temperature_rise_c` | `f64` |  |
| `ipc_violations` | `int` |  |
| `drc_violations` | `int` |  |
| `isolation_violations` | `int` |  |
| `solver_residual` | `f64` |  |
| `kcl_residual_a` | `f64` |  |

# `struct PcbRoutePlan`

**Fields**

| field | type | descriptor |
|---|---|---|
| `net_id` | `str` |  |
| `layer_id` | `str` |  |
| `width_mm` | `f64` |  |
| `topology` | `str` |  |
| `via_count` | `int` |  |
| `via_drill_mm` | `f64` |  |
| `via_pad_mm` | `f64` |  |
| `via_layer_id` | `str` |  |

# `struct PcbSegment`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `net_id` | `str` |  |
| `layer_id` | `str` |  |
| `width_mm` | `f64` |  |
| `from_pin_id` | `str` |  |
| `to_pin_id` | `str` |  |
| `via_count` | `int` |  |
| `via_drill_mm` | `f64` |  |
| `via_pad_mm` | `f64` |  |
| `via_layer_id` | `str` |  |

# `struct PcbLoad`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `board_id` | `str` |  |
| `pin_id` | `str` |  |
| `return_pin_id` | `str` |  |
| `current_a` | `f64` |  |
| `description` | `str` |  |

# `struct PcbSolution`

**Fields**

| field | type | descriptor |
|---|---|---|
| `node_ids` | `list[str]` |  |
| `node_voltages` | `list[f64]` |  |
| `branch_currents` | `list[f64]` |  |
| `fixed_count` | `int` |  |
| `solver_residual` | `f64` |  |
| `kcl_residual_a` | `f64` |  |
| `source_current_a` | `f64` |  |

# `struct PcbIsolationCheck`

**Fields**

| field | type | descriptor |
|---|---|---|
| `id` | `str` |  |
| `board_id` | `str` |  |
| `category` | `str` |  |
| `from_pin_id` | `str` |  |
| `to_pin_id` | `str` |  |
| `working_voltage_v` | `f64` |  |
| `required_mm` | `f64` |  |
| `measured_mm` | `f64` |  |
| `reinforced` | `bool` |  |
| `compliant` | `bool` |  |

# `def operating_resistivity`

```sema
def operating_resistivity() !{}
```

**Effects** `!{}`

# `def net_class_key`

```sema
def net_class_key(net_class: PcbNetClass)
```

**Parameters**

| name | type |
|---|---|
| `net_class` | `PcbNetClass` |

# `def layer_function_key`

```sema
def layer_function_key(function: PcbLayerFunction)
```

**Parameters**

| name | type |
|---|---|
| `function` | `PcbLayerFunction` |

# `def pin_kind_key`

```sema
def pin_kind_key(kind: PcbPinKind)
```

**Parameters**

| name | type |
|---|---|
| `kind` | `PcbPinKind` |

# `def pcb_pin`

```sema
def pcb_pin(refdes: str, number: int, name: str, net_id: str, x_mm: f64, y_mm: f64, kind: PcbPinKind)
```

**Parameters**

| name | type |
|---|---|
| `refdes` | `str` |
| `number` | `int` |
| `name` | `str` |
| `net_id` | `str` |
| `x_mm` | `f64` |
| `y_mm` | `f64` |
| `kind` | `PcbPinKind` |

# `def pcb_load`

```sema
def pcb_load(board_id: str, pin_id: str, return_pin_id: str, current_a: f64, description: str)
```

**Parameters**

| name | type |
|---|---|
| `board_id` | `str` |
| `pin_id` | `str` |
| `return_pin_id` | `str` |
| `current_a` | `f64` |
| `description` | `str` |

# `def route_points`

```sema
def route_points(x1: f64, y1: f64, x2: f64, y2: f64)
```

**Parameters**

| name | type |
|---|---|
| `x1` | `f64` |
| `y1` | `f64` |
| `x2` | `f64` |
| `y2` | `f64` |

# `def polyline_length_mm`

```sema
def polyline_length_mm(points: list[list[f64]])
```

**Parameters**

| name | type |
|---|---|
| `points` | `list[list[f64]]` |

# `def pin_index_map`

```sema
def pin_index_map(pins: list[PcbPin]) -> dict[str, int]
```

**Parameters**

| name | type |
|---|---|
| `pins` | `list[PcbPin]` |

**Returns** `dict[str, int]`

# `def layer_depth_mm`

```sema
def layer_depth_mm(stackup: PcbStackup, layer_id: str)
```

**Parameters**

| name | type |
|---|---|
| `stackup` | `PcbStackup` |
| `layer_id` | `str` |

# `def layer_copper_um`

```sema
def layer_copper_um(stackup: PcbStackup, layer_id: str)
```

**Parameters**

| name | type |
|---|---|
| `stackup` | `PcbStackup` |
| `layer_id` | `str` |

# `def layer_is_external`

```sema
def layer_is_external(stackup: PcbStackup, layer_id: str)
```

**Parameters**

| name | type |
|---|---|
| `stackup` | `PcbStackup` |
| `layer_id` | `str` |

# `def stackup_thickness_mm`

```sema
def stackup_thickness_mm(layers: list[PcbLayer])
```

**Parameters**

| name | type |
|---|---|
| `layers` | `list[PcbLayer]` |

# `def ipc_min_width_mm`

```sema
def ipc_min_width_mm(current_a: f64, copper_um: f64, external: bool) !{}
```

**Parameters**

| name | type |
|---|---|
| `current_a` | `f64` |
| `copper_um` | `f64` |
| `external` | `bool` |

**Effects** `!{}`

# `def ipc_temperature_rise_c`

```sema
def ipc_temperature_rise_c(current_a: f64, width_mm: f64, copper_um: f64, external: bool) !{}
```

**Parameters**

| name | type |
|---|---|
| `current_a` | `f64` |
| `width_mm` | `f64` |
| `copper_um` | `f64` |
| `external` | `bool` |

**Effects** `!{}`

# `def iec60664_creepage_mm`

```sema
def iec60664_creepage_mm(working_voltage_v: f64)
```

**Parameters**

| name | type |
|---|---|
| `working_voltage_v` | `f64` |

# `def route_plan_for`

```sema
def route_plan_for(net: PcbNet, defaults: list[PcbRoutePlan], overrides: list[PcbRoutePlan])
```

**Parameters**

| name | type |
|---|---|
| `net` | `PcbNet` |
| `defaults` | `list[PcbRoutePlan]` |
| `overrides` | `list[PcbRoutePlan]` |

# `def build_segments`

```sema
def build_segments(nets: list[PcbNet], defaults: list[PcbRoutePlan], overrides: list[PcbRoutePlan])
```

**Parameters**

| name | type |
|---|---|
| `nets` | `list[PcbNet]` |
| `defaults` | `list[PcbRoutePlan]` |
| `overrides` | `list[PcbRoutePlan]` |

# `def segment_polyline`

```sema
def segment_polyline(segment: PcbSegment, pins: list[PcbPin], index_of: dict[str, int])
```

**Parameters**

| name | type |
|---|---|
| `segment` | `PcbSegment` |
| `pins` | `list[PcbPin]` |
| `index_of` | `dict[str, int]` |

# `def single_via_resistance_ohm`

```sema
def single_via_resistance_ohm(stackup: PcbStackup, segment: PcbSegment) !{}
```

**Parameters**

| name | type |
|---|---|
| `stackup` | `PcbStackup` |
| `segment` | `PcbSegment` |

**Effects** `!{}`

# `def via_series_resistance_ohm`

```sema
def via_series_resistance_ohm(stackup: PcbStackup, segment: PcbSegment) !{}
```

**Parameters**

| name | type |
|---|---|
| `stackup` | `PcbStackup` |
| `segment` | `PcbSegment` |

**Effects** `!{}`

# `def copper_resistances_ohm`

```sema
def copper_resistances_ohm(stackup: PcbStackup, segments: list[PcbSegment], lengths_mm: list[f64]) !{}
```

**Parameters**

| name | type |
|---|---|
| `stackup` | `PcbStackup` |
| `segments` | `list[PcbSegment]` |
| `lengths_mm` | `list[f64]` |

**Effects** `!{}`

# `def solve_linear`

```sema
def solve_linear(matrix: list[list[f64]], rhs: list[f64])
```

**Parameters**

| name | type |
|---|---|
| `matrix` | `list[list[f64]]` |
| `rhs` | `list[f64]` |

# `def solve_network`

```sema
def solve_network(pins: list[PcbPin], nets: list[PcbNet], segments: list[PcbSegment], resistances_ohm: list[f64], loads: list[PcbLoad])
```

**Parameters**

| name | type |
|---|---|
| `pins` | `list[PcbPin]` |
| `nets` | `list[PcbNet]` |
| `segments` | `list[PcbSegment]` |
| `resistances_ohm` | `list[f64]` |
| `loads` | `list[PcbLoad]` |

# `def assemble_board`

```sema
def assemble_board(board_id: str, name: str, host_part_id: str, zone: str, substrate: str, stackup: PcbStackup, components: list[PcbComponent], pins: list[PcbPin], nets: list[PcbNet], defaults: list[PcbRoutePlan], overrides: list[PcbRoutePlan], loads: list[PcbLoad], outline_width_mm: f64, outline_height_mm: f64) !{}
```

**Parameters**

| name | type |
|---|---|
| `board_id` | `str` |
| `name` | `str` |
| `host_part_id` | `str` |
| `zone` | `str` |
| `substrate` | `str` |
| `stackup` | `PcbStackup` |
| `components` | `list[PcbComponent]` |
| `pins` | `list[PcbPin]` |
| `nets` | `list[PcbNet]` |
| `defaults` | `list[PcbRoutePlan]` |
| `overrides` | `list[PcbRoutePlan]` |
| `loads` | `list[PcbLoad]` |
| `outline_width_mm` | `f64` |
| `outline_height_mm` | `f64` |

**Effects** `!{}`

# `def bms_slave_stackup`

```sema
def bms_slave_stackup()
```

# `def bms_slave_components`

```sema
def bms_slave_components()
```

# `def bms_slave_pins`

```sema
def bms_slave_pins()
```

# `def bms_slave_nets`

```sema
def bms_slave_nets()
```

# `def bms_slave_plans`

```sema
def bms_slave_plans()
```

# `def bms_slave_overrides`

```sema
def bms_slave_overrides()
```

# `def bms_slave_loads`

```sema
def bms_slave_loads()
```

# `def bms_slave_board`

```sema
def bms_slave_board() !{}
```

**Effects** `!{}`

# `def front_zone_stackup`

```sema
def front_zone_stackup()
```

# `def front_zone_components`

```sema
def front_zone_components()
```

# `def front_zone_pins`

```sema
def front_zone_pins()
```

# `def front_zone_12v_load_a`

```sema
def front_zone_12v_load_a()
```

# `def front_zone_5v_load_a`

```sema
def front_zone_5v_load_a()
```

# `def front_zone_3v3_load_a`

```sema
def front_zone_3v3_load_a()
```

# `def front_zone_48v_load_a`

```sema
def front_zone_48v_load_a()
```

# `def front_zone_nets`

```sema
def front_zone_nets()
```

# `def front_zone_plans`

```sema
def front_zone_plans()
```

# `def front_zone_overrides`

```sema
def front_zone_overrides()
```

# `def front_zone_loads`

```sema
def front_zone_loads()
```

# `def front_zone_board`

```sema
def front_zone_board() !{}
```

**Effects** `!{}`

# `def hv_junction_stackup`

```sema
def hv_junction_stackup()
```

# `def hv_junction_components`

```sema
def hv_junction_components()
```

# `def hv_junction_pins`

```sema
def hv_junction_pins()
```

# `def hv_junction_12v_load_a`

```sema
def hv_junction_12v_load_a()
```

# `def hv_junction_5v_load_a`

```sema
def hv_junction_5v_load_a()
```

# `def hv_junction_nets`

```sema
def hv_junction_nets()
```

# `def hv_junction_plans`

```sema
def hv_junction_plans()
```

# `def hv_junction_overrides`

```sema
def hv_junction_overrides()
```

# `def hv_junction_loads`

```sema
def hv_junction_loads()
```

# `def hv_junction_board`

```sema
def hv_junction_board() !{}
```

**Effects** `!{}`

# `def sill_panel_stackup`

```sema
def sill_panel_stackup()
```

# `def sill_panel_components`

```sema
def sill_panel_components()
```

# `def sill_panel_pins`

```sema
def sill_panel_pins()
```

# `def sill_panel_nets`

```sema
def sill_panel_nets()
```

# `def sill_panel_plans`

```sema
def sill_panel_plans()
```

# `def sill_panel_overrides`

```sema
def sill_panel_overrides()
```

# `def sill_panel_loads`

```sema
def sill_panel_loads()
```

# `def sill_panel_board`

```sema
def sill_panel_board() !{}
```

**Effects** `!{}`

# `def board_loads`

```sema
def board_loads(board_id: str)
```

**Parameters**

| name | type |
|---|---|
| `board_id` | `str` |

# `def trace_branch_resistance_ohm`

```sema
def trace_branch_resistance_ohm(board: PcbBoard, trace: PcbTrace)
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |
| `trace` | `PcbTrace` |

# `def board_solution`

```sema
def board_solution(board: PcbBoard)
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |

# `def node_voltage`

```sema
def node_voltage(solution: PcbSolution, pin_id: str)
```

**Parameters**

| name | type |
|---|---|
| `solution` | `PcbSolution` |
| `pin_id` | `str` |

# `def net_drop_v`

```sema
def net_drop_v(board: PcbBoard, solution: PcbSolution, net: PcbNet)
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |
| `solution` | `PcbSolution` |
| `net` | `PcbNet` |

# `def board_drc_violations`

```sema
def board_drc_violations(board: PcbBoard)
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |

# `def isolated_domain_nets`

```sema
def isolated_domain_nets(board_id: str)
```

**Parameters**

| name | type |
|---|---|
| `board_id` | `str` |

# `def isolation_required_mm`

```sema
def isolation_required_mm(same_domain: bool, potential_difference_v: f64)
```

**Parameters**

| name | type |
|---|---|
| `same_domain` | `bool` |
| `potential_difference_v` | `f64` |

# `def net_lookup`

```sema
def net_lookup(nets: list[PcbNet]) -> dict[str, int]
```

**Parameters**

| name | type |
|---|---|
| `nets` | `list[PcbNet]` |

**Returns** `dict[str, int]`

# `def pcb_isolation_checks`

```sema
def pcb_isolation_checks(board: PcbBoard)
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |

# `def analyze_solved`

```sema
def analyze_solved(board: PcbBoard, field: PcbSolution, gates: list[PcbIsolationCheck])
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |
| `field` | `PcbSolution` |
| `gates` | `list[PcbIsolationCheck]` |

# `def pcb_analyze`

```sema
def pcb_analyze(board: PcbBoard)
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |

# `def pcb_netlist_text`

```sema
def pcb_netlist_text(board: PcbBoard)
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |

# `def pcb_boards`

```sema
def pcb_boards() !{}
```

**Effects** `!{}`

# `def component_kind`

```sema
def component_kind(refdes: str)
```

**Parameters**

| name | type |
|---|---|
| `refdes` | `str` |

# `def component_extent`

```sema
def component_extent(board: PcbBoard, refdes: str)
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |
| `refdes` | `str` |

# `def pad_extent`

```sema
def pad_extent(footprint: str)
```

**Parameters**

| name | type |
|---|---|
| `footprint` | `str` |

# `def layer_kind`

```sema
def layer_kind(layer: PcbLayer)
```

**Parameters**

| name | type |
|---|---|
| `layer` | `PcbLayer` |

# `def layer_color`

```sema
def layer_color(index: int, layer: PcbLayer)
```

**Parameters**

| name | type |
|---|---|
| `index` | `int` |
| `layer` | `PcbLayer` |

# `def substrate_kind`

```sema
def substrate_kind(board_id: str)
```

**Parameters**

| name | type |
|---|---|
| `board_id` | `str` |

# `def viewer_net_class`

```sema
def viewer_net_class(net_class: PcbNetClass)
```

**Parameters**

| name | type |
|---|---|
| `net_class` | `PcbNetClass` |

# `def vehicle_net_for`

```sema
def vehicle_net_for(net_id: str)
```

**Parameters**

| name | type |
|---|---|
| `net_id` | `str` |

# `def board_pours`

```sema
def board_pours(board: PcbBoard)
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |

# `def board_export`

```sema
def board_export(board: PcbBoard)
```

**Parameters**

| name | type |
|---|---|
| `board` | `PcbBoard` |

# `def pcb_export`

```sema
def pcb_export() -> dict[str, any] !{}
```

**Returns** `dict[str, any]`

**Effects** `!{}`

# `def pcb_equations`

```sema
def pcb_equations() -> list[dict[str, str]] !{}
```

**Returns** `list[dict[str, str]]`

**Effects** `!{}`



# `physics`

Coupled electric-vehicle dynamics over the panel-solved aerodynamic field.

Aerodynamic loads come from `aero.sema`: a constant-strength source-panel solve with an image
ground plane, closed by an empirical viscous and base-pressure model. Mass, centre of gravity and
yaw inertia come from `assembly.sema`, summed from the declared part placements rather than typed
in as literals. The thermal state is the eight-node coolant network from the same module, so heat
moves between components instead of each one sinking independently to ambient.

Two reduced-order surrogates are fitted to the panel solve at startup and are continuously scored
against it while the vehicle runs; that residual is what drives the model-adaptation lifecycle.
The dynamics themselves always integrate the solved loads, never the surrogate.

# `struct ControlInput`

**Fields**

| field | type | descriptor |
|---|---|---|
| `throttle` | `f64` |  |
| `brake` | `f64` |  |
| `steering` | `f64` |  |
| `wind_mps` | `f64` |  |
| `wind_yaw_deg` | `f64` |  |

# `struct VehicleState`

**Fields**

| field | type | descriptor |
|---|---|---|
| `time_s` | `f64` |  |
| `distance_m` | `f64` |  |
| `speed_mps` | `f64` |  |
| `lateral_speed_mps` | `f64` |  |
| `acceleration_mps2` | `f64` |  |
| `lateral_acceleration_mps2` | `f64` |  |
| `yaw_rate_rps` | `f64` |  |
| `yaw_angle_rad` | `f64` |  |
| `steering_angle_rad` | `f64` |  |
| `battery_soc` | `f64` |  |
| `thermal_temps_c` | `list[f64]` |  |
| `battery_temp_c` | `f64` |  |
| `motor_temp_c` | `f64` |  |
| `inverter_temp_c` | `f64` |  |
| `coolant_temp_c` | `f64` |  |
| `cabin_temp_c` | `f64` |  |
| `tire_temp_c` | `f64` |  |
| `front_disc_temp_c` | `f64` |  |
| `rear_disc_temp_c` | `f64` |  |
| `motor_rpm` | `f64` |  |
| `model_id` | `str` |  |

# `struct AeroLoads`

Body-axis aerodynamic loads. Positive drag resists forward travel; positive lift is upward.

**Fields**

| field | type | descriptor |
|---|---|---|
| `air_speed_mps` | `f64` |  |
| `sideslip_rad` | `f64` |  |
| `dynamic_pressure_pa` | `f64` |  |
| `cd` | `f64` |  |
| `cy` | `f64` |  |
| `cl` | `f64` |  |
| `cmz` | `f64` |  |
| `drag_n` | `f64` |  |
| `side_n` | `f64` |  |
| `lift_n` | `f64` |  |
| `front_lift_n` | `f64` |  |
| `rear_lift_n` | `f64` |  |
| `yaw_moment_nm` | `f64` |  |

# `struct AeroSurrogate`

Even-in-yaw drag and lift, odd-in-yaw side force and yaw moment, fitted to the panel solve.

**Fields**

| field | type | descriptor |
|---|---|---|
| `model_id` | `str` |  |
| `cd0` | `f64` |  |
| `cd2` | `f64` |  |
| `cd4` | `f64` |  |
| `cy1` | `f64` |  |
| `cy3` | `f64` |  |
| `cl0` | `f64` |  |
| `cl2` | `f64` |  |
| `cl4` | `f64` |  |
| `cmz1` | `f64` |  |
| `cmz3` | `f64` |  |
| `fit_max_yaw_rad` | `f64` |  |
| `fit_nodes` | `int` |  |
| `fit_rms_cd` | `f64` |  |

# `struct VehicleParameters`

Inertial and geometric parameters derived from the declared assembly, not typed in.

**Fields**

| field | type | descriptor |
|---|---|---|
| `mass_kg` | `f64` |  |
| `cg_to_front_axle_m` | `f64` |  |
| `cg_to_rear_axle_m` | `f64` |  |
| `cg_height_m` | `f64` |  |
| `yaw_inertia_kg_m2` | `f64` |  |
| `wheelbase_m` | `f64` |  |
| `front_mass_fraction` | `f64` |  |
| `track_m` | `f64` |  |

# `struct AeroHoldout`

**Fields**

| field | type | descriptor |
|---|---|---|
| `active_training_error` | `f64` |  |
| `candidate_training_error` | `f64` |  |
| `active_validation_error` | `f64` |  |
| `candidate_validation_error` | `f64` |  |

# `struct CornerSuspension`

One wheel station read out of the declared double-wishbone closure.

**Fields**

| field | type | descriptor |
|---|---|---|
| `vertical_load_n` | `f64` |  |
| `wheel_travel_m` | `f64` |  |
| `camber_deg` | `f64` |  |
| `toe_deg` | `f64` |  |
| `damper_length_m` | `f64` |  |
| `spring_force_n` | `f64` |  |
| `travel_utilisation` | `f64` |  |

# `struct SuspensionCorners`

The four wheel stations, each solved at its own vertical load.

Lateral load transfer moves normal force onto the outside pair, so the closure returns different
travel, camber and toe per side. With no lateral acceleration the two sides are identical.

**Fields**

| field | type | descriptor |
|---|---|---|
| `front_left` | `CornerSuspension` |  |
| `front_right` | `CornerSuspension` |  |
| `rear_left` | `CornerSuspension` |  |
| `rear_right` | `CornerSuspension` |  |

# `struct SuspensionTable`

Corner closures pre-solved on a load grid, in strictly increasing load order.

**Fields**

| field | type | descriptor |
|---|---|---|
| `loads_n` | `list[f64]` |  |
| `corners` | `list[CornerSuspension]` |  |

# `struct AxleLongitudinal`

One axle's longitudinal state: what its hardware could do, and what the tyres allowed.

Both the brake and the drive side report which declared component set the number. At the tyre
limit a stop is mass-independent; the moment the caliper is the binding constraint it is not,
and the difference is visible here rather than folded into one force.

**Fields**

| field | type | descriptor |
|---|---|---|
| `axle` | `str` |  |
| `normal_force_n` | `f64` |  |
| `friction_coefficient` | `f64` |  |
| `tire_limit_n` | `f64` |  |
| `brake_capability_n` | `f64` |  |
| `brake_force_n` | `f64` |  |
| `brake_constraint` | `str` |  |
| `motor_force_n` | `f64` |  |
| `power_force_n` | `f64` |  |
| `drive_force_n` | `f64` |  |
| `drive_constraint` | `str` |  |
| `cornering_stiffness_n_rad` | `f64` |  |
| `disc_temp_c` | `f64` |  |
| `fade_fraction` | `f64` |  |

# `struct LongitudinalDemand`

The live inputs one longitudinal solve needs, flattened out of the state and the aero solve.

Built once per step and handed to the four axle solves. Everything on it is a scalar so that the
per-call copy the interpreter makes stays cheap.

**Fields**

| field | type | descriptor |
|---|---|---|
| `throttle` | `f64` |  |
| `brake` | `f64` |  |
| `power_limit` | `f64` |  |
| `speed_mps` | `f64` |  |
| `previous_acceleration_mps2` | `f64` |  |
| `front_disc_temp_c` | `f64` |  |
| `rear_disc_temp_c` | `f64` |  |
| `front_lift_n` | `f64` |  |
| `rear_lift_n` | `f64` |  |
| `lift_n` | `f64` |  |
| `drag_n` | `f64` |  |

# `struct LongitudinalSolution`

Both axles solved together under one load transfer, with the resulting body acceleration.

**Fields**

| field | type | descriptor |
|---|---|---|
| `front` | `AxleLongitudinal` |  |
| `rear` | `AxleLongitudinal` |  |
| `rolling_force_n` | `f64` |  |
| `brake_line_pressure_pa` | `f64` |  |
| `brake_force_n` | `f64` |  |
| `drive_force_n` | `f64` |  |
| `motor_torque_nm` | `f64` |  |
| `acceleration_mps2` | `f64` |  |
| `limiting_constraint` | `str` |  |

# `struct PhysicsContext`

Everything expensive is built once here: the panel basis, the mass budget and both surrogates.

**Fields**

| field | type | descriptor |
|---|---|---|
| `basis` | `AeroBasis` |  |
| `parameters` | `VehicleParameters` |  |
| `calm` | `AeroSurrogate` |  |
| `crosswind` | `AeroSurrogate` |  |
| `holdout` | `AeroHoldout` |  |
| `suspension` | `SuspensionTable` |  |
| `hardware` | `HardwareLimits` |  |

# `struct DynamicsFrame`

**Fields**

| field | type | descriptor |
|---|---|---|
| `state` | `VehicleState` |  |
| `controls` | `ControlInput` |  |
| `sequence` | `int` |  |
| `apparent_air_speed_mps` | `f64` |  |
| `sideslip_rad` | `f64` |  |
| `dynamic_pressure_pa` | `f64` |  |
| `solved` | `AeroLoads` |  |
| `surrogate` | `AeroLoads` |  |
| `model_cd` | `f64` |  |
| `reference_cd` | `f64` |  |
| `cd_pressure` | `f64` |  |
| `cd_base` | `f64` |  |
| `cd_friction` | `f64` |  |
| `cd_wheels` | `f64` |  |
| `drag_force_n` | `f64` |  |
| `lift_force_n` | `f64` |  |
| `side_force_n` | `f64` |  |
| `aero_yaw_moment_nm` | `f64` |  |
| `rolling_force_n` | `f64` |  |
| `brake_force_n` | `f64` |  |
| `regenerative_force_n` | `f64` |  |
| `drive_force_n` | `f64` |  |
| `wheel_torque_nm` | `f64` |  |
| `tire_slip_ratio` | `f64` |  |
| `front_axle_normal_force_n` | `f64` |  |
| `rear_axle_normal_force_n` | `f64` |  |
| `tire_utilization_fraction` | `f64` |  |
| `front_axle` | `AxleLongitudinal` |  |
| `rear_axle` | `AxleLongitudinal` |  |
| `limiting_constraint` | `str` |  |
| `brake_line_pressure_pa` | `f64` |  |
| `brake_fade_fraction` | `f64` |  |
| `front_disc_temp_c` | `f64` |  |
| `rear_disc_temp_c` | `f64` |  |
| `suspension` | `SuspensionCorners` |  |
| `mechanical_power_kw` | `f64` |  |
| `electrical_power_kw` | `f64` |  |
| `regenerative_power_kw` | `f64` |  |
| `battery_power_kw` | `f64` |  |
| `battery_loss_kw` | `f64` |  |
| `motor_loss_kw` | `f64` |  |
| `inverter_loss_kw` | `f64` |  |
| `pack_open_circuit_voltage_v` | `f64` |  |
| `pack_voltage_v` | `f64` |  |
| `pack_current_a` | `f64` |  |
| `pack_resistance_ohm` | `f64` |  |
| `power_limit_fraction` | `f64` |  |
| `power_residual_kw` | `f64` |  |
| `aero_residual_fraction` | `f64` |  |
| `reynolds_number` | `f64` |  |
| `separation_x_m` | `f64` |  |
| `cp_min` | `f64` |  |
| `cp_max` | `f64` |  |
| `base_pressure_coefficient` | `f64` |  |
| `wake_width_m` | `f64` |  |
| `wake_shedding_hz` | `f64` |  |
| `turbulence_intensity` | `f64` |  |
| `wake_velocity_deficit_fraction` | `f64` |  |
| `wake_recirculation_length_m` | `f64` |  |
| `dalembert_residual_cd` | `f64` |  |
| `aero_solver_residual` | `f64` |  |
| `aero_force_closure_residual` | `f64` |  |
| `thermal_energy_residual_w` | `f64` |  |
| `energy_residual_fraction` | `f64` |  |

# `def vehicle_parameters`

```sema
def vehicle_parameters()
```

Inertia and geometry summed from the declared part placements in `assembly.sema`.

# `def least_squares_pair`

```sema
def least_squares_pair(u: list[f64], y: list[f64]) !{}
```

**Parameters**

| name | type |
|---|---|
| `u` | `list[f64]` |
| `y` | `list[f64]` |

**Effects** `!{}`

Fit y ~ a*u + b*u^2 through the origin by the 2x2 normal equations.

# `def least_squares_single`

```sema
def least_squares_single(u: list[f64], y: list[f64])
```

**Parameters**

| name | type |
|---|---|
| `u` | `list[f64]` |
| `y` | `list[f64]` |

Fit y ~ a*u through the origin.

# `def yaw_fit_nodes`

```sema
def yaw_fit_nodes(count: int, maximum_deg: f64)
```

**Parameters**

| name | type |
|---|---|
| `count` | `int` |
| `maximum_deg` | `f64` |

Yaw stations in radians, evenly spaced from zero to the declared fit boundary.

# `def fit_surrogate`

```sema
def fit_surrogate(basis: AeroBasis, model_id: str, count: int, maximum_deg: f64, quartic: bool) !{}
```

**Parameters**

| name | type |
|---|---|
| `basis` | `AeroBasis` |
| `model_id` | `str` |
| `count` | `int` |
| `maximum_deg` | `f64` |
| `quartic` | `bool` |

**Effects** `!{}`

Least-squares fit of the panel solve over a bounded yaw window.

Drag and lift are even in sideslip, side force and yaw moment are odd, so the basis functions are
beta^2/beta^4 and beta/beta^3 respectively. The zero-yaw intercepts are taken exactly from the
solve so the calibrated `C_d(0)` is preserved without relying on the fit.

# `def surrogate_loads`

```sema
def surrogate_loads(surrogate: AeroSurrogate, air_speed_mps: f64, sideslip_rad: f64) !{}
```

**Parameters**

| name | type |
|---|---|
| `surrogate` | `AeroSurrogate` |
| `air_speed_mps` | `f64` |
| `sideslip_rad` | `f64` |

**Effects** `!{}`

Evaluate the reduced-order coefficients. Cheap enough to run inside any control loop.

# `def solved_loads`

```sema
def solved_loads(forces: AeroForces)
```

**Parameters**

| name | type |
|---|---|
| `forces` | `AeroForces` |

# `def surrogate_for`

```sema
def surrogate_for(context: PhysicsContext, model_id: str)
```

**Parameters**

| name | type |
|---|---|
| `context` | `PhysicsContext` |
| `model_id` | `str` |

# `def relative_drag_error`

```sema
def relative_drag_error(basis: AeroBasis, surrogate: AeroSurrogate, degrees: list[f64]) !{}
```

**Parameters**

| name | type |
|---|---|
| `basis` | `AeroBasis` |
| `surrogate` | `AeroSurrogate` |
| `degrees` | `list[f64]` |

**Effects** `!{}`

Mean relative drag-coefficient error of a surrogate against the panel solve.

# `def physics_context`

```sema
def physics_context() -> PhysicsContext !{}
```

**Returns** `PhysicsContext`

**Effects** `!{}`

Build the panel basis, the mass budget and both surrogates. Call once at startup.

# `def aero_holdout_metrics`

```sema
def aero_holdout_metrics(context: PhysicsContext) -> dict[str, f64] !{}
```

**Parameters**

| name | type |
|---|---|
| `context` | `PhysicsContext` |

**Returns** `dict[str, f64]`

**Effects** `!{}`

Training and held-out surrogate errors measured against the live panel solve.

# `def initial_vehicle_state`

```sema
def initial_vehicle_state() -> VehicleState !{}
```

**Returns** `VehicleState`

**Effects** `!{}`

# `def state_at`

```sema
def state_at(speed_mps: f64, battery_soc: f64)
```

**Parameters**

| name | type |
|---|---|
| `speed_mps` | `f64` |
| `battery_soc` | `f64` |

A settled state at one road speed and state of charge, with cold discs and cold tyres.

# `def state_carrying`

```sema
def state_carrying(state: VehicleState, speed_mps: f64)
```

**Parameters**

| name | type |
|---|---|
| `state` | `VehicleState` |
| `speed_mps` | `f64` |

The same thermal and electrical state brought back to speed, for a repeated-stop sequence.

Only the motion resets. Disc, tyre, pack and coolant temperatures carry over, which is the whole
point: the heat a stop leaves behind is what the next one starts from.

# `def with_added_mass`

```sema
def with_added_mass(context: PhysicsContext, added_kg: f64)
```

**Parameters**

| name | type |
|---|---|
| `context` | `PhysicsContext` |
| `added_kg` | `f64` |

The same vehicle carrying more mass: everything else declared stays exactly as it is.

Only the inertial parameters move. The motors, calipers, discs and tyres are the ones the parts
list declares, which is the whole point of the comparison: what does the declared hardware do
when it has more mass to move and to stop.

# `def apparent_wind`

```sema
def apparent_wind(state: VehicleState, controls: ControlInput)
```

**Parameters**

| name | type |
|---|---|
| `state` | `VehicleState` |
| `controls` | `ControlInput` |

Apparent-wind magnitude and sideslip in the vehicle frame.

A head-on tunnel wind and forward road speed add; `wind_yaw_deg` rotates the tunnel wind around
the vehicle, so a parked car in a 27.8 m/s stream reports 27.8 m/s of apparent wind at that yaw.

# `def corner_state`

```sema
def corner_state(state: SuspensionState)
```

**Parameters**

| name | type |
|---|---|
| `state` | `SuspensionState` |

Narrow a solved corner to the fields the dynamics frame publishes.

# `def suspension_table`

```sema
def suspension_table()
```

Pre-solve the declared corner closure on a grid uniform in lower-arm rotation.

`suspension_travel` bisects the arm rotation against the polytropic air spring, which costs far
more than the whole rest of a 20 Hz step. Sampling it once at startup and interpolating keeps the
loop inside its budget without inventing a second suspension model. Rotation-uniform spacing puts
the nodes where the linkage actually moves: the air spring stiffens so sharply that a load-uniform
grid would collapse the working range into a couple of intervals. Rotations that clamp to the same
load at a stop are dropped, so the lookup never meets a zero-width interval.

# `def interpolate_corner`

```sema
def interpolate_corner(table: SuspensionTable, vertical_load_n: f64)
```

**Parameters**

| name | type |
|---|---|
| `table` | `SuspensionTable` |
| `vertical_load_n` | `f64` |

Read the pre-solved corner grid at one vertical wheel load, linearly between nodes.

# `def clamped_disc_temp`

```sema
def clamped_disc_temp(value: f64)
```

**Parameters**

| name | type |
|---|---|
| `value` | `f64` |

Hold an integrated disc temperature inside the range the state invariant declares.

# `def axle_longitudinal`

```sema
def axle_longitudinal(hardware: AxleHardware, demand: LongitudinalDemand, normal_force_n: f64, disc_temp_c: f64) !{}
```

**Parameters**

| name | type |
|---|---|
| `hardware` | `AxleHardware` |
| `demand` | `LongitudinalDemand` |
| `normal_force_n` | `f64` |
| `disc_temp_c` | `f64` |

**Effects** `!{}`

One axle solved against its own hardware and its own live vertical load.

The brake side walks the real chain: pedal demand sets line pressure, pressure and piston area
set clamp force, pad friction and the effective radius set corner torque, and the rolling radius
turns that into a wheel force. All of that is fixed by the declared components, so `hardware`
arrives with it already solved and only the pedal fraction and the fade factor are applied here.
The tyre side is the friction circle on this axle's live load, with the declared load
sensitivity, so a heavier corner returns less grip per newton. Whichever of the two is smaller
is the force the axle makes, and which one it was is published.

# `def axle_pass`

```sema
def axle_pass(front_hardware: AxleHardware, rear_hardware: AxleHardware, demand: LongitudinalDemand, static_front_n: f64, static_rear_n: f64, transfer_n: f64) !{}
```

**Parameters**

| name | type |
|---|---|
| `front_hardware` | `AxleHardware` |
| `rear_hardware` | `AxleHardware` |
| `demand` | `LongitudinalDemand` |
| `static_front_n` | `f64` |
| `static_rear_n` | `f64` |
| `transfer_n` | `f64` |

**Effects** `!{}`

Both axles solved at the load transfer one candidate acceleration implies.

# `def longitudinal_solution`

```sema
def longitudinal_solution(parameters: VehicleParameters, hardware: HardwareLimits, demand: LongitudinalDemand) !{}
```

**Parameters**

| name | type |
|---|---|
| `parameters` | `VehicleParameters` |
| `hardware` | `HardwareLimits` |
| `demand` | `LongitudinalDemand` |

**Effects** `!{}`

Solve both axles and the body acceleration together, since each one sets the other.

Load transfer depends on the acceleration, and the acceleration depends on the per-axle limits
that the transferred loads produce. One predictor from the previous step's acceleration and one
corrector from this step's is enough at 20 Hz: the transfer moves by well under a percent over
the second pass, and a fixed point is not worth the extra arithmetic inside the loop.

Nothing larger than a flat scalar struct crosses these call boundaries. The interpreter copies a
struct argument by value, and `PhysicsContext` carries the whole panel basis, so handing it to a
helper that runs four times a step costs milliseconds out of a fifty-millisecond budget.

# `def step_vehicle`

```sema
def step_vehicle(state: VehicleState, controls: ControlInput, duration_s: f64, sequence: int, model_id: str, context: PhysicsContext) -> DynamicsFrame !{}
```

**Parameters**

| name | type |
|---|---|
| `state` | `VehicleState` |
| `controls` | `ControlInput` |
| `duration_s` | `f64` |
| `sequence` | `int` |
| `model_id` | `str` |
| `context` | `PhysicsContext` |

**Returns** `DynamicsFrame`

**Effects** `!{}`

# `struct StopMeasurement`

One full-pedal stop, integrated at the same 20 Hz the live loop runs at.

**Fields**

| field | type | descriptor |
|---|---|---|
| `distance_m` | `f64` |  |
| `duration_s` | `f64` |  |
| `mean_deceleration_mps2` | `f64` |  |
| `front_constraint` | `str` |  |
| `rear_constraint` | `str` |  |
| `front_disc_temp_c` | `f64` |  |
| `rear_disc_temp_c` | `f64` |  |
| `peak_brake_force_n` | `f64` |  |

# `struct LaunchMeasurement`

One full-throttle launch to a target speed, integrated at the same 20 Hz.

**Fields**

| field | type | descriptor |
|---|---|---|
| `duration_s` | `f64` |  |
| `distance_m` | `f64` |  |
| `launch_constraint` | `str` |  |
| `final_constraint` | `str` |  |
| `peak_drive_force_n` | `f64` |  |

# `def measured_stop`

```sema
def measured_stop(context: PhysicsContext, initial_speed_mps: f64)
```

**Parameters**

| name | type |
|---|---|
| `context` | `PhysicsContext` |
| `initial_speed_mps` | `f64` |

Brake from one speed to rest at full pedal and report what the vehicle actually did.

# `def measured_launch`

```sema
def measured_launch(context: PhysicsContext, target_speed_mps: f64)
```

**Parameters**

| name | type |
|---|---|
| `context` | `PhysicsContext` |
| `target_speed_mps` | `f64` |

Accelerate from rest to one speed at full throttle and report what the hardware allowed.

# `def dynamics_equations`

```sema
def dynamics_equations() -> list[dict[str, str]] !{}
```

**Returns** `list[dict[str, str]]`

**Effects** `!{}`



# `vehicle`

Automotive-native major-assembly vehicle, E/E, and manufacturing definition.

# `def variant_value`

```sema
def variant_value(configuration_variant: str, conventional: str, circuitframe: str)
```

**Parameters**

| name | type |
|---|---|
| `configuration_variant` | `str` |
| `conventional` | `str` |
| `circuitframe` | `str` |

# `def vehicle_identity`

```sema
def vehicle_identity() -> VehicleIdentity !{}
```

**Returns** `VehicleIdentity`

**Effects** `!{}`

# `def vehicle_parts`

```sema
def vehicle_parts() -> list[PartDefinition] !{}
```

**Returns** `list[PartDefinition]`

**Effects** `!{}`

# `def vehicle_nets`

```sema
def vehicle_nets(configuration_variant: str) -> list[NetDefinition] !{}
```

**Parameters**

| name | type |
|---|---|
| `configuration_variant` | `str` |

**Returns** `list[NetDefinition]`

**Effects** `!{}`

# `def net_routing`

```sema
def net_routing() -> list[dict[str, any]]
```

**Returns** `list[dict[str, any]]`

Per-net conductor routing for both configurations.

Lengths, terminations and splice counts are bounded engineered assumptions derived from the
packaging of the reconstructed vehicle; they are not measured production harness data. The
CircuitFrame column routes through the structure, so it is shorter and needs fewer separable
interfaces, which is the claim this comparison exists to quantify.

# `def conductor_variant`

```sema
def conductor_variant(net: NetDefinition, route: list[f64], density_a_mm2: f64, jacket_factor: f64, conductor: str, route_text: str) !{}
```

**Parameters**

| name | type |
|---|---|
| `net` | `NetDefinition` |
| `route` | `list[f64]` |
| `density_a_mm2` | `f64` |
| `jacket_factor` | `f64` |
| `conductor` | `str` |
| `route_text` | `str` |

**Effects** `!{}`

Size one conductor run from its declared current and report the resulting electrical loads.

# `def vehicle_net_comparison`

```sema
def vehicle_net_comparison() -> list[dict[str, any]] !{}
```

**Returns** `list[dict[str, any]]`

**Effects** `!{}`

Conventional harness versus CircuitFrame structural conductors over the same nine nets.

# `def manufacturing_operations`

```sema
def manufacturing_operations(configuration_variant: str)
```

**Parameters**

| name | type |
|---|---|
| `configuration_variant` | `str` |

# `def validation_cases`

```sema
def validation_cases()
```

# `def evidence_records`

```sema
def evidence_records()
```

# `def source_records`

```sema
def source_records()
```

# `def source_bindings`

```sema
def source_bindings()
```

# `def source_gate`

```sema
def source_gate() -> dict[str, any] !{}
```

**Returns** `dict[str, any]`

**Effects** `!{}`

# `def completeness_report`

```sema
def completeness_report(configuration_variant: str) -> CompletenessReport !{}
```

**Parameters**

| name | type |
|---|---|
| `configuration_variant` | `str` |

**Returns** `CompletenessReport`

**Effects** `!{}`

# `def vehicle_manifest`

```sema
def vehicle_manifest(configuration_variant: str) -> dict[str, any] !{}
```

**Parameters**

| name | type |
|---|---|
| `configuration_variant` | `str` |

**Returns** `dict[str, any]`

**Effects** `!{}`
