# Geometry Rules — coordinates, facings, math

## Coordinate convention (v2 schema)

```
+x →  width (left-to-right, looking downrange from the shooter)
+y ↑  uprange (toward the shooter; AWAY from berm)
y=0   back of bay (berm side, where the AR calibration markers go)
```

**Markers** sit at `(0, 0)` and `(bayWidthFeet, 0)` — the two back corners of the bay.

**Shooter** stands somewhere in the uprange portion of the bay (high y).

The web editor internally uses v1 convention (y=0 at the shooter end) but flips on input/output via `applyStageJson` / `toStageJson`. Always think in v2 when constructing prop coordinates — the editor display also renders top-down with the berm at the top of the screen.

## Target facing convention

`facingDegrees` is the direction the cardboard's FRONT (visible face) points. The shooter must be on the same side as the cardboard front to score a face-on hit.

```
facingDegrees | cardboard front faces | shooter is | bullets travel
        0     |  -y  (south, downrange)  |  south of target  | north (+y)
       90     |  +x  (east)              |  east of target   | west (-x)
      180     |  +y  (north, uprange)    |  north of target  | south (-y)
      270     |  -x  (west)              |  west of target   | east  (+x)
      135     |  NE  (north-east)        |  NE of target     | SW (toward back/left berm)
      225     |  NW  (north-west)        |  NW of target     | SE (toward back/right berm)
       45     |  SE                      |  SE of target     | NW (rare, into uprange — DANGEROUS)
      315     |  SW                      |  SW of target     | NE (rare, into uprange — DANGEROUS)
```

**Avoid 45° and 315°** — bullets travel uprange after pass-through, toward the shooter or RO area.

Formula: `faceDirection = (sin(facingDegrees), -cos(facingDegrees))` — a unit vector pointing in the direction the cardboard face is looking.

### Inverse: computing facing FROM a center and target point

If a target's center is `(Cx, Cy)` and it should aim at point `(Tx, Ty)` (e.g., a corner of the shooting box):

```python
import math
facing = math.degrees(math.atan2(Tx - Cx, Cy - Ty)) % 360
```

(JavaScript equivalent: `(Math.atan2(Tx - Cx, Cy - Ty) * 180 / Math.PI + 360) % 360`.)

**DON'T use `270 - atan2(Δy, Δx)`.** That formula looks plausible but produces facings mirrored across the shooter axis (the targets end up pointing AWAY from the intended point). It has bitten multiple CM 25-09 drafts. If your targets visually appear angled away from where they should aim, this is the first thing to check.

Derivation: forward = `(sin θ, -cos θ) = (Tx-Cx, Ty-Cy)/d`, so `sin θ = (Tx-Cx)/d` and `cos θ = (Cy-Ty)/d`, giving `θ = atan2(Tx-Cx, Cy-Ty)`.

## Stand-endpoint formulas

The two `baseEndpoint` values define the stand's base on the floor. The stand line is perpendicular to the facing direction — the cardboard sits on top of this line.

For each facing angle, here's how the endpoints relate to the stand's center `(cx, cy)`:

```
facing=180 (cardboard faces N, shot from N):
  baseEndpointA = (cx,      cy)       — but actually it's at LEFT post
  Stand is along the x-axis (cardboard extends in x). For width 1.5 ft:
  baseEndpointA = (leftX,     y)       # use leftX = cx - 0.75
  baseEndpointB = (leftX+1.5, y)

facing=0 (cardboard faces S, shot from S — uncommon):
  Same stand orientation as facing=180 — along x-axis.

facing=90 (cardboard faces E, shot from E):
  Stand is along the y-axis. For width 1.5 ft:
  baseEndpointA = (x, southY)
  baseEndpointB = (x, southY+1.5)

facing=270 (cardboard faces W, shot from W):
  Same orientation as facing=90 — along y-axis.

facing=135 (cardboard faces NE, shot from NE):
  Stand is along the SE→NW diagonal. Stand vector = 1.5 * (-1/√2, 1/√2) = (-1.06, 1.06).
  For center (cx, cy), with half = 0.75 / √2 ≈ 0.5303:
  baseEndpointA = (cx + half, cy - half)   # SE end
  baseEndpointB = (cx - half, cy + half)   # NW end

facing=225 (cardboard faces NW, shot from NW):
  Stand is along the SW→NE diagonal.
  baseEndpointA = (cx - half, cy - half)   # SW end
  baseEndpointB = (cx + half, cy + half)   # NE end
```

## "No padding" bay sizing

Bay dimensions should match the physical stage extent with no buffer, so the schematic renders large and the AR overlay matches the bay faithfully.

```
bayWidthFeet  = (rightmost prop outside corner x)
              - (leftmost prop outside corner x; usually 0)

bayDepthFeet  = (uprange-most prop edge y)   # often the back of the shooting area
              - (downrange-most prop edge y; usually 0)
```

For a stage with targets along y=1 (back row) and a fault line at y=35 (back of shooting area), the natural bay depth is 40 (leaves 5 ft uprange of the shooting area for muzzle safety / start mark).

Useful defaults to offer the user:
- **35 × 40** — standard club bay. Fits most short / medium courses.
- **50 × 40** — wider club bay for movement-heavy designs.
- **50 × 60** — large bay, used for sprint-heavy long courses.

## Common position math

`centerline = bayWidthFeet / 2`

`paperCount = roundCount / 2`
`steelCount = roundCount - paperCount*2` (if mixing; else 0)

`distanceFt(p, q) = sqrt((p.x-q.x)² + (p.y-q.y)²)`
`distanceYards(p, q) = distanceFt(p, q) / 3`

USPSA minimum target distance = 5 yards = 15 ft.

## Hardcover variants for partial paper

For a 70-80% hardcover effect (Stoeger-style partials):

```javascript
mount('paperTarget', 'verticalLeft')      // left half covered (only right half visible)
mount('paperTarget', 'verticalRight')     // right half covered
mount('paperTarget', 'horizontalTop')     // top half covered (only A-zone neck/head visible)
mount('paperTarget', 'horizontalBottom')  // bottom half covered
mount('paperTarget', 'tuxedo')            // left + right thirds covered, center stripe visible
mount('paperTarget', 'diagonalUpperLeft')  // upper-left triangle covered
mount('paperTarget', 'diagonalUpperRight') // upper-right triangle covered
mount('paperTarget', 'diagonalLowerLeft')  // lower-left triangle covered
mount('paperTarget', 'diagonalLowerRight') // lower-right triangle covered
```

## Wall ports (cutouts)

For tight visual ports (Stoeger-style):

```javascript
wall('Port wall', x1, y1, x2, y2)
// then add to .cutouts:
{
  distanceAlongWallFeet: 4,   // distance from endpointA along the wall to LEFT edge of cutout
  widthFeet: 1,               // cutout width
  bottomHeightFeet: 4,        // port floor height
  topHeightFeet: 5,           // port top height
  label: 'Port A'
}
```

A 1 ft × 1 ft port at chest height (4-5 ft) is a tight Stoeger-style port. A 3 ft × 5 ft port is a generous "window" for normal engagement.

## Pythagorean triples (common in USPSA geometry)

Watch for clean diagonals when laying out fault lines or reference measurements:
- 3-4-5
- 6-8-10
- 5-12-13
- 8-15-17
- 9-40-41

If a diagonal lands on a clean number, double-check coordinates — it's usually a sanity check, not a coincidence.
