package engine // Profile is an ordered chain of propagators executed sequentially. Each // propagator picks up where the previous one finished. type Profile struct { // Stages are run in order. For Direction=Reverse they are still // iterated from index 0 onwards but each propagator integrates with // negative dt. Stages []*Propagator // Direction controls the sign of dt across the profile. Direction Direction // Globals are constraints evaluated alongside each stage's local // Constraints. Useful for profile-wide bounds like "stop after N hours". Globals []Constraint } // Run executes the profile from the given launch point. Returns one // Result per executed stage, including any Fallback chains that were // activated. The supplied EventSink is shared across stages and aggregates // non-fatal observations. // // events may be nil; pass NewEventSink() to capture observations. func (p *Profile) Run(t0 float64, launch State, events *EventSink) []Result { if p.Direction == 0 { p.Direction = Forward } results := make([]Result, 0, len(p.Stages)) t, s := t0, launch for _, stage := range p.Stages { res := stage.run(p.context(t0, t, launch, s), t, s, p.Globals, events) results = append(results, res) t, s = res.Path.Last() // Follow Fallback chains until none remains. for res.Outcome == OutcomeFallback && stage.Fallback != nil { stage = stage.Fallback res = stage.run(p.context(t0, t, launch, s), t, s, p.Globals, events) results = append(results, res) t, s = res.Path.Last() } } return results } // context builds the StageContext for a stage starting at (tStart, sStart). func (p *Profile) context(t0, tStart float64, launch, sStart State) StageContext { return StageContext{ ProfileStart: t0, PropagatorStart: tStart, Launch: launch, PropagatorState: sStart, Direction: p.Direction, } }