# Fixed three-level separated patches `FixedPatchStepper` supports exactly three static, blocked levels with separated rectangle sets on either adjacent pair. This is not general AMR: there is no per-region timestep, moving geometry, dynamic refinement or L1 body support. All regions share one current/spare buffer pair per level. The existing schedule advances L0 once, L1 twice and L2 four times per base step, with aggregate transfers. No CUDA kernel changes are involved. ## Explicit coordinate frames Keep the existing scalar `child=(x0,y0,x1,y1)` for a single scalar parent patch. Its coordinates retain their original meaning: half-open integer indices in the allocated L1 window, including its ghost band. The new keyword `child_rectangles_in_parent=[...]` is mutually exclusive with `child`. It accepts a scalar or a set parent and interprets every rectangle in the actual allocated L1 array, including ghost bands and holes. It never infers region-local coordinates. The physical root cell edge corresponding to an L1 index `(x,y)` is `parent.fine_origin + (x,y)/2`: - A scalar L1 parent has a shifted window origin. - A set L1 parent has origin `(0,0)` and public shape `(2*ny,2*nx)`. - A child set has public shape twice the allocated L1 shape. Its root origin is the L1 allocated origin, even when the parent is a scalar window. For a 16x16 root, scalar parent `(4,4,12,12)` and scalar child `(8,8,16,16)` are physically equivalent to parent set `[(4,4,12,12)]` with child set `[(12,12,20,20)]`. A child set on the *scalar* parent instead uses `[(8,8,16,16)]`. Reusing the same numbers after changing the parent frame can move the physical region. Both adjacent pairs require a Chebyshev gap of at least four in their own parent-cell units. The whole child union must preserve inherited ACTIVE coverage, uncovered ACTIVE donors/receivers, surviving pulls, and the upstream restriction footprint. Sparse holes remain unowned; inherited INNER remains stream-only and OUTER remains invalid. Block sizes must align with every support edge, including its four-cell ghost band. ## Example ```python run = FixedPatchStepper( cfg, [(4, 4, 20, 28), (24, 4, 44, 28)], context=ctx, base_block=(4, 4), fine_block=(4, 4), child_block=(4, 4), child_rectangles_in_parent=[(16, 16, 32, 40), (56, 16, 80, 40)], ) ``` This example uses a 48x32 root. `initialize` and `download` use three Q-major dense arrays. Public arrays and masks still scale with the full allocated extent; sparse device buffers do not remove that host-memory limit. Stationary whole-circle `child_objects` and `child_jets` use root edge coordinates and a single L2 boundary object. Body IDs are physical object-list positions, not region IDs or caller `obj_id`. Complete boundary stencils must avoid the transfer footprint. Change a child Jet with `set_jet_amplitude(index, value, level=2)`; L1 bodies/Jets remain unsupported. ## Verification CPU geometry, table, pre-CUDA rejection, schedule and cleanup tests: ```sh PYTHONPATH=src python -m unittest discover -s tests/unit -p 'test_refinement*.py' -v ``` Explicit GPU gates are in `tests/validation/multigrid/test_refinement_three_level_multipatch_gpu.py`. They cover physical-frame equivalence, twins, asymmetric refinement, multiple children, reversed order, independently varied aligned layouts, public/raw masks and two L2 bodies with static/zero/driven/switched Jets. Snapshots are 0/1/2/8. CPU mocks do not execute CUDA and do not establish numerical accuracy. GPU equivalence gates supplement the separately frozen independent dense three-level numerical replay; passing either alone is not a conservation, performance or physical-accuracy claim.