# Pixel-Perfect Hardening Plan — IMPLEMENTED 2026-07-10 (Phases 1–4)

Goal: make pixel-perfection **structural** — enforced by the engine and CI — instead of a per-call-site discipline. Phases 1–4 landed unstaged 2026-07-10; Phase 5 remains a decision gate. In-game verification pending (`/reload` + config-panel visual pass + `/orbitpixel` sweep).

## Phase 1 — `check-pixel.py` CI gate ✅

`.scripts/check-pixel.py`, fifth job in `lint.yml`. All rules ship **hard** (burn-down completed same day):

| Rule | Catches | Notes |
|------|---------|-------|
| R1 | 2-arg `SetPoint` with any nonzero literal offset ≤ 3px | the crisp-inset class; larger 2-arg + all 5-arg literals are VERBOSE-only (see refinement below) |
| R2 | `Pixel:GetScale()` arithmetic, `GetPhysicalScreenSize` outside engine | allowlist: `Pixel.lua`, `UserInterface.lua`, `PixelProbe.lua` |
| R3 | `borderPixelSize or N` | froze at 0 |
| R4 | literal 1–3 `SetWidth`/`SetHeight` | dividers converted; placeholder grow-with-content heights carry px-ok |
| px-ok hygiene | bare `px-ok` without a reason | reason mandatory |

Suppression: trailing `-- px-ok: <reason>`. `VERBOSE=1` prints the remaining literal-offset worklist (~122 sites) and SetScale callers (R5, report-only).

**Rule refinement vs original plan**: the baseline audit missed the 5-arg `SetPoint` form — 61 more literal-offset sites, nearly all HUD text/icon nudges (charge text `-2,2`, marker icons `±2`). Blanket-converting those without in-game verification was rejected; they stay VERBOSE as an opportunistic worklist, converted with visual checks as files get touched.

## Phase 2 — `Pixel:Point` + burn-down ✅

- `Pixel:Point(frame, point[, relTo, relPoint], xPx, yPx)` — offsets in physical pixels via `Multiple`; both SetPoint shapes.
- Converted: all swatch/picker texture insets (ColorPicker, ColorCurvePicker, TexturePicker, ValueSwatch, MediaMenu search) **including their paired `edgeSize = 1` backdrops** — converting the inset without its border would mismatch. Dividers/underlines (PluginManager, QoL, OrbitAdvancedSettings, MediaMenu, EditModeTour, MythicPlus) → `Multiple`.
- px-ok'd (logical units genuinely intended): WhatsNew bg insets (track dialog chrome), SelectedHighlight baseline nudges (QoL, WelcomeDialog), ConfigLayout header indent, six grow-with-content placeholder heights.
- Libs pass concluded: LibDBIcon is third-party (never edit); LibOrbitColorPicker's ±2 inset is on the cursor-following drag ghost and GlowShowcase offsets are dev-window gaps — blur invisible on moving/dev elements, left logical. Libs stay excluded from the gate.

## Phase 3 — engine guarantees ✅ (adoption survey pending)

- `Pixel:EvenSnap(value, scale)` / `Pixel:SnapSize(w, h, scale, centerAnchored)` — even pixel counts kill the CENTER-anchored odd-size half-pixel class.
- `Pixel:Enforce(frame, opts)` — `opts.centerAnchored` routes the size hooks through even snapping (flag on `frame.orbitEvenSnap`, checked dynamically so re-Enforce upgrades existing hooks); `opts.scale` hooks `SetScale` to re-snap the current size at the new effective scale.
- **Not yet adopted anywhere** — survey center-anchored resizable frames in-game with `/orbitpixel` (candidates: Minimap, StatusWidget, centered bars) before opting frames in; each adoption can shift a size by 1px.
- Sub-floor `UIParent:SetScale` / `UI_SCALE_CHANGED` magnetism knowledge relocated to `Core/Infrastructure/README.md`.

## Phase 4 — `/orbitpixel` probe ✅

`Core/Shared/PixelProbe.lua`, `@do-not-package@` in `Shared.xml`. Hover inspector: per-edge physical-grid remainder with on/off-grid coloring, frame size in px, Enforce/even-snap state, anchor point (flags CENTER + odd width), compressed non-1 scale chain. `EnumerateFrames` smallest-hit-rect targeting so mouse-disabled HUD frames are inspectable; 0.1s throttle.

## Phase 5 — vector assets (blocked on 12.1 PTR)

Unchanged: when SVG-on-Texture (Path A) clears its two PTR unknowns (`PATCH-12.1.0-PLAN/OPPORTUNITIES.md`), evaluate replacing baked border/mask TGAs — retires the stretched-pair compromise (minimap rounded-square masks) and the 32px slice-bake granularity.

## Verification checklist (in-game, pending)

1. `/reload` → config panel: swatches/pickers/dividers crisp and unshifted at pixel-perfect scale; visibly crisper at fractional scales.
2. `/orbitpixel` → hover HUD frames: edges green; hover a config swatch: green.
3. Scale slider sweep 0.30–1.15 + a `TooltipScale` change: borders stay crisp.
4. Survey CENTER+odd-width flags from the probe → adopt `Enforce(frame, { centerAnchored = true })` per finding.

## Appendix — reproduce the baseline (2026-07-10, pre-burn-down)

```
grep -rn ':SetPoint("[A-Z]*", -\?[1-9]' Orbit --include=*.lua | grep -v Core/Libs   # R1 2-arg (40 → 0 hard-class)
grep -rn 'Set\(Width\|Height\)([1-3])' Orbit --include=*.lua | grep -v Core/Libs    # R4 (15 → 0 unsuppressed)
grep -rn 'Pixel:GetScale() [*/]' Orbit --include=*.lua                              # R2 (2 → 0)
VERBOSE=1 python .scripts/check-pixel.py                                            # live worklist
```
