Multi-Shot Campaign Orchestration¶
The multi-shot campaign orchestrator runs repeated pulsed-shot admission over the CONTROL scheduler, capacitor-bank telemetry contract, and replay v1.1 metadata fields. It is a campaign-control adapter. It does not simulate the plasma, replace facility shot sequencing, or add a second physics solver.
What the adapter checks¶
For each shot, the adapter:
- Resets a fresh
PulsedScenarioScheduler. - Initialises a bounded
CapacitorBankstate from the declared bank specification. - Feeds timestamped plasma and bank telemetry samples through scheduler guards.
- Records command rows and transition rows.
- Requires the canonical lifecycle by default:
ramp_up -> flat_top -> burn -> expansion -> dump -> recharge -> cool_down -> idle. - Emits replay-compatible pulse metadata:
pulse_id,capacitor_state_initial_J,trigger_timestamp_ns,energy_recovered_J, and sortedshot_phase_logrows. - Preserves optional per-shot pulsed-MPC decision evidence through
pulsed_mpc_admission_digestandpulsed_mpc_evidence_schema_versionwhen the campaign consumes an admitted pulsed-MPC command.
Per-shot failures are fail-closed and do not abort the remaining campaign unless campaign-level input is malformed, such as duplicate shot IDs. The pulsed-MPC digest is a replay provenance binding. It does not admit a facility interlock, target-hardware actuator path, or PCS timing claim.
Python surface¶
from scpn_control.control.multi_shot_campaign import (
CampaignShotPlan,
CampaignShotSample,
MultiShotCampaignOrchestrator,
)
orchestrator = MultiShotCampaignOrchestrator(
"campaign-a",
scheduler_spec,
bank_spec,
)
report = orchestrator.run(
[
CampaignShotPlan(
shot_id="shot-001",
samples=tuple(samples),
initial_bank_voltage_V=5000.0,
pulsed_mpc_admission_digest=admitted_mpc_decision.admission_digest,
)
]
)
The returned report uses schema version
scpn-control.multi-shot-campaign.v1 and includes a SHA-256 payload digest.
If any shot supplies pulsed_mpc_admission_digest, the report also records
pulsed_mpc_admission_digest_count and binds each digest into the report
payload hash.
Rust and PyO3 surfaces¶
The Rust kernel lives in control_control::multi_shot_campaign and exposes:
CampaignShotSampleCampaignShotPlanMultiShotCampaignOrchestratorMultiShotCampaignReport
The optional PyO3 bridge exposes PyMultiShotCampaignOrchestrator.run_table().
It accepts table-shaped NumPy arrays for sample index, sample time, plasma
telemetry, bank telemetry, initial bank voltages, and optional
pulsed_mpc_admission_digests. This keeps the bridge explicit about units,
array shapes, and evidence handoff.
Benchmarks¶
Run Python local-regression evidence:
taskset -c 4,5 env PYTHONPATH=src python benchmarks/bench_multi_shot_campaign.py \
--steps 2000 \
--warmup 200 \
--json-out validation/reports/multi_shot_campaign_soft_isolated.json \
--md-out validation/reports/multi_shot_campaign_soft_isolated.md
Run native Rust local-regression evidence:
taskset -c 4,5 cargo run --manifest-path scpn-control-rs/Cargo.toml \
-p control-control \
--example bench_multi_shot_campaign \
--release \
-- \
--steps 2000 \
--warmup 200 \
--json-out validation/reports/multi_shot_campaign_rust_soft_isolated.json \
--md-out validation/reports/multi_shot_campaign_rust_soft_isolated.md
Soft-affinity workstation reports must keep production_claim_allowed=false.
Production timing claims require explicit core isolation, host-load context, and
target-runtime evidence.