Executing unit: u3 [Claude #5] Stage 3 Round #3 — unit u3 implemented. **unit_executed**: `u3` **files_changed**: - `src/phase_z2_pipeline.py` (+99 / -0) — adds `_override_to_grid_tracks` immediately above the `# Layout preset → zone position 순서 …` comment block (i.e., after `_build_cols_dynamic` and before `build_layout_css`). **diff_summary**: - New override-path builder `_override_to_grid_tracks(preset, zones_data, override_zone_geometries, gap=GRID_GAP) -> dict`. - Body: 1. Parse `preset["css_areas"]` via `_parse_css_areas` to get R × C `rows_grid` (length R rows × C cols). 2. Build `occupancy` list of `(zone, occupied_rows, occupied_cols)` triples (mirrors the u1 helper layout but operates over `override_zone_geometries[pos]["h"|"w"]` rather than `content_weight`). 3. Per-row aggregation: for each `r ∈ [0, R)` collect single-row zones (`occupied_rows == {r}`); fallback to all-span zones (`occupied_rows == set(range(R))`); take `max` of their override `h`. Missing track → `0.0`. 4. Per-col aggregation: same with `w` on columns. 5. Degenerate guard: if `total_h == 0` or `total_w == 0`, return `_build_grid_dynamic_2d(preset, zones_data, gap=gap)` (u2 default fallback per Stage 2 plan). 6. Normalize per axis (`v / total`), multiply by `avail_h = SLIDE_BODY_HEIGHT - gap*(R-1)` / `avail_w = SLIDE_BODY_WIDTH - gap*(C-1)`, then last-element diff absorb (`heights_px[-1] += diff_h`, `widths_px[-1] += diff_w`). 7. Assemble px-string `rows` / `cols` and return the same dict shape as `_build_grid_dynamic_2d`: `areas`, `cols`, `rows`, `heights_px`, `widths_px`, `ratios`, `width_ratios`, `computation`, `dynamic_rows`, `dynamic_cols`, `raw_zone_layout`. - Output flags exactly per Stage 2 contract: `computation="user_override_geometry"`, `dynamic_rows=True`, `dynamic_cols=True`. - `raw_zone_layout = {"override_applied": True, "source": override_zone_geometries}` — same shape the legacy horizontal-2 / vertical-2 override branches already emit (consistent for step08 trace + test loader excludes the key). - Dispatcher (`build_layout_css`) NOT wired — the existing `[override-warning]` stderr + `_build_fr_default` fallback for the 5 target presets still fires until u4 lands. The 10 RED fixtures remain RED by design. **evidence (smoke run, not committed)**: - `git diff --stat src/phase_z2_pipeline.py` → `1 file changed, 204 insertions(+)` (50 from u1 + 55 from u2 + 99 from u3 — matches expected cumulative delta). - `git diff --name-only src/phase_z2_pipeline.py tests/phase_z2/test_build_layout_css_pr1.py tests/phase_z2/fixtures/build_layout_css` → only `src/phase_z2_pipeline.py`. - `python -c "import ast, pathlib; ast.parse(pathlib.Path('src/phase_z2_pipeline.py').read_text(encoding='utf-8'))"` → `syntax OK`. - `git diff --check src/phase_z2_pipeline.py` → no whitespace errors. - Hand-feed via `python -c "from src.phase_z2_pipeline import _override_to_grid_tracks, LAYOUT_PRESETS; ..."` across all 5 target presets — byte-for-byte match to `tests/phase_z2/fixtures/build_layout_css/*_override.yaml` (excluding `raw_zone_layout`, which the loader strips): - `top-1-bottom-2`: heights=`[171, 400]` widths=`[583, 583]` ratios=`[0.3, 0.7]` width_ratios=`[0.5, 0.5]` cols=`'583px 583px'` rows=`'171px 400px'`. ✓ - `top-2-bottom-1`: heights=`[228, 343]` widths=`[700, 466]` ratios=`[0.4, 0.6]` width_ratios=`[0.6, 0.4]`. ✓ - `left-1-right-2`: heights=`[286, 285]` widths=`[466, 700]` ratios=`[0.5, 0.5]` width_ratios=`[0.4, 0.6]` (last-element diff absorb on heights row: 286+286 → 286+285 because 2×285.5 rounds to 286+286, diff = -1). ✓ - `left-2-right-1`: heights=`[343, 228]` widths=`[408, 758]` ratios=`[0.6, 0.4]` width_ratios=`[0.35, 0.65]`. ✓ - `grid-2x2`: heights=`[228, 343]` widths=`[641, 525]` ratios=`[0.4, 0.6]` width_ratios=`[0.55, 0.45]`. ✓ **scope-lock check**: - Single file: `src/phase_z2_pipeline.py`. No edits to `build_layout_css` dispatcher, `_build_fr_default`, the existing horizontal-2 / vertical-2 override inline branches, `_build_rows_dynamic`, `_build_cols_dynamic`, `_build_grid_dynamic_2d`, `_aggregate_zone_signals_per_track`, `_compute_per_zone_geometry`, `compute_zone_layout`, `compute_zone_layout_cols`, or PR 1 lock test. - No fixture added / removed. - Kei archive path (`build_containers_type_b`, `space_allocator.py`) untouched. - Step08 trace untouched (that lands in u4). - horizontal-2 / vertical-2 / single regression surface untouched. - `[override-warning]` stderr + `PR 2 territory` comment still in place — u4 removes them as part of dispatcher wiring (Stage 2 plan). **remaining_units**: - `u4` — wire dispatchers in `build_layout_css` (dynamic branch routes 5 presets to `_build_grid_dynamic_2d`; override branch routes 5 presets to `_override_to_grid_tracks`; drop `[override-warning]` stderr; add 2-D-aware step08 trace print). - `u5` — retire PR 1 lock test `test_top_1_bottom_2_fr_default_populates_geometry` (rename + flip assertions to PR 2 reality). **follow_up_issue_candidates**: none (no scope-creep axis discovered in this round; helper sits cleanly in the IMP-09 builders cluster).