Blueprints
Fork a running simulation into counterfactual worlds
March until the physics fires an event, pause, then continue every candidate from that shared marched state. The fork is O(1) copy-on-write and each branch is alternated into its own audited world.
Two levels. The trunk marches to an event; the study fans out from the pause it returns.
Trunk: march until the field satisfies a predicate
examples/avionics_examples/cfd/plasma_blackout/corridor/main.rs:81-89:
let onset = CfdFlow::march(&nominal)
.couple(world::corridor_coupling(1.0, 0))
.trigger(trigger)
.from_field(world::initial_field())
.until(|field, _| field.regime().map(|r| r.gnss_denied).unwrap_or(false))?;
The predicate reads the evolved field, so the pause lands where the chemistry crossed the receiver cutoff. See detect and act on a regime change.
Campaign: fork the pause
main.rs:91-112:
let corridor = CfdFlow::study("bank-angle corridor")
.cases(model::coarse_commands())
.fork(&onset)
.branch(model::bank_world)
.continue_for(constants::BRANCH_STEPS)
.reduce_all(model::score_branches) // aim point from the ballistic branch first
.inspect(|rows| utils_print::print_branches(COARSE_TITLE, rows))
.refine(&onset, model::fine_candidates) // 0.5-deg bracket around the coarse winner
.branch(model::bank_world)
.continue_for(constants::BRANCH_STEPS)
.reduce_all(model::score_branches) // same aim point: the rounds stay comparable
.record(table_path())
.gates(model::corridor_gates())
.verdict()?;
refine forks the same onset pause a second time. Both rounds call
reduce_all(model::score_branches), so they score against one shared aim point
and remain comparable.
Per-branch command injection
Branches differ only in a world-published constant. model.rs:107-130:
pub fn descent_world(
name: &'static str,
bank_deg: FloatType,
) -> Result<CompressibleMarchConfig<FloatType>, PhysicsError> {
world::descent_world(
name,
world::standard_atmosphere(),
STEPS,
&[("commanded_bank", utils::rad(bank_deg))],
)
}
pub fn bank_world(cmd: &BankCommand) -> Result<CompressibleMarchConfig<FloatType>, PhysicsError> {
descent_world(cmd.name, cmd.deg)
}
The constant reaches the marcher through the builder
(shared/world.rs:127-129):
for &(cname, value) in constants {
builder = builder.publish_constant(cname, value);
}
CommandedBank (shared/stages.rs:146-156) reads the published scalar into the
control channel each step, and CyberneticCorrect clamps it against the
envelope.
World alternation is applied by the driver
The example passes a world constructor to .branch(..). The study driver calls
alternate_context for any branch whose world name differs from the baseline
(deep_causality_cfd/src/types/flow/study/mod.rs:815-825):
let world = &c.worlds[i];
let stack = (c.coupling)(&c.cases[i], draw);
let mut run = CfdFlow::march(&c.baseline);
if world.name() != c.baseline.name() {
run = run.alternate_context(world);
}
The audit rejoin (study/mod.rs:833-840) detects the !!ContextAlternation!!
marker and labels each branch alternated or baseline.
The corridor example also calls alternate_context directly at main.rs:127
and main.rs:143, on diagnostic legs outside the study.
Measured fork cost
Sharing is O(1). Branches share the trunk read-only through Arc and take a
copy-on-write clone at first divergent write. On a plume-coupled state
(studies/qtt_rank_plume): fork setup 42 ns, per-branch continuation 0.67 to
1.04× the trunk step cost. The 0.67 case is a coast branch performing less work.
fork does not fail. A broken pause is carried through and surfaces as each
branch’s error at continue_for, which keeps the failure attributable to the
branch.
State forks compared with parameter forks
A parameter fork re-runs a model from different inputs; each branch is an independent world. The weather table has that shape.
A state fork pauses one marched coupled field and continues every branch from it, so each intervention feeds back into the flow it inherited. The corridor has that shape.
For a state fork, compare each branch against a frozen-field prediction of the same intervention: the same command schedule with the flow closure held at the fork’s value. If simple kinematics reproduces the divergence, the flow did not contribute to it.
The retropulsion descent runs that comparison as gate 4c. Its branches depart the frozen-drag prediction by up to 139.4 m/s against a 100 m/s threshold, and its flow observables spread 0.0202 across the roster where the corridor’s bank branches agreed to three digits.
Worked examples using this