Orbit-grid reconstruction

Concept

Several measurement paths do not read the orbit path from the log — they rebuild it, by re-running the static orbit geometry on a grid of N steps and then interpolating the logged progress marker onto it. The grid’s size is the whole ballgame: size it right and the reconstruction is exact; size it from the wrong quantity and every downstream time reads low, silently.

What this spans

Any measurement that reconstructs a run-time object from a log’s length rather than from the run’s configuration. Orbit-synced termination decoupled those two, so the shortcut that was exact for years is now a defect.

Constituent pages

  • orbit — the orbit path geometry itself (s_end, spacing), the thing being reconstructed

Mechanism (where it lives in code)

  1. validation/run_mission.py:169run_statics(d, robot, suppress, n_steps, freeze_floor=None) rebuilds the static orbit geometry on an n_steps grid and returns dt, s_end, spacing.

  2. When a log carries no legacy scalars, the mission cap defaults to n_steps * dt, and the grid is sized N = cap/dt.

  3. validation/investigate_mismatched_duration.py:44decompose() now sizes the grid from the config, not the log:

    n_grid = max(len(prog), int(round(float(tcfg.duration) / float(tcfg.dt))))
  4. validation/investigate_mismatched_duration.py:49 — a permanent assert catches any future mis-scaling:

    assert prog.max() <= n_path + 2, "progress exceeds the rebuilt orbit grid -- statics mismatch"

Note n_path (s_end / spacing, line 48) is a different quantity from n_grid — the assert checks the progress marker against the rebuilt path length, not against the grid it was built on.

Evidence

Before orbit-sync, len(prog) and cap/dt were the same number: a fixed-duration run logged exactly as many samples as its cap allowed (CHAIN_13: 1800 s / 0.03 s = 60,000 steps = 60,000 samples). Since the B1↔B2 merge, missions end at orbit completion, so the log is shorter than the cap. The rebuilt grid then came out under-sized, its spacing too coarse, and find_t_complete crossed the mis-scaled threshold early — rung 3 printed t_c = 427 s on a mission that demonstrably ran 702.6 s.

Fixed-duration logs are unaffected: their len(prog) dominates the max().

The falsifier is what caught it

Nothing in the numbers looked wrong — 427 s is a perfectly plausible mission time. It was caught only because the lab report had pre-registered a stop rule: t_c < T_kin is definitionally impossible (a mission cannot complete the orbit faster than the orbit’s own kinematic duration, T_kin = 689.69 s). The pre-registered falsifier fired before any conclusion shipped. This is the stop-rule discipline paying rent, not a lucky catch.

Footguns

Any grid rebuilt from a log's LENGTH is now suspect

Event-based termination decoupled log length from configured duration. Every run_statics(..., len(...)) call site must be re-checked before its output is trusted on a post-orbit-sync log. Size the grid from the config (simulation.time.duration / dt), never from len(prog) alone.

The failure is silent and one-directional

An under-sized grid has too-coarse spacing, so the progress marker crosses the completion threshold early. The error always reads as a mission that finished sooner than it did — a direction that flatters the result, which is exactly the direction least likely to prompt a second look.

Equations & references

Mission-clock accounting and the kinematic orbit duration T_kin: see the error-floor investigation’s mismatched-duration lab report. Source note: notes/orbit_grid_measurement_footgun.md.

orbit · error_floor · orbit_com_path · data_analyzer