Rate of Closure remainder: club builder, impact tensor, flight, multi-view workspace, ground and web companion (consolidates 43 PRs) - #4466
Open
dieterolson wants to merge 619 commits into
Conversation
added 30 commits
August 11, 2026 01:36
…rors My first local run passed because the verification venv had no PyQt6 or matplotlib, so mypy resolved those imports to Any under --ignore-missing-imports. CI has them installed and infers precise types, which inverted several diagnostics. The venv now carries CI's dependency set, and the local run reproduces CI exactly (23 -> 0). * Casts around Qt getters are replaced by annotated locals, which are correct whether the stubs return Any or a precise type, so this cannot flip again. * Keyword-splat dicts (course_scene style, plot_wizard common) are declared dict[str, Any] instead of being inferred as dict[str, object]. * One shared attribute had two incompatible declarations after the mixin union: the file-commands mixin declared _navigation_settings as QSettings while the navigation mixin declared the NavigationSettings protocol. Both mixins, the compositor and SimulationTab now use the protocol, which is all any of them needs (value/setValue) and which QSettings satisfies structurally. 144 PyQt tests over the affected surfaces pass.
dieterolson
disabled auto-merge
August 14, 2026 10:11
dieterolson
enabled auto-merge (squash)
August 14, 2026 10:11
added 5 commits
August 14, 2026 12:59
…solidation Three conflicts, all in the Bolt/Palette surface #4438 just landed: * motion_tabs.py — the two branches extracted *different* things from the same module: #4438 pulled the view code into motion_view.MotionViewMixin, while this branch pulled the palette and geometry helpers into motion_helpers. Kept both. The file is now based on main's post-extraction version, imports build_motion_colors/chain_path_length from motion_helpers instead of redefining them inline, and drops the rendering import that became unused. 1093 -> 1074 lines, so no duplicated helper and no module-budget pressure. * torque_utils.py and test_sidekick_ux_hardening.py — resolved to main's versions. The two torque implementations are behaviourally identical (both evaluate ascending coefficients; the merged pendulum test pins 2 + 3t), so main's Bolt form wins rather than re-imposing this branch's TorquePolynomial call. shared/python/swing_sim/torque_profiles still ships and is still consumed by the rate-of-closure torque workspace features. test_action_audit keeps timezone.utc with the noqa anchored on its own line, and the anchored grep for a top-level datetime UTC import is empty.
rate_of_closure.web_companion.runtime and web_authority.child both do a plain module-level 'import uvicorn', and tests/rate_of_closure imports them, so the whole pytest lane aborts at collection without it. uvicorn was declared only in pyproject's optional rate-of-closure-web extra, which CI does not install; CI installs from requirements.txt. Same class as the filelock gap, and the same failure that just took #4447 red. Found by walking the import graph from tests/ through first-party modules and diffing the unguarded external roots against requirements.txt plus the explicit pip lines in ci-standard.yml. The only other new-in-this-branch hits were starlette (a fastapi dependency CI installs) and mpl_toolkits (part of matplotlib), both already satisfied.
…e SPEC row The doc-union strategy that kept every family's SPEC and handoff content also concatenated every family's handoff narrative, leaving AGENT_HANDOFF.md at 4872 lines and src/rate_of_closure/AGENT_HANDOFF.md at 5091 — the exact opposite of the documented contract for those two files (current-state only, <=150 lines, history in git). Rewritten from the consolidated tree's real state at 85 and 101 lines, keeping what a next agent actually needs: the cross-client module-registry contract, the regional-ground plan ownership, the zero-size-canvas rule, the derived release-evidence chain, and the packaging rule that a module-level import in a collected test must be declared in requirements.txt. docs/development/RATE_OF_CLOSURE_CAMPAIGN_HANDOFF.md is deliberately left long: it is a campaign narrative referenced as evidence by the campaign manifest, not one of the two policy-governed handoff files. Adds the dated SPEC 1.14.96 change-log row this consolidation owes under the spec-check contract, and moves Last Spec Update to 2026-08-14 (it still read 2026-08-12 while newer rows had landed).
… contract test Merge conflicts: * .github/workflows/cross-repo-python-integration.yml and its contract test — resolved to main's versions, deliberately NOT unioned. #4480 narrowed the UpstreamDrift sparse scope to `src/shared` and deleted the pinned-submodule step, with a comment explaining that UpstreamDrift moved its consumed packages under src/shared/python and that `pip install -e .` resolves through hatchling's `packages = ["src"]`. Its test asserts set *equality* (`actual == REQUIRED_SPARSE_PATHS`), so re-adding this branch's wider scope or the submodule-init step would fail the very test #4480 fixed. The wide scope is the superseded side, not content worth preserving; nothing else references the removed step. The workflow is now byte-identical to main, keeping #4475's CI_RUNNER_MODE fallback and public-repo hosted routing. * AGENT_HANDOFF.md — kept this branch's policy-compliant rewrite and folded in main's new fact (#4406 drift-transfer diagnostics under UpstreamDrift epic #8551), 89 lines. * SPEC.md — unioned, newest first. canonical numeric JSON: the guard and the test contradicted each other and both arrived from this branch's own fold. Resolved in favour of the guard, and the test rewritten to assert the narrowed contract. Evidence that the strict default is the intended contract, not an accident: * The guard appears in three coordinated places — the float path, the integer path, and a named opt-out (`canonical_numeric_json_extended_floats`) whose docstring says "beyond the safe range while retaining safe integers". * A dedicated facade, src/rate_of_closure/variation/canonical_numeric_json.py, re-exports the extended encoder under the plain name for the one caller that needs it (capability-observation payloads carrying declared 1e20/1e21 magnitudes). That is how a contract gets narrowed while an established caller keeps working. * The strict encoder's only production consumer is regional_ground_authority_status.py — the browser-facing authority job-status wire, where a value above 2**53-1 would reach the browser as a different number. * The old test's own second case already asserted the +/-(2**53-1) boundary, i.e. the file half-agreed with the guard. The failing case was a pre-guard regression guard ("preserves established... domain") that the narrowing superseded. The default guard is untouched: loosening it would make every cross-runtime payload silently accept values JavaScript cannot represent, which is a contract that cannot fail. The test now asserts (a) the default path fails closed for out-of-range floats, (b) the opt-in path still serializes that domain exactly with no exponent notation, and (c) the opt-in path still refuses unsafe integers. canonical_numeric_float stays strict: its production consumers (ground contract_types, ground_playback_comparison) carry physical magnitudes far below the limit, so an unused opt-out would be speculative. Not fixed here: test_wind.py's abs=1e-12 cross-client tolerance is #4482's (~1600 ULP of accumulation under a different libm, not one rounding).
This was referenced Aug 15, 2026
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
…ate (#4514) Both documents had drifted far enough to actively misdirect the next agent, and the root document had also outgrown its own policy by 18x. CLAUDE.md requires handoff docs to be current-state only and at most 150 lines, with history in git. The root doc had instead accumulated 137 dated entries across 2,708 lines. Those entries are moved verbatim to docs/agent_handoff_archive/2026-08_tools_root_handoff_log.md, not deleted — the archived body is byte-identical to the original (165,356 chars, 137 entries), so no working-tree context is lost. The live doc is now 132 lines and carries an explicit do-not against appending dated entries again. Content corrections. The root doc presented PR #4119 as open with auto-merge armed and #4124/#4129 as open drafts stacked on it; in fact #4119 closed without merging and #4124/#4129 both merged. Epics #4142 and #4433 were absent entirely. Delivery has shifted from stacked PRs to scoped consolidations rebuilt onto current main. It now records the seven live epics, the four open consolidations (#4446, #4447, #4449, #4466) as the real queue, and the fact that 39 of 58 open PRs are codex/4142-* / codex/4433-* drafts superseded by merged #4473 — whose own description says so. Those should be triaged and closed, not rebased. The pendulum doc described issue #4406 as active on branch research/shoulder-velocity-drift-transfer. #4406 is closed and shipped via consolidation #4450. It now records what landed, keeps the fail-closed triple/golfer tier boundary as an explicit do-not, and points at the UpstreamDrift #8684 qualification state — including the finite-ground result where the preregistered screen admitted 0 of 384 cells, so a moving base is not a free upgrade for the higher tiers. Also recorded: the four pre-existing ruff format failures under src/data_processing/ that are on main and should not be absorbed into an unrelated PR, and the --regenerate-api-baseline trap. Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
dieterolson
pushed a commit
that referenced
this pull request
Aug 17, 2026
…e-base wall Repo policy is to update the handoff with every PR, and two things changed since #4514 landed. The 39 codex/4142-* / codex/4433-* drafts are now closed as superseded by merged #4473, which says so in its own description. Open PRs went 58 -> 21 and drafts 39 -> 0. Their work is verifiably on main: the Morris chain as application/morris/{host,client,contracts}.py, the #4433 chain as the flight, putting, plot, visual-state and tab-audit modules. Their branches are left intact, so the guidance is now reopen-rather-than-rebase — those branches have diverged far enough that merging one would remove current main content. #4466 turns out not to be mergeable at all. After #4473 squash-merged, the shared merge-base collapses to a commit predating src/rate_of_closure/, making every overlapping file a both-added conflict with no common ancestor (281 of them). #4517 is landing it as standalone slices instead, starting with swing_sim.ground. The other consolidations should expect the same wall, so the roadmap now says slice rather than merge. Also added a do-not against per-file Python version guards, pointing at the conftest floor guard that replaces them. Handoff remains within the 150-line policy at 144. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
…one slice of #4466 (#4517) * feat(swing-sim): land the ground skid/roll/bounce module as a standalone slice `consolidated/rate-closure-remainder-2026-08-13` (#4466) cannot be merged: after #4473 squash-merged, every overlapping file is a both-added conflict with no common ancestor, and neither resolution direction yields a working tree (`-X theirs` → 47 failed / 40 errors; `-X ours` → 19 collection errors). Its unique work is separable, so it lands feature-first instead. `swing_sim.ground` is the cleanest slice. Its three dependencies outside the package — `flight/result_metrics`, `solver/spatial_targets`, `solver/target_serialization` — are already byte-identical on `main`, so this adds the 92-file module, `swing_sim/canonical_numeric_json.py`, and the ten shared Python/TypeScript ground golden fixtures, and changes nothing else. Six ground tests fail on the source branch itself, verified by running them there unmerged — #4466 has armed auto-merge and could never have gone green: - the four `test_skid_roll_passivity` cases build `SurfaceRun` without `active_surface`, which arrived with regional-surface support. Every one raised `TypeError` in its fixture, so none had ever asserted a passivity property. Fixed to pass `request.surface`, matching `simulate_skid_roll`. - `test_bounce_cancellation_is_typed_and_retains_request_identity` hardcoded the request's separation time and a zero elapsed span. `RepeatedBounceResult` requires the termination to match the final trajectory point and the elapsed span to match bounce chronology, so it raised before reaching the assertions it exists for. Both are now derived from the prefix. - `ground/__init__.py` eagerly imported `to_ground_model_result`, publishing the explicitly unqualified compatibility adapter on the package despite its deliberate absence from `__all__` and the lazy-import map — exactly what `test_unqualified_compatibility_adapter_is_not_public` guards. The name is unused inside `__init__.py`; the import is dropped. 321 ground tests pass. Scoped ruff, ruff format and mypy clean; the tools manifest layout check passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(swing-sim): declare jsonschema and allowlist the ground test support module Two CI-only failures on the ground slice, neither reproducible on the local Python 3.13 run that reported 321 passing. `tests (3.11)` aborted during collection: `ModuleNotFoundError: No module named 'jsonschema'`. `test_profile_schema.py` and `test_schema_migration.py` validate the ground profile schema with `Draft202012Validator`. It is a test-only dependency — no `swing_sim` source imports it — and it happened to be installed locally, which is why the gap only showed in CI. Declared in `requirements.txt` rather than wrapped in `pytest.importorskip`: skipping would leave the profile JSON Schema contract unguarded in CI, which is the opposite of what these tests are for. `quality-gate`'s Changed Test Assertion Check flagged `ground/tests/_support.py` as a changed test file with no behavioral assertion. That is correct and intended — it builds shared requests and results, and every assertion about what it produces lives in the modules that consume it. Added to `scripts/test_assertion_allowlist.txt`, the mechanism the check names in its own failure message, next to the existing `swing_sim` entries. Verified locally: `scripts/check_test_assertions.py` passes against this branch's changed-file list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
dieterolson
pushed a commit
that referenced
this pull request
Aug 17, 2026
…e-base wall Repo policy is to update the handoff with every PR, and two things changed since #4514 landed. The 39 codex/4142-* / codex/4433-* drafts are now closed as superseded by merged #4473, which says so in its own description. Open PRs went 58 -> 21 and drafts 39 -> 0. Their work is verifiably on main: the Morris chain as application/morris/{host,client,contracts}.py, the #4433 chain as the flight, putting, plot, visual-state and tab-audit modules. Their branches are left intact, so the guidance is now reopen-rather-than-rebase — those branches have diverged far enough that merging one would remove current main content. #4466 turns out not to be mergeable at all. After #4473 squash-merged, the shared merge-base collapses to a commit predating src/rate_of_closure/, making every overlapping file a both-added conflict with no common ancestor (281 of them). #4517 is landing it as standalone slices instead, starting with swing_sim.ground. The other consolidations should expect the same wall, so the roadmap now says slice rather than merge. Also added a do-not against per-file Python version guards, pointing at the conftest floor guard that replaces them. Handoff remains within the 150-line policy at 144. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dieterolson
pushed a commit
that referenced
this pull request
Aug 17, 2026
…e-base wall Repo policy is to update the handoff with every PR, and two things changed since #4514 landed. The 39 codex/4142-* / codex/4433-* drafts are now closed as superseded by merged #4473, which says so in its own description. Open PRs went 58 -> 21 and drafts 39 -> 0. Their work is verifiably on main: the Morris chain as application/morris/{host,client,contracts}.py, the #4433 chain as the flight, putting, plot, visual-state and tab-audit modules. Their branches are left intact, so the guidance is now reopen-rather-than-rebase — those branches have diverged far enough that merging one would remove current main content. #4466 turns out not to be mergeable at all. After #4473 squash-merged, the shared merge-base collapses to a commit predating src/rate_of_closure/, making every overlapping file a both-added conflict with no common ancestor (281 of them). #4517 is landing it as standalone slices instead, starting with swing_sim.ground. The other consolidations should expect the same wall, so the roadmap now says slice rather than merge. Also added a do-not against per-file Python version guards, pointing at the conftest floor guard that replaces them. Handoff remains within the 150-line policy at 144. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
… at It (#4516) * fix(ci, tests): enforce the two-tier Python floor instead of guessing at it The `tests (3.10)` lane was running code that requires 3.11, which produced failures that looked like defects and were not. This repo is deliberately two-tier. The root distribution declares requires-python = ">=3.11" (classifiers 3.11/3.12, mypy python_version 3.11), while ten sub-packages and Rust crates declare >=3.10 and ship 3.10 wheels from six maturin workflows. The 3.10 matrix lane exists to validate those sub-packages. But it ran the whole suite, including root-package code such as src/p1am_control_system and src/shared/python. Two failures came from exactly that mismatch: - test_deployment_hardening.py imported tomllib, stdlib only on 3.11+, aborting collection for the entire session (1,218 collected, zero executed). - test_estop_shutdown_safe_state.py timed out because asyncio.wait_for was reimplemented on asyncio.timeouts in 3.11 with different cancellation propagation. Neither is a 3.10 support bug. Both are 3.11-only code being run on 3.10. conftest.py now resolves the nearest pyproject.toml for any collection path, reads its declared requires-python floor, and skips collection when the running interpreter is below it. The floor is read from the declarations rather than hardcoded, so adding a sub-package or moving a floor needs no edit here. requires-python is regex-parsed on purpose: tomllib does not exist on the very interpreter the guard has to run on. It fails closed at (3, 11) when nothing parses. Verified as a strict no-op on 3.11+ — collection is identical at 10,382/10,414 with and without the guard. CLAUDE.md advertised a flat "Python 3.10+" that the root distribution rejects. It now states the real contract, names the ten 3.10 distributions, explains what the 3.10 lane is for, and says not to add per-file version guards because the conftest handles it. tests/test_python_version_contract.py locks the declarations together: requires-python against the mypy target and the classifiers, the CI matrix against the lowest declared floor, CLAUDE.md against the root floor, and a regression guard proving root-package tests are excluded below the floor while 3.10 sub-packages are still collected. The interpreter version is faked so those guards run on every lane, not only the one that cannot execute them. This supersedes the separate investigation into the e-stop shutdown timeout: that test should not run on 3.10 at all, and no shutdown-safety code needed to change. The pytest.importorskip added to test_deployment_hardening.py in #4515 is now redundant but retained deliberately as defense in depth — if this guard ever regresses, a bare import there takes down the whole session again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(agent-handoff): record the closed draft chain and the #4466 merge-base wall Repo policy is to update the handoff with every PR, and two things changed since #4514 landed. The 39 codex/4142-* / codex/4433-* drafts are now closed as superseded by merged #4473, which says so in its own description. Open PRs went 58 -> 21 and drafts 39 -> 0. Their work is verifiably on main: the Morris chain as application/morris/{host,client,contracts}.py, the #4433 chain as the flight, putting, plot, visual-state and tab-audit modules. Their branches are left intact, so the guidance is now reopen-rather-than-rebase — those branches have diverged far enough that merging one would remove current main content. #4466 turns out not to be mergeable at all. After #4473 squash-merged, the shared merge-base collapses to a commit predating src/rate_of_closure/, making every overlapping file a both-added conflict with no common ancestor (281 of them). #4517 is landing it as standalone slices instead, starting with swing_sim.ground. The other consolidations should expect the same wall, so the roadmap now says slice rather than merge. Also added a do-not against per-file Python version guards, pointing at the conftest floor guard that replaces them. Handoff remains within the 150-line policy at 144. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tests): check each sub-package at its own floor, not the global minimum The 3.11 lane failed on the previous commit with "src/movement_optimizer declares 3.10 support but the guard would exclude it from the 3.10 lane". The guard was not the problem; the test was wrong by construction. test_sub_package_tests_still_run_on_the_lower_lane faked the interpreter to the *minimum* floor across every lower-floored package, then asserted that every one of them would still be collected. That only holds when they all declare the same floor. A single package declaring lower than the rest makes the assertion fail for packages that are being excluded entirely correctly — a 3.10 package is supposed to be excluded on 3.9. Each package is now checked at its own declared floor, which is the property actually worth guarding and is independent of what anything else declares. Two related changes: _below_declared_floor now takes the interpreter version as an optional parameter instead of the tests patching sys.version_info. That patch was process-global and read by unrelated library code, which under xdist meant a test could perturb workers running beside it. test_conftest_reads_each_package_declared_floor now checks every sub-package rather than two hand-picked ones, and reports all mismatches with their paths. Path resolution is the part most likely to differ across platforms, so a failure should name the package instead of surfacing as a confusing assertion further downstream. Also added test_nested_distributions_do_not_widen_their_parent_tree. The diagnostic turned up a real wrinkle: psa_package declares >=3.10 while living inside src/shared/python, which is root-package code. The guard handles it correctly — the nested distribution gets 3.10 and its parent tree stays at 3.11 — but nothing was pinning that, and a regression there would quietly let the lower lane start collecting root code again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: allow empty collection on lanes below the root Python floor The floor guard works, and that turned out to be the problem for this PR's own 3.10 lane: pytest exited 5, "no tests collected". On a sub-floor lane the guard excludes root-package code, including the top-level tests/ tree. A change that touches only root-package tests therefore has nothing to run there, and pytest treats an empty selection as an error. The lane failed while behaving exactly as designed. The tests step now tolerates exit code 5, but only on lanes below the root requires-python floor, which is read from pyproject.toml rather than hardcoded so it tracks the declaration. On the required lane exit 5 still fails, which is what keeps the issue #3324 vacuous-selection protection meaningful — that guard exists to catch typo'd or broken test selection, and blanket-tolerating exit 5 everywhere would defeat it. Verified before pushing, since this is a CI workflow change and the previous two rounds of surprises here were self-inflicted: - version comparison: 3.9 and 3.10 allow empty collection, 3.11 and 3.12 do not - tolerance: exit 5 returns 0 when allowed, and passes through unchanged when not - the workflow YAML parses and the extracted run block passes bash -n Deliberately avoided a python heredoc inside the indented YAML block; the floor is parsed with grep instead, because heredoc terminators in a YAML block scalar are sensitive to how the block is de-indented. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: drop the 3.10 lane from ci-standard instead of propping it up Replaces the exit-5 tolerance from the previous commit, which was treating a symptom. Two facts settle it. Every core_tests entry is root-package code — all of them are tests/** or src/shared/python/sidekick/tests/** — so once the floor guard correctly excludes root-package code, the 3.10 lane can never run a single core test. And 3.10 compatibility for the ten sub-packages that actually claim it is already proven elsewhere: each crate's maturin-*.yml runs a build + parity gate across 3.10/3.11/3.12 that builds the wheel, installs it, and asserts both that the extension imports and that the native backend is selected. So the lane was not testing 3.10 support. It was running root-package code on an interpreter that cannot execute it, and the only reason it ever looked meaningful was that a collection abort masked the emptiness. Tolerating empty collection would have institutionalised a lane that consumes a runner and reports nothing on every PR. ci-standard now runs ["3.11", "3.12"], starting at the root floor. The exit-5 tolerance is reverted, and the now-dead `!= "3.10"` sidekick guard is removed. Two contract tests replace the weaker matrix check: test_ci_matrix_starts_at_the_root_floor fails if a lane is ever added below the root floor, with an error that points at the maturin workflows as the correct home for lower interpreters. test_lower_floor_packages_keep_a_workflow_that_exercises_them fails if a sub-package declares a floor below the root and no workflow outside ci-standard actually runs that interpreter. This is the guard against the real risk in this change: dropping the lane must not leave a 3.10 claim with nothing behind it. It passes today, which is what makes the removal safe rather than assumed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tests): select sub-package pyprojects by git tracking, not a denylist The 3.11 lane failed on a vendored dependency: .cargo-home/registry/src/.../pyo3-0.24.2/pyo3-runtime/pyproject.toml declares >=3.7 but no workflow outside ci-standard runs that interpreter That file is pyo3's own packaging metadata sitting in the cargo registry cache. It is a third-party artifact, not a claim this repository makes, and the directory does not exist locally — it is materialised only on CI runners, which is why the test passed here and failed there. The helper was enumerating pyproject.toml by rglob and filtering with a directory denylist (.venv, node_modules, target, build, dist, .git). A denylist is unbounded: every new tool cache is another entry nobody adds until it breaks a build. .cargo-home was simply the first one to appear. Selection is now by `git ls-files`, which answers "is this ours?" directly and cannot drift as new caches appear. It returns exactly the eleven real sub-package pyprojects and nothing else. If git is unavailable the tests skip rather than silently degrading to a weaker check. This fixes all four tests that share the helper, not just the one that failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- 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
… counterpart (#4518) * feat(swing-sim): land the flight slice with its React parity counterpart 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> * fix(swing-sim): satisfy the changed-file mypy gate in the ground pipeline 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> * fix(swing-sim): guard the tee parity test and allowlist the pipeline 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> --------- 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
…olicy (#4521) `CLAUDE.md` caps handoff documents at 150 lines and keeps history in git. `src/rate_of_closure/AGENT_HANDOFF.md` had grown to 2,205 lines across 140 dated entries — the same append-only drift the root handoff already recorded and corrected for itself, where its own do-not list now reads "It grew to 2,708 lines that way, 18x over the policy limit." The 140 entries move verbatim to `docs/agent_handoff_archive/2026-08_rate_of_closure_handoff_log.md`, following the existing root-log archive convention and header, so no provenance is lost. The live document is rewritten as 103 lines of current state, weighted toward what actually cost time to rediscover: - the PyQt6 entry point and window title the UpstreamDrift launcher manifest depends on, and where the React mirror and each `swing_sim` package live; - why PR #4466 cannot be merged by any strategy, with the measurement rather than the assertion: `-X theirs` yields 47 failures and 40 errors, `-X ours` yields 19 collection errors, because the post-squash merge-base predates `src/rate_of_closure/` and every overlapping file is a both-added conflict; - the four files where the source branch is *older* than `main`, each of which would have silently reverted shipped work if taken wholesale; - what remains of #4466 by area, including that `application` and `web_authority` import each other and must land together; - local-environment traps: mypy 1.13 crashing on Python 3.13 for multi-file sets, the two-tier `tools_core` capability, and PowerShell rewrites normalising `SPEC.md` to CRLF and turning the next merge into a whole-file conflict. No source or test behaviour changes. SPEC 1.17.18. 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
) 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>
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.
Consolidates the remaining 43 open
src/rate_of_closurePRs into one branch. They alledit the same tree, so separate PRs would conflict with one another and multiply CI by
~43x. Every stack was folded top-down (each child into its own parent, then the parent
upward) so no parent is left a slice behind, and the two non-tip carriers the lead flagged
are explicitly included: #4171 (
8067ca79aCAD export artifact validation, absent fromtip #4172) and #4309 (
f9afa9d96strict ground result playback, absent from tip #4312).bash verify_coverage.sh consolidated/rate-closure-remainder-2026-08-13 <all 43>reportssafe=43 NOT-covered=0— closing every superseded PR drops nothing.What this carries
Correctness findings fixed here (not merge mechanics)
These are defects the merge exposed. Each was measured, not assumed.
from datetime import UTCimports (rate_of_closure/ui/pyqt6/torque_profile_controller.py,tests/unit/sidekick/agent/test_action_audit.py) arrived with feat: Swing–Impact–Ball-Flight Simulation Platform (consolidated — epic #4103) #4119's chain.datetime.UTCdoes not exist on Python 3.10, which the test matrix still runs;
mainhas onlyversion-gated imports. Both reverted to
timezone.utc, and the# noqa: UP017isre-anchored onto the physical line that uses it so a format pass cannot displace it again.
KeyError: 'regional_surfaces', 78tests). The workspace-file family hard-coded a 10-entry PyQt-to-canonical tab map while
the ground families added three tabs.
CANONICAL_MODULE_IDSnow matches the Reactclient's
PRIMARY_VIEWSexactly (12 shared modules); the PyQt-only Ground Study tab isdeclared unshared, excluded from the cross-client document, and restored in place on
import rather than dropped. Python demanded 10 module ids while React validated 12 — the
two clients disagreed, so no workspace file could round-trip between them.
Matplotlib then built a degenerate axes transform and raised
LinAlgError: Singular matrixfromaxvline. The shared canvas now keeps the figure atleast one pixel, which protects every plot in the compositor rather than one call site.
Restored end to end:
SimulationConfigvalidates a qualified binding,SimulationRuncarries
club_assembly_usage, the pipeline feeds the bound head mass and inertia tensorto the impact solver and reports
not_usedon a miss,ManualSwingSourcedeclares itsselected-head attitude, and the TypeScript twin matches.
PyQt
ControlsPaneland therate_of_closure.clubfacade, so feat(rate_of_closure): export selected clubhead STL #4326-feat(rate-of-closure): bind club assembly to simulation #4341's exports wereunreachable (
club_artifact_uiimported six names the package no longer exposed).rolling_projectionprojected onto the request surface, not the active one — after aregion transition the no-slip projection used the surface the ball had left.
filelockis imported by 15 always-collected test modules but was declared only in anoptional extra, so
tests (3.11)would have failed at collection. Declared inrequirements.txt, which is what CI installs from.four-surface capability manifest was missing the five linked ground specifications
(now 24 active specs / 39 declarations), and carriers recorded a self-referential
head_shawheredocs/release's own contract test requires the immutableevidence_commit_sha.all pass now; the ground-playback comparison overlay and event markers, the chip
forgiveness objective controls, and the tracking-aware camera were reconciled across
families. The toolstrip had two anonymous
role="status"live regions; both now havedistinct accessible names.
.codex-worktreesgitlinks pointing atcommits that exist on no remote. Removed from the index (
git ls-files -s | grep ^160000is empty). The
.gitignoreline landed with CONS-A1.Structural finding worth recording
#4119 shares no development history with the other 42 PRs beyond the 2026-08-06 base: it
is a parallel, pre-refactor implementation of the same subsystem (monolithic
simulation_tab,kinetics,plotting/catalog,App.tsx), whereas the trunk had since split those modules.Its conflicts were therefore resolved toward the trunk's later structure, with #4119's unique
deliverables (the club-assembly/STL slices above) ported forward. The same applies at smaller
scale to the flight family (#4207/#4210 chains), whose files the trunk had already refactored.
Ground playback was implemented twice in parallel (#4390 line and #4305 chain); the
app-wired implementation is kept and the #4305 chain's comparison capability was ported into
it rather than discarded.
Verification performed locally
quality-gatewas reproduced with its exact pins, and every blocking step passes:check+format --checkover all 770changed non-excluded Python files: clean. 98 modules arrived formatted by newer ruff builds
and were normalised — that mismatch is what turned 🎨 Palette & Bolt Suite: Form Submission, Accessibility, SVG & CSV Optimizations #4429's gate red.
MYPYPATH='src;src/python/src'(semicolon: the:in CI's Linux exportparses as one nonexistent directory on Windows and makes mypy pass having checked nothing),
one invocation over all 545 changed non-test modules via
@listfile, numpy<2.4:137 errors -> Success.
bandit (
-ll -ii, 495 files), and the Module Size Budget step insidequality-gate(a dead duplicate helper had pushed
motion_tabs.pyto 1203 lines): all pass.tests/rate_of_closure: 1520 passed. The 30 remaining local failures are allModuleNotFoundError: ntsecuritycon— pywin32 is absent from this box and the code path isos.name == "nt"-guarded, so Linux CI skips it.tsc --noEmitclean, ESLint zero warnings, 1097 vitest tests pass, productionbuild succeeds.
Not included
The four PRs
d-sorgcodexagent[bot]opened during this work (#4451, #4452, #4453, #4465) aredeliberately left out: they are stacked on
codex/4433-club-explorer-camera, which is#4444's head inside the Morris/variation consolidation (#4447), not on any of these five
families. Absorbing them would pull that entire 26-PR chain into this branch and duplicate
content that #4447 already carries. They belong on top of #4447.
Closes #4111
Closes #4136
Closes #4143
Closes #4144
Closes #4148
Closes #4149
Closes #4150
Closes #4159
Closes #4160
Closes #4163
Closes #4164
Closes #4189
Closes #4192
Closes #4193
Closes #4194
Closes #4195
Closes #4196
Closes #4197
Closes #4198
Closes #4199
Closes #4200
Closes #4224
Closes #4225
Closes #4261
Closes #4264
Closes #4272
Closes #4273
Closes #4274
Closes #4275
Closes #4284
Closes #4300
Closes #4379
Closes #4380
Closes #4385
Part of #4103
Part of #4104
Part of #4105
Part of #4106
Part of #4107
Part of #4108
Part of #4109
Part of #4110
Part of #4120
Part of #4125
Part of #4146
Part of #4158
Part of #4180
Part of #4191
Part of #4205
Part of #4218
Part of #4220
Part of #4260
Part of #4267
Part of #4377
Supersedes #4119, #4170, #4171, #4172, #4190, #4204, #4207, #4208, #4210, #4211, #4213,
#4214, #4215, #4216, #4299, #4301, #4303, #4305, #4306, #4307, #4308, #4309, #4312, #4324,
#4326, #4327, #4328, #4329, #4330, #4331, #4333, #4334, #4336, #4338, #4340, #4341, #4343,
#4344, #4348, #4349, #4390, #4391, #4392