Class ScenarioRunner

java.lang.Object
org.horizon36596.simloop.sim.ScenarioRunner

public final class ScenarioRunner extends Object
Runs one headless sim scenario end-to-end: owns the entire PsiKit lifecycle (sim-harness §2-3) and the fixed-timestep tick loop, so a test does not hand-write it (BACKLOG "scenario runner" / area B). Every sim test up to this point (Batch 1.4's DrivetrainSimTest, Batch 2.2's SlideSimTest) re-typed the same ~20 lines: create the RLOG directory, reset/wire/start PsiKit's Logger, tick a FakeTimer inside a periodicBeforeUser()/periodicAfterUser() bracket, call Logger.end(), then re-check by hand that the writer thread didn't overflow or emit nothing. This class is that boilerplate, written once.

What stays with the caller. This class is season-agnostic core (architecture §3, domain R7): it knows nothing about a specific robot's motors, plants, or subsystems. Building the fake hardware, commanding the robot, advancing its plants, and deciding what to log each tick is scenario-specific behavior the caller supplies as a ScenarioRunner.ScenarioBody. The SimRobotConfig parameter is not read by this class at all — it is recorded as PsiKit metadata purely so a human opening the RLOG in AdvantageScope (or a future Phase-3 scorer) can see which robot config a run used, the same way opModeName is.

Trustworthy by construction. run(java.lang.String, org.horizon36596.simloop.config.SimRobotConfig, org.horizon36596.simloop.sim.FakeTimer, int, double, java.nio.file.Path, org.horizon36596.simloop.sim.ScenarioRunner.ScenarioBody) throws if PsiKit's async writer queue overflowed (BACKLOG B3 — cycles silently dropped) or if the resulting RLOG is missing or empty (BACKLOG B11) — a scenario whose log cannot be trusted for replay is a defect the moment it happens, not something every caller should have to re-check after the fact.

  • Method Details

    • run

      public static void run(String opModeName, SimRobotConfig config, FakeTimer timer, int ticks, double deltaTimeSeconds, Path rlogPath, ScenarioRunner.ScenarioBody body)
      Runs ticks fixed-timestep sim ticks against timer, writing everything PsiKit logs to rlogPath.
      Parameters:
      opModeName - recorded as PsiKit metadata ("OpMode"), shown in AdvantageScope
      config - the robot's sim config for this scenario; recorded as metadata only (see class javadoc) — this class does not read a robot's motors/plants from it
      timer - the deterministic clock (domain R5) this scenario runs on; the caller constructs it (and must wire any sim-side infra to it, e.g. HeadlessRobotInitInfra) BEFORE calling this method, since that wiring has to happen before the robot under test is built
      ticks - number of sim ticks to run
      deltaTimeSeconds - fixed per-tick time step (sim-harness §3); timer advances by this once per tick, before body.tick(deltaTimeSeconds) runs
      rlogPath - caller-chosen output file, e.g. Paths.get("build", "sim", "run1.rlog")
      body - the scenario's per-tick behavior (see ScenarioRunner.ScenarioBody)
      Throws:
      IllegalStateException - if the RLOG this run produced cannot be trusted, for any of four reasons: PsiKit's async writer queue overflowed (BACKLOG B3); the file is missing or empty (BACKLOG B11); it decodes to a different number of frames than ticks were run (BACKLOG B32); or the writer thread stopped making progress while the producer waited for it. All four are the same statement - the log is not a record of the run - and none of them is affected by what the caller's own trajectory checks found