# Physics and rotation contract

This contract is source-derived. Historical labels do not override it.

## Coordinates and storage

- `CelerisLab/src/CelerisLab/body/geometry/circle.py` stores native linear index `k = x + y*nx`.
- `CelerisLab/src/CelerisLab/common/render.py` uses maintained `imshow(..., origin="lower")`; displayed `+y` is upward. Array storage and plotting orientation must not be silently conflated.
- Pinball body order is `[front, upper, lower]`, consistent with creation/order use in `src/drl_pinball/train/env_karman.py` and archived OID metadata.

## Rotation oracle

`LegacyCelerisLab/driver.py` stores rim direction components `(-ry/R, rx/R)` as `(y_c-y)/R, (x-x_c)/R`; `LegacyCelerisLab/kernels/kernel.cu` multiplies both by scalar surface action `u_s`. Thus `(Uw,Vw)=u_s(-ry/R,rx/R)`.

Modern `CelerisLab/src/CelerisLab/lbm/kernels/step/aux_kernels.cu` uses `(Uw,Vw)=(-Omega*ry, Omega*rx)`. Therefore `Omega=u_s/R` is the nominal continuum sign-and-unit conversion. It is not an exact discrete wall-velocity parity statement. Legacy normalizes the interface solid-cell center direction by nominal `R`; modern applies `Omega` to the boundary-hit lever arm. Those discrete vectors generally have different lengths and directions, so local wall speeds differ by the corresponding geometry factor even under the same nominal conversion.

The V5 policy adapter `src/drl_pinball/train/env_karman.py::_action_to_omega` uses
`Omega = -(a*scale + bias) U0/R`. Requested and EMA-effective action must be recorded separately in any future study.

The archived OID steady tuple `[0,-5.1,+5.1] U0` and the stated CCD tuple `[0,+5.1,-5.1] U0` are physically opposite under the same `[front,upper,lower]` ordering. Current CCD source `src/CCD_analysis/scripts/collect_vortex.py` contains `[0,-5.1,+5.1] U0`, contradicting that stated CCD label; treat this as unresolved documentation/source disagreement, not corroboration. Existing `src/steady_pinball_theory` documents also contain historical sign interpretations that conflict with this source-derived `Omega=u_s/R` oracle. They are outside this reset and were not edited.

The CPU oracle in `tests/test_physics_contract.py` binds the sign and nominal `Omega=u_s/R` conversion, and explicitly demonstrates the expected discrete geometry mismatch, without CUDA import or CFD initialization. Any future physics result must additionally record body IDs/order, requested and effective actions, action units, source hash, checkpoint hash, and plotting convention.
