feat(swing-sim): land the flight slice of #4466 with its React parity counterpart - #4518
Merged
Conversation
Second slice of #4466, after the ground module. Adds 21 `swing_sim/flight` modules — capability observation and evaluator, ground transfer, ground bounce execution, the regional ground pipeline, surface simulation, the spin-axis convention and cancellation — plus the React files that share their contract. The Python and TypeScript sides ship together because the spin-axis change is a parity contract, not an implementation detail. `spin_axis_tilt` becomes `positive_right` with `atan2(-omega_y,omega_z)` in both `result_catalog_data.py` and `ballFlightMetricContract.ts`; `deliveryDiagnostics` drops its inlined formula for the shared `spinAxisTiltDeg` helper; and the golden fixture is regenerated for the new sign. Landing one side alone would have left the fixture disagreeing with one of its two consumers. Repairs a real defect in `web/src/model/flight.ts` found by the new evaluator tests. The ground-crossing guard tested only `nextPos[2] < 0`. A descending launch starts at height 0, so the crossing is skipped on the first step by `t > dt`; on the next step the previous point is already below ground, the interpolation fraction goes negative, and `tGround` lands before zero. The metric contract rejects a negative time outright, so a descending launch surfaced as `RangeError: timeS must be nonnegative` rather than the nonconverged result it is. Requiring the previous point to be above ground means no crossing is recorded and the evaluator classifies it correctly. `tests/test_wind.py` deliberately keeps main's 1e-9 parity tolerance. The source branch still carries 1e-12, which fails on Linux for the reasons in #4513 — taking the branch file wholesale would have silently reverted that fix. Symbol-level comparison does not catch this, because the change is inside a function body. Verified: 1,163 swing_sim tests pass; the full React suite passes at 132 files / 1,095 tests; `tsc --noEmit` and `eslint` clean; scoped ruff and ruff format clean. `mypy --follow-imports=skip` crashes identically with and without this change on main's own flight package, so it is not a regression here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
dieterolson
enabled auto-merge (squash)
August 17, 2026 06:45
Both sides added a 1.17.14 SPEC change-log row. Kept both and renumbered this branch's entry to 1.17.15. main's row lands the two-tier Python floor contract; this slice is root-package code under src/shared/python, so the 3.11 floor applies unchanged and tests/test_python_version_contract.py passes (9 tests).
…line
CI's `Type Check (Mypy)` step reported one error:
regional_ground_pipeline.py:147:5: error: Returning Any from function
declared to return "float" [no-any-return]
`BounceModelSettings` comes from a module that `--follow-imports=skip` does not
load, so its attribute types degrade to `Any` and the value flowed out of a
`-> float` function. Converted explicitly; the settings object has already
validated the number, so this is for the type checker, not the value.
Worth recording why this was not caught before pushing: mypy 1.13.0 on Python
3.13 crashes with `AssertionError: Internal error: unresolved placeholder type
None` when given this file set, so the local run aborted instead of reporting.
CI runs the same mypy on Python 3.12, where it type-checks normally. The file
now passes locally in isolation, which is as close as this interpreter gets.
200 flight tests pass; scoped ruff and ruff format clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…support Two CI failures on the flight slice, both invisible locally. `tests (3.11)` failed one test with `ImportError: installed tools_core wheel lacks tee-aware full-state flight; rebuild it`. Rust capability comes in two tiers and the module-level guard only covers the lower one: a wheel can expose `simulate_trajectory` — so `is_rust_available()` is true and nothing skips — while still lacking the tee-aware full-state API, at which point the facade deliberately raises rather than silently inventing tee geometry. CI has exactly such a wheel; this machine has no wheel at all, so the whole module skipped and the gap never showed. The tee test now carries its own `skipif` on the full-state capability, and the module docstring states both tiers. `quality-gate`'s Changed Test Assertion Check flagged `flight/tests/_regional_ground_pipeline_support.py` — a shared request/result constructor with no assertions by design, the same shape as `ground/tests/_support.py`. Added to `scripts/test_assertion_allowlist.txt` beside it. Verified: 6 parity tests pass (5 run, tee one skips without a full-state wheel), `scripts/check_test_assertions.py` passes on this branch's changed files, ruff and ruff format clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
) Third slice, after ground (#4517) and flight (#4518). Adds six club modules — assembly binding and its atomic file I/O, engineering sidecar, simulation adapter, STL export — and splits the plot catalog into `_catalog_entries`, `_catalog_scalar_entries`, `_catalog_series_entries` and `_catalog_entry_types`. The catalog split was verified entry-for-entry: 78 identifiers before, 78 after, none dropped. A catalog is data, so a lost entry would not appear in a public-symbol comparison — counting them was the only way to know the refactor was faithful. `plotting/render.py` and `plotting/spec.py` keep main's versions. The source branch predates the plot point-inspector and series-selection work already on main, so its copies are a revert, not an update: taking them failed 13 tests (`assert None is not None`, rendered-pixel counts far below threshold). Restoring main's two files dropped that to zero. Makes `rate_of_closure.club` lazily export `assembly_binding`, `engineering_sidecar` and `simulation_adapter`. All three reach `shared.python.golf_club`, which transitively pulls `swing_sim.variation -> solver -> flight -> scipy.integrate`. Importing them at `__init__` scope meant `rate_of_closure.club.types` — a leaf module of frozen club specs — dragged SciPy in, because Python runs the package `__init__` first. That broke `test_ui_contract_submodules_import_without_optional_servers_ or_scipy`, which requires the Morris UI contract to import with scipy, fastapi and uvicorn blocked. The lazy map follows the shape `swing_sim.ground.__init__` already uses; all 23 lazy names and all 45 `__all__` entries still resolve. Verified: 2,538 tests pass across `tests/rate_of_closure` and `src/shared/python/swing_sim`. The two remaining failures are pre-existing and Windows-only, both confirmed against a clean tree — `test_text_reader_normalizes_decoder_resource_errors` (reproduces with these changes stashed, passes on Linux CI) and `test_new_plot_data_clears_selection` (a 15 s Qt budget that measures 13.7 s on this box, passes in isolation). Scoped ruff and ruff format clean. Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
Fourth slice, after ground (#4517), flight (#4518) and club/plotting (#4519). Adds 20 modules: capability observation, Morris host/child adapters, regional ground variation and its control surface, scalar ensemble contract/IO/wire, and the wind-strategy plot adapter. Net-new files only. For the 16 files the branch also modifies, main is a superset and the branch copies are much older — `ensemble_chunks.py` -360, `_ensemble_parser.py` -340, `plot_definition.py` -312, `confidence_ellipsoid_mesh.py` -296. Symbol comparison confirms the direction: `from_json_dict`, `read_plot_definition`, `build_dispersion_metric_variability` and `apply_global_simulation_values` exist only on main. Taking those files would have reverted shipped work, which is the same trap that `plotting/render.py` sprang in #4519 and `test_wind.py` in #4518. `simulation_adapter.py` genuinely diverges — `run_simulation_ensemble_chunks` on main against `_TRIAL_FAILURES` on the branch — so it stays at main's version pending its own reconciliation rather than being guessed at here. Two `no-any-return` findings from the changed-file MyPy gate are fixed at the boundary where `--follow-imports=skip` degrades imported types to `Any`: `capability_observation_ensemble_json` converts explicitly, and `_spin_axis` unpacks the three components instead of returning the attribute, which also pins the arity its annotation promises. Verified: 2,632 tests pass across `tests/rate_of_closure` and `src/shared/python/swing_sim`. Both changed files type-check clean; ruff and ruff format clean across the package. The remaining local failures are the known Windows-only ones already confirmed against a clean tree. Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
Fifth slice. Adds `ground_playback`, `ground_playback_comparison` and the three `ground_playback_workspace` modules under `src/rate_of_closure/simulation/`. This one was genuinely blocked, not merely unscheduled: the modules import `swing_sim.ground` (#4517), `swing_sim.flight` (#4518) and `rate_of_closure.club.{assembly_binding,simulation_adapter}` (#4519). With all three on `main` they now import cleanly — verified by importing each of the five directly, not only by the suite passing. Net-new files only, as with every slice. Of the six files the branch also modifies, `sources.py` is the only branch-superset (`world_from_selected_head`) and the new modules do not reference it, so every modified file stays at main's version rather than being taken on the strength of one symbol. One `no-any-return` finding from the changed-file MyPy gate is fixed where `--follow-imports=skip` degrades `GroundSimulationResult` to `Any`: `ground_result_json` converts explicitly, its exact-type precondition already guaranteeing the runtime value. Verified: 2,635 tests pass across `tests/rate_of_closure` and `src/shared/python/swing_sim`; all five new modules import; the Morris UI contract still imports with scipy, fastapi and uvicorn blocked — the regression #4519 had to fix in `club/__init__.py` — and the changed-test assertion gate passes. Scoped ruff, ruff format and per-file mypy clean. Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
dieterolson
added a commit
that referenced
this pull request
Aug 18, 2026
(#4530) * feat(rate-of-closure): land the first React web/src/model slice of #4466 101 files: capability observation and result export, club assembly binding and engineering sidecar wire, ground playback workspace, regional-ground variation request/workspace/target projection, scalar ensemble contract, wind-strategy plot data, and their tests. The React tree cannot be taken wholesale, and the measurement is worth recording because it is the opposite of what the earlier slices found. This tree has diverged in *both* directions: - 129 files exist on main that the branch lacks entirely — the Morris component chain (`MorrisWorkflowPanel`, `MorrisResults`, `MorrisFactorEditor`, `MorrisWorkspaceActions`), `LaunchMonitorLinkedScatter`, `morrisAuthorityProxy`, and 15 Playwright specs, all landed by #4473. This is the React mirror of the `application/morris/**` situation in #4524. - 125 of the 270 modified `model` files delete content on the branch side. The worst, `morrisGlobalSensitivityContract.ts`, is +0/-355 — a strict subset of main. I first copied 324 modified files in bulk and then reset it: against a tree where main demonstrably owns 129 files the branch never saw, that was not a safe operation, and shipping it would have reverted exactly the kind of work this whole series has been protecting. What lands instead is the provably safe subset: files whose main->branch diff deletes nothing, then an iterative drop of added files whose dependencies need a main-newer module. That converged in five rounds. `flight.ts` and `wind.test.ts` are explicitly held at main's version so the #4518 ground-crossing guard and the 1e-9 parity tolerance (#4513) survive — verified after the fact, not assumed. 29 added files are deferred. They are not broken; they need their main-side counterparts, and a later slice can carry both together. Verified: `tsc --noEmit` clean, `eslint` clean, 1,324 React tests passing across 157 files, production Vite build succeeds. No Python or Rust changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(spec): renumber the web model slice row to 1.17.29 Another agent landed 1.17.28 on main while this branch was open. Renumbered rather than leaving a duplicate version in the change log. --------- Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 18, 2026
Merged
dieterolson
added a commit
that referenced
this pull request
Aug 18, 2026
…mbol ports (#4541) Adds 23 net-new files: the regional-ground execution job/result/presentation and their file IO, the authority client, job-preparation and variation request wires, the variation workspace, three regional-ground hooks, the imported-job panel, club STL export, club engineering sidecar, and flight preparation launch. Two of the ten symbols blocking the previous slice are ported additively onto main's versions rather than by taking the branch's files, which delete content: - mesh.writeBinaryStl with binaryHeader and two byte constants; main already had BINARY_HEADER_BYTES, BINARY_RECORD_BYTES, triangleNormals. - the two regional-ground variation keys, checked byte-for-byte against the already-landed regional_ground_variation_dataset.py. Three candidate ports were investigated and deliberately not made. The capabilityOptimizer re-exports are dead here -- the only consumer imports straight from capabilityObservationContract. drawGroundPlayback is built on branch-local helper signatures whose arities differ from main's, so porting it means reworking main's renderer to unblock two files. And flight's angular-state trio would require either duplicating the integrator or replacing the one carrying #4518's ground-crossing guard. PrimaryViewTabs.test.tsx and TorqueProfilePanel.test.tsx are both restored to main's versions: each was purely additive by line count yet failed against main's unchanged component. The second was caught only because the prune guard now refuses to delete files present on origin/main. Verified: tsc clean, eslint clean, 1,420 tests across 175 files, production build succeeds, zero deletions in the staged diff. Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
dieterolson
added a commit
that referenced
this pull request
Aug 18, 2026
The last and hardest of the ten symbols blocking the React migration. flight.ts gains AngularFlightPoint, FlightSimulationOptions and simulateFlightWithOptions; FlightResult.trajectory widens to AngularFlightPoint[]; and the in-file RK4 loop moves into a new flightIntegrator.ts that both entry points delegate to, so there is one integrator rather than two that can drift. This was deferred twice because main's loop carries #4518's ground-crossing guard and a naive swap would revert it. It does not: the integrator's contact test requires currentGap > 0 strictly, so a launch starting at ground level yields a gap of zero and never records a crossing -- structurally the same protection && t > dt provides. That is not argued from the code alone. All 1,420 tests on main pass unchanged with the integrator swapped in, including wind.test.ts and the ball_flight_metrics_golden_v1 and inverse_flight_solver_golden_v1 Python-parity fixtures. The integrator adds an upfront MAX_FLIGHT_INTEGRATION_STEPS = 50_000 bound keeping synchronous UI-thread RK4 work finite; the default 10s at a 1ms step is 10,000 steps, so no existing caller changes behaviour. Unlocks flightGroundTransfer and simulationTypes, which need the angular state at landing; their 15 tests land with them. Verified: tsc clean, eslint clean, 1,435 tests across 176 files, production build succeeds. Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second slice of #4466, after
swing_sim.ground(#4517).Adds 21
swing_sim/flightmodules — capability observation and evaluator, ground transfer, ground bounce execution, the regional ground pipeline, surface simulation, spin-axis convention, cancellation — plus the React files that share their contract.Why Python and React ship together here
The spin-axis change is a parity contract, not an implementation detail.
spin_axis_tiltbecomespositive_rightwithatan2(-omega_y,omega_z)in bothresult_catalog_data.pyandballFlightMetricContract.ts;deliveryDiagnosticsdrops its inlined formula for the sharedspinAxisTiltDeghelper; andball_flight_metrics_golden_v1.jsonis regenerated for the new sign.Landing one side alone would leave the golden fixture disagreeing with one of its two consumers — which is exactly what these fixtures exist to prevent. I caught this by installing the web deps and running vitest rather than trusting the Python suite alone.
A real defect found by the new tests
web/src/model/flight.tsguarded the ground-crossing interpolation with onlynextPos[2] < 0. A descending launch starts at height 0, so the crossing is skipped on the first step byt > dt; on the next step the previous point is already below ground, the interpolation fraction goes negative, andtGroundlands before zero. The metric contract rejects a negative time, so a descending launch surfaced as:…instead of the nonconverged result it is. The guard now also requires the previous point to be above ground, so no crossing is recorded and the evaluator classifies it correctly.
A trap deliberately avoided
flight/tests/test_wind.pykeeps main's 1e-9 parity tolerance. The source branch still carries 1e-12, which fails on Linux for the reasons in #4513 — taking the branch's file wholesale would have silently reverted that fix. Symbol-level comparison does not catch it, because the change lives inside a function body.Likewise
flight.tswas not taken wholesale: the branch's copy is +31/−107 against main, i.e. a net reduction, so it would have reverted newer work. Only the targeted guard fix is applied.Verification
swing_simtests passtsc --noEmitandeslintcleanruff check/ruff formatcleanmypy --follow-imports=skipcrashes identically with and without this change on main's own flight package — pre-existing, not a regressionPart of epic #4103.
🤖 Generated with Claude Code