Skip to content

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
mainfrom
consolidated/rate-closure-remainder-2026-08-13
Open

Rate of Closure remainder: club builder, impact tensor, flight, multi-view workspace, ground and web companion (consolidates 43 PRs)#4466
dieterolson wants to merge 619 commits into
mainfrom
consolidated/rate-closure-remainder-2026-08-13

Conversation

@dieterolson

Copy link
Copy Markdown
Collaborator

Consolidates the remaining 43 open src/rate_of_closure PRs into one branch. They all
edit 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 (8067ca79a CAD export artifact validation, absent from
tip #4172) and #4309 (f9afa9d96 strict ground result playback, absent from tip #4312).

bash verify_coverage.sh consolidated/rate-closure-remainder-2026-08-13 <all 43> reports
safe=43 NOT-covered=0 — closing every superseded PR drops nothing.

What this carries

Family PRs
Club builder / impact tensor #4119, #4170, #4171, #4172, #4326, #4329, #4334, #4338, #4341
Flight / wind / wedge #4190, #4204, #4207, #4208, #4210, #4211, #4213, #4214, #4215, #4216
Multi-view workspace #4301, #4303, #4324, #4327, #4330, #4331, #4333, #4336, #4340, #4343, #4344, #4348, #4349
Ground #4299, #4305, #4306, #4307, #4308, #4309, #4312, #4328
Web companion #4390, #4391, #4392

Correctness findings fixed here (not merge mechanics)

These are defects the merge exposed. Each was measured, not assumed.

  1. Two unguarded from datetime import UTC imports (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.UTC
    does not exist on Python 3.10, which the test matrix still runs; main has only
    version-gated imports. Both reverted to timezone.utc, and the # noqa: UP017 is
    re-anchored onto the physical line that uses it so a format pass cannot displace it again.
  2. The PyQt main window could not construct at all (KeyError: 'regional_surfaces', 78
    tests). The workspace-file family hard-coded a 10-entry PyQt-to-canonical tab map while
    the ground families added three tabs. CANONICAL_MODULE_IDS now matches the React
    client's PRIMARY_VIEWS exactly (12 shared modules); the PyQt-only Ground Study tab is
    declared 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.
  3. The multi-view compositor can lay a hosted view out at zero height (measured 88x0).
    Matplotlib then built a degenerate axes transform and raised
    LinAlgError: Singular matrix from axvline. The shared canvas now keeps the figure at
    least one pixel, which protects every plot in the compositor rather than one call site.
  4. feat(rate-of-closure): bind club assembly to simulation #4341's club-assembly binding was not wired through the simulation on either client.
    Restored end to end: SimulationConfig validates a qualified binding, SimulationRun
    carries club_assembly_usage, the pipeline feeds the bound head mass and inertia tensor
    to the impact solver and reports not_used on a miss, ManualSwingSource declares its
    selected-head attitude, and the TypeScript twin matches.
  5. The club STL / engineering-sidecar / assembly-binding controls were missing from the
    PyQt ControlsPanel and the rate_of_closure.club facade, so feat(rate_of_closure): export selected clubhead STL #4326-feat(rate-of-closure): bind club assembly to simulation #4341's exports were
    unreachable (club_artifact_ui imported six names the package no longer exposed).
  6. rolling_projection projected onto the request surface, not the active one — after a
    region transition the no-slip projection used the surface the ball had left.
  7. filelock is imported by 15 always-collected test modules but was declared only in an
    optional extra
    , so tests (3.11) would have failed at collection. Declared in
    requirements.txt, which is what CI installs from.
  8. Two derived release artifacts drifted from the unioned campaign manifest: the
    four-surface capability manifest was missing the five linked ground specifications
    (now 24 active specs / 39 declarations), and carriers recorded a self-referential
    head_sha where docs/release's own contract test requires the immutable
    evidence_commit_sha.
  9. The React client did not compile. Type-check, ESLint, vitest and the production build
    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 have
    distinct accessible names.
  10. 90 of 103 open PRs carried six unreachable .codex-worktrees gitlinks pointing at
    commits that exist on no remote. Removed from the index (git ls-files -s | grep ^160000
    is empty). The .gitignore line 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-gate was reproduced with its exact pins, and every blocking step passes:

  • ruff 0.14.10 (CI's pin, not a newer local build) check + format --check over all 770
    changed 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.
  • mypy 1.13.0, MYPYPATH='src;src/python/src' (semicolon: the : in CI's Linux export
    parses 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.
  • Reject Raw Merge Conflict Markers, Minimum Test Contract, Changed Test Assertion Check,
    bandit (-ll -ii, 495 files), and the Module Size Budget step inside quality-gate
    (a dead duplicate helper had pushed motion_tabs.py to 1203 lines): all pass.
  • tests/rate_of_closure: 1520 passed. The 30 remaining local failures are all
    ModuleNotFoundError: ntsecuritycon — pywin32 is absent from this box and the code path is
    os.name == "nt"-guarded, so Linux CI skips it.
  • React: tsc --noEmit clean, ESLint zero warnings, 1097 vitest tests pass, production
    build succeeds.

Not included

The four PRs d-sorgcodexagent[bot] opened during this work (#4451, #4452, #4453, #4465) are
deliberately 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

codex-scheduled 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
dieterolson disabled auto-merge August 14, 2026 10:11
@dieterolson
dieterolson enabled auto-merge (squash) August 14, 2026 10:11
codex-scheduled 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment