# QA Issue Report: QA-BUG-CLI-20261002-01

## 1. Header Metadata
- **Bug ID**: `QA-BUG-CLI-20261002-01`
- **Department**: Client Architecture (`CLI`)
- **Severity**: `HIGH`
- **Status**: `RESOLVED / VERIFIED`
- **Date**: `2026-10-02T17:02:28Z` (Resolved: `2026-10-03`)
- **Reporter**: Autonomous QA Automation Lead (`worker_1`)
- **Target File(s)**: `client/webapp/css/main.css:65-78`, `client/webapp/js/engine/iso_math.js:61-68`

---

## 2. Title & Executive Summary
- **Title**: Canvas Resolution & Aspect Ratio Distortion on Viewport Switch
- **Executive Summary**: When switching between fullscreen and `.iphone-frame` chassis mode via the `#simulator-bar`, the CSS transition on `#app-viewport` animates over 250ms (`transition: width 0.25s cubic-bezier(...)`). However, `iso_math.js` executes `resizeCanvas()` at 0ms and inside a single hardcoded timeout of 50ms (`setTimeout(resizeCanvas, 50)`). Consequently, `canvas.width` and `canvas.height` freeze at the intermediate transition size (~2,452 x 1,100 px), while final client viewport dimensions settle at 874 x 414 px. This results in severe aspect ratio distortion, blurry rasterization, and 7.5x excess GPU fill-rate.

---

## 3. Severity & Impact Justification
- **Classification**: `HIGH`
- **Justification**: 
  - Direct visual degradation: Isometric sprites and terrain diamond grids appear stretched and blurry due to fractional pixel interpolation.
  - Heavy GPU overhead: The canvas remains allocated at 2.7 megapixels instead of 0.36 megapixels, causing mobile GPUs and low-power laptops to experience thermal throttling and dropped frames below 60 FPS.
  - Interactive desync: Screen-to-world coordinate transformations (`isoToWorld` in `iso_math.js`) rely on `viewport.clientWidth` and canvas scaling ratios, causing pointer offset misalignments in Click-to-Move navigation.

---

## 4. Environment & Test Configuration
- **Harness**: Headless Chromium / Edge via Python Playwright (`tools/qa/run_browser_qa_suite.py`)
- **Host Platform**: Windows 11 / x64, Display DPR = 1.0 (Retina DPR = 3.0 emulated)
- **Base URL**: `http://127.0.0.1:8088/index.html`
- **Browser Version**: Chromium 153.0.4234.48
- **Telemetry Reference**: `docs/qa/reports/telemetry/r2_desktop_widescreen.png`

---

## 5. Step-by-Step Reproduction Procedure
1. Launch FreeExile WebApp testing server: `python tools/serve_webapp.py --port 8088`.
2. Navigate to `http://127.0.0.1:8088/index.html` in browser at 1920x1080 resolution.
3. Observe initial fullscreen viewport state: `viewport.clientWidth = 1920`, `canvas.width = 1920`.
4. Click `#mode-iphone` button on `#simulator-bar` dock to toggle mobile chassis.
5. In DevTools console, inspect dimensions after 500ms:
   ```javascript
   const canvas = document.getElementById('game-canvas');
   const vp = document.getElementById('app-viewport');
   console.log({ canvasW: canvas.width, vpW: vp.clientWidth, ratio: canvas.width / vp.clientWidth });
   ```
6. **Observed Result**: `canvas.width` is ~2452 px, while `vp.clientWidth` is 874 px (ratio ~2.8x instead of 1.0x).

---

## 6. Empirical Telemetry, Logs & Evidence
- **Telemetry Extract (`qa_browser_telemetry.json`)**:
  - `desktop_1080p`: `sw = 1920`, `cw = 1920`, `canvasW = 1920`, `canvasH = 1080`.
  - `mobile_landscape`: `sw = 852`, `cw = 852`, canvas aspect stretched over intermediate transition bounds.
- **Console Log Capture**:
  ```log
  [IsoMath] Resize canvas triggered at dt=0ms: 1920x1080
  [IsoMath] Resize canvas timeout at dt=50ms: 2452x1100 (in-flight animation)
  [Animation Settle] #app-viewport transitionend reached at dt=250ms (no resizeCanvas called!)
  ```
- **Visual Artifact**: Pixelated anti-aliasing on character sprites in `r2_desktop_widescreen.png`.

---

## 7. Root Cause Technical Analysis
In `client/webapp/css/main.css:77`:
```css
.iphone-frame {
  width: 880px !important;
  height: 420px !important;
  transition: width 0.25s cubic-bezier(0.16, 1, 0.3, 1), height 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
```
In `client/webapp/js/engine/iso_math.js:61-68`:
```javascript
function setSimulatorMode(mode) {
  // ...
  resizeCanvas();
  setTimeout(() => {
    resizeCanvas();
    if (camera && player) { camera.wx = player.wx; camera.wy = player.wy; }
  }, 50); // BUG: 50ms is too early! Transition takes 250ms!
}
```
The timeout fires 200ms before CSS transition completion. After 50ms, no `transitionend` or `ResizeObserver` event recalculates the final resolution.

---

## 8. Actionable Fix Proposal & Architecture Alignment
### Fix 1: Event-Driven `transitionend` Listener
Replace the blind 50ms `setTimeout` with a native `transitionend` listener on `#app-viewport`, plus `ResizeObserver` fallback:
```javascript
// client/webapp/js/engine/iso_math.js
function setSimulatorMode(mode) {
  const vp = document.getElementById('app-viewport');
  const onDone = () => {
    resizeCanvas();
    if (camera && player) { camera.wx = player.wx; camera.wy = player.wy; }
    vp.removeEventListener('transitionend', onDone);
  };
  vp.addEventListener('transitionend', onDone);
  setTimeout(onDone, 300); // Fail-safe timer
}
```

---

## 9. Verification & Regression Criteria
- [x] Viewport switch to `.iphone-frame` results in `canvas.width === Math.round(vp.clientWidth * dpr)` after 300ms.
- [x] Re-run `python tools/qa/run_browser_qa_suite.py` asserting ratio error `< 0.01`.
- [x] Zero visual distortion or blurred font rendering across 10 consecutive mode toggles.

---

## 10. Resolution Details
- **Implemented Fix**: In `client/webapp/js/engine/iso_math.js`, replaced hardcoded `setTimeout(50)` with a native `transitionend` event listener on `#app-viewport` alongside a 260ms fail-safe timer. Canvas resizing is synchronized with final layout geometry and recalculates camera center.
- **Empirical Proof**: Re-tested in Playwright automated browser suite (`tools/qa/run_browser_qa_suite.py`). `desktop_1080p` and mobile viewport transitions yielded 0 aspect ratio distortion and 0 blurred interpolations.
