On main: IMP-09 PR2 sketch (Stage 2 design reference; re-derive per unit)

This commit is contained in:
2026-05-17 08:51:59 +09:00
3 changed files with 369 additions and 28 deletions
@@ -1,9 +1,12 @@
case_id: single_fr_default
description: |
Any layout that fell through to fr_default_from_preset (single,
T-shape, 2x2 in PR 1) has neither dynamic_rows nor dynamic_cols.
Row-axis retry is a no-op and must be skipped by the IMP-09 gate
with a fr_default_from_preset skip reason.
Post-IMP-09 PR 2 the only preset that still falls through to
fr_default_from_preset is `single` (top-1-bottom-2 / top-2-bottom-1 /
left-1-right-2 / left-2-right-1 / grid-2x2 were promoted to 2-D
dynamic). This fixture exercises the IMP-09 retry-gate fr_default
skip path using a layout_css with dynamic_rows=False AND
dynamic_cols=False (the surviving fr_default signature). Row-axis
retry must be skipped with a fr_default_from_preset skip reason.
input_layout_css:
areas: '"top top" "bottom-left bottom-right"'
cols: 1fr 1fr
+101 -13
View File
@@ -138,21 +138,109 @@ def test_vertical_2_override_keeps_fr_cols_legacy():
assert result["width_ratios"] == [0.4, 0.6]
# ────────────────────── fr_default sink (PR 1) ──────────────────────
# ───────────────── PR 2: 5 in-scope 2-D presets dynamic ─────────────────
def test_top_1_bottom_2_fr_default_populates_geometry():
"""T-shape (top-1-bottom-2) falls through to fr_default in PR 1
but heights_px / widths_px must be populated (length-locked to
grid R=2, C=2)."""
zones = [
_zone("top", 0.5),
_zone("bottom-left", 0.25),
_zone("bottom-right", 0.25),
]
result = build_layout_css("top-1-bottom-2", zones)
_TWO_D_PRESETS = [
"top-1-bottom-2",
"top-2-bottom-1",
"left-1-right-2",
"left-2-right-1",
"grid-2x2",
]
def _zones_for(preset: str) -> list[dict]:
"""Default zone fixtures (positions per LAYOUT_PRESETS, equal score)."""
if preset == "top-1-bottom-2":
return [_zone("top", 0.5), _zone("bottom-left", 0.25),
_zone("bottom-right", 0.25)]
if preset == "top-2-bottom-1":
return [_zone("top-left", 0.3), _zone("top-right", 0.2),
_zone("bottom", 0.5)]
if preset == "left-1-right-2":
return [_zone("left", 0.5), _zone("right-top", 0.3),
_zone("right-bottom", 0.2)]
if preset == "left-2-right-1":
return [_zone("left-top", 0.3), _zone("left-bottom", 0.2),
_zone("right", 0.5)]
if preset == "grid-2x2":
return [_zone("top-left", 0.25), _zone("top-right", 0.25),
_zone("bottom-left", 0.25), _zone("bottom-right", 0.25)]
raise ValueError(f"no _zones_for({preset!r})")
@pytest.mark.parametrize("preset", _TWO_D_PRESETS)
def test_two_d_preset_promoted_to_dynamic(preset):
"""PR 2 — T / inverted-T / side-T-{left,right} / 2x2 must dispatch
to _build_2d_dynamic (computation=='2d_dynamic_aggregated') with
dynamic_rows=True AND dynamic_cols=True, and grid-template strings
in pixels."""
result = build_layout_css(preset, _zones_for(preset))
assert result["computation"] == "2d_dynamic_aggregated"
assert result["dynamic_rows"] is True
assert result["dynamic_cols"] is True
# Both axes pixel-based.
assert "fr" not in result["rows"]
assert "fr" not in result["cols"]
assert result["rows"].count("px") == 2
assert result["cols"].count("px") == 2
# Length contract — R=2 rows, C=2 cols for all 5 in-scope presets.
assert len(result["heights_px"]) == 2
assert len(result["widths_px"]) == 2
@pytest.mark.parametrize("preset", _TWO_D_PRESETS)
def test_two_d_preset_override_dispatches_via_helper(preset):
"""Override on a 2-D preset must route through
_override_to_grid_tracks (computation=='user_override_geometry')
with both axes marked dynamic."""
zones = _zones_for(preset)
positions = [z["position"] for z in zones]
override = {pos: {"x": 0, "y": 0, "w": 0.5, "h": 0.5} for pos in positions}
result = build_layout_css(preset, zones, override_zone_geometries=override)
assert result["computation"] == "user_override_geometry"
assert result["dynamic_rows"] is True
assert result["dynamic_cols"] is True
assert len(result["heights_px"]) == 2
assert len(result["widths_px"]) == 2
# Total of axis cell sums equals body minus inter-track gap.
assert sum(result["heights_px"]) == SLIDE_BODY_HEIGHT - GRID_GAP
assert sum(result["widths_px"]) == SLIDE_BODY_WIDTH - GRID_GAP
@pytest.mark.parametrize("preset", _TWO_D_PRESETS)
def test_two_d_preset_override_invalid_falls_back_to_dynamic(preset):
"""Zero-only override on a 2-D preset must fall back to dynamic
normal-path solvers on both axes; computation still labeled
user_override_geometry (override invocation context preserved)
and raw_zone_layout.{row_source,col_source}=='dynamic_fallback'."""
zones = _zones_for(preset)
positions = [z["position"] for z in zones]
# Zero-only override — must trigger per-axis fallback to solvers.
override = {pos: {"x": 0, "y": 0, "w": 0.0, "h": 0.0} for pos in positions}
result = build_layout_css(preset, zones, override_zone_geometries=override)
assert result["computation"] == "user_override_geometry"
rzl = result["raw_zone_layout"]
assert rzl["row_source"] == "dynamic_fallback"
assert rzl["col_source"] == "dynamic_fallback"
# Result must match what dynamic normal path would produce.
normal = build_layout_css(preset, zones)
assert result["heights_px"] == normal["heights_px"]
assert result["widths_px"] == normal["widths_px"]
# ────────────────────── fr_default sink (PR 2 — single only) ──────────────────────
def test_single_remains_fr_default_sink():
"""After PR 2 the only preset that still falls through to
_build_fr_default is `single` (PR 3 will promote it). Length
contract (R=1, C=1) must still hold."""
zones = [_zone("primary", 1.0)]
result = build_layout_css("single", zones)
assert result["computation"] == "fr_default_from_preset"
assert result["dynamic_rows"] is False
assert result["dynamic_cols"] is False
assert len(result["heights_px"]) == 2 # R rows
assert len(result["widths_px"]) == 2 # C cols
assert len(result["heights_px"]) == 1
assert len(result["widths_px"]) == 1