Add the orbit-for-all-exercises handoff plan

Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
This commit is contained in:
2026-07-06 21:35:39 -04:00
parent 6d9eff1a71
commit ed906535b6
+90
View File
@@ -0,0 +1,90 @@
# Handoff: complete camera rotation (slow orbit) for all exercises
Repo: `~/Documents/indie/Workouts`. Read `Exercise Library/SYSTEM.md` first
(format spec + visual language), then `Exercise Library/kinematics.py` and
`render.py` (reference renderer), and `Workouts/ExerciseFigure/`
(`MotionSolver.swift`, `ExerciseFigureView.swift`, `ExerciseMotion.swift`
the Swift port, kept in lockstep with Python; shared by the iOS and watch
targets).
## Current state
The figure rig is an anatomical 3D skeleton with a per-exercise orthographic
camera (`camera: {yaw, pitch}`, default pitch 10°). In the app,
`FigureAnimation` (ExerciseFigureView.swift) slowly orbits the camera (360°
per 24 s, `orbitPeriod`) — but only when `props.isEmpty && hide.isEmpty`,
i.e. the 8 bodyweight moves. The 14 machine exercises stay at their authored
view because their equipment can't rotate. The goal: **every exercise
orbits.**
## What blocks it, in order of work
1. **`scene` props are 2D canvas billboards** (seats, masts, benches —
`line`/`rect`/`circle` in canvas coords). They need world-space 3D form.
Follow the exercise-mat pattern (`geo["floor"]` in `render.py` /
`FigureGeometry.floor` in Swift — a quad built in model space relative to
the root anchor, rotated through `Rx(pitch)·Ry(yaw)`, then projected):
extend the scene-shape schema so each shape carries depth — suggest an
optional `"z"` (depth plane, default 0) plus `"depth"` (extrusion
half-width) so a seat rect becomes a slab and a mast line a post; author
full 3D endpoints only where needed. Convert authored canvas coords to
model space by subtracting the motion's root anchor (same as the mat).
Re-author all 14 machines' props minimally — schematic silhouettes, not
scale models; check each at yaw 0 (must look identical to today), ±45°,
and 90°.
2. **`cable`** `from` anchors and **`bar`** fixed world angles are
authored-view constructs: make both world-space (the anchor gets a depth,
the bar becomes a segment defined in the authored view and rotated like
the mat — it must stay welded to the hands).
3. **`roller`** and **`pad`/`dumbbell`** perpendiculars are computed in
screen space per frame — under orbit compute them in the flat authored
view, then rotate (the resolve-then-rotate invariant below).
4. **`hide` lists** name limbs occluded in one authored view — wrong under
orbit. Only Side Plank uses one; re-author it with genuine 3D (root
`roll`, real abduction) instead, then delete the `hide` mechanism if
nothing else uses it.
5. Relax the `orbits` gate in `FigureAnimation` to always-on once 14 hold
up visually.
## Invariants — do not break
- **Resolve flat, then rotate.** IK pins and prop attachment logic operate
in the *authored* view (yaw as authored, pitch 0); the *posed result* is
rotated for presentation. Never re-solve pins or recompute screen-space
perpendiculars in the rotated view — that class of bug ("hands stuck
mid-orbit") was already fixed twice.
- **Both renderers in lockstep.** Every geometry change lands in
`render.py`/`kinematics.py` *and* the Swift port, same conventions and
constants. `WorkoutsTests/Fixtures/figure-fixtures.json` pins Swift to
Python (0.5 px); regenerate it from the Python pipeline whenever projected
geometry changes (per exercise/keyframe snapshot: order, shade, spine,
head, nose, limb chains, plus tween + orbit samples — extend the orbit
samples to a machine exercise once props rotate).
- **Visual parity at the authored view**: rendering any exercise at its
authored yaw must look the same as today. Verify with
`python3 render.py --sheet` before/after, and orbit strips
(`--orbit <name>`, or a multi-yaw contact sheet) for each converted
machine.
## Verification workflow
`python3 render.py` + `--sheet`/`--orbit` and eyeball the PNGs;
`python3 render.py --export` (verbatim copy into
`Workouts/Resources/ExerciseMotions/`); `xcodegen generate` if project.yml
changes; `xcodebuild test -scheme Workouts -destination 'platform=iOS
Simulator,name=iPhone 17 Pro' -only-testing:WorkoutsTests` (48 tests must
stay green, plus whatever you add); `xcodebuild build -scheme "Workouts
Watch App" -destination 'generic/platform=watchOS Simulator'`. Update
`SYSTEM.md` (props section) and add one end-user CHANGELOG.md entry per the
app-changelog skill (one sentence, no bullets, top of the July 2026 block —
e.g. machine form guides now rotate too). Commit per feature with the
repo's existing message style and push. Delete this file in the final
commit.
## Judgment calls you're empowered to make
Exact 3D prop schema shape (keep it minimal and hand-authorable),
per-machine depth values, and whether a couple of visually-degenerate
machines (e.g. cable stacks) look better with a reduced orbit sweep (±40°
pendulum instead of full 360°) — if so, make sweep style a per-motion
`camera` option with full-360 the default.