# src coherence stage (from main 914906e) Owner direction 2026-10-28: unified style over historical compatibility, inside `src`. No numerical change is intended. Stages after 5f69eba: 65339ab (one boundary/Jet record, host preparation helpers, pair accessors), c5a3cdb (`block_collide` families), 442979b (exchange metadata on the boundary record). All three received parent/candidate generated-config and host/GPU equality checks. Only the collision slice ran a new PTX matrix (a4fc46d, whose src is identical to c5a3cdb); 65339ab and 442979b reused prior PTX evidence after unchanged CUDA/compiler and generated-input checks. In the collision matrix, only the two channel-root SRT modules changed PTX (omega load/clamp moved); their parent/candidate outputs matched exactly in the exercised cases. ## Conventions (defined once, in `lbm.hierarchy.Hierarchy`) - Level `l = 0 .. depth-1` (0 = root). Pair `hierarchy.pairs[l-1]` builds level `l`. - Array shapes are `(ny, nx)`; rectangles, origins and centers are `(x, y)`. - Coarse/fine name the two members (parent/child) of ANY pair, not absolute L0/L1: `fine_shape`, `fine_origin`, `prepare_fine_boundary`, `blocked_fine_tables`, `blocked_coarse_tables`. - Refined levels (l >= 1) of FixedPatchStepper place bodies/Jets in the root EDGE frame (root cell `i` = `[i, i+1)`). Uniform `Simulation` objects AND the static circles of a single-level (depth 1) FixedPatchStepper use the integer-NODE frame. Nothing converts between them. This concerns input placement only: within the EDGE frame, refined levels still project root-edge coordinates to fine-local cell indices with the half-cell shift (`prepare_fine_boundary`: `(v - o)/dx - .5`; hits back via `fine_hits_to_edge_frame` `origin + dx*(hit + .5)`), using `edge_frame`/`fine_origin` below. - `Hierarchy.edge_frame(l)` -> `((ox, oy), h)`: cell `(i, j)` of level `l`'s allocated array (ghost band and holes included) covers `[o + h*i, o + h*(i+1))`. A scalar FixedPatch2D window carries the half-coarse-cell ghost shift `bounds - HALO/2` in `fine_origin`; a set's level-wide grid has origin 0. Level 1 boundaries are built from the root config (the wall-aware fields live there); deeper ones from the parent frame. - `FixedPatch2D.fine_level_offset` (was `fine_level_origin`) is an array INDEX offset `(y, x)`, not an `(x, y)` origin. ## FixedPatchStepper migration map Historical: left column = names at or after 914906e that no longer exist; right column = the current name. | removed / changed | replacement | |---|---| | `patch`, `child_patch` | `hierarchy.pairs[0]`, `hierarchy.pairs[1]` | | `base_layout`, `fine_layout`, `child_layout` (stepper properties) | `layouts[0]`, `layouts[1]`, `layouts[2]` (`FixedPatchSet2D.fine_layout(block)` is unchanged) | | `fine_boundary`, `child_boundary` | `boundaries[1]`, `boundaries[2]` (host geometry; `None` = no bodies/Jets) | | `_jets` | `jets[1]` | | `_boundaries[l]` (dict from L1, level keys), `_level_boundaries` (b4eacf7), `_device_boundaries` (65339ab) | `_boundaries[l]`: the one combined per-level `_BlockedBoundary` record (list from L0, `None` at L0 unless single-level static circles): host geometry `.host`, `.tables`, `.jets`, `.jet_cache`, exchange `.spacing`/`.centers`, device handles | | `_fine_tables`, `_actuated`, `_jet_cache` | `_boundaries[1].tables` / `.actuated` / `.jet_cache` | | `_level_jets` (private) | `_boundaries[l].jets` (public `jets[l]` is derived) | | `_exchange_spacing[l]`, `_exchange_centers[l]` (private) | `_boundaries[l].spacing` (h_l, root units) / `.centers` (root edge frame); only levels with a boundary report exchange | | `_level_blocks` (private) | `_layouts[l].block` | | `_remap_cfg` (private) | `_root_cfg` | | `refinement_tables.blocked_base_tables` (also via `refinement_stepper`) | `refinement_tables.blocked_coarse_tables` (import from `refinement_tables`; the stepper no longer re-exports) | | `_fine_leaf`, `_child_leaf` | `leaf[1]`, `leaf[2]` (copies) | | `_middle_kind` | `hierarchy.kinds[1]` | | `_boundary_hosts` (private) | public `boundaries[l]` = `_boundaries[l].host` (host geometry only; the private record is the combined one) | | `remap_child(rects, child_objects=...)` | `remap_finest(rects, objects=...)` | | `set_jet_amplitude(i, a)` (implicit level 1) | `set_jet_amplitude(i, a, level=l)` (required) | | `read_raw_exchange(b)` / `read_exchange_window(b)` (implicit level 1) | `read_raw_exchange(b, level=l)` / `read_exchange_window(b, level=l)` (required) | | `_level_name(l)` "base/fine/child" messages | `"level l"` | | `refinement.MIN_PATCH_GAP` | deleted (no gap policy exists) | | `params.cuh HaloPlan`, `collide_trt_no_force` | deleted (zero references) | Kept: `step_count`, constructor keywords (`refinements`, `level_blocks`, `level_objects`, `level_jets`, `coarse_boundary`, `explosion`), `Simulation.set_jet_amplitude(i, a)` (one level), `RemapTopology.from_config(..., child_objects=, child_jets=)` (internal remap helper; "child" = the replaced level relative to its parent). ## Responsibilities - Construction: `_check_levels` (inputs); host preparation, numpy only, before any device work: `_level_layouts`, `_kernel_configs` (per-level kernel configs), `_host_boundaries`, `_prepare_blocked_boundary`, `_fluid_leaves`; `Hierarchy.edge_frame` (geometry frames); then `_planned_bytes()` (memory preflight) and `_build_level` / `_build_pair` / `_upload_boundary` (device). A construction failure closes the partial instance. Observation reads share `_observed_boundary` + `_summarize`. - Pair accessors: `rectangles` (a scalar FixedPatch2D gives `(bounds,)`) and `fine_level_offset` (`(0, 0)` for a set) on both pair types; the types and their storage frames stay distinct. - Per-level state, by lifecycle: - Fixed plan structure (built once in `__init__`, read by `_planned_bytes()` before any allocation, readable after `close()` through the public views): `_layouts`, `_level_cfgs`, `_leaf`, `_state`, and in each `_boundaries[l]` record the host geometry `.host`, link tables, `.jet_cache`, `.spacing`, `.centers`. - Mutable boundary data in the same record: `.jets` and `.tables["surface_velocity"]`, changed only by `set_jet_amplitude` (host prepare -> device write -> commit; an uncertain write fails the instance); device handles (`.flags`, `.device`, `.kernel`, ledgers) are set by `_upload_boundary` and cleared by `close()`. - Runtime: `_levels` (`_Level`) holds compiled modules, kernels, the two population buffers (swapped per step) and transfer/kind handles. It is created only after the memory preflight and cleared on `close()`; `.layout`/`.boundary` reference the plan for `_hop`. Folding the plan into `_Level` would mix both lifecycles in one record. - Blocked kernels: `operators/block_collision.cuh block_collide` is the one collision extension point of `BlockStep` and `BlockChannelStep`. It computes rho/u once and reads no runtime force (`d_params.fx/fy`). Two compile-time families: uniform-equivalent (MRT on any level, SRT on the channel root `BLOCK_ROOT_BC == 2`): clamped omega, `collide_mrt_no_force` or `collide_srt` with its zero Guo term; and level SRT (`BLOCK_ROOT_BC` 0/1): unclamped omega, `collide_srt_no_force`, with the shifted direct-momentum feq under `USE_DDF_SHIFTING`. The uniform path keeps `collide_dispatch` (runtime force, LES, TRT). - `explosion="projected_linear"` stays an optional capability; `"constant"` stays default. ## Not done / open - Two per-level config derivations stay distinct on purpose: `Hierarchy.configs` (`_middle_config`, wall fields reset on every refined level, geometry validation) vs. `FixedPatchStepper._kernel_configs` (reset only on level 1 of a slot_reservoir root; generates each level's config.h). Folding them needs a config.h/PTX diff gate. - No single-`Simulation` facade over FixedPatchStepper: the two own different state models (uniform node frame, body sync, forcing, checkpoint vs. fixed blocked hierarchy, edge frame, remap-as-new-instance). A facade would only forward calls; no concrete need found. - Deferred, each needing its own evidence first: sharing the channel-root cell/donor selector with OneStep (the closure functions are already shared; a shared selector changes uniform signatures and needs a uniform PTX gate); merging FixedPatch2D into FixedPatchSet2D (different origin, ghost band, layout size and addressing; needs a geometry/parity table and would need tiles that are not grid-aligned); one `clamp_omega` helper for uniform and blocked paths (it lives in the hash-pinned LES header `turbulence_smag.cuh`). - Tests, examples and validation callers are migrated separately (pinned runners stay frozen).